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    package org.apache.wiki.auth.login;
020    
021    import java.security.Principal;
022    import java.util.Collection;
023    import java.util.HashSet;
024    import java.util.Map;
025    
026    import javax.security.auth.Subject;
027    import javax.security.auth.callback.CallbackHandler;
028    import javax.security.auth.login.LoginException;
029    import javax.security.auth.spi.LoginModule;
030    
031    import org.apache.log4j.Logger;
032    
033    import org.apache.wiki.auth.WikiPrincipal;
034    
035    /**
036     * Abstract JAAS {@link javax.security.auth.spi.LoginModule}that implements
037     * base functionality. The methods {@link #login()} and {@link #commit()} must
038     * be implemented by subclasses. The default implementations of
039     * {@link #initialize(Subject, CallbackHandler, Map, Map)}, {@link #abort()} and
040     * {@link #logout()} should be sufficient for most purposes.
041     * @since 2.3
042     */
043    public abstract class AbstractLoginModule implements LoginModule
044    {
045    
046        private static final Logger   log = Logger.getLogger( AbstractLoginModule.class );
047    
048        protected CallbackHandler m_handler;
049    
050        protected Map<String,?>             m_options;
051    
052        /**
053         * Collection of Principals set during login module initialization.
054         * These represent the user's identities prior to the overall login.
055         * Typically these will contain earlier, less-authoritative principals
056         * like a WikiPrincipal for the user cookie, or an IP address.
057         * These Principals are forcibly removed during the commit phase
058         * if login succeeds.
059         * @deprecated
060         */
061        protected Collection<Principal>      m_previousWikiPrincipals;
062    
063        /**
064         * Implementing classes should add Principals to this collection; these
065         * will be added to the principal set when the overall login succeeds.
066         * These Principals will be added to the Subject
067         * during the {@link #commit()} phase of login.
068         */
069        protected Collection<Principal> m_principals;
070    
071        /**
072         * Implementing classes should add Principals to this collection
073         * to specify what Principals <em>must</em> be removed if login for
074         * this module, or for the entire login configuration overall, fails.
075         * Generally, these will be Principals of type
076         * {@link org.apache.wiki.auth.authorize.Role}.
077         * @deprecated
078         */
079        protected Collection<Principal>      m_principalsToRemove;
080    
081        /**
082         * Implementing classes should add Principals to this collection to specify
083         * what Principals, perhaps suppled by other LoginModules, <em>must</em>
084         * be removed if login for this module, or for the entire login
085         * configuration overall, succeeds. Generally, these will be Principals of
086         * type {@link org.apache.wiki.auth.authorize.Role}. For example,
087         * {@link CookieAssertionLoginModule} adds
088         * {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS} to its
089         * <code>m_principalsToOverwrite</code> collection because when it
090         * succeeds, its own {@link org.apache.wiki.auth.authorize.Role#AUTHENTICATED}
091         * should over-write {@link org.apache.wiki.auth.authorize.Role#ANONYMOUS}.
092         * @deprecated
093         */
094        protected Collection<Principal>      m_principalsToOverwrite;
095    
096        protected Map<String,?>             m_state;
097    
098        protected Subject         m_subject;
099    
100        protected static final String NULL           = "(null)";
101    
102        /**
103         * Aborts the login; called if the LoginContext's overall authentication
104         * failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
105         * LoginModules did not succeed). Specifically, it removes
106         * Principals from the Subject that are associated with the
107         * individual LoginModule; these will be those contained in
108         * {@link #m_principalsToRemove}.
109         * It always returns <code>true</code>.
110         * @see javax.security.auth.spi.LoginModule#abort()
111         * @throws LoginException if the abort itself fails
112         * @return True, always.
113         */
114        public final boolean abort() throws LoginException
115        {
116            removePrincipals( m_principals );
117            removePrincipals( m_principalsToRemove );
118    
119            // Clear the principals/principalsToRemove sets
120            m_principals.clear();
121            m_principalsToRemove.clear();
122    
123            return true;
124        }
125    
126        /**
127         * Commits the login. If the overall login method succeeded, adds
128         * principals to the Subject's set; generally, these will be the user's
129         * actual Principal, plus one or more Role principals. The state of the
130         * <code>m_principals</code> member variable is consulted to determine
131         * whether to add the principals. If its size is 0 (because the login
132         * failed), the login is considered to have failed; in this case,
133         * all principals in {@link #m_principalsToRemove} are removed
134         * from the Subject's set. Otherwise, the principals added to
135         * <code>m_principals</code> in the {@link #login()} method are added to
136         * the Subject's set.
137         * @return <code>true</code> if the commit succeeded, or
138         *         <code>false</code> if the previous call to {@link #login()}
139         *         failed
140         * @see javax.security.auth.spi.LoginModule#commit()
141         */
142        public final boolean commit()
143        {
144            if ( succeeded() )
145            {
146                removePrincipals( m_previousWikiPrincipals );
147                for ( Principal principal : m_principals )
148                {
149                    m_subject.getPrincipals().add( principal );
150                    if ( log.isDebugEnabled() )
151                    {
152                        log.debug("Committed Principal " + principal.getName() );
153                    }
154                }
155                removePrincipals( m_principalsToOverwrite );
156                return true;
157            }
158    
159            // If login did not succeed, clean up after ourselves
160            removePrincipals( m_principals );
161            removePrincipals( m_principalsToRemove );
162    
163            // Clear the principals/principalsToRemove sets
164            m_principals.clear();
165            m_principalsToRemove.clear();
166    
167            return false;
168        }
169    
170        /**
171         * Initializes the LoginModule with a given <code>Subject</code>,
172         * callback handler, options and shared state. In particular, the member
173         * variable <code>m_principals</code> is initialized as a blank Set.
174         * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
175         *      javax.security.auth.callback.CallbackHandler, java.util.Map,
176         *      java.util.Map)
177         *      
178         * @param subject {@inheritDoc}
179         * @param callbackHandler {@inheritDoc}
180         * @param sharedState {@inheritDoc}
181         * @param options {@inheritDoc}
182         */
183        public final void initialize( Subject subject, CallbackHandler callbackHandler, Map<String,?> sharedState, Map<String,?> options )
184        {
185            m_previousWikiPrincipals = new HashSet<Principal>();
186            m_principals = new HashSet<Principal>();
187            m_principalsToRemove = new HashSet<Principal>();
188            m_principalsToOverwrite = new HashSet<Principal>();
189            m_subject = subject;
190            m_handler = callbackHandler;
191            m_state = sharedState;
192            m_options = options;
193            if ( subject == null )
194            {
195                throw new IllegalStateException( "Subject cannot be null" );
196            }
197            if ( callbackHandler == null )
198            {
199                throw new IllegalStateException( "Callback handler cannot be null" );
200            }
201            // Stash the previous WikiPrincipals; we will flush these if login succeeds
202            m_previousWikiPrincipals.addAll( subject.getPrincipals( WikiPrincipal.class ) );
203        }
204    
205        /**
206         * Logs in the user by calling back to the registered CallbackHandler with a
207         * series of callbacks. If the login succeeds, this method returns
208         * <code>true</code>
209         * @return <code>true</code> if the commit succeeded, or
210         *         <code>false</code> if this LoginModule should be ignored.
211         * @throws LoginException if the authentication fails
212         * @see javax.security.auth.spi.LoginModule#login()
213         */
214        public abstract boolean login() throws LoginException;
215    
216        /**
217         * Logs the user out. Removes all principals in {@link #m_principalsToRemove}
218         * from the Subject's principal set.
219         * @return <code>true</code> if the commit succeeded, or
220         *         <code>false</code> if this LoginModule should be ignored
221         * @throws LoginException if the logout itself fails
222         * @see javax.security.auth.spi.LoginModule#logout()
223         */
224        public final boolean logout() throws LoginException
225        {
226            removePrincipals( m_principals );
227            removePrincipals( m_principalsToRemove );
228    
229            // Clear the principals/principalsToRemove sets
230            m_principals.clear();
231            m_principalsToRemove.clear();
232    
233            return true;
234        }
235    
236        /**
237         * Returns <code>true</code> if the number of principals
238         * contained in {@link #m_principals} is non-zero;
239         * <code>false</code> otherwise.
240         * @return True, if a login has succeeded.
241         */
242        private boolean succeeded()
243        {
244            return m_principals.size() > 0;
245        }
246    
247        /**
248         * Removes a specified collection of Principals from the Subject's
249         * Principal set.
250         * @param principals the principals to remove
251         */
252        private void removePrincipals( Collection<Principal> principals )
253        {
254            for ( Principal principal : principals )
255            {
256                if ( m_subject.getPrincipals().contains( principal ) )
257                {
258                    m_subject.getPrincipals().remove( principal );
259                    if ( log.isDebugEnabled() )
260                    {
261                        log.debug("Removed Principal " + principal.getName() );
262                    }
263                }
264            }
265        }
266    
267    }