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