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.HashMap;
026import java.util.Iterator;
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.ui.web.auth.CachableUserIdentificationInfo;
039import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin;
040import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationSessionManager;
041import org.nuxeo.ecm.platform.web.common.session.NuxeoHttpSessionMonitor;
042import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
043import org.nuxeo.runtime.model.ComponentContext;
044import org.nuxeo.runtime.model.ComponentInstance;
045import org.nuxeo.runtime.model.DefaultComponent;
046
047public class PluggableAuthenticationService extends DefaultComponent {
048
049    public static final String NAME = "org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService";
050
051    public static final String EP_AUTHENTICATOR = "authenticators";
052
053    public static final String EP_SESSIONMANAGER = "sessionManager";
054
055    public static final String EP_CHAIN = "chain";
056
057    public static final String EP_SPECIFIC_CHAINS = "specificChains";
058
059    public static final String EP_STARTURL = "startURL";
060
061    public static final String EP_OPENURL = "openUrl";
062
063    public static final String EP_LOGINSCREEN = "loginScreen";
064
065    private static final Log log = LogFactory.getLog(PluggableAuthenticationService.class);
066
067    private Map<String, AuthenticationPluginDescriptor> authenticatorsDescriptors;
068
069    private Map<String, NuxeoAuthenticationPlugin> authenticators;
070
071    private Map<String, NuxeoAuthenticationSessionManager> sessionManagers;
072
073    private List<String> authChain;
074
075    private final Map<String, SpecificAuthChainDescriptor> specificAuthChains = new HashMap<>();
076
077    private final List<OpenUrlDescriptor> openUrls = new ArrayList<>();
078
079    private final List<String> startupURLs = new ArrayList<>();
080
081    private LoginScreenConfigRegistry loginScreenConfigRegistry;
082
083    @Override
084    public void activate(ComponentContext context) {
085        authenticatorsDescriptors = new HashMap<>();
086        authChain = new ArrayList<>();
087        authenticators = new HashMap<>();
088        sessionManagers = new HashMap<>();
089        loginScreenConfigRegistry = new LoginScreenConfigRegistry();
090    }
091
092    @Override
093    public void deactivate(ComponentContext context) {
094        authenticatorsDescriptors = null;
095        authenticators = null;
096        authChain = null;
097        sessionManagers = null;
098        loginScreenConfigRegistry = null;
099    }
100
101    @Override
102    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
103
104        if (extensionPoint.equals(EP_AUTHENTICATOR)) {
105            AuthenticationPluginDescriptor descriptor = (AuthenticationPluginDescriptor) contribution;
106            if (authenticatorsDescriptors.containsKey(descriptor.getName())) {
107                mergeDescriptors(descriptor);
108                log.debug("merged AuthenticationPluginDescriptor: " + descriptor.getName());
109            } else {
110                authenticatorsDescriptors.put(descriptor.getName(), descriptor);
111                log.debug("registered AuthenticationPluginDescriptor: " + descriptor.getName());
112            }
113
114            // create the new instance
115            AuthenticationPluginDescriptor actualDescriptor = authenticatorsDescriptors.get(descriptor.getName());
116            try {
117                NuxeoAuthenticationPlugin authPlugin = actualDescriptor.getClassName().getDeclaredConstructor().newInstance();
118                authPlugin.initPlugin(actualDescriptor.getParameters());
119                authenticators.put(actualDescriptor.getName(), authPlugin);
120            } catch (ReflectiveOperationException e) {
121                log.error(
122                        "Unable to create AuthPlugin for : " + actualDescriptor.getName() + "Error : " + e.getMessage(),
123                        e);
124            }
125
126        } else if (extensionPoint.equals(EP_CHAIN)) {
127            AuthenticationChainDescriptor chainContrib = (AuthenticationChainDescriptor) contribution;
128            log.debug("New authentication chain powered by " + contributor.getName());
129            authChain.clear();
130            authChain.addAll(chainContrib.getPluginsNames());
131        } else if (extensionPoint.equals(EP_OPENURL)) {
132            OpenUrlDescriptor openUrlContrib = (OpenUrlDescriptor) contribution;
133            openUrls.add(openUrlContrib);
134        } else if (extensionPoint.equals(EP_STARTURL)) {
135            StartURLPatternDescriptor startupURLContrib = (StartURLPatternDescriptor) contribution;
136            startupURLs.addAll(startupURLContrib.getStartURLPatterns());
137        } else if (extensionPoint.equals(EP_SESSIONMANAGER)) {
138            SessionManagerDescriptor smContrib = (SessionManagerDescriptor) contribution;
139            if (smContrib.enabled) {
140                try {
141                    NuxeoAuthenticationSessionManager sm = smContrib.getClassName()
142                                                                    .getDeclaredConstructor()
143                                                                    .newInstance();
144                    sessionManagers.put(smContrib.getName(), sm);
145                } catch (ReflectiveOperationException e) {
146                    log.error("Unable to create session manager", e);
147                }
148            } else {
149                sessionManagers.remove(smContrib.getName());
150            }
151        } else if (extensionPoint.equals(EP_SPECIFIC_CHAINS)) {
152            SpecificAuthChainDescriptor desc = (SpecificAuthChainDescriptor) contribution;
153            specificAuthChains.put(desc.name, desc);
154        } else if (extensionPoint.equals(EP_LOGINSCREEN)) {
155            LoginScreenConfig newConfig = (LoginScreenConfig) contribution;
156            registerLoginScreenConfig(newConfig);
157        }
158    }
159
160    @Override
161    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
162
163        if (extensionPoint.equals(EP_AUTHENTICATOR)) {
164            AuthenticationPluginDescriptor descriptor = (AuthenticationPluginDescriptor) contribution;
165            authenticatorsDescriptors.remove(descriptor.getName());
166            log.debug("unregistered AuthenticationPlugin: " + descriptor.getName());
167        } else if (extensionPoint.equals(EP_LOGINSCREEN)) {
168            LoginScreenConfig newConfig = (LoginScreenConfig) contribution;
169            unregisterLoginScreenConfig(newConfig);
170        }
171    }
172
173    private void mergeDescriptors(AuthenticationPluginDescriptor newContrib) {
174        AuthenticationPluginDescriptor oldDescriptor = authenticatorsDescriptors.get(newContrib.getName());
175
176        // Enable/Disable
177        oldDescriptor.setEnabled(newContrib.getEnabled());
178
179        // Merge parameters
180        Map<String, String> oldParameters = oldDescriptor.getParameters();
181        oldParameters.putAll(newContrib.getParameters());
182        oldDescriptor.setParameters(oldParameters);
183
184        oldDescriptor.setStateful(newContrib.getStateful());
185
186        if (newContrib.getClassName() != null) {
187            oldDescriptor.setClassName(newContrib.getClassName());
188        }
189
190        oldDescriptor.setNeedStartingURLSaving(newContrib.getNeedStartingURLSaving());
191    }
192
193    // Service API
194
195    public List<String> getStartURLPatterns() {
196        return startupURLs;
197    }
198
199    public List<String> getAuthChain() {
200        return authChain;
201    }
202
203    public List<String> getAuthChain(HttpServletRequest request) {
204
205        if (specificAuthChains == null || specificAuthChains.isEmpty()) {
206            return authChain;
207        }
208
209        SpecificAuthChainDescriptor desc = getAuthChainDescriptor(request);
210
211        if (desc != null) {
212            return desc.computeResultingChain(authChain);
213        } else {
214            return authChain;
215        }
216    }
217
218    public boolean doHandlePrompt(HttpServletRequest request) {
219        if (specificAuthChains == null || specificAuthChains.isEmpty()) {
220            return true;
221        }
222
223        SpecificAuthChainDescriptor desc = getAuthChainDescriptor(request);
224
225        return desc != null ? desc.doHandlePrompt() : SpecificAuthChainDescriptor.DEFAULT_HANDLE_PROMPT_VALUE;
226
227    }
228
229    private SpecificAuthChainDescriptor getAuthChainDescriptor(HttpServletRequest request) {
230        String specificAuthChainName = getSpecificAuthChainName(request);
231        SpecificAuthChainDescriptor desc = specificAuthChains.get(specificAuthChainName);
232        return desc;
233    }
234
235    public String getSpecificAuthChainName(HttpServletRequest request) {
236        for (String specificAuthChainName : specificAuthChains.keySet()) {
237            SpecificAuthChainDescriptor desc = specificAuthChains.get(specificAuthChainName);
238
239            List<Pattern> urlPatterns = desc.getUrlPatterns();
240            if (!urlPatterns.isEmpty()) {
241                // test on URI
242                String requestUrl = request.getRequestURI();
243                for (Pattern pattern : urlPatterns) {
244                    Matcher m = pattern.matcher(requestUrl);
245                    if (m.matches()) {
246                        return specificAuthChainName;
247                    }
248                }
249            }
250
251            Map<String, Pattern> headerPattern = desc.getHeaderPatterns();
252
253            for (String headerName : headerPattern.keySet()) {
254                String headerValue = request.getHeader(headerName);
255                if (headerValue != null) {
256                    Matcher m = headerPattern.get(headerName).matcher(headerValue);
257                    if (m.matches()) {
258                        return specificAuthChainName;
259                    }
260                }
261            }
262        }
263        return null;
264    }
265
266    public List<NuxeoAuthenticationPlugin> getPluginChain() {
267        List<NuxeoAuthenticationPlugin> result = new ArrayList<>();
268
269        for (String pluginName : authChain) {
270            if (authenticatorsDescriptors.containsKey(pluginName)
271                    && authenticatorsDescriptors.get(pluginName).getEnabled()) {
272                if (authenticators.containsKey(pluginName)) {
273                    result.add(authenticators.get(pluginName));
274                }
275            }
276        }
277        return result;
278    }
279
280    public NuxeoAuthenticationPlugin getPlugin(String pluginName) {
281        if (authenticatorsDescriptors.containsKey(pluginName)
282                && authenticatorsDescriptors.get(pluginName).getEnabled()) {
283            if (authenticators.containsKey(pluginName)) {
284                return authenticators.get(pluginName);
285            }
286        }
287        return null;
288    }
289
290    public AuthenticationPluginDescriptor getDescriptor(String pluginName) {
291        if (authenticatorsDescriptors.containsKey(pluginName)) {
292            return authenticatorsDescriptors.get(pluginName);
293        } else {
294            log.error("Plugin " + pluginName + " not registered or not created");
295            return null;
296        }
297    }
298
299    public void invalidateSession(ServletRequest request) {
300        boolean done = false;
301        if (!sessionManagers.isEmpty()) {
302            Iterator<NuxeoAuthenticationSessionManager> it = sessionManagers.values().iterator();
303            while (it.hasNext() && !(done = it.next().invalidateSession(request))) {
304            }
305        }
306        if (!done) {
307            HttpServletRequest httpRequest = (HttpServletRequest) request;
308            HttpSession session = httpRequest.getSession(false);
309            if (session != null) {
310                session.invalidate();
311            }
312        }
313    }
314
315    public HttpSession reinitSession(HttpServletRequest httpRequest) {
316        if (!sessionManagers.isEmpty()) {
317            for (String smName : sessionManagers.keySet()) {
318                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
319                sm.onBeforeSessionReinit(httpRequest);
320            }
321        }
322
323        HttpSession session = httpRequest.getSession(true);
324
325        if (!sessionManagers.isEmpty()) {
326            for (String smName : sessionManagers.keySet()) {
327                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
328                sm.onAfterSessionReinit(httpRequest);
329            }
330        }
331        return session;
332    }
333
334    public boolean canBypassRequest(ServletRequest request) {
335        if (!sessionManagers.isEmpty()) {
336            for (String smName : sessionManagers.keySet()) {
337                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
338                if (sm.canBypassRequest(request)) {
339                    return true;
340                }
341            }
342        }
343        return false;
344    }
345
346    public boolean needResetLogin(ServletRequest request) {
347        if (!sessionManagers.isEmpty()) {
348            for (NuxeoAuthenticationSessionManager sm : sessionManagers.values()) {
349                if (sm.needResetLogin(request)) {
350                    return true;
351                }
352            }
353        }
354        return false;
355    }
356
357    public String getBaseURL(ServletRequest request) {
358        return VirtualHostHelper.getBaseURL(request);
359    }
360
361    public void onAuthenticatedSessionCreated(ServletRequest request, HttpSession session,
362            CachableUserIdentificationInfo cachebleUserInfo) {
363
364        NuxeoHttpSessionMonitor.instance().associatedUser(session, cachebleUserInfo.getPrincipal().getName());
365
366        if (!sessionManagers.isEmpty()) {
367            for (String smName : sessionManagers.keySet()) {
368                NuxeoAuthenticationSessionManager sm = sessionManagers.get(smName);
369                sm.onAuthenticatedSessionCreated(request, session, cachebleUserInfo);
370            }
371        }
372    }
373
374    public List<OpenUrlDescriptor> getOpenUrls() {
375        return openUrls;
376    }
377
378    public LoginScreenConfig getLoginScreenConfig() {
379        return loginScreenConfigRegistry.getConfig();
380    }
381
382    /**
383     * @since 10.10
384     */
385    public void registerLoginScreenConfig(LoginScreenConfig config) {
386        loginScreenConfigRegistry.addContribution(config);
387    }
388
389    /**
390     * @since 10.10
391     */
392    public void unregisterLoginScreenConfig(LoginScreenConfig config) {
393        loginScreenConfigRegistry.removeContribution(config);
394    }
395
396}