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            for( final Principal userPrincipal : userPrincipals ) {
193                if( userPrincipal.getName().equals( principalName ) ) {
194                    return true;
195                }
196            }
197        }
198        return false;
199    }
200
201    /** {@inheritDoc} */
202    @Override
203    public boolean hasAccess( final Context context, final HttpServletResponse response, final boolean redirect ) throws IOException {
204        final boolean allowed = checkPermission( context.getWikiSession(), context.requiredPermission() );
205
206        // Stash the wiki context
207        if ( context.getHttpRequest() != null && context.getHttpRequest().getAttribute( Context.ATTR_CONTEXT ) == null ) {
208            context.getHttpRequest().setAttribute( Context.ATTR_CONTEXT, context );
209        }
210
211        // If access not allowed, redirect
212        if( !allowed && redirect ) {
213            final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE );
214            final Principal currentUser  = context.getWikiSession().getUserPrincipal();
215            final String pageurl = context.getPage().getName();
216            if( context.getWikiSession().isAuthenticated() ) {
217                LOG.info( "User {} has no access - forbidden (permission={})", currentUser.getName(), context.requiredPermission() );
218                context.getWikiSession().addMessage( MessageFormat.format( rb.getString( "security.error.noaccess.logged" ), context.getName()) );
219            } else {
220                LOG.info( "User {} has no access - redirecting (permission={})", currentUser.getName(), context.requiredPermission() );
221                context.getWikiSession().addMessage( MessageFormat.format( rb.getString( "security.error.noaccess" ), context.getName() ) );
222            }
223            response.sendRedirect( m_engine.getURL( ContextEnum.WIKI_LOGIN.getRequestContext(), pageurl, null ) );
224        }
225        return allowed;
226    }
227
228    /**
229     * {@inheritDoc}
230     *
231     * Expects to find property 'jspwiki.authorizer' with a valid Authorizer implementation name to take care of role lookup operations.
232     */
233    @Override
234    public void initialize( final Engine engine, final Properties properties ) throws WikiException {
235        m_engine = engine;
236
237        //  JAAS authorization continues
238        m_authorizer = getAuthorizerImplementation( properties );
239        m_authorizer.initialize( engine, properties );
240
241        // Initialize local security policy
242        try {
243            final String policyFileName = properties.getProperty( POLICY, DEFAULT_POLICY );
244            final URL policyURL = engine.findConfigFile( policyFileName );
245
246            if (policyURL != null) {
247                final File policyFile = new File( policyURL.toURI().getPath() );
248                LOG.info("We found security policy URL: {} and transformed it to file {}",policyURL, policyFile.getAbsolutePath());
249                m_localPolicy = new LocalPolicy( policyFile, engine.getContentEncoding().displayName() );
250                m_localPolicy.refresh();
251                LOG.info( "Initialized default security policy: {}", policyFile.getAbsolutePath() );
252            } else {
253                final String sb = "JSPWiki was unable to initialize the default security policy (WEB-INF/jspwiki.policy) file. " +
254                                  "Please ensure that the jspwiki.policy file exists in the default location. " +
255                                  "This file should exist regardless of the existance of a global policy file. " +
256                                  "The global policy file is identified by the java.security.policy variable. ";
257                final WikiSecurityException wse = new WikiSecurityException( sb );
258                LOG.fatal( sb, wse );
259                throw wse;
260            }
261        } catch ( final Exception e) {
262            LOG.error("Could not initialize local security policy: {}", e.getMessage() );
263            throw new WikiException( "Could not initialize local security policy: " + e.getMessage(), e );
264        }
265    }
266
267    /**
268     * Attempts to locate and initialize an Authorizer to use with this manager. Throws a WikiException if no entry is found, or if one
269     * fails to initialize.
270     *
271     * @param props jspwiki.properties, containing a 'jspwiki.authorization.provider' class name.
272     * @return an Authorizer used to get page authorization information.
273     * @throws WikiException if there are problems finding the authorizer implementation.
274     */
275    private Authorizer getAuthorizerImplementation( final Properties props ) throws WikiException {
276        final String authClassName = props.getProperty( PROP_AUTHORIZER, DEFAULT_AUTHORIZER );
277        return locateImplementation( authClassName );
278    }
279
280    private Authorizer locateImplementation( final String clazz ) throws WikiException {
281        if ( clazz != null ) {
282            try {
283                return ClassUtil.buildInstance( "org.apache.wiki.auth.authorize", clazz );
284            } catch( final ReflectiveOperationException e ) {
285                LOG.fatal( "Authorizer {} cannot be instantiated", clazz, e );
286                throw new WikiException( "Authorizer " + clazz + " cannot be instantiated", e );
287            }
288        }
289
290        throw new NoRequiredPropertyException( "Unable to find a " + PROP_AUTHORIZER + " entry in the properties.", PROP_AUTHORIZER );
291    }
292
293    /** {@inheritDoc} */
294    @Override
295    public boolean allowedByLocalPolicy( final Principal[] principals, final Permission permission ) {
296        for ( final Principal principal : principals ) {
297            // Get ProtectionDomain for this Principal from cache, or create new one
298            ProtectionDomain pd = m_cachedPds.get( principal );
299            if ( pd == null ) {
300                final ClassLoader cl = this.getClass().getClassLoader();
301                final CodeSource cs = new CodeSource( null, (Certificate[])null );
302                pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principal } );
303                m_cachedPds.put( principal, pd );
304            }
305
306            // Consult the local policy and get the answer
307            if ( m_localPolicy.implies( pd, permission ) ) {
308                return true;
309            }
310        }
311        return false;
312    }
313
314    /** {@inheritDoc} */
315    @Override
316    public boolean checkStaticPermission( final Session session, final Permission permission ) {
317        return ( Boolean )Session.doPrivileged( session, ( PrivilegedAction< Boolean > )() -> {
318            try {
319                // Check the JVM-wide security policy first
320                AccessController.checkPermission( permission );
321                return Boolean.TRUE;
322            } catch( final AccessControlException e ) {
323                // Global policy denied the permission
324            }
325
326            // Try the local policy - check each Role/Group and User Principal
327            if ( allowedByLocalPolicy( session.getRoles(), permission ) || allowedByLocalPolicy( session.getPrincipals(), permission ) ) {
328                return Boolean.TRUE;
329            }
330            return Boolean.FALSE;
331        } );
332    }
333
334    /** {@inheritDoc} */
335    @Override
336    public Principal resolvePrincipal( final String name ) {
337        // Check built-in Roles first
338        final Role role = new Role(name);
339        if ( Role.isBuiltInRole( role ) ) {
340            return role;
341        }
342
343        // Check Authorizer Roles
344        Principal principal = m_authorizer.findRole( name );
345        if ( principal != null ) {
346            return principal;
347        }
348
349        // Check Groups
350        principal = m_engine.getManager( GroupManager.class ).findRole( name );
351        if ( principal != null ) {
352            return principal;
353        }
354
355        // Ok, no luck---this must be a user principal
356        final Principal[] principals;
357        final UserProfile profile;
358        final UserDatabase db = m_engine.getManager( UserManager.class ).getUserDatabase();
359        try {
360            profile = db.find( name );
361            principals = db.getPrincipals( profile.getLoginName() );
362            for( final Principal value : principals ) {
363                principal = value;
364                if( principal.getName().equals( name ) ) {
365                    return principal;
366                }
367            }
368        } catch( final NoSuchPrincipalException e ) {
369            // We couldn't find the user...
370        }
371        // Ok, no luck---mark this as unresolved and move on
372        return new UnresolvedPrincipal( name );
373    }
374
375
376    // events processing .......................................................
377
378    /** {@inheritDoc} */
379    @Override
380    public synchronized void addWikiEventListener( final WikiEventListener listener ) {
381        WikiEventManager.addWikiEventListener( this, listener );
382    }
383
384    /** {@inheritDoc} */
385    @Override
386    public synchronized void removeWikiEventListener( final WikiEventListener listener ) {
387        WikiEventManager.removeWikiEventListener( this, listener );
388    }
389
390}