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