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