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}