001 /** 002 * 003 * Copyright 2003-2005 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.security.realm.providers; 019 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.net.URI; 023 import java.security.cert.X509Certificate; 024 import java.util.Enumeration; 025 import java.util.HashMap; 026 import java.util.HashSet; 027 import java.util.Iterator; 028 import java.util.Map; 029 import java.util.Properties; 030 import java.util.Set; 031 import java.util.Collection; 032 import javax.security.auth.Subject; 033 import javax.security.auth.callback.Callback; 034 import javax.security.auth.callback.CallbackHandler; 035 import javax.security.auth.callback.UnsupportedCallbackException; 036 import javax.security.auth.login.LoginException; 037 import javax.security.auth.login.FailedLoginException; 038 import javax.security.auth.spi.LoginModule; 039 import javax.security.auth.x500.X500Principal; 040 041 import org.apache.commons.logging.Log; 042 import org.apache.commons.logging.LogFactory; 043 import org.apache.geronimo.common.GeronimoSecurityException; 044 import org.apache.geronimo.security.jaas.JaasLoginModuleUse; 045 import org.apache.geronimo.system.serverinfo.ServerInfo; 046 047 048 /** 049 * An example LoginModule that reads a list of users and group from a file on disk. 050 * Authentication is provided by the SSL layer supplying the client certificate. 051 * All we check is that it is present. The 052 * file should be formatted using standard Java properties syntax. Expects 053 * to be run by a GenericSecurityRealm (doesn't work on its own). 054 * 055 * The usersURI property file should have lines of the form token=certificatename 056 * where certificate name is X509Certificate.getSubjectX500Principal().getName() 057 * 058 * The groupsURI property file should have lines of the form group=token1,token2,... 059 * where the tokens were associated to the certificate names in the usersURI properties file. 060 * 061 * @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $ 062 */ 063 public class CertificatePropertiesFileLoginModule implements LoginModule { 064 public final static String USERS_URI = "usersURI"; 065 public final static String GROUPS_URI = "groupsURI"; 066 private static Log log = LogFactory.getLog(CertificatePropertiesFileLoginModule.class); 067 private final Map users = new HashMap(); 068 final Map groups = new HashMap(); 069 070 Subject subject; 071 CallbackHandler handler; 072 X500Principal principal; 073 074 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { 075 this.subject = subject; 076 this.handler = callbackHandler; 077 try { 078 ServerInfo serverInfo = (ServerInfo) options.get(JaasLoginModuleUse.SERVERINFO_LM_OPTION); 079 URI usersURI = new URI((String)options.get(USERS_URI)); 080 URI groupsURI = new URI((String)options.get(GROUPS_URI)); 081 loadProperties(serverInfo, usersURI, groupsURI); 082 } catch (Exception e) { 083 log.error(e); 084 throw new IllegalArgumentException("Unable to configure properties file login module: "+e); 085 } 086 } 087 088 public void loadProperties(ServerInfo serverInfo, URI usersURI, URI groupURI) throws GeronimoSecurityException { 089 try { 090 URI userFile = serverInfo.resolve(usersURI); 091 URI groupFile = serverInfo.resolve(groupURI); 092 InputStream stream = userFile.toURL().openStream(); 093 Properties tmpUsers = new Properties(); 094 tmpUsers.load(stream); 095 stream.close(); 096 097 for (Iterator iterator = tmpUsers.entrySet().iterator(); iterator.hasNext();) { 098 Map.Entry entry = (Map.Entry) iterator.next(); 099 users.put(entry.getValue(), entry.getKey()); 100 } 101 102 Properties temp = new Properties(); 103 stream = groupFile.toURL().openStream(); 104 temp.load(stream); 105 stream.close(); 106 107 Enumeration e = temp.keys(); 108 while (e.hasMoreElements()) { 109 String groupName = (String) e.nextElement(); 110 String[] userList = ((String) temp.get(groupName)).split(","); 111 112 Set userset = (Set) groups.get(groupName); 113 if (userset == null) { 114 userset = new HashSet(); 115 groups.put(groupName, userset); 116 } 117 118 for (int i = 0; i < userList.length; i++) { 119 String userName = userList[i]; 120 userset.add(userName); 121 } 122 } 123 124 } catch (Exception e) { 125 log.error("Properties File Login Module - data load failed", e); 126 throw new GeronimoSecurityException(e); 127 } 128 } 129 130 131 public boolean login() throws LoginException { 132 Callback[] callbacks = new Callback[1]; 133 134 callbacks[0] = new CertificateCallback(); 135 try { 136 handler.handle(callbacks); 137 } catch (IOException ioe) { 138 throw (LoginException) new LoginException().initCause(ioe); 139 } catch (UnsupportedCallbackException uce) { 140 throw (LoginException) new LoginException().initCause(uce); 141 } 142 assert callbacks.length == 1; 143 X509Certificate certificate = ((CertificateCallback)callbacks[0]).getCertificate(); 144 if (certificate == null) { 145 return false; 146 } 147 principal = certificate.getSubjectX500Principal(); 148 149 if(!users.containsKey(principal.getName())) { 150 throw new FailedLoginException(); 151 } 152 return true; 153 } 154 155 public boolean commit() throws LoginException { 156 Set principals = subject.getPrincipals(); 157 158 principals.add(principal); 159 String userName = (String) users.get(principal.getName()); 160 principals.add(new GeronimoUserPrincipal(userName)); 161 162 Iterator e = groups.keySet().iterator(); 163 while (e.hasNext()) { 164 String groupName = (String) e.next(); 165 Set users = (Set) groups.get(groupName); 166 Iterator iter = users.iterator(); 167 while (iter.hasNext()) { 168 String user = (String) iter.next(); 169 if (userName.equals(user)) { 170 principals.add(new GeronimoGroupPrincipal(groupName)); 171 break; 172 } 173 } 174 } 175 176 return true; 177 } 178 179 public boolean abort() throws LoginException { 180 principal = null; 181 182 return true; 183 } 184 185 public boolean logout() throws LoginException { 186 principal = null; 187 //todo: should remove principals added by commit 188 return true; 189 } 190 191 /** 192 * Gets the names of all principal classes that may be populated into 193 * a Subject. 194 */ 195 public String[] getPrincipalClassNames() { 196 return new String[]{GeronimoUserPrincipal.class.getName(), GeronimoGroupPrincipal.class.getName()}; 197 } 198 199 /** 200 * Gets a list of all the principals of a particular type (identified by 201 * the principal class). These are available for manual role mapping. 202 */ 203 public String[] getPrincipalsOfClass(String className) { 204 Collection s; 205 if(className.equals(GeronimoGroupPrincipal.class.getName())) { 206 s = groups.keySet(); 207 } else if(className.equals(GeronimoUserPrincipal.class.getName())) { 208 s = users.values(); 209 } else if(className.equals(X500Principal.class.getName())) { 210 s = users.keySet(); 211 } else { 212 throw new IllegalArgumentException("No such principal class "+className); 213 } 214 return (String[]) s.toArray(new String[s.size()]); 215 } 216 }