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