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