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.HashSet;
026    import java.util.Map;
027    import java.util.Properties;
028    import java.util.Set;
029    import java.util.regex.Matcher;
030    import java.util.regex.Pattern;
031    
032    import javax.security.auth.Subject;
033    import javax.security.auth.callback.Callback;
034    import javax.security.auth.callback.CallbackHandler;
035    import javax.security.auth.callback.NameCallback;
036    import javax.security.auth.callback.UnsupportedCallbackException;
037    import javax.security.auth.login.LoginException;
038    import javax.security.auth.spi.LoginModule;
039    
040    import org.apache.commons.logging.Log;
041    import org.apache.commons.logging.LogFactory;
042    import org.apache.geronimo.common.GeronimoSecurityException;
043    import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
044    import org.apache.geronimo.security.jaas.NamedUsernamePasswordCredential;
045    import org.apache.geronimo.system.serverinfo.ServerInfo;
046    
047    /**
048     * GeronimoPropertiesFileMappedPasswordCredentialLoginModule adds NamedUsernamePasswordCredentials to the Subject.
049     * The NamedUsernamePasswordCredential are specified in a properties file specified in the options. Each line of the
050     * properties file is of the form:
051     *
052     * username=credentials
053     *
054     * where credentials is a comma-separated list of credentials and a credential is of the form
055     * name:username=password
056     *
057     * Thus a typical line would be:
058     *
059     * whee=foo:bar=baz,foo2:bar2=baz2
060     *
061     * This login module does not check credentials so it should never be able to cause a login to succeed.
062     * Therefore the lifecycle methods must return false to indicate success or throw a LoginException to indicate failure.
063     *
064     * @version $Rev: 565912 $ $Date: 2007-08-14 17:03:11 -0400 (Tue, 14 Aug 2007) $
065     */
066    public class GeronimoPropertiesFileMappedPasswordCredentialLoginModule implements LoginModule {
067    
068        private static final Log log = LogFactory.getLog(GeronimoPropertiesFileMappedPasswordCredentialLoginModule.class);
069        public final static String CREDENTIALS_URI = "credentialsURI";
070        private final static Pattern pattern = Pattern.compile("([^:,=]*):([^:,=]*)=([^:,=]*)");
071    
072        private final Set<NamedUsernamePasswordCredential> passwordCredentials = new HashSet<NamedUsernamePasswordCredential>();
073        private final Properties credentials = new Properties();
074    
075        private Subject subject;
076        private CallbackHandler callbackHandler;
077    
078        public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
079            this.subject = subject;
080            this.callbackHandler = callbackHandler;
081            try {
082                ServerInfo serverInfo = (ServerInfo) options.get(JaasLoginModuleUse.SERVERINFO_LM_OPTION);
083                final String credentials = (String) options.get(CREDENTIALS_URI);
084                if (credentials == null) {
085                    throw new IllegalArgumentException(CREDENTIALS_URI + " must be provided!");
086                }
087                URI usersURI = new URI(credentials);
088                loadProperties(serverInfo, usersURI);
089            } catch (Exception e) {
090                log.error("Initialization failed", e);
091                throw new IllegalArgumentException("Unable to configure properties file login module: " + e.getMessage(), e);
092            }
093        }
094    
095        private void loadProperties(ServerInfo serverInfo, URI credentialsURI) throws GeronimoSecurityException {
096            try {
097                URI userFile = serverInfo.resolveServer(credentialsURI);
098                InputStream stream = userFile.toURL().openStream();
099                credentials.load(stream);
100                stream.close();
101            } catch (Exception e) {
102                log.error("Properties File Login Module - data load failed", e);
103                throw new GeronimoSecurityException(e);
104            }
105        }
106    
107        public boolean login() throws LoginException {
108            Callback[] callbacks = new Callback[1];
109            callbacks[0] = new NameCallback("User name");
110            try {
111                callbackHandler.handle(callbacks);
112            } catch (java.io.IOException e) {
113                throw (LoginException) new LoginException("Unlikely IOException").initCause(e);
114            } catch (UnsupportedCallbackException e) {
115                throw (LoginException) new LoginException("Unlikely UnsupportedCallbackException").initCause(e);
116            }
117            String userName = ((NameCallback) callbacks[0]).getName();
118            String unparsedCredentials = credentials.getProperty(userName);
119            if (unparsedCredentials != null) {
120                parseCredentials(unparsedCredentials, passwordCredentials);
121            }
122            return false;
123        }
124    
125        void parseCredentials(String unparsedCredentials, Set<NamedUsernamePasswordCredential> passwordCredentials) {
126            Matcher matcher = pattern.matcher(unparsedCredentials);
127            while (matcher.find()) {
128                String credentialName = matcher.group(1);
129                String credentialUser = matcher.group(2);
130                String credentialPassword = matcher.group(3);
131                NamedUsernamePasswordCredential credential = new NamedUsernamePasswordCredential(credentialUser, credentialPassword.toCharArray(), credentialName);
132                passwordCredentials.add(credential);
133            }
134        }
135    
136        public boolean commit() throws LoginException {
137            subject.getPrivateCredentials().addAll(passwordCredentials);
138            return false;
139        }
140    
141        public boolean abort() throws LoginException {
142            passwordCredentials.clear();
143            return false;
144        }
145    
146        public boolean logout() throws LoginException {
147            passwordCredentials.clear();
148            return false;
149        }
150    }