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
021
022import java.io.File;
023import java.io.IOException;
024import java.net.URL;
025import java.security.AccessControlException;
026import java.security.AccessController;
027import java.security.CodeSource;
028import java.security.Permission;
029import java.security.Principal;
030import java.security.PrivilegedAction;
031import java.security.ProtectionDomain;
032import java.security.cert.Certificate;
033import java.text.MessageFormat;
034import java.util.Arrays;
035import java.util.Map;
036import java.util.Properties;
037import java.util.ResourceBundle;
038import java.util.WeakHashMap;
039
040import javax.servlet.http.HttpServletResponse;
041
042import org.apache.log4j.Logger;
043import org.apache.wiki.WikiContext;
044import org.apache.wiki.WikiEngine;
045import org.apache.wiki.WikiPage;
046import org.apache.wiki.WikiSession;
047import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
048import org.apache.wiki.api.exceptions.WikiException;
049import org.apache.wiki.auth.acl.Acl;
050import org.apache.wiki.auth.acl.AclEntry;
051import org.apache.wiki.auth.acl.UnresolvedPrincipal;
052import org.apache.wiki.auth.authorize.Role;
053import org.apache.wiki.auth.permissions.AllPermission;
054import org.apache.wiki.auth.permissions.PagePermission;
055import org.apache.wiki.auth.user.UserDatabase;
056import org.apache.wiki.auth.user.UserProfile;
057import org.apache.wiki.event.WikiEventListener;
058import org.apache.wiki.event.WikiEventManager;
059import org.apache.wiki.event.WikiSecurityEvent;
060import org.apache.wiki.i18n.InternationalizationManager;
061import org.apache.wiki.preferences.Preferences;
062import org.apache.wiki.tags.WikiTagBase;
063import org.apache.wiki.util.ClassUtil;
064import org.freshcookies.security.policy.LocalPolicy;
065
066/**
067 * <p>Manages all access control and authorization; determines what authenticated
068 * users are allowed to do.</p>
069 * <p>Privileges in JSPWiki are expressed as Java-standard {@link java.security.Permission}
070 * classes. There are two types of permissions:</p>
071 * <ul>
072 *   <li>{@link org.apache.wiki.auth.permissions.WikiPermission} - privileges that apply
073 *   to an entire wiki instance: <em>e.g.,</em> editing user profiles, creating pages, creating groups</li>
074 *   <li>{@link org.apache.wiki.auth.permissions.PagePermission} - privileges that apply
075 *   to a single wiki page or range of pages: <em>e.g.,</em> reading, editing, renaming
076 * </ul>
077 * <p>Calling classes determine whether they are entitled to perform a particular action
078 * by constructing the appropriate permission first, then passing it and the current
079 * {@link org.apache.wiki.WikiSession} to the
080 * {@link #checkPermission(WikiSession, Permission)} method. If the session's
081 * Subject possesses the permission, the action is allowed.</p>
082 * <p>For WikiPermissions, the decision criteria is relatively simple: the caller either
083 * possesses the permission, as granted by the wiki security policy -- or not.</p>
084 * <p>For PagePermissions, the logic is exactly the same if the page being checked
085 * does not have an access control list. However, if the page does have an ACL, the
086 * authorization decision is made based the <em>union</em> of the permissions
087 * granted in the ACL and in the security policy. In other words, the user must
088 * be named in the ACL (or belong to a group or role that is named in the ACL)
089 * <em>and</em> be granted (at least) the same permission in the security policy. We
090 * do this to prevent a user from gaining more permissions than they already
091 * have, based on the security policy.</p>
092 * <p>See the {@link #checkPermission(WikiSession, Permission)} and
093 * {@link #hasRoleOrPrincipal(WikiSession, Principal)} methods for more information
094 * on the authorization logic.</p>
095 * @since 2.3
096 * @see AuthenticationManager
097 */
098public class AuthorizationManager {
099
100    private static final Logger log = Logger.getLogger( AuthorizationManager.class );
101    /**
102     * The default external Authorizer is the {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer}
103     */
104    public static final String                DEFAULT_AUTHORIZER = "org.apache.wiki.auth.authorize.WebContainerAuthorizer";
105
106    /** Property that supplies the security policy file name, in WEB-INF. */
107    protected static final String             POLICY      = "jspwiki.policy.file";
108
109    /** Name of the default security policy file, in WEB-INF. */
110    protected static final String             DEFAULT_POLICY      = "jspwiki.policy";
111
112    /**
113     * The property name in jspwiki.properties for specifying the external {@link Authorizer}.
114     */
115    public static final String                PROP_AUTHORIZER   = "jspwiki.authorizer";
116
117    private Authorizer                        m_authorizer      = null;
118
119    /** Cache for storing ProtectionDomains used to evaluate the local policy. */
120    private Map<Principal, ProtectionDomain>                               m_cachedPds       = new WeakHashMap<Principal, ProtectionDomain>();
121
122    private WikiEngine                        m_engine          = null;
123
124    private LocalPolicy                       m_localPolicy     = null;
125
126    /**
127     * Constructs a new AuthorizationManager instance.
128     */
129    public AuthorizationManager()
130    {
131    }
132
133    /**
134     * Returns <code>true</code> or <code>false</code>, depending on
135     * whether a Permission is allowed for the Subject associated with
136     * a supplied WikiSession. The access control algorithm works this way:
137     * <ol>
138     * <li>The {@link org.apache.wiki.auth.acl.Acl} for the page is obtained</li>
139     * <li>The Subject associated with the current
140     * {@link org.apache.wiki.WikiSession} is obtained</li>
141     * <li>If the Subject's Principal set includes the Role Principal that is
142     * the administrator group, always allow the Permission</li>
143     * <li>For all permissions, check to see if the Permission is allowed according
144     * to the default security policy. If it isn't, deny the permission and halt
145     * further processing.</li>
146     * <li>If there is an Acl, get the list of Principals assigned this
147     * Permission in the Acl: these will be role, group or user Principals, or
148     * {@link org.apache.wiki.auth.acl.UnresolvedPrincipal}s (see below).
149     * Then iterate through the Subject's Principal set and determine whether
150     * the user (Subject) possesses any one of these specified Roles or
151     * Principals. The matching process delegates to
152     * {@link #hasRoleOrPrincipal(WikiSession, Principal)}.
153     * </ol>
154     * <p>
155     * Note that when iterating through the Acl's list of authorized Principals,
156     * it is possible that one or more of the Acl's Principal entries are of
157     * type <code>UnresolvedPrincipal</code>. This means that the last time
158     * the ACL was read, the Principal (user, built-in Role, authorizer Role, or
159     * wiki Group) could not be resolved: the Role was not valid, the user
160     * wasn't found in the UserDatabase, or the Group wasn't known to (e.g.,
161     * cached) in the GroupManager. If an <code>UnresolvedPrincipal</code> is
162     * encountered, this method will attempt to resolve it first <em>before</em>
163     * checking to see if the Subject possesses this principal, by calling
164     * {@link #resolvePrincipal(String)}. If the (re-)resolution does not
165     * succeed, the access check for the principal will fail by definition (the
166     * Subject should never contain UnresolvedPrincipals).
167     * </p>
168     * <p>
169     * If security not set to JAAS, will return true.
170     * </p>
171     * @param session the current wiki session
172     * @param permission the Permission being checked
173     * @see #hasRoleOrPrincipal(WikiSession, Principal)
174     * @return the result of the Permission check
175     */
176    public boolean checkPermission( WikiSession session, Permission permission )
177    {
178        //
179        //  A slight sanity check.
180        //
181        if ( session == null || permission == null )
182        {
183            fireEvent( WikiSecurityEvent.ACCESS_DENIED, null, permission );
184            return false;
185        }
186
187        Principal user = session.getLoginPrincipal();
188
189        // Always allow the action if user has AllPermission
190        Permission allPermission = new AllPermission( m_engine.getApplicationName() );
191        boolean hasAllPermission = checkStaticPermission( session, allPermission );
192        if ( hasAllPermission )
193        {
194            fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission );
195            return true;
196        }
197
198        // If the user doesn't have *at least* the permission
199        // granted by policy, return false.
200        boolean hasPolicyPermission = checkStaticPermission( session, permission );
201        if ( !hasPolicyPermission )
202        {
203            fireEvent( WikiSecurityEvent.ACCESS_DENIED, user, permission );
204            return false;
205        }
206
207        // If this isn't a PagePermission, it's allowed
208        if ( ! ( permission instanceof PagePermission ) )
209        {
210            fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission );
211            return true;
212        }
213
214        //
215        // If the page or ACL is null, it's allowed.
216        //
217        String pageName = ((PagePermission)permission).getPage();
218        WikiPage page = m_engine.getPage( pageName );
219        Acl acl = ( page == null) ? null : m_engine.getAclManager().getPermissions( page );
220        if ( page == null ||  acl == null || acl.isEmpty() )
221        {
222            fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission );
223            return true;
224        }
225
226        //
227        //  Next, iterate through the Principal objects assigned
228        //  this permission. If the context's subject possesses
229        //  any of these, the action is allowed.
230
231        Principal[] aclPrincipals = acl.findPrincipals( permission );
232
233        log.debug( "Checking ACL entries..." );
234        log.debug( "Acl for this page is: " + acl );
235        log.debug( "Checking for principal: " + Arrays.toString( aclPrincipals ) );
236        log.debug( "Permission: " + permission );
237
238        for( Principal aclPrincipal : aclPrincipals )
239        {
240            // If the ACL principal we're looking at is unresolved,
241            // try to resolve it here & correct the Acl
242            if ( aclPrincipal instanceof UnresolvedPrincipal )
243            {
244                AclEntry aclEntry = acl.getEntry( aclPrincipal );
245                aclPrincipal = resolvePrincipal( aclPrincipal.getName() );
246                if ( aclEntry != null && !( aclPrincipal instanceof UnresolvedPrincipal ) )
247                {
248                    aclEntry.setPrincipal( aclPrincipal );
249                }
250            }
251
252            if ( hasRoleOrPrincipal( session, aclPrincipal ) )
253            {
254                fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission );
255                return true;
256            }
257        }
258        fireEvent( WikiSecurityEvent.ACCESS_DENIED, user, permission );
259        return false;
260    }
261
262    /**
263     * <p>Determines if the Subject associated with a
264     * supplied WikiSession contains a desired Role or GroupPrincipal.
265     * The algorithm simply checks to see if the Subject possesses
266     * the Role or GroupPrincipal it in its Principal set. Note that
267     * any user (anonymous, asserted, authenticated) can possess
268     * a built-in role. But a user <em>must</em> be authenticated to
269     * possess a role other than one of the built-in ones.
270     * We do this to prevent privilege escalation.</p>
271     * <p>For all other cases, this method returns <code>false</code>.</p>
272     * <p>Note that this method does <em>not</em> consult the external
273     * Authorizer or GroupManager; it relies on the Principals that
274     * have been injected into the user's Subject at login time, or
275     * after group creation/modification/deletion.</p>
276     * @param session the current wiki session, which must be non-null. If null,
277     *            the result of this method always returns <code>false</code>
278     * @param principal the Principal (role or group principal) to look
279     *            for, which must be non-<code>null</code>. If <code>null</code>,
280     *            the result of this method always returns <code>false</code>
281     * @return <code>true</code> if the Subject supplied with the WikiContext
282     *         posesses the Role or GroupPrincipal, <code>false</code> otherwise
283     */
284    public boolean isUserInRole( WikiSession session, Principal principal )
285    {
286        if ( session == null || principal == null ||
287             AuthenticationManager.isUserPrincipal( principal ) )
288        {
289            return false;
290        }
291
292        // Any type of user can possess a built-in role
293        if ( principal instanceof Role && Role.isBuiltInRole( (Role)principal ) )
294        {
295            return session.hasPrincipal( principal );
296        }
297
298        // Only authenticated users can possess groups or custom roles
299        if ( session.isAuthenticated() && AuthenticationManager.isRolePrincipal( principal ) )
300        {
301            return session.hasPrincipal( principal );
302        }
303        return false;
304    }
305
306    /**
307     * Returns the current external {@link Authorizer} in use. This method
308     * is guaranteed to return a properly-initialized Authorizer, unless
309     * it could not be initialized. In that case, this method throws
310     * a {@link org.apache.wiki.auth.WikiSecurityException}.
311     * @throws org.apache.wiki.auth.WikiSecurityException if the Authorizer could
312     * not be initialized
313     * @return the current Authorizer
314     */
315    public Authorizer getAuthorizer() throws WikiSecurityException
316    {
317        if ( m_authorizer != null )
318        {
319            return m_authorizer;
320        }
321        throw new WikiSecurityException( "Authorizer did not initialize properly. Check the logs." );
322    }
323
324    /**
325     * <p>Determines if the Subject associated with a supplied WikiSession contains
326     * a desired user Principal or built-in Role principal, OR is a member a
327     * Group or external Role. The rules are as follows:</p>
328     * <ol>
329     * <li>First, if desired Principal is a Role or GroupPrincipal, delegate to
330     * {@link #isUserInRole(WikiSession, Principal)} and
331     * return the result.</li>
332     * <li>Otherwise, we're looking for a user Principal,
333     * so iterate through the Principal set and see if
334     * any share the same name as the one we are looking for.</li>
335     * </ol>
336     * <p><em>Note: if the Principal parameter is a user principal, the session
337     * must be authenticated in order for the user to "possess it". Anonymous
338     * or asserted sessions will never posseess a named user principal.</em></p>
339     * @param session the current wiki session, which must be non-null. If null,
340     *            the result of this method always returns <code>false</code>
341     * @param principal the Principal (role, group, or user principal) to look
342     *            for, which must be non-null. If null, the result of this
343     *            method always returns <code>false</code>
344     * @return <code>true</code> if the Subject supplied with the WikiContext
345     *         posesses the Role, GroupPrincipal or desired
346     *         user Principal, <code>false</code> otherwise
347     */
348    protected boolean hasRoleOrPrincipal( WikiSession session, Principal principal )
349    {
350        // If either parameter is null, always deny
351        if( session == null || principal == null )
352        {
353            return false;
354        }
355
356        // If principal is role, delegate to isUserInRole
357        if( AuthenticationManager.isRolePrincipal( principal ) )
358        {
359            return isUserInRole( session, principal );
360        }
361
362        // We must be looking for a user principal, assuming that the user
363        // has been properly logged in.
364        // So just look for a name match.
365        if( session.isAuthenticated() && AuthenticationManager.isUserPrincipal( principal ) )
366        {
367            String principalName = principal.getName();
368            Principal[] userPrincipals = session.getPrincipals();
369            for( Principal userPrincipal : userPrincipals )
370            {
371                if( userPrincipal.getName().equals( principalName ) )
372                {
373                    return true;
374                }
375            }
376        }
377        return false;
378    }
379
380    /**
381     * Checks whether the current user has access to the wiki context,
382     * by obtaining the required Permission ({@link WikiContext#requiredPermission()})
383     * and delegating the access check to {@link #checkPermission(WikiSession, Permission)}.
384     * If the user is allowed, this method returns <code>true</code>;
385     * <code>false</code> otherwise. If access is allowed,
386     * the wiki context will be added to the request as an attribute
387     * with the key name {@link org.apache.wiki.tags.WikiTagBase#ATTR_CONTEXT}.
388     * Note that this method will automatically redirect the user to
389     * a login or error page, as appropriate, if access fails. This is
390     * NOT guaranteed to be default behavior in the future.
391     *
392     * @param context wiki context to check if it is accesible
393     * @param response the http response
394     * @return the result of the access check
395     * @throws IOException In case something goes wrong
396     */
397    public boolean hasAccess( WikiContext context, HttpServletResponse response ) throws IOException
398    {
399        return hasAccess( context, response, true );
400    }
401
402    /**
403     * Checks whether the current user has access to the wiki context (and
404     * optionally redirects if not), by obtaining the required Permission ({@link WikiContext#requiredPermission()})
405     * and delegating the access check to {@link #checkPermission(WikiSession, Permission)}.
406     * If the user is allowed, this method returns <code>true</code>;
407     * <code>false</code> otherwise. Also, the wiki context will be added to the request as attribute
408     * with the key name {@link org.apache.wiki.tags.WikiTagBase#ATTR_CONTEXT}.
409     *
410     * @param context wiki context to check if it is accesible
411     * @param response The servlet response object
412     * @param redirect If true, makes an automatic redirect to the response
413     * @return the result of the access check
414     * @throws IOException If something goes wrong
415     */
416    public boolean hasAccess( WikiContext context, HttpServletResponse response, boolean redirect ) throws IOException
417    {
418        boolean allowed = checkPermission( context.getWikiSession(), context.requiredPermission() );
419        ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE );
420
421        // Stash the wiki context
422        if ( context.getHttpRequest() != null && context.getHttpRequest().getAttribute( WikiTagBase.ATTR_CONTEXT ) == null )
423        {
424            context.getHttpRequest().setAttribute( WikiTagBase.ATTR_CONTEXT, context );
425        }
426
427        // If access not allowed, redirect
428        if( !allowed && redirect )
429        {
430            Principal currentUser  = context.getWikiSession().getUserPrincipal();
431            String pageurl = context.getPage().getName();
432            if( context.getWikiSession().isAuthenticated() )
433            {
434                log.info("User "+currentUser.getName()+" has no access - forbidden (permission=" + context.requiredPermission() + ")" );
435                context.getWikiSession().addMessage(
436                               MessageFormat.format( rb.getString("security.error.noaccess.logged"), context.getName()) );
437            }
438            else
439            {
440                log.info("User "+currentUser.getName()+" has no access - redirecting (permission=" + context.requiredPermission() + ")");
441                context.getWikiSession().addMessage(
442                               MessageFormat.format( rb.getString("security.error.noaccess"), context.getName()) );
443            }
444            response.sendRedirect( m_engine.getURL(WikiContext.LOGIN, pageurl, null, false ) );
445        }
446        return allowed;
447    }
448
449    /**
450     * Initializes AuthorizationManager with an engine and set of properties.
451     * Expects to find property 'jspwiki.authorizer' with a valid Authorizer
452     * implementation name to take care of role lookup operations.
453     * @param engine the wiki engine
454     * @param properties the set of properties used to initialize the wiki engine
455     * @throws WikiException if the AuthorizationManager cannot be initialized
456     */
457    public void initialize( WikiEngine engine, Properties properties ) throws WikiException
458    {
459        m_engine = engine;
460
461        //
462        //  JAAS authorization continues
463        //
464        m_authorizer = getAuthorizerImplementation( properties );
465        m_authorizer.initialize( engine, properties );
466
467        // Initialize local security policy
468        try
469        {
470            String policyFileName = properties.getProperty( POLICY, DEFAULT_POLICY );
471            URL policyURL = AuthenticationManager.findConfigFile( engine, policyFileName );
472
473            if (policyURL != null)
474            {
475                File policyFile = new File( policyURL.toURI().getPath() );
476                log.info("We found security policy URL: " + policyURL + " and transformed it to file " + policyFile.getAbsolutePath());
477                m_localPolicy = new LocalPolicy( policyFile, engine.getContentEncoding() );
478                m_localPolicy.refresh();
479                log.info( "Initialized default security policy: " + policyFile.getAbsolutePath() );
480            }
481            else
482            {
483                String sb = "JSPWiki was unable to initialize the default security policy (WEB-INF/jspwiki.policy) file. " +
484                            "Please ensure that the jspwiki.policy file exists in the default location. " +
485                            "This file should exist regardless of the existance of a global policy file. " +
486                            "The global policy file is identified by the java.security.policy variable. ";
487                WikiSecurityException wse = new WikiSecurityException( sb );
488                log.fatal( sb, wse );
489                throw wse;
490            }
491        }
492        catch ( Exception e)
493        {
494            log.error("Could not initialize local security policy: " + e.getMessage() );
495            throw new WikiException( "Could not initialize local security policy: " + e.getMessage(), e );
496        }
497    }
498
499    /**
500     * Returns <code>true</code> if JSPWiki's JAAS authorization system
501     * is used for authorization in addition to container controls.
502     * @return the result
503     * @deprecated functionality deprecated - returns true always. To be removed on 2.11.0
504     */
505    @Deprecated
506    protected boolean isJAASAuthorized()
507    {
508        return true;
509    }
510
511    /**
512     * Attempts to locate and initialize a Authorizer to use with this manager.
513     * Throws a WikiException if no entry is found, or if one fails to
514     * initialize.
515     * @param props jspwiki.properties, containing a
516     *            'jspwiki.authorization.provider' class name
517     * @return a Authorizer used to get page authorization information
518     * @throws WikiException
519     */
520    private Authorizer getAuthorizerImplementation( Properties props ) throws WikiException
521    {
522        String authClassName = props.getProperty( PROP_AUTHORIZER, DEFAULT_AUTHORIZER );
523        return (Authorizer) locateImplementation( authClassName );
524    }
525
526    private Object locateImplementation( String clazz ) throws WikiException
527    {
528        if ( clazz != null )
529        {
530            try
531            {
532                Class<?> authClass = ClassUtil.findClass( "org.apache.wiki.auth.authorize", clazz );
533                Object impl = authClass.newInstance();
534                return impl;
535            }
536            catch( ClassNotFoundException e )
537            {
538                log.fatal( "Authorizer " + clazz + " cannot be found", e );
539                throw new WikiException( "Authorizer " + clazz + " cannot be found", e );
540            }
541            catch( InstantiationException e )
542            {
543                log.fatal( "Authorizer " + clazz + " cannot be created", e );
544                throw new WikiException( "Authorizer " + clazz + " cannot be created", e );
545            }
546            catch( IllegalAccessException e )
547            {
548                log.fatal( "You are not allowed to access this authorizer class", e );
549                throw new WikiException( "You are not allowed to access this authorizer class", e );
550            }
551        }
552
553        throw new NoRequiredPropertyException( "Unable to find a " + PROP_AUTHORIZER + " entry in the properties.",
554                                               PROP_AUTHORIZER );
555    }
556
557    /**
558     * Checks to see if the local security policy allows a particular static Permission.
559     * Do not use this method for normal permission checks; use
560     * {@link #checkPermission(WikiSession, Permission)} instead.
561     * @param principals the Principals to check
562     * @param permission the Permission
563     * @return the result
564     */
565    protected boolean allowedByLocalPolicy( Principal[] principals, Permission permission )
566    {
567        for ( Principal principal : principals )
568        {
569            // Get ProtectionDomain for this Principal from cache, or create new one
570            ProtectionDomain pd = m_cachedPds.get( principal );
571            if ( pd == null )
572            {
573                ClassLoader cl = this.getClass().getClassLoader();
574                CodeSource cs = new CodeSource( null, (Certificate[])null );
575                pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principal } );
576                m_cachedPds.put( principal, pd );
577            }
578
579            // Consult the local policy and get the answer
580            if ( m_localPolicy.implies( pd, permission ) )
581            {
582                return true;
583            }
584        }
585        return false;
586    }
587
588    /**
589     * Determines whether a Subject possesses a given "static" Permission as
590     * defined in the security policy file. This method uses standard Java 2
591     * security calls to do its work. Note that the current access control
592     * context's <code>codeBase</code> is effectively <em>this class</em>,
593     * not that of the caller. Therefore, this method will work best when what
594     * matters in the policy is <em>who</em> makes the permission check, not
595     * what the caller's code source is. Internally, this method works by
596     * executing <code>Subject.doAsPrivileged</code> with a privileged action
597     * that simply calls {@link java.security.AccessController#checkPermission(Permission)}.
598     * @see AccessController#checkPermission(java.security.Permission) . A
599     *       caught exception (or lack thereof) determines whether the privilege
600     *       is absent (or present).
601     * @param session the WikiSession whose permission status is being queried
602     * @param permission the Permission the Subject must possess
603     * @return <code>true</code> if the Subject possesses the permission,
604     *         <code>false</code> otherwise
605     */
606    protected boolean checkStaticPermission( final WikiSession session, final Permission permission )
607    {
608        Boolean allowed = (Boolean) WikiSession.doPrivileged( session, new PrivilegedAction<Boolean>()
609        {
610            public Boolean run()
611            {
612                try
613                {
614                    // Check the JVM-wide security policy first
615                    AccessController.checkPermission( permission );
616                    return Boolean.TRUE;
617                }
618                catch( AccessControlException e )
619                {
620                    // Global policy denied the permission
621                }
622
623                // Try the local policy - check each Role/Group and User Principal
624                if ( allowedByLocalPolicy( session.getRoles(), permission ) ||
625                     allowedByLocalPolicy( session.getPrincipals(), permission ) )
626                {
627                    return Boolean.TRUE;
628                }
629                return Boolean.FALSE;
630            }
631        } );
632        return allowed.booleanValue();
633    }
634
635    /**
636     * <p>Given a supplied string representing a Principal's name from an Acl, this
637     * method resolves the correct type of Principal (role, group, or user).
638     * This method is guaranteed to always return a Principal.
639     * The algorithm is straightforward:</p>
640     * <ol>
641     * <li>If the name matches one of the built-in {@link org.apache.wiki.auth.authorize.Role} names,
642     * return that built-in Role</li>
643     * <li>If the name matches one supplied by the current
644     * {@link org.apache.wiki.auth.Authorizer}, return that Role</li>
645     * <li>If the name matches a group managed by the
646     * current {@link org.apache.wiki.auth.authorize.GroupManager}, return that Group</li>
647     * <li>Otherwise, assume that the name represents a user
648     * principal. Using the current {@link org.apache.wiki.auth.user.UserDatabase}, find the
649     * first user who matches the supplied name by calling
650     * {@link org.apache.wiki.auth.user.UserDatabase#find(String)}.</li>
651     * <li>Finally, if a user cannot be found, manufacture
652     * and return a generic {@link org.apache.wiki.auth.acl.UnresolvedPrincipal}</li>
653     * </ol>
654     * @param name the name of the Principal to resolve
655     * @return the fully-resolved Principal
656     */
657    public Principal resolvePrincipal( String name )
658    {
659        // Check built-in Roles first
660        Role role = new Role(name);
661        if ( Role.isBuiltInRole( role ) )
662        {
663            return role;
664        }
665
666        // Check Authorizer Roles
667        Principal principal = m_authorizer.findRole( name );
668        if ( principal != null )
669        {
670            return principal;
671        }
672
673        // Check Groups
674        principal = m_engine.getGroupManager().findRole( name );
675        if ( principal != null )
676        {
677            return principal;
678        }
679
680        // Ok, no luck---this must be a user principal
681        Principal[] principals = null;
682        UserProfile profile = null;
683        UserDatabase db = m_engine.getUserManager().getUserDatabase();
684        try
685        {
686            profile = db.find( name );
687            principals = db.getPrincipals( profile.getLoginName() );
688            for (int i = 0; i < principals.length; i++)
689            {
690                principal = principals[i];
691                if ( principal.getName().equals( name ) )
692                {
693                    return principal;
694                }
695            }
696        }
697        catch( NoSuchPrincipalException e )
698        {
699            // We couldn't find the user...
700        }
701        // Ok, no luck---mark this as unresolved and move on
702        return new UnresolvedPrincipal( name );
703    }
704
705
706    // events processing .......................................................
707
708    /**
709     * Registers a WikiEventListener with this instance.
710     * @param listener the event listener
711     */
712    public synchronized void addWikiEventListener( WikiEventListener listener )
713    {
714        WikiEventManager.addWikiEventListener( this, listener );
715    }
716
717    /**
718     * Un-registers a WikiEventListener with this instance.
719     * @param listener the event listener
720     */
721    public synchronized void removeWikiEventListener( WikiEventListener listener )
722    {
723        WikiEventManager.removeWikiEventListener( this, listener );
724    }
725
726    /**
727     *  Fires a WikiSecurityEvent of the provided type, user,
728     *  and permission to all registered listeners.
729     *
730     * @see org.apache.wiki.event.WikiSecurityEvent
731     * @param type        the event type to be fired
732     * @param user        the user associated with the event
733     * @param permission  the permission the subject must possess
734     */
735    protected void fireEvent( int type, Principal user, Object permission )
736    {
737        if ( WikiEventManager.isListening(this) )
738        {
739            WikiEventManager.fireEvent(this,new WikiSecurityEvent(this,type,user,permission));
740        }
741    }
742
743}