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