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 }