001/*
002 * (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 * $Id: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $
020 */
021
022package org.nuxeo.ecm.platform.ui.web.auth.service;
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029import java.util.regex.Matcher;
030import java.util.regex.Pattern;
031
032import javax.servlet.ServletRequest;
033import javax.servlet.http.HttpServletRequest;
034import javax.servlet.http.HttpSession;
035
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
039import org.nuxeo.ecm.platform.api.login.UserIdentificationInfoCallbackHandler;
040import org.nuxeo.ecm.platform.ui.web.auth.CachableUserIdentificationInfo;
041import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
042import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPropagator;
043import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationSessionManager;
044import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoCallbackHandlerFactory;
045import org.nuxeo.ecm.platform.ui.web.auth.plugins.DefaultSessionManager;
046import org.nuxeo.ecm.platform.web.common.session.NuxeoHttpSessionMonitor;
047import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
048import org.nuxeo.runtime.model.ComponentContext;
049import org.nuxeo.runtime.model.ComponentInstance;
050import org.nuxeo.runtime.model.DefaultComponent;
051
052public class PluggableAuthenticationService extends DefaultComponent {
053
054    public static final String NAME = "org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService";
055
056    public static final String EP_AUTHENTICATOR = "authenticators";
057
058    public static final String EP_SESSIONMANAGER = "sessionManager";
059
060    public static final String EP_CHAIN = "chain";
061
062    public static final String EP_SPECIFIC_CHAINS = "specificChains";
063
064    public static final String EP_PROPAGATOR = "propagator";
065
066    public static final String EP_CBFACTORY = "JbossCallbackfactory";
067
068    public static final String EP_STARTURL = "startURL";
069
070    public static final String EP_OPENURL = "openUrl";
071
072    public static final String EP_LOGINSCREEN = "loginScreen";
073
074    private static final Log log = LogFactory.getLog(PluggableAuthenticationService.class);
075
076    private Map<String, AuthenticationPluginDescriptor> authenticatorsDescriptors;
077
078    private Map<String, NuxeoAuthenticationPlugin> authenticators;
079
080    private Map<String, NuxeoAuthenticationSessionManager> sessionManagers;
081
082    // NB: not used. Remove?
083    private NuxeoAuthenticationSessionManager defaultSessionManager;
084
085    private NuxeoAuthenticationPropagator propagator;
086
087    private NuxeoCallbackHandlerFactory cbhFactory;
088
089    private List<String> authChain;
090
091    private final Map<String, SpecificAuthChainDescriptor> specificAuthChains = new HashMap<String, SpecificAuthChainDescriptor>();
092
093    private final List<OpenUrlDescriptor> openUrls = new ArrayList<OpenUrlDescriptor>();
094
095    private final List<String> startupURLs = new ArrayList<String>();
096
097    private LoginScreenConfigRegistry loginScreenConfigRegistry;
098
099    @Override
100    public void activate(ComponentContext context) {
101        authenticatorsDescriptors = new HashMap<String, AuthenticationPluginDescriptor>();
102        authChain = new ArrayList<String>();
103        authenticators = new HashMap<String, NuxeoAuthenticationPlugin>();
104        sessionManagers = new HashMap<String, NuxeoAuthenticationSessionManager>();
105        defaultSessionManager = new DefaultSessionManager();
106        loginScreenConfigRegistry = new LoginScreenConfigRegistry();
107    }
108
109    @Override
110    public void deactivate(ComponentContext context) {
111        authenticatorsDescriptors = null;
112        authenticators = null;
113        authChain = null;
114        sessionManagers = null;
115        defaultSessionManager = null;
116        loginScreenConfigRegistry = null;
117    }
118
119    @Override
120    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
121
122        if (extensionPoint.equals(EP_AUTHENTICATOR)) {
123            AuthenticationPluginDescriptor descriptor = (AuthenticationPluginDescriptor) contribution;
124            if (authenticatorsDescriptors.containsKey(descriptor.getName())) {
125                mergeDescriptors(descriptor);
126                log.debug("merged AuthenticationPluginDescriptor: " + descriptor.getName());
127            } else {
128                authenticatorsDescriptors.put(descriptor.getName(), descriptor);
129                log.debug("registered AuthenticationPluginDescriptor: " + descriptor.getName());
130            }
131
132            // create the new instance
133            AuthenticationPluginDescriptor actualDescriptor = authenticatorsDescriptors.get(descriptor.getName());
134            try {
135                NuxeoAuthenticationPlugin authPlugin = actualDescriptor.getClassName().newInstance();
136                authPlugin.initPlugin(actualDescriptor.getParameters());
137                authenticators.put(actualDescriptor.getName(), authPlugin);
138            } catch (InstantiationException e) {
139                log.error(
140                        "Unable to create AuthPlugin for : " + actualDescriptor.getName() + "Error : " + e.getMessage(),
141                        e);
142            } catch (IllegalAccessException e) {
143                log.error(
144                        "Unable to create AuthPlugin for : " + actualDescriptor.getName() + "Error : " + e.getMessage(),
145                        e);
146            }
147
148        } else if (extensionPoint.equals(EP_CHAIN)) {
149            AuthenticationChainDescriptor chainContrib = (AuthenticationChainDescriptor) contribution;
150            log.debug("New authentication chain powered by " + contributor.getName());
151            authChain.clear();
152            authChain.addAll(chainContrib.getPluginsNames());
153        } else if (extensionPoint.equals(EP_OPENURL)) {
154            OpenUrlDescriptor openUrlContrib = (OpenUrlDescriptor) contribution;
155            openUrls.add(openUrlContrib);
156        } else if (extensionPoint.equals(EP_STARTURL)) {
157            StartURLPatternDescriptor startupURLContrib = (StartURLPatternDescriptor) contribution;
158            startupURLs.addAll(startupURLContrib.getStartURLPatterns());
159        } else if (extensionPoint.equals(EP_PROPAGATOR)) {
160            AuthenticationPropagatorDescriptor propagationContrib = (AuthenticationPropagatorDescriptor) contribution;
161
162            // create the new instance
163            try {
164                propagator = propagationContrib.getClassName().newInstance();
165            } catch (InstantiationException e) {
166                log.error("Unable to create propagator", e);
167            } catch (IllegalAccessException e) {
168                log.error("Unable to create propagator", e);
169            }
170        } else if (extensionPoint.equals(EP_CBFACTORY)) {
171            CallbackHandlerFactoryDescriptor cbhfContrib = (CallbackHandlerFactoryDescriptor) contribution;
172
173            // create the new instance
174            try {
175                cbhFactory = cbhfContrib.getClassName().newInstance();
176            } catch (InstantiationException e) {
177                log.error("Unable to create callback handler factory", e);
178            } catch (IllegalAccessException e) {
179                log.error("Unable to create callback handler factory", e);
180            }
181        } else if (extensionPoint.equals(EP_SESSIONMANAGER)) {
182            SessionManagerDescriptor smContrib = (SessionManagerDescriptor) contribution;
183            if (smContrib.enabled) {
184                try {
185                    NuxeoAuthenticationSessionManager sm = smContrib.getClassName().newInstance();
186                    sessionManagers.put(smContrib.getName(), sm);
187                } catch (ReflectiveOperationException e) {
188                    log.error("Unable to create session manager", e);
189                }
190            } else {
191                sessionManagers.remove(smContrib.getName());
192            }
193        } else if (extensionPoint.equals(EP_SPECIFIC_CHAINS)) {
194            SpecificAuthChainDescriptor desc = (SpecificAuthChainDescriptor) contribution;
195            specificAuthChains.put(desc.name, desc);
196        } else if (extensionPoint.equals(EP_LOGINSCREEN)) {
197            LoginScreenConfig newConfig = (LoginScreenConfig) contribution;
198            registerLoginScreenConfig(newConfig);
199        }
200    }
201
202    @Override
203    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
204
205        if (extensionPoint.equals(EP_AUTHENTICATOR)) {
206            AuthenticationPluginDescriptor descriptor = (AuthenticationPluginDescriptor) contribution;
207            authenticatorsDescriptors.remove(descriptor.getName());
208            log.debug("unregistered AuthenticationPlugin: " + descriptor.getName());
209        } else if (extensionPoint.equals(EP_LOGINSCREEN)) {
210            LoginScreenConfig newConfig = (LoginScreenConfig) contribution;
211            unregisterLoginScreenConfig(newConfig);
212        }
213    }
214
215    private void mergeDescriptors(AuthenticationPluginDescriptor newContrib) {
216        AuthenticationPluginDescriptor oldDescriptor = authenticatorsDescriptors.get(newContrib.getName());
217
218        // Enable/Disable
219        oldDescriptor.setEnabled(newContrib.getEnabled());
220
221        // Merge parameters
222        Map<String, String> oldParameters = oldDescriptor.getParameters();
223        oldParameters.putAll(newContrib.getParameters());
224        oldDescriptor.setParameters(oldParameters);
225
226        // override LoginLModule
227        if (newContrib.getLoginModulePlugin() != null && newContrib.getLoginModulePlugin().length() > 0) {
228            oldDescriptor.setLoginModulePlugin(newContrib.getLoginModulePlugin());
229        }
230
231        oldDescriptor.setStateful(newContrib.getStateful());
232
233        if (newContrib.getClassName() != null) {
234            oldDescriptor.setClassName(newContrib.getClassName());
235        }
236
237        oldDescriptor.setNeedStartingURLSaving(newContrib.getNeedStartingURLSaving());
238    }
239
240    // Service API
241
242    public List<String> getStartURLPatterns() {
243        return startupURLs;
244    }
245
246    public List<String> getAuthChain() {
247        return authChain;
248    }
249
250    public List<String> getAuthChain(HttpServletRequest request) {
251
252        if (specificAuthChains == null || specificAuthChains.isEmpty()) {
253            return authChain;
254        }
255
256        SpecificAuthChainDescriptor desc = getAuthChainDescriptor(request);
257
258        if (desc != null) {
259            return desc.computeResultingChain(authChain);
260        } else {
261            return authChain;
262        }
263    }
264
265    public boolean doHandlePrompt(HttpServletRequest request) {
266        if (specificAuthChains == null || specificAuthChains.isEmpty()) {
267            return true;
268        }
269
270        SpecificAuthChainDescriptor desc = getAuthChainDescriptor(request);
271
272        return desc != null ? desc.doHandlePrompt() : SpecificAuthChainDescriptor.DEFAULT_HANDLE_PROMPT_VALUE;
273
274    }
275
276    private SpecificAuthChainDescriptor getAuthChainDescriptor(HttpServletRequest request) {
277        String specificAuthChainName = getSpecificAuthChainName(request);
278        SpecificAuthChainDescriptor desc = specificAuthChains.get(specificAuthChainName);
279        return desc;
280    }
281
282    public String getSpecificAuthChainName(HttpServletRequest request) {
283        for (String specificAuthChainName : specificAuthChains.keySet()) {
284            SpecificAuthChainDescriptor desc = specificAuthChains.get(specificAuthChainName);
285
286            List<Pattern> urlPatterns = desc.getUrlPatterns();
287            if (!urlPatterns.isEmpty()) {
288                // test on URI
289                String requestUrl = request.getRequestURI();
290                for (Pattern pattern : urlPatterns) {
291                    Matcher m = pattern.matcher(requestUrl);
292                    if (m.matches()) {
293                        return specificAuthChainName;
294                    }
295                }
296            }
297
298            Map<String, Pattern> headerPattern = desc.getHeaderPatterns();
299
300            for (String headerName : headerPattern.keySet()) {
301                String headerValue = request.getHeader(headerName);
302                if (headerValue != null) {
303                    Matcher m = headerPattern.get(headerName).matcher(headerValue);
304                    if (m.matches()) {
305                        return specificAuthChainName;
306                    }
307                }
308            }
309        }
310        return null;
311    }
312
313    public UserIdentificationInfoCallbackHandler getCallbackHandler(UserIdentificationInfo userIdent) {
314        if (cbhFactory == null) {
315            return new UserIdentificationInfoCallbackHandler(userIdent);
316        }
317        return cbhFactory.createCallbackHandler(userIdent);
318    }
319
320    public NuxeoAuthenticationPropagator.CleanupCallback propagateUserIdentificationInformation(
321            CachableUserIdentificationInfo cachableUserIdent) {
322        if (propagator != null) {
323            return propagator.propagateUserIdentificationInformation(cachableUserIdent);
324        }
325        return null;
326    }
327
328    public List<NuxeoAuthenticationPlugin> getPluginChain() {
329        List<NuxeoAuthenticationPlugin> result = new ArrayList<NuxeoAuthenticationPlugin>();
330
331        for (String pluginName : authChain) {
332            if (authenticatorsDescriptors.containsKey(pluginName)
333                    && authenticatorsDescriptors.get(pluginName).getEnabled()) {
334                if (authenticators.containsKey(pluginName)) {
335                    result.add(authenticators.get(pluginName));
336                }
337            }
338        }
339        return result;
340    }
341
342    public NuxeoAuthenticationPlugin getPlugin(String pluginName) {
343        if (authenticatorsDescriptors.containsKey(pluginName)
344                && authenticatorsDescriptors.get(pluginName).getEnabled()) {
345            if (authenticators.containsKey(pluginName)) {
346                return authenticators.get(pluginName);
347            }
348        }
349        return null;
350    }
351
352    public AuthenticationPluginDescriptor getDescriptor(String pluginName) {
353        if (authenticatorsDescriptors.containsKey(pluginName)) {
354            return authenticatorsDescriptors.get(pluginName);
355        } else {
356            log.error("Plugin " + pluginName + " not registered or not created");
357            return null;
358        }
359    }
360
361    public void invalidateSession(ServletRequest request) {
362        boolean done = false;
363        if (!sessionManagers.isEmpty()) {
364            Iterator<NuxeoAuthenticationSessionManager> it = sessionManagers.values().iterator();
365            while (it.hasNext() && !(done = it.next().invalidateSession(request))) {
366            }
367        }
368        if (!done) {
369            HttpServletRequest httpRequest = (HttpServletRequest) request;
370            HttpSession session = httpRequest.getSession(false);
371            if (session != null) {
372                session.invalidate();
373            }
374        }
375    }
376
377    public HttpSession reinitSession(HttpServletRequest httpRequest) {
378        if (!sessionManagers.isEmpty()) {
379            for (String smName : sessionManagers.keySet()) {
380                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
381                sm.onBeforeSessionReinit(httpRequest);
382            }
383        }
384
385        HttpSession session = httpRequest.getSession(true);
386
387        if (!sessionManagers.isEmpty()) {
388            for (String smName : sessionManagers.keySet()) {
389                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
390                sm.onAfterSessionReinit(httpRequest);
391            }
392        }
393        return session;
394    }
395
396    public boolean canBypassRequest(ServletRequest request) {
397        if (!sessionManagers.isEmpty()) {
398            for (String smName : sessionManagers.keySet()) {
399                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
400                if (sm.canBypassRequest(request)) {
401                    return true;
402                }
403            }
404        }
405        return false;
406    }
407
408    public boolean needResetLogin(ServletRequest request) {
409        if (!sessionManagers.isEmpty()) {
410            for (NuxeoAuthenticationSessionManager sm : sessionManagers.values()) {
411                if (sm.needResetLogin(request)) {
412                    return true;
413                }
414            }
415        }
416        return false;
417    }
418
419    public String getBaseURL(ServletRequest request) {
420        return VirtualHostHelper.getBaseURL(request);
421    }
422
423    public void onAuthenticatedSessionCreated(ServletRequest request, HttpSession session,
424            CachableUserIdentificationInfo cachebleUserInfo) {
425
426        NuxeoHttpSessionMonitor.instance().associatedUser(session, cachebleUserInfo.getPrincipal().getName());
427
428        if (!sessionManagers.isEmpty()) {
429            for (String smName : sessionManagers.keySet()) {
430                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
431                sm.onAuthenticatedSessionCreated(request, session, cachebleUserInfo);
432            }
433        }
434    }
435
436    public List<OpenUrlDescriptor> getOpenUrls() {
437        return openUrls;
438    }
439
440    public LoginScreenConfig getLoginScreenConfig() {
441        return loginScreenConfigRegistry.getConfig();
442    }
443
444    /**
445     * @since 10.10
446     */
447    public void registerLoginScreenConfig(LoginScreenConfig config) {
448        loginScreenConfigRegistry.addContribution(config);
449    }
450
451    /**
452     * @since 10.10
453     */
454    public void unregisterLoginScreenConfig(LoginScreenConfig config) {
455        loginScreenConfigRegistry.removeContribution(config);
456    }
457
458}