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. If access is allowed,
408     * the wiki context will be added to the request as attribute
409     * with the key name {@link org.apache.wiki.tags.WikiTagBase#ATTR_CONTEXT}.
410     * 
411     * @param context wiki context to check if it is accesible
412     * @param response The servlet response object
413     * @param redirect If true, makes an automatic redirect to the response
414     * @return the result of the access check
415     * @throws IOException If something goes wrong
416     */
417    public boolean hasAccess( WikiContext context, HttpServletResponse response, boolean redirect ) throws IOException
418    {
419        boolean allowed = checkPermission( context.getWikiSession(), context.requiredPermission() );
420        ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE );
421
422        // Stash the wiki context
423        if( allowed )
424        {
425            if ( context.getHttpRequest() != null && context.getHttpRequest().getAttribute( WikiTagBase.ATTR_CONTEXT ) == null )
426            {
427                context.getHttpRequest().setAttribute( WikiTagBase.ATTR_CONTEXT, context );
428            }
429        }
430
431        // If access not allowed, redirect
432        if( !allowed && redirect )
433        {
434            Principal currentUser  = context.getWikiSession().getUserPrincipal();
435            String pageurl = context.getPage().getName();
436            if( context.getWikiSession().isAuthenticated() )
437            {
438                log.info("User "+currentUser.getName()+" has no access - forbidden (permission=" + context.requiredPermission() + ")" );
439                context.getWikiSession().addMessage( 
440                               MessageFormat.format( rb.getString("security.error.noaccess.logged"), context.getName()) );
441            }
442            else
443            {
444                log.info("User "+currentUser.getName()+" has no access - redirecting (permission=" + context.requiredPermission() + ")");
445                context.getWikiSession().addMessage( 
446                               MessageFormat.format( rb.getString("security.error.noaccess"), context.getName()) );
447            }
448            response.sendRedirect( m_engine.getURL(WikiContext.LOGIN, pageurl, null, false ) );
449        }
450        return allowed;
451    }
452
453    /**
454     * Initializes AuthorizationManager with an engine and set of properties.
455     * Expects to find property 'jspwiki.authorizer' with a valid Authorizer
456     * implementation name to take care of role lookup operations.
457     * @param engine the wiki engine
458     * @param properties the set of properties used to initialize the wiki engine
459     * @throws WikiException if the AuthorizationManager cannot be initialized
460     */
461    public void initialize( WikiEngine engine, Properties properties ) throws WikiException
462    {
463        m_engine = engine;
464
465        //
466        //  JAAS authorization continues
467        //
468        m_authorizer = getAuthorizerImplementation( properties );
469        m_authorizer.initialize( engine, properties );
470
471        // Initialize local security policy
472        try
473        {
474            String policyFileName = properties.getProperty( POLICY, DEFAULT_POLICY );
475            URL policyURL = AuthenticationManager.findConfigFile( engine, policyFileName );
476            
477            if (policyURL != null) 
478            {
479                File policyFile = new File( policyURL.toURI().getPath() );
480                log.info("We found security policy URL: " + policyURL + " and transformed it to file " + policyFile.getAbsolutePath());
481                m_localPolicy = new LocalPolicy( policyFile, engine.getContentEncoding() );
482                m_localPolicy.refresh();
483                log.info( "Initialized default security policy: " + policyFile.getAbsolutePath() );
484            }
485            else
486            {
487                String sb = "JSPWiki was unable to initialize the default security policy (WEB-INF/jspwiki.policy) file. " + 
488                            "Please ensure that the jspwiki.policy file exists in the default location. " + 
489                            "This file should exist regardless of the existance of a global policy file. " +
490                            "The global policy file is identified by the java.security.policy variable. ";
491                WikiSecurityException wse = new WikiSecurityException( sb );
492                log.fatal( sb, wse );
493                throw wse;
494            }
495        }
496        catch ( Exception e)
497        {
498            log.error("Could not initialize local security policy: " + e.getMessage() );
499            throw new WikiException( "Could not initialize local security policy: " + e.getMessage(), e );
500        }
501    }
502
503    /**
504     * Returns <code>true</code> if JSPWiki's JAAS authorization system
505     * is used for authorization in addition to container controls.
506     * @return the result
507     * @deprecated functionality deprecated - returns true always. To be removed on 2.11.0
508     */
509    @Deprecated
510    protected boolean isJAASAuthorized()
511    {
512        return true;
513    }
514
515    /**
516     * Attempts to locate and initialize a Authorizer to use with this manager.
517     * Throws a WikiException if no entry is found, or if one fails to
518     * initialize.
519     * @param props jspwiki.properties, containing a
520     *            'jspwiki.authorization.provider' class name
521     * @return a Authorizer used to get page authorization information
522     * @throws WikiException
523     */
524    private Authorizer getAuthorizerImplementation( Properties props ) throws WikiException
525    {
526        String authClassName = props.getProperty( PROP_AUTHORIZER, DEFAULT_AUTHORIZER );
527        return (Authorizer) locateImplementation( authClassName );
528    }
529
530    private Object locateImplementation( String clazz ) throws WikiException
531    {
532        if ( clazz != null )
533        {
534            try
535            {
536                Class<?> authClass = ClassUtil.findClass( "org.apache.wiki.auth.authorize", clazz );
537                Object impl = authClass.newInstance();
538                return impl;
539            }
540            catch( ClassNotFoundException e )
541            {
542                log.fatal( "Authorizer " + clazz + " cannot be found", e );
543                throw new WikiException( "Authorizer " + clazz + " cannot be found", e );
544            }
545            catch( InstantiationException e )
546            {
547                log.fatal( "Authorizer " + clazz + " cannot be created", e );
548                throw new WikiException( "Authorizer " + clazz + " cannot be created", e );
549            }
550            catch( IllegalAccessException e )
551            {
552                log.fatal( "You are not allowed to access this authorizer class", e );
553                throw new WikiException( "You are not allowed to access this authorizer class", e );
554            }
555        }
556
557        throw new NoRequiredPropertyException( "Unable to find a " + PROP_AUTHORIZER + " entry in the properties.",
558                                               PROP_AUTHORIZER );
559    }
560
561    /**
562     * Checks to see if the local security policy allows a particular static Permission.
563     * Do not use this method for normal permission checks; use
564     * {@link #checkPermission(WikiSession, Permission)} instead.
565     * @param principals the Principals to check
566     * @param permission the Permission
567     * @return the result
568     */
569    protected boolean allowedByLocalPolicy( Principal[] principals, Permission permission )
570    {
571        for ( Principal principal : principals )
572        {
573            // Get ProtectionDomain for this Principal from cache, or create new one
574            ProtectionDomain pd = m_cachedPds.get( principal );
575            if ( pd == null )
576            {
577                ClassLoader cl = this.getClass().getClassLoader();
578                CodeSource cs = new CodeSource( null, (Certificate[])null );
579                pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principal } );
580                m_cachedPds.put( principal, pd );
581            }
582
583            // Consult the local policy and get the answer
584            if ( m_localPolicy.implies( pd, permission ) )
585            {
586                return true;
587            }
588        }
589        return false;
590    }
591
592    /**
593     * Determines whether a Subject possesses a given "static" Permission as
594     * defined in the security policy file. This method uses standard Java 2
595     * security calls to do its work. Note that the current access control
596     * context's <code>codeBase</code> is effectively <em>this class</em>,
597     * not that of the caller. Therefore, this method will work best when what
598     * matters in the policy is <em>who</em> makes the permission check, not
599     * what the caller's code source is. Internally, this method works by
600     * executing <code>Subject.doAsPrivileged</code> with a privileged action
601     * that simply calls {@link java.security.AccessController#checkPermission(Permission)}.
602     * @see AccessController#checkPermission(java.security.Permission) . A
603     *       caught exception (or lack thereof) determines whether the privilege
604     *       is absent (or present).
605     * @param session the WikiSession whose permission status is being queried
606     * @param permission the Permission the Subject must possess
607     * @return <code>true</code> if the Subject possesses the permission,
608     *         <code>false</code> otherwise
609     */
610    protected boolean checkStaticPermission( final WikiSession session, final Permission permission )
611    {
612        Boolean allowed = (Boolean) WikiSession.doPrivileged( session, new PrivilegedAction<Boolean>()
613        {
614            public Boolean run()
615            {
616                try
617                {
618                    // Check the JVM-wide security policy first
619                    AccessController.checkPermission( permission );
620                    return Boolean.TRUE;
621                }
622                catch( AccessControlException e )
623                {
624                    // Global policy denied the permission
625                }
626
627                // Try the local policy - check each Role/Group and User Principal
628                if ( allowedByLocalPolicy( session.getRoles(), permission ) ||
629                     allowedByLocalPolicy( session.getPrincipals(), permission ) )
630                {
631                    return Boolean.TRUE;
632                }
633                return Boolean.FALSE;
634            }
635        } );
636        return allowed.booleanValue();
637    }
638
639    /**
640     * <p>Given a supplied string representing a Principal's name from an Acl, this
641     * method resolves the correct type of Principal (role, group, or user).
642     * This method is guaranteed to always return a Principal.
643     * The algorithm is straightforward:</p>
644     * <ol>
645     * <li>If the name matches one of the built-in {@link org.apache.wiki.auth.authorize.Role} names,
646     * return that built-in Role</li>
647     * <li>If the name matches one supplied by the current
648     * {@link org.apache.wiki.auth.Authorizer}, return that Role</li>
649     * <li>If the name matches a group managed by the
650     * current {@link org.apache.wiki.auth.authorize.GroupManager}, return that Group</li>
651     * <li>Otherwise, assume that the name represents a user
652     * principal. Using the current {@link org.apache.wiki.auth.user.UserDatabase}, find the
653     * first user who matches the supplied name by calling
654     * {@link org.apache.wiki.auth.user.UserDatabase#find(String)}.</li>
655     * <li>Finally, if a user cannot be found, manufacture
656     * and return a generic {@link org.apache.wiki.auth.acl.UnresolvedPrincipal}</li>
657     * </ol>
658     * @param name the name of the Principal to resolve
659     * @return the fully-resolved Principal
660     */
661    public Principal resolvePrincipal( String name )
662    {
663        // Check built-in Roles first
664        Role role = new Role(name);
665        if ( Role.isBuiltInRole( role ) )
666        {
667            return role;
668        }
669
670        // Check Authorizer Roles
671        Principal principal = m_authorizer.findRole( name );
672        if ( principal != null )
673        {
674            return principal;
675        }
676
677        // Check Groups
678        principal = m_engine.getGroupManager().findRole( name );
679        if ( principal != null )
680        {
681            return principal;
682        }
683
684        // Ok, no luck---this must be a user principal
685        Principal[] principals = null;
686        UserProfile profile = null;
687        UserDatabase db = m_engine.getUserManager().getUserDatabase();
688        try
689        {
690            profile = db.find( name );
691            principals = db.getPrincipals( profile.getLoginName() );
692            for (int i = 0; i < principals.length; i++)
693            {
694                principal = principals[i];
695                if ( principal.getName().equals( name ) )
696                {
697                    return principal;
698                }
699            }
700        }
701        catch( NoSuchPrincipalException e )
702        {
703            // We couldn't find the user...
704        }
705        // Ok, no luck---mark this as unresolved and move on
706        return new UnresolvedPrincipal( name );
707    }
708
709
710    // events processing .......................................................
711
712    /**
713     * Registers a WikiEventListener with this instance.
714     * @param listener the event listener
715     */
716    public synchronized void addWikiEventListener( WikiEventListener listener )
717    {
718        WikiEventManager.addWikiEventListener( this, listener );
719    }
720
721    /**
722     * Un-registers a WikiEventListener with this instance.
723     * @param listener the event listener
724     */
725    public synchronized void removeWikiEventListener( WikiEventListener listener )
726    {
727        WikiEventManager.removeWikiEventListener( this, listener );
728    }
729
730    /**
731     *  Fires a WikiSecurityEvent of the provided type, user,
732     *  and permission to all registered listeners.
733     *
734     * @see org.apache.wiki.event.WikiSecurityEvent
735     * @param type        the event type to be fired
736     * @param user        the user associated with the event
737     * @param permission  the permission the subject must possess
738     */
739    protected void fireEvent( int type, Principal user, Object permission )
740    {
741        if ( WikiEventManager.isListening(this) )
742        {
743            WikiEventManager.fireEvent(this,new WikiSecurityEvent(this,type,user,permission));
744        }
745    }
746
747}