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