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 */
019package org.apache.wiki.auth.login;
020
021import org.apache.log4j.Logger;
022
023import javax.security.auth.Subject;
024import javax.security.auth.callback.CallbackHandler;
025import javax.security.auth.login.LoginException;
026import javax.security.auth.spi.LoginModule;
027import java.security.Principal;
028import java.util.Collection;
029import java.util.HashSet;
030import java.util.Map;
031
032/**
033 * Abstract JAAS {@link javax.security.auth.spi.LoginModule}that implements
034 * base functionality. The methods {@link #login()} and {@link #commit()} must
035 * be implemented by subclasses. The default implementations of
036 * {@link #initialize(Subject, CallbackHandler, Map, Map)}, {@link #abort()} and
037 * {@link #logout()} should be sufficient for most purposes.
038 * @since 2.3
039 */
040public abstract class AbstractLoginModule implements LoginModule {
041
042    private static final Logger log = Logger.getLogger( AbstractLoginModule.class );
043
044    protected CallbackHandler m_handler;
045    protected Map< String, ? > m_options;
046
047    /**
048     * Implementing classes should add Principals to this collection; these
049     * will be added to the principal set when the overall login succeeds.
050     * These Principals will be added to the Subject
051     * during the {@link #commit()} phase of login.
052     */
053    protected Collection< Principal > m_principals;
054
055    protected Map< String, ? > m_state;
056
057    protected Subject m_subject;
058
059    protected static final String NULL = "(null)";
060
061    /**
062     * Aborts the login; called if the LoginContext's overall authentication
063     * failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
064     * LoginModules did not succeed). Specifically, it removes
065     * Principals from the Subject that are associated with the
066     * individual LoginModule; these will be those contained in
067     * {@link #m_principals}.
068     * It always returns <code>true</code>.
069     * @see javax.security.auth.spi.LoginModule#abort()
070     * @return True, always.
071     */
072    public final boolean abort()
073    {
074        removePrincipals( m_principals );
075
076        // Clear the principals/principalsToRemove sets
077        m_principals.clear();
078
079        return true;
080    }
081
082    /**
083     * Commits the login. If the overall login method succeeded, adds
084     * principals to the Subject's set; generally, these will be the user's
085     * actual Principal, plus one or more Role principals. The state of the
086     * <code>m_principals</code> member variable is consulted to determine
087     * whether to add the principals. If its size is 0 (because the login
088     * failed), the login is considered to have failed; in this case,
089     * all principals in {@link #m_principals} are removed
090     * from the Subject's set. Otherwise, the principals added to
091     * <code>m_principals</code> in the {@link #login()} method are added to
092     * the Subject's set.
093     * @return <code>true</code> if the commit succeeded, or
094     *         <code>false</code> if the previous call to {@link #login()}
095     *         failed
096     * @see javax.security.auth.spi.LoginModule#commit()
097     */
098    public final boolean commit() {
099        if ( succeeded() ) {
100            for ( final Principal principal : m_principals ) {
101                m_subject.getPrincipals().add( principal );
102                if ( log.isDebugEnabled() ) {
103                    log.debug("Committed Principal " + principal.getName() );
104                }
105            }
106            return true;
107        }
108
109        // If login did not succeed, clean up after ourselves
110        removePrincipals( m_principals );
111
112        // Clear the principals/principalsToRemove sets
113        m_principals.clear();
114
115        return false;
116    }
117
118    /**
119     * Initializes the LoginModule with a given <code>Subject</code>,
120     * callback handler, options and shared state. In particular, the member
121     * variable <code>m_principals</code> is initialized as a blank Set.
122     * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
123     *      javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
124     *      
125     * @param subject {@inheritDoc}
126     * @param callbackHandler {@inheritDoc}
127     * @param sharedState {@inheritDoc}
128     * @param options {@inheritDoc}
129     */
130    public final void initialize( final Subject subject, final CallbackHandler callbackHandler, final Map<String,?> sharedState, final Map<String,?> options ) {
131        m_principals = new HashSet<>();
132        m_subject = subject;
133        m_handler = callbackHandler;
134        m_state = sharedState;
135        m_options = options;
136        if ( subject == null ) {
137            throw new IllegalStateException( "Subject cannot be null" );
138        }
139        if ( callbackHandler == null ) {
140            throw new IllegalStateException( "Callback handler cannot be null" );
141        }
142    }
143
144    /**
145     * Logs in the user by calling back to the registered CallbackHandler with a
146     * series of callbacks. If the login succeeds, this method returns
147     * <code>true</code>
148     * @return <code>true</code> if the commit succeeded, or
149     *         <code>false</code> if this LoginModule should be ignored.
150     * @throws LoginException if the authentication fails
151     * @see javax.security.auth.spi.LoginModule#login()
152     */
153    public abstract boolean login() throws LoginException;
154
155    /**
156     * Logs the user out. Removes all principals in {@link #m_principals}
157     * from the Subject's principal set.
158     * @return <code>true</code> if the commit succeeded, or
159     *         <code>false</code> if this LoginModule should be ignored
160     * @see javax.security.auth.spi.LoginModule#logout()
161     */
162    public final boolean logout() {
163        removePrincipals( m_principals );
164
165        // Clear the principals/principalsToRemove sets
166        m_principals.clear();
167
168        return true;
169    }
170
171    /**
172     * Returns <code>true</code> if the number of principals
173     * contained in {@link #m_principals} is non-zero;
174     * <code>false</code> otherwise.
175     * @return True, if a login has succeeded.
176     */
177    private boolean succeeded()
178    {
179        return m_principals.size() > 0;
180    }
181
182    /**
183     * Removes a specified collection of Principals from the Subject's
184     * Principal set.
185     * @param principals the principals to remove
186     */
187    private void removePrincipals( final Collection<Principal> principals ) {
188        for ( final Principal principal : principals ) {
189            if ( m_subject.getPrincipals().contains( principal ) ) {
190                m_subject.getPrincipals().remove( principal );
191                if ( log.isDebugEnabled() ) {
192                    log.debug("Removed Principal " + principal.getName() );
193                }
194            }
195        }
196    }
197
198}