    001/*
    002    Licensed to the Apache Software Foundation (ASF) under one
    003    or more contributor license agreements.  See the NOTICE file
    004    distributed with this work for additional information
    005    regarding copyright ownership.  The ASF licenses this file
    006    to you under the Apache License, Version 2.0 (the
    007    "License"); you may not use this file except in compliance
    008    with the License.  You may obtain a copy of the License at
    009
    010       http://www.apache.org/licenses/LICENSE-2.0
    011
    012    Unless required by applicable law or agreed to in writing,
    013    software distributed under the License is distributed on an
    014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015    KIND, either express or implied.  See the License for the
    016    specific language governing permissions and limitations
    017    under the License.
    018 */
    019package org.apache.wiki.auth;
    020
    021import org.apache.logging.log4j.LogManager;
    022import org.apache.logging.log4j.Logger;
    023import org.apache.wiki.api.core.Acl;
    024import org.apache.wiki.api.core.AclEntry;
    025import org.apache.wiki.api.core.Context;
    026import org.apache.wiki.api.core.ContextEnum;
    027import org.apache.wiki.api.core.Engine;
    028import org.apache.wiki.api.core.Page;
    029import org.apache.wiki.api.core.Session;
    030import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
    031import org.apache.wiki.api.exceptions.WikiException;
    032import org.apache.wiki.auth.acl.AclManager;
    033import org.apache.wiki.auth.acl.UnresolvedPrincipal;
    034import org.apache.wiki.auth.authorize.GroupManager;
    035import org.apache.wiki.auth.authorize.Role;
    036import org.apache.wiki.auth.permissions.AllPermission;
    037import org.apache.wiki.auth.permissions.PagePermission;
    038import org.apache.wiki.auth.user.UserDatabase;
    039import org.apache.wiki.auth.user.UserProfile;
    040import org.apache.wiki.event.WikiEventListener;
    041import org.apache.wiki.event.WikiEventManager;
    042import org.apache.wiki.event.WikiSecurityEvent;
    043import org.apache.wiki.i18n.InternationalizationManager;
    044import org.apache.wiki.pages.PageManager;
    045import org.apache.wiki.preferences.Preferences;
    046import org.apache.wiki.util.ClassUtil;
    047import org.freshcookies.security.policy.LocalPolicy;
    048
    049import javax.servlet.http.HttpServletResponse;
    050import java.io.File;
    051import java.io.IOException;
    052import java.net.URL;
    053import java.security.AccessControlException;
    054import java.security.AccessController;
    055import java.security.CodeSource;
    056import java.security.Permission;
    057import java.security.Principal;
    058import java.security.PrivilegedAction;
    059import java.security.ProtectionDomain;
    060import java.security.cert.Certificate;
    061import java.text.MessageFormat;
    062import java.util.Arrays;
    063import java.util.Map;
    064import java.util.Properties;
    065import java.util.ResourceBundle;
    066import java.util.WeakHashMap;
    067
    068
    069/**
    070 * <p>Default implementation for {@link AuthorizationManager}</p>
    071 * {@inheritDoc}
    072 *
    073 * <p>See the {@link #checkPermission(Session, Permission)} and {@link #hasRoleOrPrincipal(Session, Principal)} methods for more
    074 * information on the authorization logic.</p>
    075 * @since 2.3
    076 * @see AuthenticationManager
    077 */
    078public class DefaultAuthorizationManager implements AuthorizationManager {
    079
    080    private static final Logger LOG = LogManager.getLogger( DefaultAuthorizationManager.class );
    081
    082    private Authorizer m_authorizer;
    083
    084    /** Cache for storing ProtectionDomains used to evaluate the local policy. */
    085    private final Map< Principal, ProtectionDomain > m_cachedPds = new WeakHashMap<>();
    086
    087    private Engine m_engine;
    088
    089    private LocalPolicy m_localPolicy;
    090
    091    /**
    092     * Constructs a new DefaultAuthorizationManager instance.
    093     */
    094    public DefaultAuthorizationManager() {
    095    }
    096
    097    /** {@inheritDoc} */
    098    @Override
    099    public boolean checkPermission( final Session session, final Permission permission ) {
    100        // A slight sanity check.
    101        if( session == null || permission == null ) {
    102            fireEvent( WikiSecurityEvent.ACCESS_DENIED, null, permission );
    103            return false;
    104        }
    105
    106        final Principal user = session.getLoginPrincipal();
    107
    108        // Always allow the action if user has AllPermission
    109        final Permission allPermission = new AllPermission( m_engine.getApplicationName() );
    110        final boolean hasAllPermission = checkStaticPermission( session, allPermission );
    111        if( hasAllPermission ) {
    112            fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission );
    113            return true;
    114        }
    115
    116        // If the user doesn't have *at least* the permission granted by policy, return false.
    117        final boolean hasPolicyPermission = checkStaticPermission( session, permission );
    118        if( !hasPolicyPermission ) {
    119            fireEvent( WikiSecurityEvent.ACCESS_DENIED, user, permission );
    120            return false;
    121        }
    122
    123        // If this isn't a PagePermission, it's allowed
    124        if( !( permission instanceof PagePermission ) ) {
    125            fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission );
    126            return true;
    127        }
    128
    129        // If the page or ACL is null, it's allowed.
    130        final String pageName = ((PagePermission)permission).getPage();
    131        final Page page = m_engine.getManager( PageManager.class ).getPage( pageName );
    132        final Acl acl = ( page == null) ? null : m_engine.getManager( AclManager.class ).getPermissions( page );
    133        if( page == null ||  acl == null || acl.isEmpty() ) {
    134            fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission );
    135            return true;
    136        }
    137
    138        // Next, iterate through the Principal objects assigned this permission. If the context's subject possesses
    139        // any of these, the action is allowed.
    140        final Principal[] aclPrincipals = acl.findPrincipals( permission );
    141
    142        LOG.debug( "Checking ACL entries..." );
    143        LOG.debug( "Acl for this page is: {}", acl );
    144        LOG.debug( "Checking for principal: {}", Arrays.toString( aclPrincipals ) );
    145        LOG.debug( "Permission: {}", permission );
    146
    147        for( Principal aclPrincipal : aclPrincipals ) {
    148            // If the ACL principal we're looking at is unresolved, try to resolve it here & correct the Acl
    149            if ( aclPrincipal instanceof UnresolvedPrincipal ) {
    150                final AclEntry aclEntry = acl.getAclEntry( aclPrincipal );
    151                aclPrincipal = resolvePrincipal( aclPrincipal.getName() );
    152                if ( aclEntry != null && !( aclPrincipal instanceof UnresolvedPrincipal ) ) {
    153                    aclEntry.setPrincipal( aclPrincipal );
    154                }
    155            }
    156
    157            if ( hasRoleOrPrincipal( session, aclPrincipal ) ) {
    158                fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission );
    159                return true;
    160            }
    161        }
    162        fireEvent( WikiSecurityEvent.ACCESS_DENIED, user, permission );
    163        return false;
    164    }
    165
    166    /** {@inheritDoc} */
    167    @Override
    168    public Authorizer getAuthorizer() throws WikiSecurityException {
    169        if ( m_authorizer != null ) {
    170            return m_authorizer;
    171        }
    172        throw new WikiSecurityException( "Authorizer did not initialize properly. Check the logs." );
    173    }
    174
    175    /** {@inheritDoc} */
    176    @Override
    177    public boolean hasRoleOrPrincipal( final Session session, final Principal principal ) {
    178        // If either parameter is null, always deny
    179        if( session == null || principal == null ) {
    180            return false;
    181        }
    182
    183        // If principal is role, delegate to isUserInRole
    184        if( AuthenticationManager.isRolePrincipal( principal ) ) {
    185            return isUserInRole( session, principal );
    186        }
    187
    188        // We must be looking for a user principal, assuming that the user has been properly logged in. So just look for a name match.
    189        if( session.isAuthenticated() && AuthenticationManager.isUserPrincipal( principal ) ) {
    190            final String principalName = principal.getName();
    191            final Principal[] userPrincipals = session.getPrincipals();
    192            return Arrays.stream(userPrincipals).anyMatch(userPrincipal -> userPrincipal.getName().equals(principalName));
    193        }
    194        return false;
    195    }
    196
    197    /** {@inheritDoc} */
    198    @Override
    199    public boolean hasAccess( final Context context, final HttpServletResponse response, final boolean redirect ) throws IOException {
    200        final boolean allowed = checkPermission( context.getWikiSession(), context.requiredPermission() );
    201
    202        // Stash the wiki context
    203        if ( context.getHttpRequest() != null && context.getHttpRequest().getAttribute( Context.ATTR_CONTEXT ) == null ) {
    204            context.getHttpRequest().setAttribute( Context.ATTR_CONTEXT, context );
    205        }
    206
    207        // If access not allowed, redirect
    208        if( !allowed && redirect ) {
    209            final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE );
    210            final Principal currentUser  = context.getWikiSession().getUserPrincipal();
    211            final String pageurl = context.getPage().getName();
    212            if( context.getWikiSession().isAuthenticated() ) {
    213                LOG.info( "User {} has no access - forbidden (permission={})", currentUser.getName(), context.requiredPermission() );
    214                context.getWikiSession().addMessage( MessageFormat.format( rb.getString( "security.error.noaccess.logged" ), context.getName()) );
    215            } else {
    216                LOG.info( "User {} has no access - redirecting (permission={})", currentUser.getName(), context.requiredPermission() );
    217                context.getWikiSession().addMessage( MessageFormat.format( rb.getString( "security.error.noaccess" ), context.getName() ) );
    218            }
    219            response.sendRedirect( m_engine.getURL( ContextEnum.WIKI_LOGIN.getRequestContext(), pageurl, null ) );
    220        }
    221        return allowed;
    222    }
    223
    224    /**
    225     * {@inheritDoc}
    226     *
    227     * Expects to find property 'jspwiki.authorizer' with a valid Authorizer implementation name to take care of role lookup operations.
    228     */
    229    @Override
    230    public void initialize( final Engine engine, final Properties properties ) throws WikiException {
    231        m_engine = engine;
    232
    233        //  JAAS authorization continues
    234        m_authorizer = getAuthorizerImplementation( properties );
    235        m_authorizer.initialize( engine, properties );
    236
    237        // Initialize local security policy
    238        try {
    239            final String policyFileName = properties.getProperty( POLICY, DEFAULT_POLICY );
    240            final URL policyURL = engine.findConfigFile( policyFileName );
    241
    242            if (policyURL != null) {
    243                final File policyFile = new File( policyURL.toURI().getPath() );
    244                LOG.info("We found security policy URL: {} and transformed it to file {}",policyURL, policyFile.getAbsolutePath());
    245                m_localPolicy = new LocalPolicy( policyFile, engine.getContentEncoding().displayName() );
    246                m_localPolicy.refresh();
    247                LOG.info( "Initialized default security policy: {}", policyFile.getAbsolutePath() );
    248            } else {
    249                final String sb = "JSPWiki was unable to initialize the default security policy (WEB-INF/jspwiki.policy) file. " +
    250                                  "Please ensure that the jspwiki.policy file exists in the default location. " +
    251                                  "This file should exist regardless of the existance of a global policy file. " +
    252                                  "The global policy file is identified by the java.security.policy variable. ";
    253                final WikiSecurityException wse = new WikiSecurityException( sb );
    254                LOG.fatal( sb, wse );
    255                throw wse;
    256            }
    257        } catch ( final Exception e) {
    258            LOG.error("Could not initialize local security policy: {}", e.getMessage() );
    259            throw new WikiException( "Could not initialize local security policy: " + e.getMessage(), e );
    260        }
    261    }
    262
    263    /**
    264     * Attempts to locate and initialize an Authorizer to use with this manager. Throws a WikiException if no entry is found, or if one
    265     * fails to initialize.
    266     *
    267     * @param props jspwiki.properties, containing a 'jspwiki.authorization.provider' class name.
    268     * @return an Authorizer used to get page authorization information.
    269     * @throws WikiException if there are problems finding the authorizer implementation.
    270     */
    271    private Authorizer getAuthorizerImplementation( final Properties props ) throws WikiException {
    272        final String authClassName = props.getProperty( PROP_AUTHORIZER, DEFAULT_AUTHORIZER );
    273        return locateImplementation( authClassName );
    274    }
    275
    276    private Authorizer locateImplementation( final String clazz ) throws WikiException {
    277        if ( clazz != null ) {
    278            try {
    279                return ClassUtil.buildInstance( "org.apache.wiki.auth.authorize", clazz );
    280            } catch( final ReflectiveOperationException e ) {
    281                LOG.fatal( "Authorizer {} cannot be instantiated", clazz, e );
    282                throw new WikiException( "Authorizer " + clazz + " cannot be instantiated", e );
    283            }
    284        }
    285
    286        throw new NoRequiredPropertyException( "Unable to find a " + PROP_AUTHORIZER + " entry in the properties.", PROP_AUTHORIZER );
    287    }
    288
    289    /** {@inheritDoc} */
    290    @Override
    291    public boolean allowedByLocalPolicy( final Principal[] principals, final Permission permission ) {
    292        for ( final Principal principal : principals ) {
    293            // Get ProtectionDomain for this Principal from cache, or create new one
    294            ProtectionDomain pd = m_cachedPds.get( principal );
    295            if ( pd == null ) {
    296                final ClassLoader cl = this.getClass().getClassLoader();
    297                final CodeSource cs = new CodeSource( null, (Certificate[])null );
    298                pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principal } );
    299                m_cachedPds.put( principal, pd );
    300            }
    301
    302            // Consult the local policy and get the answer
    303            if ( m_localPolicy.implies( pd, permission ) ) {
    304                return true;
    305            }
    306        }
    307        return false;
    308    }
    309
    310    /** {@inheritDoc} */
    311    @Override
    312    public boolean checkStaticPermission( final Session session, final Permission permission ) {
    313        return ( Boolean )Session.doPrivileged( session, ( PrivilegedAction< Boolean > )() -> {
    314            try {
    315                // Check the JVM-wide security policy first
    316                AccessController.checkPermission( permission );
    317                return Boolean.TRUE;
    318            } catch( final AccessControlException e ) {
    319                // Global policy denied the permission
    320            }
    321
    322            // Try the local policy - check each Role/Group and User Principal
    323            if ( allowedByLocalPolicy( session.getRoles(), permission ) || allowedByLocalPolicy( session.getPrincipals(), permission ) ) {
    324                return Boolean.TRUE;
    325            }
    326            return Boolean.FALSE;
    327        } );
    328    }
    329
    330    /** {@inheritDoc} */
    331    @Override
    332    public Principal resolvePrincipal( final String name ) {
    333        // Check built-in Roles first
    334        final Role role = new Role(name);
    335        if ( Role.isBuiltInRole( role ) ) {
    336            return role;
    337        }
    338
    339        // Check Authorizer Roles
    340        Principal principal = m_authorizer.findRole( name );
    341        if ( principal != null ) {
    342            return principal;
    343        }
    344
    345        // Check Groups
    346        principal = m_engine.getManager( GroupManager.class ).findRole( name );
    347        if ( principal != null ) {
    348            return principal;
    349        }
    350
    351        // Ok, no luck---this must be a user principal
    352        final Principal[] principals;
    353        final UserProfile profile;
    354        final UserDatabase db = m_engine.getManager( UserManager.class ).getUserDatabase();
    355        try {
    356            profile = db.find( name );
    357            principals = db.getPrincipals( profile.getLoginName() );
    358            for( final Principal value : principals ) {
    359                principal = value;
    360                if( principal.getName().equals( name ) ) {
    361                    return principal;
    362                }
    363            }
    364        } catch( final NoSuchPrincipalException e ) {
    365            // We couldn't find the user...
    366        }
    367        // Ok, no luck---mark this as unresolved and move on
    368        return new UnresolvedPrincipal( name );
    369    }
    370
    371
    372    // events processing .......................................................
    373
    374    /** {@inheritDoc} */
    375    @Override
    376    public synchronized void addWikiEventListener( final WikiEventListener listener ) {
    377        WikiEventManager.addWikiEventListener( this, listener );
    378    }
    379
    380    /** {@inheritDoc} */
    381    @Override
    382    public synchronized void removeWikiEventListener( final WikiEventListener listener ) {
    383        WikiEventManager.removeWikiEventListener( this, listener );
    384    }
    385
    386}