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