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}