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