001/*
002 * (C) Copyright 2006-2008 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$
020 */
021
022package org.nuxeo.ecm.platform.login;
023
024import java.security.Principal;
025import java.security.acl.Group;
026import java.util.Enumeration;
027import java.util.Map;
028import java.util.Set;
029
030import javax.security.auth.Subject;
031import javax.security.auth.callback.CallbackHandler;
032import javax.security.auth.login.LoginException;
033import javax.security.auth.spi.LoginModule;
034
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.ecm.core.api.NuxeoPrincipal;
038
039/**
040 * Abstract implementation of the {@link LoginModule} SPI from {@code javax.security.auth.spi}.
041 */
042public abstract class NuxeoAbstractServerLoginModule implements LoginModule {
043
044    private static final Log log = LogFactory.getLog(NuxeoAbstractServerLoginModule.class);
045
046    protected Subject subject;
047
048    protected Map sharedState;
049
050    protected Map options;
051
052    protected boolean loginOk;
053
054    /** An optional custom Principal class implementation */
055    protected String principalClassName;
056
057    /** the principal to use when a null username and password are seen */
058    protected NuxeoPrincipal unauthenticatedIdentity;
059
060    protected CallbackHandler callbackHandler;
061
062    /** Flag indicating if the shared credential should be used */
063    protected boolean useFirstPass;
064
065    protected abstract NuxeoPrincipal getIdentity();
066
067    protected abstract Group[] getRoleSets() throws LoginException;
068
069    protected abstract NuxeoPrincipal createIdentity(String username) throws LoginException;
070
071    public boolean abort() throws LoginException {
072        log.trace("abort");
073        return true;
074    }
075
076    public boolean commit() throws LoginException {
077        log.trace("commit, loginOk=" + loginOk);
078        if (!loginOk) {
079            return false;
080        }
081
082        Set<Principal> principals = subject.getPrincipals();
083        Principal identity = getIdentity();
084        principals.add(identity);
085        Group[] roleSets = getRoleSets();
086        for (Group group : roleSets) {
087            String name = group.getName();
088            Group subjectGroup = createGroup(name, principals);
089
090            /*
091             * if( subjectGroup instanceof NestableGroup ) { SimpleGroup tmp = new SimpleGroup("Roles");
092             * subjectGroup.addMember(tmp); subjectGroup = tmp; }
093             */
094
095            // Copy the group members to the Subject group
096            Enumeration<? extends Principal> members = group.members();
097            while (members.hasMoreElements()) {
098                Principal role = members.nextElement();
099                subjectGroup.addMember(role);
100            }
101        }
102        return true;
103    }
104
105    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
106            Map<String, ?> options) {
107        this.subject = subject;
108        this.callbackHandler = callbackHandler;
109        this.sharedState = sharedState;
110        this.options = options;
111        if (log.isTraceEnabled()) {
112            log.trace("initialize, instance=@" + System.identityHashCode(this));
113        }
114
115        /*
116         * Check for password sharing options. Any non-null value for password_stacking sets useFirstPass as this module
117         * has no way to validate any shared password.
118         */
119        String passwordStacking = (String) options.get("password-stacking");
120        if (passwordStacking != null && passwordStacking.equalsIgnoreCase("useFirstPass")) {
121            useFirstPass = true;
122        }
123
124        // Check for a custom Principal implementation
125        principalClassName = (String) options.get("principalClass");
126
127        // Check for unauthenticatedIdentity option.
128        String name = (String) options.get("unauthenticatedIdentity");
129        if (name != null) {
130            try {
131                unauthenticatedIdentity = createIdentity(name);
132                log.trace("Saw unauthenticatedIdentity=" + name);
133            } catch (LoginException e) {
134                log.warn("Failed to create custom unauthenticatedIdentity", e);
135            }
136        }
137    }
138
139    public boolean logout() throws LoginException {
140        log.trace("logout");
141        // Remove the user identity
142        Principal identity = getIdentity();
143        Set<Principal> principals = subject.getPrincipals();
144        principals.remove(identity);
145        // Remove any added Groups...
146        return true;
147    }
148
149    /**
150     * Finds or creates a Group with the given name. Subclasses should use this method to locate the 'Roles' group or
151     * create additional types of groups.
152     *
153     * @return A named Group from the principals set.
154     */
155    protected Group createGroup(String name, Set<Principal> principals) {
156        Group roles = null;
157        for (Principal principal : principals) {
158            if (!(principal instanceof Group)) {
159                continue;
160            }
161            Group grp = (Group) principal;
162            if (grp.getName().equals(name)) {
163                roles = grp;
164                break;
165            }
166        }
167        // If we did not find a group, create one
168        if (roles == null) {
169            roles = new GroupImpl(name);
170            principals.add(roles);
171        }
172        return roles;
173    }
174
175}