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