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