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 ( Authorizer )locateImplementation( authClassName ); 279 } 280 281 private Object locateImplementation( final String clazz ) throws WikiException { 282 if ( clazz != null ) { 283 try { 284 final Class< ? > authClass = ClassUtil.findClass( "org.apache.wiki.auth.authorize", clazz ); 285 return authClass.newInstance(); 286 } catch( final ClassNotFoundException e ) { 287 log.fatal( "Authorizer " + clazz + " cannot be found", e ); 288 throw new WikiException( "Authorizer " + clazz + " cannot be found", e ); 289 } catch( final InstantiationException e ) { 290 log.fatal( "Authorizer " + clazz + " cannot be created", e ); 291 throw new WikiException( "Authorizer " + clazz + " cannot be created", e ); 292 } catch( final IllegalAccessException e ) { 293 log.fatal( "You are not allowed to access this authorizer class", e ); 294 throw new WikiException( "You are not allowed to access this authorizer class", e ); 295 } 296 } 297 298 throw new NoRequiredPropertyException( "Unable to find a " + PROP_AUTHORIZER + " entry in the properties.", PROP_AUTHORIZER ); 299 } 300 301 /** {@inheritDoc} */ 302 @Override 303 public boolean allowedByLocalPolicy( final Principal[] principals, final Permission permission ) { 304 for ( final Principal principal : principals ) { 305 // Get ProtectionDomain for this Principal from cache, or create new one 306 ProtectionDomain pd = m_cachedPds.get( principal ); 307 if ( pd == null ) { 308 final ClassLoader cl = this.getClass().getClassLoader(); 309 final CodeSource cs = new CodeSource( null, (Certificate[])null ); 310 pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principal } ); 311 m_cachedPds.put( principal, pd ); 312 } 313 314 // Consult the local policy and get the answer 315 if ( m_localPolicy.implies( pd, permission ) ) { 316 return true; 317 } 318 } 319 return false; 320 } 321 322 /** {@inheritDoc} */ 323 @Override 324 public boolean checkStaticPermission( final Session session, final Permission permission ) { 325 return ( Boolean )Session.doPrivileged( session, ( PrivilegedAction< Boolean > )() -> { 326 try { 327 // Check the JVM-wide security policy first 328 AccessController.checkPermission( permission ); 329 return Boolean.TRUE; 330 } catch( final AccessControlException e ) { 331 // Global policy denied the permission 332 } 333 334 // Try the local policy - check each Role/Group and User Principal 335 if ( allowedByLocalPolicy( session.getRoles(), permission ) || allowedByLocalPolicy( session.getPrincipals(), permission ) ) { 336 return Boolean.TRUE; 337 } 338 return Boolean.FALSE; 339 } ); 340 } 341 342 /** {@inheritDoc} */ 343 @Override 344 public Principal resolvePrincipal( final String name ) { 345 // Check built-in Roles first 346 final Role role = new Role(name); 347 if ( Role.isBuiltInRole( role ) ) { 348 return role; 349 } 350 351 // Check Authorizer Roles 352 Principal principal = m_authorizer.findRole( name ); 353 if ( principal != null ) { 354 return principal; 355 } 356 357 // Check Groups 358 principal = m_engine.getManager( GroupManager.class ).findRole( name ); 359 if ( principal != null ) { 360 return principal; 361 } 362 363 // Ok, no luck---this must be a user principal 364 final Principal[] principals; 365 final UserProfile profile; 366 final UserDatabase db = m_engine.getManager( UserManager.class ).getUserDatabase(); 367 try { 368 profile = db.find( name ); 369 principals = db.getPrincipals( profile.getLoginName() ); 370 for( final Principal value : principals ) { 371 principal = value; 372 if( principal.getName().equals( name ) ) { 373 return principal; 374 } 375 } 376 } catch( final NoSuchPrincipalException e ) { 377 // We couldn't find the user... 378 } 379 // Ok, no luck---mark this as unresolved and move on 380 return new UnresolvedPrincipal( name ); 381 } 382 383 384 // events processing ....................................................... 385 386 /** {@inheritDoc} */ 387 @Override 388 public synchronized void addWikiEventListener( final WikiEventListener listener ) { 389 WikiEventManager.addWikiEventListener( this, listener ); 390 } 391 392 /** {@inheritDoc} */ 393 @Override 394 public synchronized void removeWikiEventListener( final WikiEventListener listener ) { 395 WikiEventManager.removeWikiEventListener( this, listener ); 396 } 397 398}