001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 021 package org.apache.geronimo.security.realm.providers; 022 023 import java.io.InputStream; 024 import java.net.URI; 025 import java.util.Arrays; 026 import java.util.Collections; 027 import java.util.HashSet; 028 import java.util.List; 029 import java.util.Map; 030 import java.util.Properties; 031 import java.util.Set; 032 import java.util.regex.Matcher; 033 import java.util.regex.Pattern; 034 035 import javax.security.auth.DestroyFailedException; 036 import javax.security.auth.Subject; 037 import javax.security.auth.callback.Callback; 038 import javax.security.auth.callback.CallbackHandler; 039 import javax.security.auth.callback.NameCallback; 040 import javax.security.auth.callback.UnsupportedCallbackException; 041 import javax.security.auth.login.LoginException; 042 import javax.security.auth.spi.LoginModule; 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.NamedUsernamePasswordCredential; 049 import org.apache.geronimo.security.jaas.WrappingLoginModule; 050 import org.apache.geronimo.system.serverinfo.ServerInfo; 051 052 /** 053 * GeronimoPropertiesFileMappedPasswordCredentialLoginModule adds NamedUsernamePasswordCredentials to the Subject. 054 * The NamedUsernamePasswordCredential are specified in a properties file specified in the options. Each line of the 055 * properties file is of the form: 056 * 057 * username=credentials 058 * 059 * where credentials is a comma-separated list of credentials and a credential is of the form 060 * name:username=password 061 * 062 * Thus a typical line would be: 063 * 064 * whee=foo:bar=baz,foo2:bar2=baz2 065 * 066 * This login module does not check credentials so it should never be able to cause a login to succeed. 067 * Therefore the lifecycle methods must return false to indicate success or throw a LoginException to indicate failure. 068 * 069 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 070 */ 071 public class GeronimoPropertiesFileMappedPasswordCredentialLoginModule implements LoginModule { 072 073 private static final Log log = LogFactory.getLog(GeronimoPropertiesFileMappedPasswordCredentialLoginModule.class); 074 public final static String CREDENTIALS_URI = "credentialsURI"; 075 public final static List<String> supportedOptions = Collections.unmodifiableList(Arrays.asList(CREDENTIALS_URI)); 076 private final static Pattern pattern = Pattern.compile("([^:,=]*):([^:,=]*)=([^:,=]*)"); 077 078 private final Set<NamedUsernamePasswordCredential> passwordCredentials = new HashSet<NamedUsernamePasswordCredential>(); 079 private final Properties credentials = new Properties(); 080 private String userName; 081 082 private Subject subject; 083 private CallbackHandler callbackHandler; 084 085 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { 086 this.subject = subject; 087 this.callbackHandler = 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 final String credentials = (String) options.get(CREDENTIALS_URI); 097 if (credentials == null) { 098 throw new IllegalArgumentException(CREDENTIALS_URI + " must be provided!"); 099 } 100 URI usersURI = new URI(credentials); 101 loadProperties(serverInfo, usersURI); 102 } catch (Exception e) { 103 log.error("Initialization failed", e); 104 throw new IllegalArgumentException("Unable to configure properties file login module: " + e.getMessage(), e); 105 } 106 } 107 108 private void loadProperties(ServerInfo serverInfo, URI credentialsURI) throws GeronimoSecurityException { 109 try { 110 URI userFile = serverInfo.resolveServer(credentialsURI); 111 InputStream stream = userFile.toURL().openStream(); 112 credentials.load(stream); 113 stream.close(); 114 } catch (Exception e) { 115 log.error("Properties File Login Module - data load failed", e); 116 throw new GeronimoSecurityException(e); 117 } 118 } 119 120 public boolean login() throws LoginException { 121 Callback[] callbacks = new Callback[1]; 122 callbacks[0] = new NameCallback("User name"); 123 try { 124 callbackHandler.handle(callbacks); 125 } catch (java.io.IOException e) { 126 throw (LoginException) new LoginException("Unlikely IOException").initCause(e); 127 } catch (UnsupportedCallbackException e) { 128 throw (LoginException) new LoginException("Unlikely UnsupportedCallbackException").initCause(e); 129 } 130 userName = ((NameCallback) callbacks[0]).getName(); 131 return false; 132 } 133 134 void parseCredentials(String unparsedCredentials, Set<NamedUsernamePasswordCredential> passwordCredentials) { 135 Matcher matcher = pattern.matcher(unparsedCredentials); 136 while (matcher.find()) { 137 String credentialName = matcher.group(1); 138 String credentialUser = matcher.group(2); 139 String credentialPassword = matcher.group(3); 140 NamedUsernamePasswordCredential credential = new NamedUsernamePasswordCredential(credentialUser, credentialPassword.toCharArray(), credentialName); 141 passwordCredentials.add(credential); 142 } 143 } 144 145 public boolean commit() throws LoginException { 146 String unparsedCredentials = credentials.getProperty(userName); 147 if (unparsedCredentials != null) { 148 parseCredentials(unparsedCredentials, passwordCredentials); 149 } 150 subject.getPrivateCredentials().addAll(passwordCredentials); 151 152 userName = null; 153 return false; 154 } 155 156 public boolean abort() throws LoginException { 157 userName = null; 158 for(NamedUsernamePasswordCredential credential : passwordCredentials) { 159 try{ 160 credential.destroy(); 161 } catch (DestroyFailedException e) { 162 // do nothing 163 } 164 } 165 passwordCredentials.clear(); 166 return false; 167 } 168 169 public boolean logout() throws LoginException { 170 if(!subject.isReadOnly()) { 171 subject.getPrivateCredentials().removeAll(passwordCredentials); 172 } 173 userName = null; 174 for(NamedUsernamePasswordCredential credential : passwordCredentials) { 175 try{ 176 credential.destroy(); 177 } catch (DestroyFailedException e) { 178 // do nothing 179 } 180 } 181 passwordCredentials.clear(); 182 return false; 183 } 184 }