| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| package org.apache.wiki.auth; |
| |
| import org.apache.log4j.Logger; |
| import org.apache.wiki.api.core.Acl; |
| import org.apache.wiki.api.core.AclEntry; |
| import org.apache.wiki.api.core.Context; |
| import org.apache.wiki.api.core.ContextEnum; |
| import org.apache.wiki.api.core.Engine; |
| import org.apache.wiki.api.core.Page; |
| import org.apache.wiki.api.core.Session; |
| import org.apache.wiki.api.exceptions.NoRequiredPropertyException; |
| import org.apache.wiki.api.exceptions.WikiException; |
| import org.apache.wiki.auth.acl.AclManager; |
| import org.apache.wiki.auth.acl.UnresolvedPrincipal; |
| import org.apache.wiki.auth.authorize.GroupManager; |
| import org.apache.wiki.auth.authorize.Role; |
| import org.apache.wiki.auth.permissions.AllPermission; |
| import org.apache.wiki.auth.permissions.PagePermission; |
| import org.apache.wiki.auth.user.UserDatabase; |
| import org.apache.wiki.auth.user.UserProfile; |
| import org.apache.wiki.event.WikiEventListener; |
| import org.apache.wiki.event.WikiEventManager; |
| import org.apache.wiki.event.WikiSecurityEvent; |
| import org.apache.wiki.i18n.InternationalizationManager; |
| import org.apache.wiki.pages.PageManager; |
| import org.apache.wiki.preferences.Preferences; |
| import org.apache.wiki.util.ClassUtil; |
| import org.freshcookies.security.policy.LocalPolicy; |
| |
| import javax.servlet.http.HttpServletResponse; |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.security.AccessControlException; |
| import java.security.AccessController; |
| import java.security.CodeSource; |
| import java.security.Permission; |
| import java.security.Principal; |
| import java.security.PrivilegedAction; |
| import java.security.ProtectionDomain; |
| import java.security.cert.Certificate; |
| import java.text.MessageFormat; |
| import java.util.Arrays; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.ResourceBundle; |
| import java.util.WeakHashMap; |
| |
| |
| /** |
| * <p>Default implementation for {@link AuthorizationManager}</p> |
| * {@inheritDoc} |
| * |
| * <p>See the {@link #checkPermission(Session, Permission)} and {@link #hasRoleOrPrincipal(Session, Principal)} methods for more |
| * information on the authorization logic.</p> |
| * @since 2.3 |
| * @see AuthenticationManager |
| */ |
| public class DefaultAuthorizationManager implements AuthorizationManager { |
| |
| private static final Logger log = Logger.getLogger( DefaultAuthorizationManager.class ); |
| |
| private Authorizer m_authorizer = null; |
| |
| /** Cache for storing ProtectionDomains used to evaluate the local policy. */ |
| private Map< Principal, ProtectionDomain > m_cachedPds = new WeakHashMap<>(); |
| |
| private Engine m_engine = null; |
| |
| private LocalPolicy m_localPolicy = null; |
| |
| /** |
| * Constructs a new DefaultAuthorizationManager instance. |
| */ |
| public DefaultAuthorizationManager() { |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean checkPermission( final Session session, final Permission permission ) { |
| // A slight sanity check. |
| if( session == null || permission == null ) { |
| fireEvent( WikiSecurityEvent.ACCESS_DENIED, null, permission ); |
| return false; |
| } |
| |
| final Principal user = session.getLoginPrincipal(); |
| |
| // Always allow the action if user has AllPermission |
| final Permission allPermission = new AllPermission( m_engine.getApplicationName() ); |
| final boolean hasAllPermission = checkStaticPermission( session, allPermission ); |
| if( hasAllPermission ) { |
| fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission ); |
| return true; |
| } |
| |
| // If the user doesn't have *at least* the permission granted by policy, return false. |
| final boolean hasPolicyPermission = checkStaticPermission( session, permission ); |
| if( !hasPolicyPermission ) { |
| fireEvent( WikiSecurityEvent.ACCESS_DENIED, user, permission ); |
| return false; |
| } |
| |
| // If this isn't a PagePermission, it's allowed |
| if( !( permission instanceof PagePermission ) ) { |
| fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission ); |
| return true; |
| } |
| |
| // If the page or ACL is null, it's allowed. |
| final String pageName = ((PagePermission)permission).getPage(); |
| final Page page = m_engine.getManager( PageManager.class ).getPage( pageName ); |
| final Acl acl = ( page == null) ? null : m_engine.getManager( AclManager.class ).getPermissions( page ); |
| if( page == null || acl == null || acl.isEmpty() ) { |
| fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission ); |
| return true; |
| } |
| |
| // Next, iterate through the Principal objects assigned this permission. If the context's subject possesses |
| // any of these, the action is allowed. |
| final Principal[] aclPrincipals = acl.findPrincipals( permission ); |
| |
| log.debug( "Checking ACL entries..." ); |
| log.debug( "Acl for this page is: " + acl ); |
| log.debug( "Checking for principal: " + Arrays.toString( aclPrincipals ) ); |
| log.debug( "Permission: " + permission ); |
| |
| for( Principal aclPrincipal : aclPrincipals ) { |
| // If the ACL principal we're looking at is unresolved, try to resolve it here & correct the Acl |
| if ( aclPrincipal instanceof UnresolvedPrincipal ) { |
| final AclEntry aclEntry = acl.getAclEntry( aclPrincipal ); |
| aclPrincipal = resolvePrincipal( aclPrincipal.getName() ); |
| if ( aclEntry != null && !( aclPrincipal instanceof UnresolvedPrincipal ) ) { |
| aclEntry.setPrincipal( aclPrincipal ); |
| } |
| } |
| |
| if ( hasRoleOrPrincipal( session, aclPrincipal ) ) { |
| fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission ); |
| return true; |
| } |
| } |
| fireEvent( WikiSecurityEvent.ACCESS_DENIED, user, permission ); |
| return false; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Authorizer getAuthorizer() throws WikiSecurityException { |
| if ( m_authorizer != null ) { |
| return m_authorizer; |
| } |
| throw new WikiSecurityException( "Authorizer did not initialize properly. Check the logs." ); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean hasRoleOrPrincipal( final Session session, final Principal principal ) { |
| // If either parameter is null, always deny |
| if( session == null || principal == null ) { |
| return false; |
| } |
| |
| // If principal is role, delegate to isUserInRole |
| if( AuthenticationManager.isRolePrincipal( principal ) ) { |
| return isUserInRole( session, principal ); |
| } |
| |
| // We must be looking for a user principal, assuming that the user has been properly logged in. So just look for a name match. |
| if( session.isAuthenticated() && AuthenticationManager.isUserPrincipal( principal ) ) { |
| final String principalName = principal.getName(); |
| final Principal[] userPrincipals = session.getPrincipals(); |
| for( final Principal userPrincipal : userPrincipals ) { |
| if( userPrincipal.getName().equals( principalName ) ) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean hasAccess( final Context context, final HttpServletResponse response, final boolean redirect ) throws IOException { |
| final boolean allowed = checkPermission( context.getWikiSession(), context.requiredPermission() ); |
| final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE ); |
| |
| // Stash the wiki context |
| if ( context.getHttpRequest() != null && context.getHttpRequest().getAttribute( Context.ATTR_CONTEXT ) == null ) { |
| context.getHttpRequest().setAttribute( Context.ATTR_CONTEXT, context ); |
| } |
| |
| // If access not allowed, redirect |
| if( !allowed && redirect ) { |
| final Principal currentUser = context.getWikiSession().getUserPrincipal(); |
| final String pageurl = context.getPage().getName(); |
| if( context.getWikiSession().isAuthenticated() ) { |
| log.info( "User " + currentUser.getName() + " has no access - forbidden (permission=" + context.requiredPermission() + ")" ); |
| context.getWikiSession().addMessage( MessageFormat.format( rb.getString( "security.error.noaccess.logged" ), |
| context.getName()) ); |
| } else { |
| log.info( "User " + currentUser.getName() + " has no access - redirecting (permission=" + context.requiredPermission() + ")" ); |
| context.getWikiSession().addMessage( MessageFormat.format( rb.getString("security.error.noaccess"), context.getName() ) ); |
| } |
| response.sendRedirect( m_engine.getURL( ContextEnum.WIKI_LOGIN.getRequestContext(), pageurl, null ) ); |
| } |
| return allowed; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Expects to find property 'jspwiki.authorizer' with a valid Authorizer implementation name to take care of role lookup operations. |
| */ |
| @Override |
| public void initialize( final Engine engine, final Properties properties ) throws WikiException { |
| m_engine = engine; |
| |
| // JAAS authorization continues |
| m_authorizer = getAuthorizerImplementation( properties ); |
| m_authorizer.initialize( engine, properties ); |
| |
| // Initialize local security policy |
| try { |
| final String policyFileName = properties.getProperty( POLICY, DEFAULT_POLICY ); |
| final URL policyURL = engine.findConfigFile( policyFileName ); |
| |
| if (policyURL != null) { |
| final File policyFile = new File( policyURL.toURI().getPath() ); |
| log.info("We found security policy URL: " + policyURL + " and transformed it to file " + policyFile.getAbsolutePath()); |
| m_localPolicy = new LocalPolicy( policyFile, engine.getContentEncoding().displayName() ); |
| m_localPolicy.refresh(); |
| log.info( "Initialized default security policy: " + policyFile.getAbsolutePath() ); |
| } else { |
| final String sb = "JSPWiki was unable to initialize the default security policy (WEB-INF/jspwiki.policy) file. " + |
| "Please ensure that the jspwiki.policy file exists in the default location. " + |
| "This file should exist regardless of the existance of a global policy file. " + |
| "The global policy file is identified by the java.security.policy variable. "; |
| final WikiSecurityException wse = new WikiSecurityException( sb ); |
| log.fatal( sb, wse ); |
| throw wse; |
| } |
| } catch ( final Exception e) { |
| log.error("Could not initialize local security policy: " + e.getMessage() ); |
| throw new WikiException( "Could not initialize local security policy: " + e.getMessage(), e ); |
| } |
| } |
| |
| /** |
| * Attempts to locate and initialize a Authorizer to use with this manager. Throws a WikiException if no entry is found, or if one |
| * fails to initialize. |
| * |
| * @param props jspwiki.properties, containing a 'jspwiki.authorization.provider' class name. |
| * @return a Authorizer used to get page authorization information. |
| * @throws WikiException if there are problems finding the authorizer implementation. |
| */ |
| private Authorizer getAuthorizerImplementation( final Properties props ) throws WikiException { |
| final String authClassName = props.getProperty( PROP_AUTHORIZER, DEFAULT_AUTHORIZER ); |
| return ( Authorizer )locateImplementation( authClassName ); |
| } |
| |
| private Object locateImplementation( final String clazz ) throws WikiException { |
| if ( clazz != null ) { |
| try { |
| final Class< ? > authClass = ClassUtil.findClass( "org.apache.wiki.auth.authorize", clazz ); |
| return authClass.newInstance(); |
| } catch( final ClassNotFoundException e ) { |
| log.fatal( "Authorizer " + clazz + " cannot be found", e ); |
| throw new WikiException( "Authorizer " + clazz + " cannot be found", e ); |
| } catch( final InstantiationException e ) { |
| log.fatal( "Authorizer " + clazz + " cannot be created", e ); |
| throw new WikiException( "Authorizer " + clazz + " cannot be created", e ); |
| } catch( final IllegalAccessException e ) { |
| log.fatal( "You are not allowed to access this authorizer class", e ); |
| throw new WikiException( "You are not allowed to access this authorizer class", e ); |
| } |
| } |
| |
| throw new NoRequiredPropertyException( "Unable to find a " + PROP_AUTHORIZER + " entry in the properties.", PROP_AUTHORIZER ); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean allowedByLocalPolicy( final Principal[] principals, final Permission permission ) { |
| for ( final Principal principal : principals ) { |
| // Get ProtectionDomain for this Principal from cache, or create new one |
| ProtectionDomain pd = m_cachedPds.get( principal ); |
| if ( pd == null ) { |
| final ClassLoader cl = this.getClass().getClassLoader(); |
| final CodeSource cs = new CodeSource( null, (Certificate[])null ); |
| pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principal } ); |
| m_cachedPds.put( principal, pd ); |
| } |
| |
| // Consult the local policy and get the answer |
| if ( m_localPolicy.implies( pd, permission ) ) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean checkStaticPermission( final Session session, final Permission permission ) { |
| return ( Boolean )Session.doPrivileged( session, ( PrivilegedAction< Boolean > )() -> { |
| try { |
| // Check the JVM-wide security policy first |
| AccessController.checkPermission( permission ); |
| return Boolean.TRUE; |
| } catch( final AccessControlException e ) { |
| // Global policy denied the permission |
| } |
| |
| // Try the local policy - check each Role/Group and User Principal |
| if ( allowedByLocalPolicy( session.getRoles(), permission ) || allowedByLocalPolicy( session.getPrincipals(), permission ) ) { |
| return Boolean.TRUE; |
| } |
| return Boolean.FALSE; |
| } ); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Principal resolvePrincipal( final String name ) { |
| // Check built-in Roles first |
| final Role role = new Role(name); |
| if ( Role.isBuiltInRole( role ) ) { |
| return role; |
| } |
| |
| // Check Authorizer Roles |
| Principal principal = m_authorizer.findRole( name ); |
| if ( principal != null ) { |
| return principal; |
| } |
| |
| // Check Groups |
| principal = m_engine.getManager( GroupManager.class ).findRole( name ); |
| if ( principal != null ) { |
| return principal; |
| } |
| |
| // Ok, no luck---this must be a user principal |
| final Principal[] principals; |
| final UserProfile profile; |
| final UserDatabase db = m_engine.getManager( UserManager.class ).getUserDatabase(); |
| try { |
| profile = db.find( name ); |
| principals = db.getPrincipals( profile.getLoginName() ); |
| for( final Principal value : principals ) { |
| principal = value; |
| if( principal.getName().equals( name ) ) { |
| return principal; |
| } |
| } |
| } catch( final NoSuchPrincipalException e ) { |
| // We couldn't find the user... |
| } |
| // Ok, no luck---mark this as unresolved and move on |
| return new UnresolvedPrincipal( name ); |
| } |
| |
| |
| // events processing ....................................................... |
| |
| /** {@inheritDoc} */ |
| @Override |
| public synchronized void addWikiEventListener( final WikiEventListener listener ) { |
| WikiEventManager.addWikiEventListener( this, listener ); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public synchronized void removeWikiEventListener( final WikiEventListener listener ) { |
| WikiEventManager.removeWikiEventListener( this, listener ); |
| } |
| |
| } |