001 /** 002 * 003 * Copyright 2003-2004 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 org.apache.commons.logging.Log; 021 import org.apache.commons.logging.LogFactory; 022 import org.apache.geronimo.common.GeronimoSecurityException; 023 import org.apache.geronimo.security.jaas.JaasLoginModuleUse; 024 import org.apache.geronimo.system.serverinfo.ServerInfo; 025 026 import javax.security.auth.Subject; 027 import javax.security.auth.callback.Callback; 028 import javax.security.auth.callback.CallbackHandler; 029 import javax.security.auth.callback.NameCallback; 030 import javax.security.auth.callback.PasswordCallback; 031 import javax.security.auth.callback.UnsupportedCallbackException; 032 import javax.security.auth.login.FailedLoginException; 033 import javax.security.auth.login.LoginException; 034 import javax.security.auth.spi.LoginModule; 035 import java.io.IOException; 036 import java.io.InputStream; 037 import java.net.URI; 038 import java.util.Enumeration; 039 import java.util.HashMap; 040 import java.util.HashSet; 041 import java.util.Iterator; 042 import java.util.Map; 043 import java.util.Properties; 044 import java.util.Set; 045 046 047 /** 048 * A LoginModule that reads a list of users and group from files on disk. The 049 * files should be formatted using standard Java properties syntax. Expects 050 * to be run by a GenericSecurityRealm (doesn't work on its own). 051 * 052 * @version $Rev: 406493 $ $Date: 2006-05-14 18:14:11 -0700 (Sun, 14 May 2006) $ 053 */ 054 public class PropertiesFileLoginModule implements LoginModule { 055 public final static String USERS_URI = "usersURI"; 056 public final static String GROUPS_URI = "groupsURI"; 057 private static Log log = LogFactory.getLog(PropertiesFileLoginModule.class); 058 final Properties users = new Properties(); 059 final Map groups = new HashMap(); 060 061 Subject subject; 062 CallbackHandler handler; 063 String username; 064 String password; 065 066 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { 067 this.subject = subject; 068 this.handler = callbackHandler; 069 try { 070 ServerInfo serverInfo = (ServerInfo) options.get(JaasLoginModuleUse.SERVERINFO_LM_OPTION); 071 final String users = (String)options.get(USERS_URI); 072 final String groups = (String)options.get(GROUPS_URI); 073 if(users == null || groups == null) { 074 throw new IllegalArgumentException("Both "+USERS_URI+" and "+GROUPS_URI+" must be provided!"); 075 } 076 URI usersURI = new URI(users); 077 URI groupsURI = new URI(groups); 078 loadProperties(serverInfo, usersURI, groupsURI); 079 } catch (Exception e) { 080 log.error("Initialization failed", e); 081 throw new IllegalArgumentException("Unable to configure properties file login module: "+e.getMessage()); 082 } 083 } 084 085 public void loadProperties(ServerInfo serverInfo, URI userURI, URI groupURI) throws GeronimoSecurityException { 086 try { 087 URI userFile = serverInfo.resolveServer(userURI); 088 URI groupFile = serverInfo.resolveServer(groupURI); 089 InputStream stream = userFile.toURL().openStream(); 090 users.load(stream); 091 stream.close(); 092 093 Properties temp = new Properties(); 094 stream = groupFile.toURL().openStream(); 095 temp.load(stream); 096 stream.close(); 097 098 Enumeration e = temp.keys(); 099 while (e.hasMoreElements()) { 100 String groupName = (String) e.nextElement(); 101 String[] userList = ((String) temp.get(groupName)).split(","); 102 103 Set userset = (Set) groups.get(groupName); 104 if (userset == null) { 105 userset = new HashSet(); 106 groups.put(groupName, userset); 107 } 108 109 for (int i = 0; i < userList.length; i++) { 110 userset.add(userList[i]); 111 } 112 } 113 114 } catch (Exception e) { 115 log.error("Properties File Login Module - data load failed", e); 116 throw new GeronimoSecurityException(e); 117 } 118 } 119 120 121 public boolean login() throws LoginException { 122 Callback[] callbacks = new Callback[2]; 123 124 callbacks[0] = new NameCallback("User name"); 125 callbacks[1] = new PasswordCallback("Password", false); 126 try { 127 handler.handle(callbacks); 128 } catch (IOException ioe) { 129 throw (LoginException) new LoginException().initCause(ioe); 130 } catch (UnsupportedCallbackException uce) { 131 throw (LoginException) new LoginException().initCause(uce); 132 } 133 assert callbacks.length == 2; 134 username = ((NameCallback) callbacks[0]).getName(); 135 if(username == null || username.equals("")) { 136 return false; 137 } 138 String realPassword = users.getProperty(username); 139 char[] entered = ((PasswordCallback) callbacks[1]).getPassword(); 140 password = entered == null ? null : new String(entered); 141 boolean result = (realPassword == null && password == null) || 142 (realPassword != null && password != null && realPassword.equals(password)); 143 if(!result) { 144 throw new FailedLoginException(); 145 } 146 return true; 147 } 148 149 public boolean commit() throws LoginException { 150 Set principals = subject.getPrincipals(); 151 152 principals.add(new GeronimoUserPrincipal(username)); 153 154 Iterator e = groups.keySet().iterator(); 155 while (e.hasNext()) { 156 String groupName = (String) e.next(); 157 Set users = (Set) groups.get(groupName); 158 Iterator iter = users.iterator(); 159 while (iter.hasNext()) { 160 String user = (String) iter.next(); 161 if (username.equals(user)) { 162 principals.add(new GeronimoGroupPrincipal(groupName)); 163 break; 164 } 165 } 166 } 167 168 return true; 169 } 170 171 public boolean abort() throws LoginException { 172 username = null; 173 password = null; 174 175 return true; 176 } 177 178 public boolean logout() throws LoginException { 179 username = null; 180 password = null; 181 //todo: should remove principals added by commit 182 return true; 183 } 184 185 /** 186 * Gets the names of all principal classes that may be populated into 187 * a Subject. 188 */ 189 public String[] getPrincipalClassNames() { 190 return new String[]{GeronimoUserPrincipal.class.getName(), GeronimoGroupPrincipal.class.getName()}; 191 } 192 193 /** 194 * Gets a list of all the principals of a particular type (identified by 195 * the principal class). These are available for manual role mapping. 196 */ 197 public String[] getPrincipalsOfClass(String className) { 198 Set s; 199 if(className.equals(GeronimoGroupPrincipal.class.getName())) { 200 s = groups.keySet(); 201 } else if(className.equals(GeronimoUserPrincipal.class.getName())) { 202 s = users.keySet(); 203 } else { 204 throw new IllegalArgumentException("No such principal class "+className); 205 } 206 return (String[]) s.toArray(new String[s.size()]); 207 } 208 }