001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 *
012 * $Id$
013 */
014
015package org.nuxeo.ecm.core.api.local;
016
017import java.security.Principal;
018import java.util.Map;
019import java.util.Set;
020
021import javax.security.auth.Subject;
022import javax.security.auth.callback.CallbackHandler;
023import javax.security.auth.login.LoginException;
024import javax.security.auth.spi.LoginModule;
025
026import org.nuxeo.ecm.core.api.NuxeoPrincipal;
027import org.nuxeo.ecm.core.api.SystemPrincipal;
028import org.nuxeo.runtime.api.login.LoginComponent;
029
030/**
031 * A login module that is propagating the login information into the core login stack.
032 * <p>
033 * This login module doesn't make any authentication - it is called only after the authentication is successfully done
034 * by a previous login module.
035 * <p>
036 * The static method of this class can also be used to manage the current login stack.
037 *
038 * @author eionica@nuxeo.com
039 */
040public class ClientLoginModule implements LoginModule {
041
042    /**
043     * The global login stack
044     */
045    protected static final LoginStack globalInstance = LoginStack.synchronizedStack();
046
047    /**
048     * The thread local login stack - for per thread logins
049     */
050    protected static final ThreadLocal<LoginStack> threadInstance = new ThreadLocal<LoginStack>() {
051        @Override
052        protected LoginStack initialValue() {
053            return new LoginStack();
054        }
055    };
056
057    /**
058     * @since 5.7
059     */
060    public static void clearThreadLocalLogin() {
061        threadInstance.remove();
062    }
063
064    public static LoginStack getThreadLocalLogin() {
065        return threadInstance.get();
066    }
067
068    public static LoginStack.Entry getCurrentLogin() {
069        LoginStack.Entry entry = threadInstance.get().peek();
070        if (entry == null) {
071            entry = globalInstance.peek();
072        }
073        return entry;
074    }
075
076    /**
077     * Returns the current logged {@link NuxeoPrincipal} from the login stack
078     *
079     * @since 5.6
080     */
081    public static NuxeoPrincipal getCurrentPrincipal() {
082        LoginStack.Entry entry = getCurrentLogin();
083        if (entry != null) {
084            Principal p = entry.getPrincipal();
085            if (p instanceof NuxeoPrincipal) {
086                return (NuxeoPrincipal) p;
087            } else if (LoginComponent.isSystemLogin(p)) {
088                return new SystemPrincipal(p.getName());
089            }
090        }
091        return null;
092    }
093
094    private Subject subject;
095
096    private Map sharedState;
097
098    // active login stack
099    private LoginStack stack;
100
101    // whether or not the login was propagated
102    private boolean commited = false;
103
104    /**
105     * Initialize this LoginModule.
106     */
107    @Override
108    public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
109        this.subject = subject;
110        this.sharedState = sharedState;
111        // Check if login must be propagated to entire JVM or only to the
112        // current thread.
113        // the default is per-thread login
114        boolean globalLogin = true;
115        String global = (String) options.get("global");
116        if (global != null) {
117            globalLogin = Boolean.parseBoolean(global);
118        }
119        if (globalLogin) {
120            // propagate the login only for the current thread
121            stack = threadInstance.get();
122        } else {
123            // propagate the login for all threads in JVM
124            stack = globalInstance;
125        }
126    }
127
128    @Override
129    public boolean login() throws LoginException {
130        // this login module doesn't make any user authentication
131        // it simply propagate the login to the login stack.
132        // So it must be put after the authenticating module.
133        // The authenticating module should update the sharedState map
134        // with the login info.
135        return true;
136    }
137
138    @Override
139    public boolean commit() throws LoginException {
140        Principal p = null;
141        Object user = sharedState.get("javax.security.auth.login.name");
142        if (user instanceof Principal) {
143            p = (Principal) user;
144        } else {
145            Set<Principal> principals = subject.getPrincipals();
146            if (!principals.isEmpty()) {
147                p = principals.iterator().next();
148            }
149        }
150        if (p != null) {
151            Object credential = sharedState.get("javax.security.auth.login.password");
152            stack.push(p, credential, subject);
153            commited = true;
154        }
155        return true;
156    }
157
158    @Override
159    public boolean abort() throws LoginException {
160        commited = false;
161        stack.clear();
162        return true;
163    }
164
165    @Override
166    public boolean logout() throws LoginException {
167        if (commited) {
168            stack.pop();
169            commited = false;
170        }
171        return true;
172    }
173
174}