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            preFiltersDesc.put(desc.getName(), desc);
211        } else if (extensionPoint.equals(EP_LOGINSCREEN)) {
212            LoginScreenConfig newConfig = (LoginScreenConfig) contribution;
213            loginScreenConfigRegistry.addContribution(newConfig);
214        }
215    }
216
217    @Override
218    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
219
220        if (extensionPoint.equals(EP_AUTHENTICATOR)) {
221            AuthenticationPluginDescriptor descriptor = (AuthenticationPluginDescriptor) contribution;
222            authenticatorsDescriptors.remove(descriptor.getName());
223            log.debug("unregistered AuthenticationPlugin: " + descriptor.getName());
224        } else if (extensionPoint.equals(EP_LOGINSCREEN)) {
225            LoginScreenConfig newConfig = (LoginScreenConfig) contribution;
226            loginScreenConfigRegistry.removeContribution(newConfig);
227        }
228    }
229
230    private void mergeDescriptors(AuthenticationPluginDescriptor newContrib) {
231        AuthenticationPluginDescriptor oldDescriptor = authenticatorsDescriptors.get(newContrib.getName());
232
233        // Enable/Disable
234        oldDescriptor.setEnabled(newContrib.getEnabled());
235
236        // Merge parameters
237        Map<String, String> oldParameters = oldDescriptor.getParameters();
238        oldParameters.putAll(newContrib.getParameters());
239        oldDescriptor.setParameters(oldParameters);
240
241        // override LoginLModule
242        if (newContrib.getLoginModulePlugin() != null && newContrib.getLoginModulePlugin().length() > 0) {
243            oldDescriptor.setLoginModulePlugin(newContrib.getLoginModulePlugin());
244        }
245
246        oldDescriptor.setStateful(newContrib.getStateful());
247
248        if (newContrib.getClassName() != null) {
249            oldDescriptor.setClassName(newContrib.getClassName());
250        }
251
252        oldDescriptor.setNeedStartingURLSaving(newContrib.getNeedStartingURLSaving());
253    }
254
255    // Service API
256
257    public List<String> getStartURLPatterns() {
258        return startupURLs;
259    }
260
261    public List<String> getAuthChain() {
262        return authChain;
263    }
264
265    public List<String> getAuthChain(HttpServletRequest request) {
266
267        if (specificAuthChains == null || specificAuthChains.isEmpty()) {
268            return authChain;
269        }
270
271        SpecificAuthChainDescriptor desc = getAuthChainDescriptor(request);
272
273        if (desc != null) {
274            return desc.computeResultingChain(authChain);
275        } else {
276            return authChain;
277        }
278    }
279
280    public boolean doHandlePrompt(HttpServletRequest request) {
281        if (specificAuthChains == null || specificAuthChains.isEmpty()) {
282            return true;
283        }
284
285        SpecificAuthChainDescriptor desc = getAuthChainDescriptor(request);
286
287        return desc != null ? desc.doHandlePrompt() : SpecificAuthChainDescriptor.DEFAULT_HANDLE_PROMPT_VALUE;
288
289    }
290
291    private SpecificAuthChainDescriptor getAuthChainDescriptor(HttpServletRequest request) {
292        String specificAuthChainName = getSpecificAuthChainName(request);
293        SpecificAuthChainDescriptor desc = specificAuthChains.get(specificAuthChainName);
294        return desc;
295    }
296
297    public String getSpecificAuthChainName(HttpServletRequest request) {
298        for (String specificAuthChainName : specificAuthChains.keySet()) {
299            SpecificAuthChainDescriptor desc = specificAuthChains.get(specificAuthChainName);
300
301            List<Pattern> urlPatterns = desc.getUrlPatterns();
302            if (!urlPatterns.isEmpty()) {
303                // test on URI
304                String requestUrl = request.getRequestURI();
305                for (Pattern pattern : urlPatterns) {
306                    Matcher m = pattern.matcher(requestUrl);
307                    if (m.matches()) {
308                        return specificAuthChainName;
309                    }
310                }
311            }
312
313            Map<String, Pattern> headerPattern = desc.getHeaderPatterns();
314
315            for (String headerName : headerPattern.keySet()) {
316                String headerValue = request.getHeader(headerName);
317                if (headerValue != null) {
318                    Matcher m = headerPattern.get(headerName).matcher(headerValue);
319                    if (m.matches()) {
320                        return specificAuthChainName;
321                    }
322                }
323            }
324        }
325        return null;
326    }
327
328    public UserIdentificationInfoCallbackHandler getCallbackHandler(UserIdentificationInfo userIdent) {
329        if (cbhFactory == null) {
330            return new UserIdentificationInfoCallbackHandler(userIdent);
331        }
332        return cbhFactory.createCallbackHandler(userIdent);
333    }
334
335    public NuxeoAuthenticationPropagator.CleanupCallback propagateUserIdentificationInformation(
336            CachableUserIdentificationInfo cachableUserIdent) {
337        if (propagator != null) {
338            return propagator.propagateUserIdentificationInformation(cachableUserIdent);
339        }
340        return null;
341    }
342
343    public List<NuxeoAuthenticationPlugin> getPluginChain() {
344        List<NuxeoAuthenticationPlugin> result = new ArrayList<NuxeoAuthenticationPlugin>();
345
346        for (String pluginName : authChain) {
347            if (authenticatorsDescriptors.containsKey(pluginName)
348                    && authenticatorsDescriptors.get(pluginName).getEnabled()) {
349                if (authenticators.containsKey(pluginName)) {
350                    result.add(authenticators.get(pluginName));
351                }
352            }
353        }
354        return result;
355    }
356
357    public NuxeoAuthenticationPlugin getPlugin(String pluginName) {
358        if (authenticatorsDescriptors.containsKey(pluginName) && authenticatorsDescriptors.get(pluginName).getEnabled()) {
359            if (authenticators.containsKey(pluginName)) {
360                return authenticators.get(pluginName);
361            }
362        }
363        return null;
364    }
365
366    public AuthenticationPluginDescriptor getDescriptor(String pluginName) {
367        if (authenticatorsDescriptors.containsKey(pluginName)) {
368            return authenticatorsDescriptors.get(pluginName);
369        } else {
370            log.error("Plugin " + pluginName + " not registered or not created");
371            return null;
372        }
373    }
374
375    public void invalidateSession(ServletRequest request) {
376        boolean done = false;
377        if (!sessionManagers.isEmpty()) {
378            Iterator<NuxeoAuthenticationSessionManager> it = sessionManagers.values().iterator();
379            while (it.hasNext() && !(done = it.next().invalidateSession(request))) {
380            }
381        }
382        if (!done) {
383            HttpServletRequest httpRequest = (HttpServletRequest) request;
384            HttpSession session = httpRequest.getSession(false);
385            if (session != null) {
386                session.invalidate();
387            }
388        }
389    }
390
391    public HttpSession reinitSession(HttpServletRequest httpRequest) {
392        if (!sessionManagers.isEmpty()) {
393            for (String smName : sessionManagers.keySet()) {
394                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
395                sm.onBeforeSessionReinit(httpRequest);
396            }
397        }
398
399        HttpSession session = httpRequest.getSession(true);
400
401        if (!sessionManagers.isEmpty()) {
402            for (String smName : sessionManagers.keySet()) {
403                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
404                sm.onAfterSessionReinit(httpRequest);
405            }
406        }
407        return session;
408    }
409
410    public boolean canBypassRequest(ServletRequest request) {
411        if (!sessionManagers.isEmpty()) {
412            for (String smName : sessionManagers.keySet()) {
413                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
414                if (sm.canBypassRequest(request)) {
415                    return true;
416                }
417            }
418        }
419        return false;
420    }
421
422    public boolean needResetLogin(ServletRequest request) {
423        if (!sessionManagers.isEmpty()) {
424            for (NuxeoAuthenticationSessionManager sm : sessionManagers.values()) {
425                if (sm.needResetLogin(request)) {
426                    return true;
427                }
428            }
429        }
430        return false;
431    }
432
433    public String getBaseURL(ServletRequest request) {
434        return VirtualHostHelper.getBaseURL(request);
435    }
436
437    public void onAuthenticatedSessionCreated(ServletRequest request, HttpSession session,
438            CachableUserIdentificationInfo cachebleUserInfo) {
439
440        NuxeoHttpSessionMonitor.instance().associatedUser(session, cachebleUserInfo.getPrincipal().getName());
441
442        if (!sessionManagers.isEmpty()) {
443            for (String smName : sessionManagers.keySet()) {
444                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
445                sm.onAuthenticatedSessionCreated(request, session, cachebleUserInfo);
446            }
447        }
448    }
449
450    public List<OpenUrlDescriptor> getOpenUrls() {
451        return openUrls;
452    }
453
454    // preFilter management
455
456    public synchronized void initPreFilters() {
457
458        if (preFiltersDesc != null) {
459            List<AuthPreFilterDescriptor> sortableDesc = new ArrayList<AuthPreFilterDescriptor>();
460
461            sortableDesc.addAll(preFiltersDesc.values());
462
463            Collections.sort(sortableDesc);
464
465            preFilters = new ArrayList<NuxeoAuthPreFilter>();
466
467            for (AuthPreFilterDescriptor desc : sortableDesc) {
468                try {
469                    NuxeoAuthPreFilter preFilter = (NuxeoAuthPreFilter) desc.getClassName().newInstance();
470                    preFilters.add(preFilter);
471                } catch (ReflectiveOperationException e) {
472                    log.error("Unable to create preFilter " + desc.getName() + " and class" + desc.getClassName(), e);
473                }
474            }
475        }
476    }
477
478    public List<NuxeoAuthPreFilter> getPreFilters() {
479        if (preFilters == null || preFilters.isEmpty()) {
480            return null;
481        }
482        return preFilters;
483    }
484
485    @SuppressWarnings("unchecked")
486    @Override
487    public <T> T getAdapter(Class<T> adapter) {
488        if (LoginAs.class == adapter) {
489            return (T) new LoginAsImpl();
490        }
491        return super.getAdapter(adapter);
492    }
493
494    public LoginScreenConfig getLoginScreenConfig() {
495        return loginScreenConfigRegistry.getConfig();
496    }
497
498}