    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;
    020
    021import org.apache.wiki.api.core.Context;
    022import org.apache.wiki.api.core.Session;
    023import org.apache.wiki.api.engine.Initializable;
    024import org.apache.wiki.auth.authorize.Role;
    025import org.apache.wiki.event.WikiEventListener;
    026import org.apache.wiki.event.WikiEventManager;
    027import org.apache.wiki.event.WikiSecurityEvent;
    028
    029import javax.servlet.http.HttpServletResponse;
    030import java.io.IOException;
    031import java.security.AccessController;
    032import java.security.Permission;
    033import java.security.Principal;
    034
    035
    036/**
    037 * <p>Manages all access control and authorization; determines what authenticated users are allowed to do.</p>
    038 * <p>Privileges in JSPWiki are expressed as Java-standard {@link java.security.Permission} classes. There are two types of permissions:</p>
    039 * <ul>
    040 *   <li>{@link org.apache.wiki.auth.permissions.WikiPermission} - privileges that apply to an entire wiki instance: <em>e.g.,</em>
    041 *   editing user profiles, creating pages, creating groups</li>
    042 *   <li>{@link org.apache.wiki.auth.permissions.PagePermission} - privileges that apply to a single wiki page or range of pages:
    043 *   <em>e.g.,</em> reading, editing, renaming
    044 * </ul>
    045 * <p>Calling classes determine whether they are entitled to perform a particular action by constructing the appropriate permission first,
    046 * then passing it and the current {@link Session} to the {@link #checkPermission(Session, Permission)} method. If
    047 * the session's Subject possesses the permission, the action is allowed.</p>
    048 * <p>For WikiPermissions, the decision criteria is relatively simple: the caller either possesses the permission, as granted by the wiki
    049 * security policy -- or not.</p>
    050 * <p>For PagePermissions, the logic is exactly the same if the page being checked does not have an access control list. However, if the
    051 * page does have an ACL, the authorization decision is made based the <em>union</em> of the permissions granted in the ACL and in the
    052 * security policy. In other words, the user must be named in the ACL (or belong to a group or role that is named in the ACL) <em>and</em>
    053 * be granted (at least) the same permission in the security policy. We do this to prevent a user from gaining more permissions than they
    054 * already have, based on the security policy.</p>
    055 * <p>See the implementation on {@link #checkPermission(Session, Permission)} method for more information on the authorization logic.</p>
    056 *
    057 * @since 2.3
    058 * @see AuthenticationManager
    059 */
    060public interface AuthorizationManager extends Initializable {
    061
    062    /** The default external Authorizer is the {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer} */
    063    String DEFAULT_AUTHORIZER = "org.apache.wiki.auth.authorize.WebContainerAuthorizer";
    064
    065    /** Property that supplies the security policy file name, in WEB-INF. */
    066    String POLICY = "jspwiki.policy.file";
    067
    068    /** Name of the default security policy file, in WEB-INF. */
    069    String DEFAULT_POLICY = "jspwiki.policy";
    070
    071    /** The property name in jspwiki.properties for specifying the external {@link Authorizer}. */
    072    String PROP_AUTHORIZER = "jspwiki.authorizer";
    073
    074    /**
    075     * Returns <code>true</code> or <code>false</code>, depending on whether a Permission is allowed for the Subject associated with
    076     * a supplied Session. The access control algorithm works this way:
    077     * <ol>
    078     * <li>The {@link org.apache.wiki.api.core.Acl} for the page is obtained</li>
    079     * <li>The Subject associated with the current {@link org.apache.wiki.api.core.Session} is obtained</li>
    080     * <li>If the Subject's Principal set includes the Role Principal that is the administrator group, always allow the Permission</li>
    081     * <li>For all permissions, check to see if the Permission is allowed according to the default security policy. If it isn't, deny
    082     * the permission and halt further processing.</li>
    083     * <li>If there is an Acl, get the list of Principals assigned this Permission in the Acl: these will be role, group or user Principals,
    084     * or {@link org.apache.wiki.auth.acl.UnresolvedPrincipal}s (see below). Then iterate through the Subject's Principal set and determine
    085     * whether the user (Subject) possesses any one of these specified Roles or Principals.</li>
    086     * </ol>
    087     * <p>
    088     * Note that when iterating through the Acl's list of authorized Principals, it is possible that one or more of the Acl's Principal
    089     * entries are of type <code>UnresolvedPrincipal</code>. This means that the last time the ACL was read, the Principal (user, built-in
    090     * Role, authorizer Role, or wiki Group) could not be resolved: the Role was not valid, the user wasn't found in the UserDatabase, or
    091     * the Group wasn't known to (e.g., cached) in the GroupManager. If an <code>UnresolvedPrincipal</code> is encountered, this method
    092     * will attempt to resolve it first <em>before</em> checking to see if the Subject possesses this principal, by calling
    093     * {@link #resolvePrincipal(String)}. If the (re-)resolution does not succeed, the access check for the principal will fail by
    094     * definition (the Subject should never contain UnresolvedPrincipals).
    095     * </p>
    096     * <p>
    097     * If security not set to JAAS, will return true.
    098     * </p>
    099     *
    100     * @param session the current wiki session
    101     * @param permission the Permission being checked
    102     * @return the result of the Permission check
    103     */
    104    boolean checkPermission( Session session, Permission permission );
    105
    106    /**
    107     * <p>Determines if the Subject associated with a supplied Session contains a desired Role or GroupPrincipal. The algorithm
    108     * simply checks to see if the Subject possesses the Role or GroupPrincipal it in its Principal set. Note that any user (anonymous,
    109     * asserted, authenticated) can possess a built-in role. But a user <em>must</em> be authenticated to possess a role other than one
    110     * of the built-in ones. We do this to prevent privilege escalation.</p>
    111     * <p>For all other cases, this method returns <code>false</code>.</p>
    112     * <p>Note that this method does <em>not</em> consult the external Authorizer or GroupManager; it relies on the Principals that
    113     * have been injected into the user's Subject at login time, or after group creation/modification/deletion.</p>
    114     *
    115     * @param session the current wiki session, which must be non-null. If null, the result of this method always returns <code>false</code>
    116     * @param principal the Principal (role or group principal) to look for, which must be non-<code>null</code>. If <code>null</code>,
    117     *                  the result of this method always returns <code>false</code>
    118     * @return <code>true</code> if the Subject supplied with the WikiContext posesses the Role or GroupPrincipal, <code>false</code> otherwise
    119     */
    120    default boolean isUserInRole( final Session session, final Principal principal ) {
    121        if ( session == null || principal == null || AuthenticationManager.isUserPrincipal( principal ) ) {
    122            return false;
    123        }
    124
    125        // Any type of user can possess a built-in role
    126        if ( principal instanceof Role && Role.isBuiltInRole( (Role)principal ) ) {
    127            return session.hasPrincipal( principal );
    128        }
    129
    130        // Only authenticated users can possess groups or custom roles
    131        if ( session.isAuthenticated() && AuthenticationManager.isRolePrincipal( principal ) ) {
    132            return session.hasPrincipal( principal );
    133        }
    134        return false;
    135    }
    136
    137    /**
    138     * Returns the current external {@link Authorizer} in use. This method is guaranteed to return a properly-initialized Authorizer, unless
    139     * it could not be initialized. In that case, this method throws a {@link org.apache.wiki.auth.WikiSecurityException}.
    140     *
    141     * @throws org.apache.wiki.auth.WikiSecurityException if the Authorizer could not be initialized
    142     * @return the current Authorizer
    143     */
    144    Authorizer getAuthorizer() throws WikiSecurityException;
    145
    146    /**
    147     * <p>Determines if the Subject associated with a supplied Session contains a desired user Principal or built-in Role principal,
    148     * OR is a member a Group or external Role. The rules are as follows:</p>
    149     * <ol>
    150     * <li>First, if desired Principal is a Role or GroupPrincipal, delegate to {@link #isUserInRole(Session, Principal)} and
    151     * return the result.</li>
    152     * <li>Otherwise, we're looking for a user Principal, so iterate through the Principal set and see if any share the same name as the
    153     * one we are looking for.</li>
    154     * </ol>
    155     * <p><em>Note: if the Principal parameter is a user principal, the session must be authenticated in order for the user to "possess it".
    156     * Anonymous or asserted sessions will never posseess a named user principal.</em></p>
    157     *
    158     * @param session the current wiki session, which must be non-null. If null, the result of this method always returns <code>false</code>
    159     * @param principal the Principal (role, group, or user principal) to look for, which must be non-null. If null, the result of this
    160     *                  method always returns <code>false</code>
    161     * @return <code>true</code> if the Subject supplied with the WikiContext posesses the Role, GroupPrincipal or desired
    162     *         user Principal, <code>false</code> otherwise
    163     */
    164    boolean hasRoleOrPrincipal( Session session, Principal principal );
    165
    166    /**
    167     * Checks whether the current user has access to the wiki context, by obtaining the required Permission ({@link Context#requiredPermission()})
    168     * and delegating the access check to {@link #checkPermission(Session, Permission)}. If the user is allowed, this method returns
    169     * <code>true</code>; <code>false</code> otherwise. If access is allowed, the wiki context will be added to the request as an attribute
    170     * with the key name {@link org.apache.wiki.api.core.Context#ATTR_CONTEXT}. Note that this method will automatically redirect the user to
    171     * a login or error page, as appropriate, if access fails. This is NOT guaranteed to be default behavior in the future.
    172     *
    173     * @param context wiki context to check if it is accesible
    174     * @param response the http response
    175     * @return the result of the access check
    176     * @throws IOException In case something goes wrong
    177     */
    178    default boolean hasAccess( final Context context, final HttpServletResponse response ) throws IOException {
    179        return hasAccess( context, response, true );
    180    }
    181
    182    /**
    183     * Checks whether the current user has access to the wiki context (and
    184     * optionally redirects if not), by obtaining the required Permission ({@link Context#requiredPermission()})
    185     * and delegating the access check to {@link #checkPermission(Session, Permission)}.
    186     * If the user is allowed, this method returns <code>true</code>;
    187     * <code>false</code> otherwise. Also, the wiki context will be added to the request as attribute
    188     * with the key name {@link org.apache.wiki.api.core.Context#ATTR_CONTEXT}.
    189     *
    190     * @param context wiki context to check if it is accesible
    191     * @param response The servlet response object
    192     * @param redirect If true, makes an automatic redirect to the response
    193     * @return the result of the access check
    194     * @throws IOException If something goes wrong
    195     */
    196    boolean hasAccess( final Context context, final HttpServletResponse response, final boolean redirect ) throws IOException;
    197
    198    /**
    199     * Checks to see if the local security policy allows a particular static Permission.
    200     * Do not use this method for normal permission checks; use {@link #checkPermission(Session, Permission)} instead.
    201     *
    202     * @param principals the Principals to check
    203     * @param permission the Permission
    204     * @return the result
    205     */
    206    boolean allowedByLocalPolicy( Principal[] principals, Permission permission );
    207
    208    /**
    209     * Determines whether a Subject possesses a given "static" Permission as defined in the security policy file. This method uses standard
    210     * Java 2 security calls to do its work. Note that the current access control context's <code>codeBase</code> is effectively <em>this
    211     * class</em>, not that of the caller. Therefore, this method will work best when what matters in the policy is <em>who</em> makes the
    212     * permission check, not what the caller's code source is. Internally, this method works by executing <code>Subject.doAsPrivileged</code>
    213     * with a privileged action that simply calls {@link AccessController#checkPermission(Permission)}.
    214     *
    215     * @see AccessController#checkPermission(Permission) . A caught exception (or lack thereof) determines whether the
    216     *       privilege is absent (or present).
    217     * @param session the Session whose permission status is being queried
    218     * @param permission the Permission the Subject must possess
    219     * @return <code>true</code> if the Subject possesses the permission, <code>false</code> otherwise
    220     */
    221    boolean checkStaticPermission( Session session, Permission permission );
    222
    223    /**
    224     * <p>Given a supplied string representing a Principal's name from an Acl, this method resolves the correct type of Principal (role,
    225     * group, or user). This method is guaranteed to always return a Principal. The algorithm is straightforward:</p>
    226     * <ol>
    227     * <li>If the name matches one of the built-in {@link org.apache.wiki.auth.authorize.Role} names, return that built-in Role</li>
    228     * <li>If the name matches one supplied by the current {@link org.apache.wiki.auth.Authorizer}, return that Role</li>
    229     * <li>If the name matches a group managed by the current {@link org.apache.wiki.auth.authorize.GroupManager}, return that Group</li>
    230     * <li>Otherwise, assume that the name represents a user principal. Using the current {@link org.apache.wiki.auth.user.UserDatabase},
    231     * find the first user who matches the supplied name by calling {@link org.apache.wiki.auth.user.UserDatabase#find(String)}.</li>
    232     * <li>Finally, if a user cannot be found, manufacture and return a generic {@link org.apache.wiki.auth.acl.UnresolvedPrincipal}</li>
    233     * </ol>
    234     *
    235     * @param name the name of the Principal to resolve
    236     * @return the fully-resolved Principal
    237     */
    238    Principal resolvePrincipal( final String name );
    239
    240
    241    // events processing .......................................................
    242
    243    /**
    244     * Registers a WikiEventListener with this instance.
    245     *
    246     * @param listener the event listener
    247     */
    248    void addWikiEventListener( WikiEventListener listener );
    249
    250    /**
    251     * Un-registers a WikiEventListener with this instance.
    252     *
    253     * @param listener the event listener
    254     */
    255    void removeWikiEventListener( final WikiEventListener listener );
    256
    257    /**
    258     * Fires a WikiSecurityEvent of the provided type, user, and permission to all registered listeners.
    259     *
    260     * @see org.apache.wiki.event.WikiSecurityEvent
    261     * @param type        the event type to be fired
    262     * @param user        the user associated with the event
    263     * @param permission  the permission the subject must possess
    264     */
    265    default void fireEvent( final int type, final Principal user, final Object permission ) {
    266        if( WikiEventManager.isListening( this ) ) {
    267            WikiEventManager.fireEvent( this, new WikiSecurityEvent( this, type, user, permission ) );
    268        }
    269    }
    270
    271}