001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.platform.login;
021
022import java.security.Principal;
023import java.security.acl.Group;
024import java.util.Enumeration;
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.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036/**
037 * Abstract implementation of the {@link LoginModule} SPI from {@code javax.security.auth.spi}.
038 */
039public abstract class NuxeoAbstractServerLoginModule implements LoginModule {
040
041    private static final Log log = LogFactory.getLog(NuxeoAbstractServerLoginModule.class);
042
043    protected Subject subject;
044
045    protected Map sharedState;
046
047    protected Map options;
048
049    protected boolean loginOk;
050
051    /** An optional custom Principal class implementation */
052    protected String principalClassName;
053
054    /** the principal to use when a null username and password are seen */
055    protected Principal unauthenticatedIdentity;
056
057    protected CallbackHandler callbackHandler;
058
059    /** Flag indicating if the shared credential should be used */
060    protected boolean useFirstPass;
061
062    protected abstract Principal getIdentity();
063
064    protected abstract Group[] getRoleSets() throws LoginException;
065
066    protected abstract Principal createIdentity(String username) throws LoginException;
067
068    public boolean abort() throws LoginException {
069        log.trace("abort");
070        return true;
071    }
072
073    public boolean commit() throws LoginException {
074        log.trace("commit, loginOk=" + loginOk);
075        if (!loginOk) {
076            return false;
077        }
078
079        Set<Principal> principals = subject.getPrincipals();
080        Principal identity = getIdentity();
081        principals.add(identity);
082        Group[] roleSets = getRoleSets();
083        for (Group group : roleSets) {
084            String name = group.getName();
085            Group subjectGroup = createGroup(name, principals);
086
087            /*
088             * if( subjectGroup instanceof NestableGroup ) { SimpleGroup tmp = new SimpleGroup("Roles");
089             * subjectGroup.addMember(tmp); subjectGroup = tmp; }
090             */
091
092            // Copy the group members to the Subject group
093            Enumeration<? extends Principal> members = group.members();
094            while (members.hasMoreElements()) {
095                Principal role = members.nextElement();
096                subjectGroup.addMember(role);
097            }
098        }
099        return true;
100    }
101
102    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
103            Map<String, ?> options) {
104        this.subject = subject;
105        this.callbackHandler = callbackHandler;
106        this.sharedState = sharedState;
107        this.options = options;
108        if (log.isTraceEnabled()) {
109            log.trace("initialize, instance=@" + System.identityHashCode(this));
110        }
111
112        /*
113         * Check for password sharing options. Any non-null value for password_stacking sets useFirstPass as this module
114         * has no way to validate any shared password.
115         */
116        String passwordStacking = (String) options.get("password-stacking");
117        if (passwordStacking != null && passwordStacking.equalsIgnoreCase("useFirstPass")) {
118            useFirstPass = true;
119        }
120
121        // Check for a custom Principal implementation
122        principalClassName = (String) options.get("principalClass");
123
124        // Check for unauthenticatedIdentity option.
125        String name = (String) options.get("unauthenticatedIdentity");
126        if (name != null) {
127            try {
128                unauthenticatedIdentity = createIdentity(name);
129                log.trace("Saw unauthenticatedIdentity=" + name);
130            } catch (LoginException e) {
131                log.warn("Failed to create custom unauthenticatedIdentity", e);
132            }
133        }
134    }
135
136    public boolean logout() throws LoginException {
137        log.trace("logout");
138        // Remove the user identity
139        Principal identity = getIdentity();
140        Set<Principal> principals = subject.getPrincipals();
141        principals.remove(identity);
142        // Remove any added Groups...
143        return true;
144    }
145
146    /**
147     * Finds or creates a Group with the given name. Subclasses should use this method to locate the 'Roles' group or
148     * create additional types of groups.
149     *
150     * @return A named Group from the principals set.
151     */
152    protected Group createGroup(String name, Set<Principal> principals) {
153        Group roles = null;
154        for (Principal principal : principals) {
155            if (!(principal instanceof Group)) {
156                continue;
157            }
158            Group grp = (Group) principal;
159            if (grp.getName().equals(name)) {
160                roles = grp;
161                break;
162            }
163        }
164        // If we did not find a group, create one
165        if (roles == null) {
166            roles = new GroupImpl(name);
167            principals.add(roles);
168        }
169        return roles;
170    }
171
172}