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 }