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}