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}