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    }