001    /*
002     * Copyright 2010 the original author or authors.
003     * 
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     *  Unless required by applicable law or agreed to in writing, software
011     *  distributed under the License is distributed on an "AS IS" BASIS,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package com.hs.mail.security.login;
017    
018    import java.security.Principal;
019    import java.util.Map;
020    import java.util.Set;
021    
022    import javax.security.auth.Subject;
023    import javax.security.auth.callback.Callback;
024    import javax.security.auth.callback.CallbackHandler;
025    import javax.security.auth.callback.NameCallback;
026    import javax.security.auth.callback.PasswordCallback;
027    import javax.security.auth.login.FailedLoginException;
028    import javax.security.auth.login.LoginException;
029    import javax.security.auth.spi.LoginModule;
030    
031    /**
032     * Base class for custom LoginModule.
033     * 
034     * @author Won Chul Doh
035     * @since Jul 18, 2007
036     * 
037     */
038    public abstract class BasicLoginModule implements LoginModule {
039    
040            /**
041             * The authentication status.
042             */
043            protected boolean success;
044    
045            /**
046             * The commit status.
047             */
048            protected boolean commitSuccess;
049    
050            /**
051             * The Subject to be authenticated.
052             */
053            protected Subject subject;
054    
055            /**
056             * The Principals authenticated.
057             */
058            protected Principal[] principals;
059    
060            /**
061             * A CallbackHandler for communicating with the end user (prompting for
062             * usernames and passwords, for example).
063             */
064            protected CallbackHandler callbackHandler;
065    
066            /**
067             * State shared with other configured LoginModules.
068             */
069            protected Map sharedState;
070    
071            /**
072             * Options specified in the login Configuration for this particular
073             * LoginModule.
074             */
075            protected Map options;
076    
077            /**
078             * Class for password encoder
079             */
080            protected Class encoder;
081            
082            protected boolean debug = false;
083    
084            private static final Class DEFAULT_ENCODER_CLASS = com.hs.mail.security.login.PlaintextPasswordEncoder.class;
085    
086            protected BasicLoginModule() {
087                    success = false;
088                    commitSuccess = false;
089                    principals = null;
090                    encoder = DEFAULT_ENCODER_CLASS;
091            }
092    
093        /**
094         * Overriding to allow for proper initialization.
095         * 
096         * Standard JAAS.
097         */
098            public void initialize(Subject subject, CallbackHandler callbackHandler,
099                            Map<String, ?> sharedState, Map<String, ?> options) {
100            this.subject = subject;
101            this.callbackHandler = callbackHandler;
102            this.sharedState = sharedState;
103            this.options = options;
104            String s = getOption("encoder", null);
105            if (s != null) {
106                    try {
107                            encoder = Class.forName(s);
108                    } catch (ClassNotFoundException ex) {
109                                    throw new IllegalArgumentException(
110                                                    "Password encoder not found: " + s);
111                    }
112            }
113            debug = "true".equals(getOption("debug", "false"));
114            }
115    
116        /**
117         * Overriding to allow for certificate-based login.
118         * 
119         * Standard JAAS.
120         */
121            public boolean login() throws LoginException {
122                    if (null == callbackHandler) {
123                            throw new LoginException("Error: no CallbackHandler available "
124                                            + "to gather authentication information from the user");
125                    }
126                    try {
127                            // Setup default callback handlers.
128                            Callback[] callbacks = getDefaultCallbacks();
129    
130                            callbackHandler.handle(callbacks);
131    
132                            principals = validate(callbacks);
133    
134                            if (null == principals) {
135                                    throw new FailedLoginException(
136                                                    "Authentication failed: Password does not match");
137                            } else {
138                                    success = true;
139                            }
140                            return true;
141                    } catch (LoginException ex) {
142                            throw ex;
143                    } catch (Exception ex) {
144                            throw new LoginException(ex.getMessage());
145                    }
146            }
147    
148        /**
149         * Standard JAAS override.
150         */
151            public boolean logout() throws LoginException {
152                    subject.getPrincipals().clear();
153            success = false;
154            commitSuccess = false;
155                    principals = null;
156                    return true;
157            }
158    
159        /**
160         * Standard JAAS override.
161         */
162            public boolean abort() throws LoginException {
163                    // Clean out state
164            logout();
165                    return true;
166            }
167    
168        /**
169         * Overriding to complete login process.
170         * 
171         * Standard JAAS.
172         */
173            public boolean commit() throws LoginException {
174                    if (success) {
175                            if (subject.isReadOnly()) {
176                                    throw new LoginException("Subject is Readonly");
177                            }
178                            Set<Principal> p = subject.getPrincipals();
179                            for (int i = 0; i < principals.length; i++)
180                                    p.add(principals[i]);
181                            commitSuccess = true;
182                    }
183                    return commitSuccess;
184            }
185            
186            protected boolean checkPassword(String encoded, char plain[])
187                            throws LoginException {
188                    try {
189                            PasswordEncoder e = (PasswordEncoder) encoder.newInstance();
190                            return e.compare(encoded, plain);
191                    } catch (Exception e) {
192                            throw new LoginException("Internal error");
193                    }
194            }
195    
196            protected abstract Principal[] validate(Callback[] callbacks)
197                            throws LoginException;
198    
199            protected Callback[] getDefaultCallbacks() {
200                    // Setup default callback handlers.
201                    Callback[] callbacks = { new NameCallback("user name: "),
202                                    new PasswordCallback("password: ", false) };
203                    return callbacks;
204            }
205    
206            protected String getOption(String name, String defaultValue) {
207                    if (null == options) {
208                            return defaultValue;
209                    }
210                    String option = (String) options.get(name);
211                    return (null == option) ? defaultValue : option;
212            }
213    
214    }