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