001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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.Principal; 024 import java.security.cert.X509Certificate; 025 import java.util.Arrays; 026 import java.util.Collections; 027 import java.util.Enumeration; 028 import java.util.HashMap; 029 import java.util.HashSet; 030 import java.util.Iterator; 031 import java.util.List; 032 import java.util.Map; 033 import java.util.Properties; 034 import java.util.Set; 035 import javax.security.auth.Subject; 036 import javax.security.auth.callback.Callback; 037 import javax.security.auth.callback.CallbackHandler; 038 import javax.security.auth.callback.UnsupportedCallbackException; 039 import javax.security.auth.login.LoginException; 040 import javax.security.auth.login.FailedLoginException; 041 import javax.security.auth.spi.LoginModule; 042 import javax.security.auth.x500.X500Principal; 043 044 import org.apache.commons.logging.Log; 045 import org.apache.commons.logging.LogFactory; 046 import org.apache.geronimo.common.GeronimoSecurityException; 047 import org.apache.geronimo.security.jaas.JaasLoginModuleUse; 048 import org.apache.geronimo.security.jaas.WrappingLoginModule; 049 import org.apache.geronimo.system.serverinfo.ServerInfo; 050 051 052 /** 053 * An example LoginModule that reads a list of credentials and group from a file on disk. 054 * Authentication is provided by the SSL layer supplying the client certificate. 055 * All we check is that it is present. The 056 * file should be formatted using standard Java properties syntax. Expects 057 * to be run by a GenericSecurityRealm (doesn't work on its own). 058 * 059 * The usersURI property file should have lines of the form token=certificatename 060 * where certificate name is X509Certificate.getSubjectX500Principal().getName() 061 * 062 * The groupsURI property file should have lines of the form group=token1,token2,... 063 * where the tokens were associated to the certificate names in the usersURI properties file. 064 * 065 * This login module checks security credentials so the lifecycle methods must return true to indicate success 066 * or throw LoginException to indicate failure. 067 * 068 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 069 */ 070 public class CertificatePropertiesFileLoginModule implements LoginModule { 071 private static Log log = LogFactory.getLog(CertificatePropertiesFileLoginModule.class); 072 public final static String USERS_URI = "usersURI"; 073 public final static String GROUPS_URI = "groupsURI"; 074 public final static List<String> supportedOptions = Collections.unmodifiableList(Arrays.asList(USERS_URI, GROUPS_URI)); 075 076 private final Map users = new HashMap(); 077 final Map groups = new HashMap(); 078 079 private Subject subject; 080 private CallbackHandler handler; 081 private X500Principal principal; 082 private boolean loginSucceeded; 083 private final Set<Principal> allPrincipals = new HashSet<Principal>(); 084 085 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { 086 this.subject = subject; 087 this.handler = callbackHandler; 088 for(Object option: options.keySet()) { 089 if(!supportedOptions.contains(option) && !JaasLoginModuleUse.supportedOptions.contains(option) 090 && !WrappingLoginModule.supportedOptions.contains(option)) { 091 log.warn("Ignoring option: "+option+". Not supported."); 092 } 093 } 094 try { 095 ServerInfo serverInfo = (ServerInfo) options.get(JaasLoginModuleUse.SERVERINFO_LM_OPTION); 096 URI usersURI = new URI((String)options.get(USERS_URI)); 097 URI groupsURI = new URI((String)options.get(GROUPS_URI)); 098 loadProperties(serverInfo, usersURI, groupsURI); 099 } catch (Exception e) { 100 log.error(e); 101 throw new IllegalArgumentException("Unable to configure properties file login module: "+e.getMessage(), e); 102 } 103 } 104 105 public void loadProperties(ServerInfo serverInfo, URI usersURI, URI groupURI) throws GeronimoSecurityException { 106 try { 107 URI userFile = serverInfo.resolve(usersURI); 108 URI groupFile = serverInfo.resolve(groupURI); 109 InputStream stream = userFile.toURL().openStream(); 110 Properties tmpUsers = new Properties(); 111 tmpUsers.load(stream); 112 stream.close(); 113 114 for (Iterator iterator = tmpUsers.entrySet().iterator(); iterator.hasNext();) { 115 Map.Entry entry = (Map.Entry) iterator.next(); 116 users.put(entry.getValue(), entry.getKey()); 117 } 118 119 Properties temp = new Properties(); 120 stream = groupFile.toURL().openStream(); 121 temp.load(stream); 122 stream.close(); 123 124 Enumeration e = temp.keys(); 125 while (e.hasMoreElements()) { 126 String groupName = (String) e.nextElement(); 127 String[] userList = ((String) temp.get(groupName)).split(","); 128 129 Set userset = (Set) groups.get(groupName); 130 if (userset == null) { 131 userset = new HashSet(); 132 groups.put(groupName, userset); 133 } 134 135 for (int i = 0; i < userList.length; i++) { 136 String userName = userList[i]; 137 userset.add(userName); 138 } 139 } 140 141 } catch (Exception e) { 142 log.error("Properties File Login Module - data load failed", e); 143 throw new GeronimoSecurityException(e); 144 } 145 } 146 147 148 /** 149 * This LoginModule is not to be ignored. So, this method should never return false. 150 * @return true if authentication succeeds, or throw a LoginException such as FailedLoginException 151 * if authentication fails 152 */ 153 public boolean login() throws LoginException { 154 loginSucceeded = false; 155 Callback[] callbacks = new Callback[1]; 156 157 callbacks[0] = new CertificateCallback(); 158 try { 159 handler.handle(callbacks); 160 } catch (IOException ioe) { 161 throw (LoginException) new LoginException().initCause(ioe); 162 } catch (UnsupportedCallbackException uce) { 163 throw (LoginException) new LoginException().initCause(uce); 164 } 165 assert callbacks.length == 1; 166 X509Certificate certificate = ((CertificateCallback)callbacks[0]).getCertificate(); 167 if (certificate == null) { 168 throw new FailedLoginException(); 169 } 170 principal = certificate.getSubjectX500Principal(); 171 172 if(!users.containsKey(principal.getName())) { 173 // Clear out the private state 174 principal = null; 175 throw new FailedLoginException(); 176 } 177 178 loginSucceeded = true; 179 return true; 180 } 181 182 /* 183 * @exception LoginException if login succeeded but commit failed. 184 * 185 * @return true if login succeeded and commit succeeded, or false if login failed but commit succeeded. 186 */ 187 public boolean commit() throws LoginException { 188 if(loginSucceeded) { 189 allPrincipals.add(principal); 190 String userName = (String) users.get(principal.getName()); 191 allPrincipals.add(new GeronimoUserPrincipal(userName)); 192 193 Iterator e = groups.keySet().iterator(); 194 while (e.hasNext()) { 195 String groupName = (String) e.next(); 196 Set users = (Set) groups.get(groupName); 197 Iterator iter = users.iterator(); 198 while (iter.hasNext()) { 199 String user = (String) iter.next(); 200 if (userName.equals(user)) { 201 allPrincipals.add(new GeronimoGroupPrincipal(groupName)); 202 break; 203 } 204 } 205 } 206 subject.getPrincipals().addAll(allPrincipals); 207 } 208 // Clear out the private state 209 principal = null; 210 211 return loginSucceeded; 212 } 213 214 public boolean abort() throws LoginException { 215 if(loginSucceeded) { 216 // Clear out the private state 217 principal = null; 218 allPrincipals.clear(); 219 } 220 return loginSucceeded; 221 } 222 223 public boolean logout() throws LoginException { 224 // Clear out the private state 225 loginSucceeded = false; 226 principal = null; 227 if(!subject.isReadOnly()) { 228 // Remove principals added by this LoginModule 229 subject.getPrincipals().removeAll(allPrincipals); 230 } 231 allPrincipals.clear(); 232 return true; 233 } 234 235 }