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 final LoginScreenConfig loginScreenConfig = new LoginScreenConfig();
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    }
113
114    @Override
115    public void deactivate(ComponentContext context) {
116        authenticatorsDescriptors = null;
117        authenticators = null;
118        authChain = null;
119        sessionManagers = null;
120        defaultSessionManager = null;
121    }
122
123    @Override
124    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
125
126        if (extensionPoint.equals(EP_AUTHENTICATOR)) {
127            AuthenticationPluginDescriptor descriptor = (AuthenticationPluginDescriptor) contribution;
128            if (authenticatorsDescriptors.containsKey(descriptor.getName())) {
129                mergeDescriptors(descriptor);
130                log.debug("merged AuthenticationPluginDescriptor: " + descriptor.getName());
131            } else {
132                authenticatorsDescriptors.put(descriptor.getName(), descriptor);
133                log.debug("registered AuthenticationPluginDescriptor: " + descriptor.getName());
134            }
135
136            // create the new instance
137            AuthenticationPluginDescriptor actualDescriptor = authenticatorsDescriptors.get(descriptor.getName());
138            try {
139                NuxeoAuthenticationPlugin authPlugin = actualDescriptor.getClassName().newInstance();
140                authPlugin.initPlugin(actualDescriptor.getParameters());
141                authenticators.put(actualDescriptor.getName(), authPlugin);
142            } catch (InstantiationException e) {
143                log.error(
144                        "Unable to create AuthPlugin for : " + actualDescriptor.getName() + "Error : " + e.getMessage(),
145                        e);
146            } catch (IllegalAccessException e) {
147                log.error(
148                        "Unable to create AuthPlugin for : " + actualDescriptor.getName() + "Error : " + e.getMessage(),
149                        e);
150            }
151
152        } else if (extensionPoint.equals(EP_CHAIN)) {
153            AuthenticationChainDescriptor chainContrib = (AuthenticationChainDescriptor) contribution;
154            log.debug("New authentication chain powered by " + contributor.getName());
155            authChain.clear();
156            authChain.addAll(chainContrib.getPluginsNames());
157        } else if (extensionPoint.equals(EP_OPENURL)) {
158            OpenUrlDescriptor openUrlContrib = (OpenUrlDescriptor) contribution;
159            openUrls.add(openUrlContrib);
160        } else if (extensionPoint.equals(EP_STARTURL)) {
161            StartURLPatternDescriptor startupURLContrib = (StartURLPatternDescriptor) contribution;
162            startupURLs.addAll(startupURLContrib.getStartURLPatterns());
163        } else if (extensionPoint.equals(EP_PROPAGATOR)) {
164            AuthenticationPropagatorDescriptor propagationContrib = (AuthenticationPropagatorDescriptor) contribution;
165
166            // create the new instance
167            try {
168                propagator = propagationContrib.getClassName().newInstance();
169            } catch (InstantiationException e) {
170                log.error("Unable to create propagator", e);
171            } catch (IllegalAccessException e) {
172                log.error("Unable to create propagator", e);
173            }
174        } else if (extensionPoint.equals(EP_CBFACTORY)) {
175            CallbackHandlerFactoryDescriptor cbhfContrib = (CallbackHandlerFactoryDescriptor) contribution;
176
177            // create the new instance
178            try {
179                cbhFactory = cbhfContrib.getClassName().newInstance();
180            } catch (InstantiationException e) {
181                log.error("Unable to create callback handler factory", e);
182            } catch (IllegalAccessException e) {
183                log.error("Unable to create callback handler factory", e);
184            }
185        } else if (extensionPoint.equals(EP_SESSIONMANAGER)) {
186            SessionManagerDescriptor smContrib = (SessionManagerDescriptor) contribution;
187            if (smContrib.enabled) {
188                try {
189                    NuxeoAuthenticationSessionManager sm = smContrib.getClassName().newInstance();
190                    sessionManagers.put(smContrib.getName(), sm);
191                } catch (ReflectiveOperationException e) {
192                    log.error("Unable to create session manager", e);
193                }
194            } else {
195                sessionManagers.remove(smContrib.getName());
196            }
197        } else if (extensionPoint.equals(EP_SPECIFIC_CHAINS)) {
198            SpecificAuthChainDescriptor desc = (SpecificAuthChainDescriptor) contribution;
199            specificAuthChains.put(desc.name, desc);
200        } else if (extensionPoint.equals(EP_PREFILTER)) {
201            AuthPreFilterDescriptor desc = (AuthPreFilterDescriptor) contribution;
202            if (preFiltersDesc == null) {
203                preFiltersDesc = new HashMap<String, AuthPreFilterDescriptor>();
204            }
205            preFiltersDesc.put(desc.getName(), desc);
206        } else if (extensionPoint.equals(EP_LOGINSCREEN)) {
207            LoginScreenConfig newConfig = (LoginScreenConfig) contribution;
208            loginScreenConfig.merge(newConfig);
209        }
210    }
211
212    @Override
213    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
214
215        if (extensionPoint.equals(EP_AUTHENTICATOR)) {
216            AuthenticationPluginDescriptor descriptor = (AuthenticationPluginDescriptor) contribution;
217            authenticatorsDescriptors.remove(descriptor.getName());
218            log.debug("unregistered AuthenticationPlugin: " + descriptor.getName());
219        }
220    }
221
222    private void mergeDescriptors(AuthenticationPluginDescriptor newContrib) {
223        AuthenticationPluginDescriptor oldDescriptor = authenticatorsDescriptors.get(newContrib.getName());
224
225        // Enable/Disable
226        oldDescriptor.setEnabled(newContrib.getEnabled());
227
228        // Merge parameters
229        Map<String, String> oldParameters = oldDescriptor.getParameters();
230        oldParameters.putAll(newContrib.getParameters());
231        oldDescriptor.setParameters(oldParameters);
232
233        // override LoginLModule
234        if (newContrib.getLoginModulePlugin() != null && newContrib.getLoginModulePlugin().length() > 0) {
235            oldDescriptor.setLoginModulePlugin(newContrib.getLoginModulePlugin());
236        }
237
238        oldDescriptor.setStateful(newContrib.getStateful());
239
240        if (newContrib.getClassName() != null) {
241            oldDescriptor.setClassName(newContrib.getClassName());
242        }
243
244        oldDescriptor.setNeedStartingURLSaving(newContrib.getNeedStartingURLSaving());
245    }
246
247    // Service API
248
249    public List<String> getStartURLPatterns() {
250        return startupURLs;
251    }
252
253    public List<String> getAuthChain() {
254        return authChain;
255    }
256
257    public List<String> getAuthChain(HttpServletRequest request) {
258
259        if (specificAuthChains == null || specificAuthChains.isEmpty()) {
260            return authChain;
261        }
262
263        String specificAuthChainName = getSpecificAuthChainName(request);
264        SpecificAuthChainDescriptor desc = specificAuthChains.get(specificAuthChainName);
265
266        if (desc != null) {
267            return desc.computeResultingChain(authChain);
268        } else {
269            return authChain;
270        }
271    }
272
273    public String getSpecificAuthChainName(HttpServletRequest request) {
274        for (String specificAuthChainName : specificAuthChains.keySet()) {
275            SpecificAuthChainDescriptor desc = specificAuthChains.get(specificAuthChainName);
276
277            List<Pattern> urlPatterns = desc.getUrlPatterns();
278            if (!urlPatterns.isEmpty()) {
279                // test on URI
280                String requestUrl = request.getRequestURI();
281                for (Pattern pattern : urlPatterns) {
282                    Matcher m = pattern.matcher(requestUrl);
283                    if (m.matches()) {
284                        return specificAuthChainName;
285                    }
286                }
287            }
288
289            Map<String, Pattern> headerPattern = desc.getHeaderPatterns();
290
291            for (String headerName : headerPattern.keySet()) {
292                String headerValue = request.getHeader(headerName);
293                if (headerValue != null) {
294                    Matcher m = headerPattern.get(headerName).matcher(headerValue);
295                    if (m.matches()) {
296                        return specificAuthChainName;
297                    }
298                }
299            }
300        }
301        return null;
302    }
303
304    public UserIdentificationInfoCallbackHandler getCallbackHandler(UserIdentificationInfo userIdent) {
305        if (cbhFactory == null) {
306            return new UserIdentificationInfoCallbackHandler(userIdent);
307        }
308        return cbhFactory.createCallbackHandler(userIdent);
309    }
310
311    public NuxeoAuthenticationPropagator.CleanupCallback propagateUserIdentificationInformation(
312            CachableUserIdentificationInfo cachableUserIdent) {
313        if (propagator != null) {
314            return propagator.propagateUserIdentificationInformation(cachableUserIdent);
315        }
316        return null;
317    }
318
319    public List<NuxeoAuthenticationPlugin> getPluginChain() {
320        List<NuxeoAuthenticationPlugin> result = new ArrayList<NuxeoAuthenticationPlugin>();
321
322        for (String pluginName : authChain) {
323            if (authenticatorsDescriptors.containsKey(pluginName)
324                    && authenticatorsDescriptors.get(pluginName).getEnabled()) {
325                if (authenticators.containsKey(pluginName)) {
326                    result.add(authenticators.get(pluginName));
327                }
328            }
329        }
330        return result;
331    }
332
333    public NuxeoAuthenticationPlugin getPlugin(String pluginName) {
334        if (authenticatorsDescriptors.containsKey(pluginName) && authenticatorsDescriptors.get(pluginName).getEnabled()) {
335            if (authenticators.containsKey(pluginName)) {
336                return authenticators.get(pluginName);
337            }
338        }
339        return null;
340    }
341
342    public AuthenticationPluginDescriptor getDescriptor(String pluginName) {
343        if (authenticatorsDescriptors.containsKey(pluginName)) {
344            return authenticatorsDescriptors.get(pluginName);
345        } else {
346            log.error("Plugin " + pluginName + " not registered or not created");
347            return null;
348        }
349    }
350
351    public void invalidateSession(ServletRequest request) {
352        if (!sessionManagers.isEmpty()) {
353            for (String smName : sessionManagers.keySet()) {
354                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
355                sm.onBeforeSessionInvalidate(request);
356            }
357        }
358        HttpServletRequest httpRequest = (HttpServletRequest) request;
359        HttpSession session = httpRequest.getSession(false);
360        if (session != null) {
361            session.invalidate();
362        }
363    }
364
365    public HttpSession reinitSession(HttpServletRequest httpRequest) {
366        if (!sessionManagers.isEmpty()) {
367            for (String smName : sessionManagers.keySet()) {
368                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
369                sm.onBeforeSessionReinit(httpRequest);
370            }
371        }
372
373        HttpSession session = httpRequest.getSession(true);
374
375        if (!sessionManagers.isEmpty()) {
376            for (String smName : sessionManagers.keySet()) {
377                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
378                sm.onAfterSessionReinit(httpRequest);
379            }
380        }
381        return session;
382    }
383
384    public boolean canBypassRequest(ServletRequest request) {
385        if (!sessionManagers.isEmpty()) {
386            for (String smName : sessionManagers.keySet()) {
387                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
388                if (sm.canBypassRequest(request)) {
389                    return true;
390                }
391            }
392        }
393        return false;
394    }
395
396    public boolean needResetLogin(ServletRequest request) {
397        if (!sessionManagers.isEmpty()) {
398            for (NuxeoAuthenticationSessionManager sm : sessionManagers.values()) {
399                if (sm.needResetLogin(request)) {
400                    return true;
401                }
402            }
403        }
404        return false;
405    }
406
407    public String getBaseURL(ServletRequest request) {
408        return VirtualHostHelper.getBaseURL(request);
409    }
410
411    public void onAuthenticatedSessionCreated(ServletRequest request, HttpSession session,
412            CachableUserIdentificationInfo cachebleUserInfo) {
413
414        NuxeoHttpSessionMonitor.instance().associatedUser(session, cachebleUserInfo.getPrincipal().getName());
415
416        if (!sessionManagers.isEmpty()) {
417            for (String smName : sessionManagers.keySet()) {
418                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
419                sm.onAuthenticatedSessionCreated(request, session, cachebleUserInfo);
420            }
421        }
422    }
423
424    public List<OpenUrlDescriptor> getOpenUrls() {
425        return openUrls;
426    }
427
428    // preFilter management
429
430    public synchronized void initPreFilters() {
431
432        if (preFiltersDesc != null) {
433            List<AuthPreFilterDescriptor> sortableDesc = new ArrayList<AuthPreFilterDescriptor>();
434
435            sortableDesc.addAll(preFiltersDesc.values());
436
437            Collections.sort(sortableDesc);
438
439            preFilters = new ArrayList<NuxeoAuthPreFilter>();
440
441            for (AuthPreFilterDescriptor desc : sortableDesc) {
442                try {
443                    NuxeoAuthPreFilter preFilter = (NuxeoAuthPreFilter) desc.getClassName().newInstance();
444                    preFilters.add(preFilter);
445                } catch (ReflectiveOperationException e) {
446                    log.error("Unable to create preFilter " + desc.getName() + " and class" + desc.getClassName(), e);
447                }
448            }
449        }
450    }
451
452    public List<NuxeoAuthPreFilter> getPreFilters() {
453        if (preFilters == null || preFilters.isEmpty()) {
454            return null;
455        }
456        return preFilters;
457    }
458
459    @SuppressWarnings("unchecked")
460    @Override
461    public <T> T getAdapter(Class<T> adapter) {
462        if (LoginAs.class == adapter) {
463            return (T) new LoginAsImpl();
464        }
465        return super.getAdapter(adapter);
466    }
467
468    public LoginScreenConfig getLoginScreenConfig() {
469        return loginScreenConfig;
470    }
471
472}