    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    @Override
    074    public final boolean abort()
    075    {
    076        removePrincipals( m_principals );
    077
    078        // Clear the principals/principalsToRemove sets
    079        m_principals.clear();
    080
    081        return true;
    082    }
    083
    084    /**
    085     * Commits the login. If the overall login method succeeded, adds
    086     * principals to the Subject's set; generally, these will be the user's
    087     * actual Principal, plus one or more Role principals. The state of the
    088     * <code>m_principals</code> member variable is consulted to determine
    089     * whether to add the principals. If its size is 0 (because the login
    090     * failed), the login is considered to have failed; in this case,
    091     * all principals in {@link #m_principals} are removed
    092     * from the Subject's set. Otherwise, the principals added to
    093     * <code>m_principals</code> in the {@link #login()} method are added to
    094     * the Subject's set.
    095     * @return <code>true</code> if the commit succeeded, or
    096     *         <code>false</code> if the previous call to {@link #login()}
    097     *         failed
    098     * @see javax.security.auth.spi.LoginModule#commit()
    099     */
    100    @Override
    101    public final boolean commit() {
    102        if ( succeeded() ) {
    103            for ( final Principal principal : m_principals ) {
    104                m_subject.getPrincipals().add( principal );
    105                LOG.debug("Committed Principal {}", principal.getName() );
    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    @Override
    132    public final void initialize(final Subject subject, final CallbackHandler callbackHandler, final Map<String,?> sharedState, final Map<String,?> options ) {
    133        m_principals = new HashSet<>();
    134        m_subject = subject;
    135        m_handler = callbackHandler;
    136        m_state = sharedState;
    137        m_options = options;
    138        if ( subject == null ) {
    139            throw new IllegalStateException( "Subject cannot be null" );
    140        }
    141        if ( callbackHandler == null ) {
    142            throw new IllegalStateException( "Callback handler cannot be null" );
    143        }
    144    }
    145
    146    /**
    147     * Logs in the user by calling back to the registered CallbackHandler with a
    148     * series of callbacks. If the login succeeds, this method returns
    149     * <code>true</code>
    150     * @return <code>true</code> if the commit succeeded, or
    151     *         <code>false</code> if this LoginModule should be ignored.
    152     * @throws LoginException if the authentication fails
    153     * @see javax.security.auth.spi.LoginModule#login()
    154     */
    155    @Override
    156    public abstract boolean login() throws LoginException;
    157
    158    /**
    159     * Logs the user out. Removes all principals in {@link #m_principals}
    160     * from the Subject's principal set.
    161     * @return <code>true</code> if the commit succeeded, or
    162     *         <code>false</code> if this LoginModule should be ignored
    163     * @see javax.security.auth.spi.LoginModule#logout()
    164     */
    165    @Override
    166    public final boolean logout() {
    167        removePrincipals( m_principals );
    168
    169        // Clear the principals/principalsToRemove sets
    170        m_principals.clear();
    171
    172        return true;
    173    }
    174
    175    /**
    176     * Returns <code>true</code> if the number of principals
    177     * contained in {@link #m_principals} is non-zero;
    178     * <code>false</code> otherwise.
    179     * @return True, if a login has succeeded.
    180     */
    181    private boolean succeeded()
    182    {
    183        return !m_principals.isEmpty();
    184    }
    185
    186    /**
    187     * Removes a specified collection of Principals from the Subject's
    188     * Principal set.
    189     * @param principals the principals to remove
    190     */
    191    private void removePrincipals( final Collection<Principal> principals ) {
    192        for( final Principal principal : principals ) {
    193            if ( m_subject.getPrincipals().contains( principal ) ) {
    194                m_subject.getPrincipals().remove( principal );
    195                LOG.debug("Removed Principal {}", principal.getName() );
    196            }
    197        }
    198    }
    199
    200}