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