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.log4j.Logger; 022import org.apache.wiki.api.core.Acl; 023import org.apache.wiki.api.core.AclEntry; 024import org.apache.wiki.api.core.Context; 025import org.apache.wiki.api.core.ContextEnum; 026import org.apache.wiki.api.core.Engine; 027import org.apache.wiki.api.core.Page; 028import org.apache.wiki.api.core.Session; 029import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 030import org.apache.wiki.api.exceptions.WikiException; 031import org.apache.wiki.auth.acl.AclManager; 032import org.apache.wiki.auth.acl.UnresolvedPrincipal; 033import org.apache.wiki.auth.authorize.GroupManager; 034import org.apache.wiki.auth.authorize.Role; 035import org.apache.wiki.auth.permissions.AllPermission; 036import org.apache.wiki.auth.permissions.PagePermission; 037import org.apache.wiki.auth.user.UserDatabase; 038import org.apache.wiki.auth.user.UserProfile; 039import org.apache.wiki.event.WikiEventListener; 040import org.apache.wiki.event.WikiEventManager; 041import org.apache.wiki.event.WikiSecurityEvent; 042import org.apache.wiki.i18n.InternationalizationManager; 043import org.apache.wiki.pages.PageManager; 044import org.apache.wiki.preferences.Preferences; 045import org.apache.wiki.util.ClassUtil; 046import org.freshcookies.security.policy.LocalPolicy; 047 048import javax.servlet.http.HttpServletResponse; 049import java.io.File; 050import java.io.IOException; 051import java.net.URL; 052import java.security.AccessControlException; 053import java.security.AccessController; 054import java.security.CodeSource; 055import java.security.Permission; 056import java.security.Principal; 057import java.security.PrivilegedAction; 058import java.security.ProtectionDomain; 059import java.security.cert.Certificate; 060import java.text.MessageFormat; 061import java.util.Arrays; 062import java.util.Map; 063import java.util.Properties; 064import java.util.ResourceBundle; 065import java.util.WeakHashMap; 066 067 068/** 069 * <p>Default implementation for {@link AuthorizationManager}</p> 070 * {@inheritDoc} 071 * 072 * <p>See the {@link #checkPermission(Session, Permission)} and {@link #hasRoleOrPrincipal(Session, Principal)} methods for more 073 * information on the authorization logic.</p> 074 * @since 2.3 075 * @see AuthenticationManager 076 */ 077public class DefaultAuthorizationManager implements AuthorizationManager { 078 079 private static final Logger log = Logger.getLogger( DefaultAuthorizationManager.class ); 080 081 private Authorizer m_authorizer = null; 082 083 /** Cache for storing ProtectionDomains used to evaluate the local policy. */ 084 private Map< Principal, ProtectionDomain > m_cachedPds = new WeakHashMap<>(); 085 086 private Engine m_engine = null; 087 088 private LocalPolicy m_localPolicy = null; 089 090 /** 091 * Constructs a new DefaultAuthorizationManager instance. 092 */ 093 public DefaultAuthorizationManager() { 094 } 095 096 /** {@inheritDoc} */ 097 @Override 098 public boolean checkPermission( final Session session, final Permission permission ) { 099 // A slight sanity check. 100 if( session == null || permission == null ) { 101 fireEvent( WikiSecurityEvent.ACCESS_DENIED, null, permission ); 102 return false; 103 } 104 105 final Principal user = session.getLoginPrincipal(); 106 107 // Always allow the action if user has AllPermission 108 final Permission allPermission = new AllPermission( m_engine.getApplicationName() ); 109 final boolean hasAllPermission = checkStaticPermission( session, allPermission ); 110 if( hasAllPermission ) { 111 fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission ); 112 return true; 113 } 114 115 // If the user doesn't have *at least* the permission granted by policy, return false. 116 final boolean hasPolicyPermission = checkStaticPermission( session, permission ); 117 if( !hasPolicyPermission ) { 118 fireEvent( WikiSecurityEvent.ACCESS_DENIED, user, permission ); 119 return false; 120 } 121 122 // If this isn't a PagePermission, it's allowed 123 if( !( permission instanceof PagePermission ) ) { 124 fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission ); 125 return true; 126 } 127 128 // If the page or ACL is null, it's allowed. 129 final String pageName = ((PagePermission)permission).getPage(); 130 final Page page = m_engine.getManager( PageManager.class ).getPage( pageName ); 131 final Acl acl = ( page == null) ? null : m_engine.getManager( AclManager.class ).getPermissions( page ); 132 if( page == null || acl == null || acl.isEmpty() ) { 133 fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission ); 134 return true; 135 } 136 137 // Next, iterate through the Principal objects assigned this permission. If the context's subject possesses 138 // any of these, the action is allowed. 139 final Principal[] aclPrincipals = acl.findPrincipals( permission ); 140 141 log.debug( "Checking ACL entries..." ); 142 log.debug( "Acl for this page is: " + acl ); 143 log.debug( "Checking for principal: " + Arrays.toString( aclPrincipals ) ); 144 log.debug( "Permission: " + permission ); 145 146 for( Principal aclPrincipal : aclPrincipals ) { 147 // If the ACL principal we're looking at is unresolved, try to resolve it here & correct the Acl 148 if ( aclPrincipal instanceof UnresolvedPrincipal ) { 149 final AclEntry aclEntry = acl.getAclEntry( aclPrincipal ); 150 aclPrincipal = resolvePrincipal( aclPrincipal.getName() ); 151 if ( aclEntry != null && !( aclPrincipal instanceof UnresolvedPrincipal ) ) { 152 aclEntry.setPrincipal( aclPrincipal ); 153 } 154 } 155 156 if ( hasRoleOrPrincipal( session, aclPrincipal ) ) { 157 fireEvent( WikiSecurityEvent.ACCESS_ALLOWED, user, permission ); 158 return true; 159 } 160 } 161 fireEvent( WikiSecurityEvent.ACCESS_DENIED, user, permission ); 162 return false; 163 } 164 165 /** {@inheritDoc} */ 166 @Override 167 public Authorizer getAuthorizer() throws WikiSecurityException { 168 if ( m_authorizer != null ) { 169 return m_authorizer; 170 } 171 throw new WikiSecurityException( "Authorizer did not initialize properly. Check the logs." ); 172 } 173 174 /** {@inheritDoc} */ 175 @Override 176 public boolean hasRoleOrPrincipal( final Session session, final Principal principal ) { 177 // If either parameter is null, always deny 178 if( session == null || principal == null ) { 179 return false; 180 } 181 182 // If principal is role, delegate to isUserInRole 183 if( AuthenticationManager.isRolePrincipal( principal ) ) { 184 return isUserInRole( session, principal ); 185 } 186 187 // We must be looking for a user principal, assuming that the user has been properly logged in. So just look for a name match. 188 if( session.isAuthenticated() && AuthenticationManager.isUserPrincipal( principal ) ) { 189 final String principalName = principal.getName(); 190 final Principal[] userPrincipals = session.getPrincipals(); 191 for( final Principal userPrincipal : userPrincipals ) { 192 if( userPrincipal.getName().equals( principalName ) ) { 193 return true; 194 } 195 } 196 } 197 return false; 198 } 199 200 /** {@inheritDoc} */ 201 @Override 202 public boolean hasAccess( final Context context, final HttpServletResponse response, final boolean redirect ) throws IOException { 203 final boolean allowed = checkPermission( context.getWikiSession(), context.requiredPermission() ); 204 final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE ); 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 Principal currentUser = context.getWikiSession().getUserPrincipal(); 214 final String pageurl = context.getPage().getName(); 215 if( context.getWikiSession().isAuthenticated() ) { 216 log.info( "User " + currentUser.getName() + " has no access - forbidden (permission=" + context.requiredPermission() + ")" ); 217 context.getWikiSession().addMessage( MessageFormat.format( rb.getString( "security.error.noaccess.logged" ), 218 context.getName()) ); 219 } else { 220 log.info( "User " + currentUser.getName() + " has no access - redirecting (permission=" + 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: " + policyURL + " and transformed it to file " + 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 a 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 a 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 ( Authorizer )locateImplementation( authClassName ); 278 } 279 280 private Object locateImplementation( final String clazz ) throws WikiException { 281 if ( clazz != null ) { 282 try { 283 final Class< ? > authClass = ClassUtil.findClass( "org.apache.wiki.auth.authorize", clazz ); 284 return authClass.newInstance(); 285 } catch( final ClassNotFoundException e ) { 286 log.fatal( "Authorizer " + clazz + " cannot be found", e ); 287 throw new WikiException( "Authorizer " + clazz + " cannot be found", e ); 288 } catch( final InstantiationException e ) { 289 log.fatal( "Authorizer " + clazz + " cannot be created", e ); 290 throw new WikiException( "Authorizer " + clazz + " cannot be created", e ); 291 } catch( final IllegalAccessException e ) { 292 log.fatal( "You are not allowed to access this authorizer class", e ); 293 throw new WikiException( "You are not allowed to access this authorizer class", e ); 294 } 295 } 296 297 throw new NoRequiredPropertyException( "Unable to find a " + PROP_AUTHORIZER + " entry in the properties.", PROP_AUTHORIZER ); 298 } 299 300 /** {@inheritDoc} */ 301 @Override 302 public boolean allowedByLocalPolicy( final Principal[] principals, final Permission permission ) { 303 for ( final Principal principal : principals ) { 304 // Get ProtectionDomain for this Principal from cache, or create new one 305 ProtectionDomain pd = m_cachedPds.get( principal ); 306 if ( pd == null ) { 307 final ClassLoader cl = this.getClass().getClassLoader(); 308 final CodeSource cs = new CodeSource( null, (Certificate[])null ); 309 pd = new ProtectionDomain( cs, null, cl, new Principal[]{ principal } ); 310 m_cachedPds.put( principal, pd ); 311 } 312 313 // Consult the local policy and get the answer 314 if ( m_localPolicy.implies( pd, permission ) ) { 315 return true; 316 } 317 } 318 return false; 319 } 320 321 /** {@inheritDoc} */ 322 @Override 323 public boolean checkStaticPermission( final Session session, final Permission permission ) { 324 return ( Boolean )Session.doPrivileged( session, ( PrivilegedAction< Boolean > )() -> { 325 try { 326 // Check the JVM-wide security policy first 327 AccessController.checkPermission( permission ); 328 return Boolean.TRUE; 329 } catch( final AccessControlException e ) { 330 // Global policy denied the permission 331 } 332 333 // Try the local policy - check each Role/Group and User Principal 334 if ( allowedByLocalPolicy( session.getRoles(), permission ) || allowedByLocalPolicy( session.getPrincipals(), permission ) ) { 335 return Boolean.TRUE; 336 } 337 return Boolean.FALSE; 338 } ); 339 } 340 341 /** {@inheritDoc} */ 342 @Override 343 public Principal resolvePrincipal( final String name ) { 344 // Check built-in Roles first 345 final Role role = new Role(name); 346 if ( Role.isBuiltInRole( role ) ) { 347 return role; 348 } 349 350 // Check Authorizer Roles 351 Principal principal = m_authorizer.findRole( name ); 352 if ( principal != null ) { 353 return principal; 354 } 355 356 // Check Groups 357 principal = m_engine.getManager( GroupManager.class ).findRole( name ); 358 if ( principal != null ) { 359 return principal; 360 } 361 362 // Ok, no luck---this must be a user principal 363 final Principal[] principals; 364 final UserProfile profile; 365 final UserDatabase db = m_engine.getManager( UserManager.class ).getUserDatabase(); 366 try { 367 profile = db.find( name ); 368 principals = db.getPrincipals( profile.getLoginName() ); 369 for( final Principal value : principals ) { 370 principal = value; 371 if( principal.getName().equals( name ) ) { 372 return principal; 373 } 374 } 375 } catch( final NoSuchPrincipalException e ) { 376 // We couldn't find the user... 377 } 378 // Ok, no luck---mark this as unresolved and move on 379 return new UnresolvedPrincipal( name ); 380 } 381 382 383 // events processing ....................................................... 384 385 /** {@inheritDoc} */ 386 @Override 387 public synchronized void addWikiEventListener( final WikiEventListener listener ) { 388 WikiEventManager.addWikiEventListener( this, listener ); 389 } 390 391 /** {@inheritDoc} */ 392 @Override 393 public synchronized void removeWikiEventListener( final WikiEventListener listener ) { 394 WikiEventManager.removeWikiEventListener( this, listener ); 395 } 396 397}