    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.event;
    020
    021import org.apache.commons.lang3.ArrayUtils;
    022import org.apache.logging.log4j.Level;
    023import org.apache.logging.log4j.LogManager;
    024import org.apache.logging.log4j.Logger;
    025
    026import java.security.Principal;
    027
    028/**
    029 * <p>Event class for security events: login/logout, wiki group adds/changes, and authorization decisions. When a WikiSecurityEvent
    030 * is constructed, the security logger {@link #LOG} is notified.</p>
    031 * <p>These events are logged with priority <code>ERROR</code>:</p>
    032 * <ul>
    033 *   <li>login failed - bad credential or password</li>
    034 * </ul>
    035 * <p>These events are logged with priority <code>WARN</code>:</p>
    036 * <ul>
    037 *   <li>access denied</li>
    038 *   <li>login failed - credential expired</li>
    039 *   <li>login failed - account expired</li>
    040 * </ul>
    041 * <p>These events are logged with priority <code>INFO</code>:</p>
    042 * <ul>
    043 *   <li>login succeeded</li>
    044 *   <li>logout</li>
    045 *   <li>user profile name changed</li>
    046 * </ul>
    047 * <p>These events are logged with priority <code>DEBUG</code>:</p>
    048 * <ul>
    049 *   <li>access allowed</li>
    050 *   <li>add group</li>
    051 *   <li>remove group</li>
    052 *   <li>clear all groups</li>
    053 *   <li>add group member</li>
    054 *   <li>remove group member</li>
    055 *   <li>clear all members from group</li>
    056 * </ul>
    057 * @since 2.3.79
    058 */
    059public final class WikiSecurityEvent extends WikiEvent {
    060
    061    private static final long serialVersionUID    = -6751950399721334496L;
    062
    063    /** When a user's attempts to log in as guest, via cookies, using a password or otherwise. */
    064    public static final int   LOGIN_INITIATED          = 30;
    065
    066    /** When a user first accesses JSPWiki, but before logging in or setting a cookie. */
    067    public static final int   LOGIN_ANONYMOUS          = 31;
    068
    069    /** When a user sets a cookie to assert their identity. */
    070    public static final int   LOGIN_ASSERTED           = 32;
    071
    072    /** When a user authenticates with a username and password, or via container auth. */
    073    public static final int   LOGIN_AUTHENTICATED      = 40;
    074
    075    /** When a login fails due to account expiration. */
    076    public static final int   LOGIN_ACCOUNT_EXPIRED    = 41;
    077
    078    /** When a login fails due to credential expiration. */
    079    public static final int   LOGIN_CREDENTIAL_EXPIRED = 42;
    080
    081    /** When a login fails due to wrong username or password. */
    082    public static final int   LOGIN_FAILED             = 43;
    083
    084    /** When a user logs out. */
    085    public static final int   LOGOUT                   = 44;
    086
    087    /** When a Principal should be added to the Session */
    088    public static final int PRINCIPAL_ADD               = 35;
    089
    090    /** When a session expires. */
    091    public static final int   SESSION_EXPIRED          = 45;
    092
    093    /** When a new wiki group is added. */
    094    public static final int   GROUP_ADD                = 46;
    095
    096    /** When a wiki group is deleted. */
    097    public static final int   GROUP_REMOVE             = 47;
    098
    099    /** When all wiki groups are removed from GroupDatabase. */
    100    public static final int   GROUP_CLEAR_GROUPS       = 48;
    101
    102    /** When access to a resource is allowed. */
    103    public static final int   ACCESS_ALLOWED           = 51;
    104
    105    /** When access to a resource is allowed. */
    106    public static final int   ACCESS_DENIED            = 52;
    107
    108    /** When a user profile is saved. */
    109    public static final int   PROFILE_SAVE             = 53;
    110
    111    /** When a user profile name changes. */
    112    public static final int   PROFILE_NAME_CHANGED     = 54;
    113
    114    /** The security logging service. */
    115    private static final Logger LOG = LogManager.getLogger( "SecurityLog" );
    116
    117    private final Principal m_principal;
    118
    119    private final Object      m_target;
    120
    121    private static final int[] ERROR_EVENTS = { LOGIN_FAILED };
    122
    123    private static final int[] WARN_EVENTS  = { LOGIN_ACCOUNT_EXPIRED, LOGIN_CREDENTIAL_EXPIRED };
    124
    125    private static final int[] INFO_EVENTS  = { LOGIN_AUTHENTICATED, SESSION_EXPIRED, LOGOUT, PROFILE_NAME_CHANGED };
    126
    127    /**
    128     * Constructs a new instance of this event type, which signals a security event has occurred. The <code>source</code> parameter is
    129     * required, and may not be <code>null</code>. When the WikiSecurityEvent is constructed, the security logger {@link #LOG} is notified.
    130     *
    131     * @param src the source of the event, which can be any object: a wiki page, group or authentication/authentication/group manager.
    132     * @param type the type of event
    133     * @param principal the subject of the event, which may be <code>null</code>
    134     * @param target the changed Object, which may be <code>null</code>
    135     */
    136    public WikiSecurityEvent( final Object src, final int type, final Principal principal, final Object target ) {
    137        super( src, type );
    138        if( src == null ) {
    139            throw new IllegalArgumentException( "Argument(s) cannot be null." );
    140        }
    141        this.m_principal = principal;
    142        this.m_target = target;
    143        if( LOG.isEnabled( Level.ERROR ) && ArrayUtils.contains( ERROR_EVENTS, type ) ) {
    144            LOG.error( this );
    145        } else if( LOG.isEnabled( Level.WARN ) && ArrayUtils.contains( WARN_EVENTS, type ) ) {
    146            LOG.warn( this );
    147        } else if( LOG.isEnabled( Level.INFO ) && ArrayUtils.contains( INFO_EVENTS, type ) ) {
    148            LOG.info( this );
    149        }
    150        LOG.debug( this );
    151    }
    152
    153    /**
    154     * Constructs a new instance of this event type, which signals a security event has occurred. The <code>source</code> parameter
    155     * is required, and may not be <code>null</code>. When the WikiSecurityEvent is constructed, the security logger {@link #LOG}
    156     * is notified.
    157     *
    158     * @param src the source of the event, which can be any object: a wiki page, group or authentication/authentication/group manager.
    159     * @param type the type of event
    160     * @param target the changed Object, which may be <code>null</code>.
    161     */
    162    public WikiSecurityEvent( final Object src, final int type, final Object target ) {
    163        this( src, type, null, target );
    164    }
    165
    166    /**
    167     * Returns the principal to whom the operation applied, if supplied. This method may return <code>null</code>
    168     * <em>&#8212; and calling methods should check for this condition</em>.
    169     *
    170     * @return the changed object
    171     */
    172    public Object getPrincipal() {
    173        return m_principal;
    174    }
    175
    176    /**
    177     * Returns the object that was operated on, if supplied. This method may return <code>null</code>
    178     * <em>&#8212; and calling methods should check for this condition</em>.
    179     *
    180     * @return the changed object
    181     */
    182    public Object getTarget() {
    183        return m_target;
    184    }
    185
    186    /**
    187     * Prints a String (human-readable) representation of this object.
    188     *
    189     * @see java.lang.Object#toString()
    190     */
    191    @Override
    192    public String toString() {
    193        final StringBuilder msg = new StringBuilder();
    194        msg.append( "WikiSecurityEvent." );
    195        msg.append(  eventName( getType() ) );
    196        final Object obj = getSrc(); // cfr. https://forums.oracle.com/forums/thread.jspa?threadID=1184115
    197        msg.append( " [source=" ).append( obj.toString() );
    198        if( m_principal != null ) {
    199            msg.append( ", principal=" ).append( m_principal.getClass().getName() );
    200            msg.append( " " ).append( m_principal.getName() );
    201        }
    202        msg.append( ", target=" ).append( m_target );
    203        msg.append( "]" );
    204        return msg.toString();
    205    }
    206
    207    /**
    208     * Returns a textual representation of an event type.
    209     *
    210     * @param type the type
    211     * @return the string representation
    212     */
    213    public String eventName( final int type ) {
    214        switch( type ) {
    215            case LOGIN_AUTHENTICATED:       return "LOGIN_AUTHENTICATED";
    216            case LOGIN_ACCOUNT_EXPIRED:     return "LOGIN_ACCOUNT_EXPIRED";
    217            case LOGIN_CREDENTIAL_EXPIRED:  return "LOGIN_ACCOUNT_EXPIRED";
    218            case LOGIN_FAILED:              return "LOGIN_FAILED";
    219            case LOGOUT:                    return "LOGOUT";
    220            case PRINCIPAL_ADD:             return "PRINCIPAL_ADD";
    221            case SESSION_EXPIRED:           return "SESSION_EXPIRED";
    222            case GROUP_ADD:                 return "GROUP_ADD";
    223            case GROUP_REMOVE:              return "GROUP_REMOVE";
    224            case GROUP_CLEAR_GROUPS:        return "GROUP_CLEAR_GROUPS";
    225            case ACCESS_ALLOWED:            return "ACCESS_ALLOWED";
    226            case ACCESS_DENIED:             return "ACCESS_DENIED";
    227            case PROFILE_NAME_CHANGED:      return "PROFILE_NAME_CHANGED";
    228            case PROFILE_SAVE:              return "PROFILE_SAVE";
    229            default:                        return super.eventName();
    230        }
    231    }
    232
    233    /**
    234     *  Returns a human-readable description of the event type.
    235     *
    236     * @return a String description of the type
    237     */
    238    @Override
    239    public String getTypeDescription() {
    240        switch ( getType() ) {
    241            case LOGIN_AUTHENTICATED:       return "login authenticated";
    242            case LOGIN_ACCOUNT_EXPIRED:     return "login failed: expired account";
    243            case LOGIN_CREDENTIAL_EXPIRED:  return "login failed: credential expired";
    244            case LOGIN_FAILED:              return "login failed";
    245            case LOGOUT:                    return "user logged out";
    246            case PRINCIPAL_ADD:             return "new principal added";
    247            case SESSION_EXPIRED:           return "session expired";
    248            case GROUP_ADD:                 return "new group added";
    249            case GROUP_REMOVE:              return "group removed";
    250            case GROUP_CLEAR_GROUPS:        return "all groups cleared";
    251            case ACCESS_ALLOWED:            return "access allowed";
    252            case ACCESS_DENIED:             return "access denied";
    253            case PROFILE_NAME_CHANGED:      return "user profile name changed";
    254            case PROFILE_SAVE:              return "user profile saved";
    255            default:                        return super.getTypeDescription();
    256        }
    257    }
    258
    259}