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.WikiEngine; 023import org.apache.wiki.WikiSession; 024import org.apache.wiki.api.exceptions.WikiException; 025import org.apache.wiki.auth.authorize.Role; 026import org.apache.wiki.auth.authorize.WebAuthorizer; 027import org.apache.wiki.auth.authorize.WebContainerAuthorizer; 028import org.apache.wiki.auth.login.AnonymousLoginModule; 029import org.apache.wiki.auth.login.CookieAssertionLoginModule; 030import org.apache.wiki.auth.login.CookieAuthenticationLoginModule; 031import org.apache.wiki.auth.login.UserDatabaseLoginModule; 032import org.apache.wiki.auth.login.WebContainerCallbackHandler; 033import org.apache.wiki.auth.login.WebContainerLoginModule; 034import org.apache.wiki.auth.login.WikiCallbackHandler; 035import org.apache.wiki.event.WikiEventListener; 036import org.apache.wiki.event.WikiEventManager; 037import org.apache.wiki.event.WikiSecurityEvent; 038import org.apache.wiki.util.TextUtil; 039import org.apache.wiki.util.TimedCounterList; 040 041import javax.security.auth.Subject; 042import javax.security.auth.callback.CallbackHandler; 043import javax.security.auth.login.LoginException; 044import javax.security.auth.spi.LoginModule; 045import javax.servlet.http.HttpServletRequest; 046import javax.servlet.http.HttpSession; 047import java.io.File; 048import java.io.FileNotFoundException; 049import java.io.FileOutputStream; 050import java.io.IOException; 051import java.io.InputStream; 052import java.io.OutputStream; 053import java.lang.reflect.InvocationTargetException; 054import java.net.MalformedURLException; 055import java.net.URL; 056import java.security.Principal; 057import java.util.Collections; 058import java.util.HashMap; 059import java.util.HashSet; 060import java.util.Map; 061import java.util.Properties; 062import java.util.Set; 063 064/** 065 * Manages authentication activities for a WikiEngine: user login, logout, and 066 * credential refreshes. This class uses JAAS to determine how users log in. 067 * <p> 068 * The login procedure is protected in addition by a mechanism which prevents 069 * a hacker to try and force-guess passwords by slowing down attempts to log in 070 * into the same account. Every login attempt is recorded, and stored for a while 071 * (currently ten minutes), and each login attempt during that time incurs a penalty 072 * of 2^login attempts milliseconds - that is, 10 login attempts incur a login penalty of 1.024 seconds. 073 * The delay is currently capped to 20 seconds. 074 * 075 * @since 2.3 076 */ 077public class AuthenticationManager { 078 079 /** How many milliseconds the logins are stored before they're cleaned away. */ 080 private static final long LASTLOGINS_CLEANUP_TIME = 10*60*1000L; // Ten minutes 081 082 private static final long MAX_LOGIN_DELAY = 20*1000L; // 20 seconds 083 084 /** The name of the built-in cookie assertion module */ 085 public static final String COOKIE_MODULE = CookieAssertionLoginModule.class.getName(); 086 087 /** The name of the built-in cookie authentication module */ 088 public static final String COOKIE_AUTHENTICATION_MODULE = CookieAuthenticationLoginModule.class.getName(); 089 090 /** If this jspwiki.properties property is <code>true</code>, logs the IP address of the editor on saving. */ 091 public static final String PROP_STOREIPADDRESS = "jspwiki.storeIPAddress"; 092 093 /** If this jspwiki.properties property is <code>true</code>, allow cookies to be used for authentication. */ 094 public static final String PROP_ALLOW_COOKIE_AUTH = "jspwiki.cookieAuthentication"; 095 096 /** Whether logins should be throttled to limit brute-forcing attempts. Defaults to true. */ 097 public static final String PROP_LOGIN_THROTTLING = "jspwiki.login.throttling"; 098 099 protected static final Logger log = Logger.getLogger( AuthenticationManager.class ); 100 101 /** Prefix for LoginModule options key/value pairs. */ 102 protected static final String PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options."; 103 104 /** If this jspwiki.properties property is <code>true</code>, allow cookies to be used to assert identities. */ 105 protected static final String PROP_ALLOW_COOKIE_ASSERTIONS = "jspwiki.cookieAssertions"; 106 107 /** The {@link javax.security.auth.spi.LoginModule} to use for custom authentication. */ 108 protected static final String PROP_LOGIN_MODULE = "jspwiki.loginModule.class"; 109 110 /** Empty Map passed to JAAS {@link #doJAASLogin(Class, CallbackHandler, Map)} method. */ 111 protected static final Map<String,String> EMPTY_MAP = Collections.unmodifiableMap( new HashMap<String,String>() ); 112 113 /** Class (of type LoginModule) to use for custom authentication. */ 114 protected Class<? extends LoginModule> m_loginModuleClass = UserDatabaseLoginModule.class; 115 116 /** Options passed to {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)}; 117 * initialized by {@link #initialize(WikiEngine, Properties)}. */ 118 protected Map<String,String> m_loginModuleOptions = new HashMap<String,String>(); 119 120 /** The default {@link javax.security.auth.spi.LoginModule} class name to use for custom authentication. */ 121 private static final String DEFAULT_LOGIN_MODULE = "org.apache.wiki.auth.login.UserDatabaseLoginModule"; 122 123 /** Empty principal set. */ 124 private static final Set<Principal> NO_PRINCIPALS = new HashSet<>(); 125 126 /** Static Boolean for lazily-initializing the "allows assertions" flag */ 127 private boolean m_allowsCookieAssertions = true; 128 129 private boolean m_throttleLogins = true; 130 131 /** Static Boolean for lazily-initializing the "allows cookie authentication" flag */ 132 private boolean m_allowsCookieAuthentication = false; 133 134 private WikiEngine m_engine = null; 135 136 /** If true, logs the IP address of the editor */ 137 private boolean m_storeIPAddress = true; 138 139 /** Keeps a list of the usernames who have attempted a login recently. */ 140 private TimedCounterList<String> m_lastLoginAttempts = new TimedCounterList<String>(); 141 142 /** 143 * Creates an AuthenticationManager instance for the given WikiEngine and 144 * the specified set of properties. All initialization for the modules is 145 * done here. 146 * @param engine the wiki engine 147 * @param props the properties used to initialize the wiki engine 148 * @throws WikiException if the AuthenticationManager cannot be initialized 149 */ 150 @SuppressWarnings("unchecked") 151 public void initialize( WikiEngine engine, Properties props ) throws WikiException 152 { 153 m_engine = engine; 154 m_storeIPAddress = TextUtil.getBooleanProperty( props, PROP_STOREIPADDRESS, m_storeIPAddress ); 155 156 // Should we allow cookies for assertions? (default: yes) 157 m_allowsCookieAssertions = TextUtil.getBooleanProperty( props, 158 PROP_ALLOW_COOKIE_ASSERTIONS, 159 true ); 160 161 // Should we allow cookies for authentication? (default: no) 162 m_allowsCookieAuthentication = TextUtil.getBooleanProperty( props, 163 PROP_ALLOW_COOKIE_AUTH, 164 false ); 165 166 // Should we throttle logins? (default: yes) 167 m_throttleLogins = TextUtil.getBooleanProperty( props, 168 PROP_LOGIN_THROTTLING, 169 true ); 170 171 // Look up the LoginModule class 172 String loginModuleClassName = TextUtil.getStringProperty( props, PROP_LOGIN_MODULE, DEFAULT_LOGIN_MODULE ); 173 try 174 { 175 m_loginModuleClass = (Class<? extends LoginModule>) Class.forName( loginModuleClassName ); 176 } 177 catch (ClassNotFoundException e) 178 { 179 log.error( e.getMessage(), e ); 180 throw new WikiException( "Could not instantiate LoginModule class.", e ); 181 } 182 183 // Initialize the LoginModule options 184 initLoginModuleOptions( props ); 185 } 186 187 /** 188 * Returns true if this WikiEngine uses container-managed authentication. 189 * This method is used primarily for cosmetic purposes in the JSP tier, and 190 * performs no meaningful security function per se. Delegates to 191 * {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer#isContainerAuthorized()}, 192 * if used as the external authorizer; otherwise, returns <code>false</code>. 193 * @return <code>true</code> if the wiki's authentication is managed by 194 * the container, <code>false</code> otherwise 195 */ 196 public boolean isContainerAuthenticated() 197 { 198 try 199 { 200 Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer(); 201 if ( authorizer instanceof WebContainerAuthorizer ) 202 { 203 return ( ( WebContainerAuthorizer )authorizer ).isContainerAuthorized(); 204 } 205 } 206 catch ( WikiException e ) 207 { 208 // It's probably ok to fail silently... 209 } 210 return false; 211 } 212 213 /** 214 * <p>Logs in the user by attempting to populate a WikiSession Subject from 215 * a web servlet request by examining the request 216 * for the presence of container credentials and user cookies. The processing 217 * logic is as follows: 218 * </p> 219 * <ul> 220 * <li>If the WikiSession had previously been unauthenticated, check to see if 221 * user has subsequently authenticated. To be considered "authenticated," 222 * the request must supply one of the following (in order of preference): 223 * the container <code>userPrincipal</code>, container <code>remoteUser</code>, 224 * or authentication cookie. If the user is authenticated, this method fires event 225 * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_AUTHENTICATED} 226 * with two parameters: a Principal representing the login principal, 227 * and the current WikiSession. In addition, if the authorizer is of type 228 * WebContainerAuthorizer, this method iterates through the container roles returned by 229 * {@link org.apache.wiki.auth.authorize.WebContainerAuthorizer#getRoles()}, 230 * tests for membership in each one, and adds those that pass to the Subject's principal set.</li> 231 * <li>If, after checking for authentication, the WikiSession is still Anonymous, 232 * this method next checks to see if the user has "asserted" an identity 233 * by supplying an assertion cookie. If the user is found to be asserted, 234 * this method fires event {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_ASSERTED} 235 * with two parameters: <code>WikiPrincipal(<em>cookievalue</em>)</code>, and 236 * the current WikiSession.</li> 237 * <li>If, after checking for authenticated and asserted status, the WikiSession is 238 * <em>still</em> anonymous, this method fires event 239 * {@link org.apache.wiki.event.WikiSecurityEvent#LOGIN_ANONYMOUS} with 240 * two parameters: <code>WikiPrincipal(<em>remoteAddress</em>)</code>, 241 * and the current WikiSession </li> 242 * </ul> 243 * @param request servlet request for this user 244 * @return always returns <code>true</code> (because anonymous login, at least, will always succeed) 245 * @throws org.apache.wiki.auth.WikiSecurityException if the user cannot be logged in for any reason 246 * @since 2.3 247 */ 248 public boolean login( HttpServletRequest request ) throws WikiSecurityException 249 { 250 HttpSession httpSession = request.getSession(); 251 WikiSession session = SessionMonitor.getInstance(m_engine).find( httpSession ); 252 AuthenticationManager authenticationMgr = m_engine.getAuthenticationManager(); 253 AuthorizationManager authorizationMgr = m_engine.getAuthorizationManager(); 254 CallbackHandler handler = null; 255 Map<String,String> options = EMPTY_MAP; 256 257 // If user not authenticated, check if container logged them in, or if 258 // there's an authentication cookie 259 if ( !session.isAuthenticated() ) 260 { 261 // Create a callback handler 262 handler = new WebContainerCallbackHandler( m_engine, request ); 263 264 // Execute the container login module, then (if that fails) the cookie auth module 265 Set<Principal> principals = authenticationMgr.doJAASLogin( WebContainerLoginModule.class, handler, options ); 266 if ( principals.size() == 0 && authenticationMgr.allowsCookieAuthentication() ) 267 { 268 principals = authenticationMgr.doJAASLogin( CookieAuthenticationLoginModule.class, handler, options ); 269 } 270 271 // If the container logged the user in successfully, tell the WikiSession (and add all of the Principals) 272 if ( principals.size() > 0 ) 273 { 274 fireEvent( WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session ); 275 for ( Principal principal : principals ) 276 { 277 fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session ); 278 } 279 280 // Add all appropriate Authorizer roles 281 injectAuthorizerRoles( session, authorizationMgr.getAuthorizer(), request ); 282 } 283 } 284 285 // If user still not authenticated, check if assertion cookie was supplied 286 if ( !session.isAuthenticated() && authenticationMgr.allowsCookieAssertions() ) 287 { 288 // Execute the cookie assertion login module 289 Set<Principal> principals = authenticationMgr.doJAASLogin( CookieAssertionLoginModule.class, handler, options ); 290 if ( principals.size() > 0 ) 291 { 292 fireEvent( WikiSecurityEvent.LOGIN_ASSERTED, getLoginPrincipal( principals ), session); 293 } 294 } 295 296 // If user still anonymous, use the remote address 297 if (session.isAnonymous() ) 298 { 299 Set<Principal> principals = authenticationMgr.doJAASLogin( AnonymousLoginModule.class, handler, options ); 300 if ( principals.size() > 0 ) 301 { 302 fireEvent( WikiSecurityEvent.LOGIN_ANONYMOUS, getLoginPrincipal( principals ), session ); 303 return true; 304 } 305 } 306 307 // If by some unusual turn of events the Anonymous login module doesn't work, login failed! 308 return false; 309 } 310 311 /** 312 * Attempts to perform a WikiSession login for the given username/password 313 * combination using JSPWiki's custom authentication mode. In order to log in, 314 * the JAAS LoginModule supplied by the WikiEngine property {@link #PROP_LOGIN_MODULE} 315 * will be instantiated, and its 316 * {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)} 317 * method will be invoked. By default, the {@link org.apache.wiki.auth.login.UserDatabaseLoginModule} 318 * class will be used. When the LoginModule's <code>initialize</code> method is invoked, 319 * an options Map populated by properties keys prefixed by {@link #PREFIX_LOGIN_MODULE_OPTIONS} 320 * will be passed as a parameter. 321 * @param session the current wiki session; may not be <code>null</code>. 322 * @param request the user's HTTP request. This parameter may be <code>null</code>, but the configured 323 * LoginModule will not have access to the HTTP request in this case. 324 * @param username The user name. This is a login name, not a WikiName. In 325 * most cases they are the same, but in some cases, they might 326 * not be. 327 * @param password the password 328 * @return true, if the username/password is valid 329 * @throws org.apache.wiki.auth.WikiSecurityException if the Authorizer or UserManager cannot be obtained 330 */ 331 public boolean login( WikiSession session, HttpServletRequest request, String username, String password ) throws WikiSecurityException 332 { 333 if ( session == null ) 334 { 335 log.error( "No wiki session provided, cannot log in." ); 336 return false; 337 } 338 339 // Protect against brute-force password guessing if configured to do so 340 if ( m_throttleLogins ) 341 { 342 delayLogin(username); 343 } 344 345 CallbackHandler handler = new WikiCallbackHandler( m_engine, null, username, password ); 346 347 // Execute the user's specified login module 348 Set<Principal> principals = doJAASLogin( m_loginModuleClass, handler, m_loginModuleOptions ); 349 if (principals.size() > 0) 350 { 351 fireEvent(WikiSecurityEvent.LOGIN_AUTHENTICATED, getLoginPrincipal( principals ), session ); 352 for ( Principal principal : principals ) 353 { 354 fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, principal, session ); 355 } 356 357 // Add all appropriate Authorizer roles 358 injectAuthorizerRoles( session, m_engine.getAuthorizationManager().getAuthorizer(), null ); 359 360 return true; 361 } 362 return false; 363 } 364 365 /** 366 * This method builds a database of login names that are being attempted, and will try to 367 * delay if there are too many requests coming in for the same username. 368 * <p> 369 * The current algorithm uses 2^loginattempts as the delay in milliseconds, i.e. 370 * at 10 login attempts it'll add 1.024 seconds to the login. 371 * 372 * @param username The username that is being logged in 373 */ 374 private void delayLogin( String username ) 375 { 376 try 377 { 378 m_lastLoginAttempts.cleanup( LASTLOGINS_CLEANUP_TIME ); 379 int count = m_lastLoginAttempts.count( username ); 380 381 long delay = Math.min( 1<<count, MAX_LOGIN_DELAY ); 382 log.debug( "Sleeping for "+delay+" ms to allow login." ); 383 Thread.sleep( delay ); 384 385 m_lastLoginAttempts.add( username ); 386 } 387 catch( InterruptedException e ) 388 { 389 // FALLTHROUGH is fine 390 } 391 } 392 393 /** 394 * Logs the user out by retrieving the WikiSession associated with the 395 * HttpServletRequest and unbinding all of the Subject's Principals, 396 * except for {@link Role#ALL}, {@link Role#ANONYMOUS}. 397 * is a cheap-and-cheerful way to do it without invoking JAAS LoginModules. 398 * The logout operation will also flush the JSESSIONID cookie from 399 * the user's browser session, if it was set. 400 * @param request the current HTTP request 401 */ 402 public void logout( HttpServletRequest request ) 403 { 404 if( request == null ) 405 { 406 log.error( "No HTTP reqest provided; cannot log out." ); 407 return; 408 } 409 410 HttpSession session = request.getSession(); 411 String sid = ( session == null ) ? "(null)" : session.getId(); 412 if( log.isDebugEnabled() ) 413 { 414 log.debug( "Invalidating WikiSession for session ID=" + sid ); 415 } 416 // Retrieve the associated WikiSession and clear the Principal set 417 WikiSession wikiSession = WikiSession.getWikiSession( m_engine, request ); 418 Principal originalPrincipal = wikiSession.getLoginPrincipal(); 419 wikiSession.invalidate(); 420 421 // Remove the wikiSession from the WikiSession cache 422 WikiSession.removeWikiSession( m_engine, request ); 423 424 // We need to flush the HTTP session too 425 if ( session != null ) 426 { 427 session.invalidate(); 428 } 429 430 // Log the event 431 fireEvent( WikiSecurityEvent.LOGOUT, originalPrincipal, null ); 432 } 433 434 /** 435 * Determines whether this WikiEngine allows users to assert identities using 436 * cookies instead of passwords. This is determined by inspecting 437 * the WikiEngine property {@link #PROP_ALLOW_COOKIE_ASSERTIONS}. 438 * @return <code>true</code> if cookies are allowed 439 */ 440 public boolean allowsCookieAssertions() 441 { 442 return m_allowsCookieAssertions; 443 } 444 445 /** 446 * Determines whether this WikiEngine allows users to authenticate using 447 * cookies instead of passwords. This is determined by inspecting 448 * the WikiEngine property {@link #PROP_ALLOW_COOKIE_AUTH}. 449 * @return <code>true</code> if cookies are allowed for authentication 450 * @since 2.5.62 451 */ 452 public boolean allowsCookieAuthentication() 453 { 454 return m_allowsCookieAuthentication; 455 } 456 457 /** 458 * Determines whether the supplied Principal is a "role principal". 459 * @param principal the principal to test 460 * @return <code>true</code> if the Principal is of type 461 * {@link GroupPrincipal} or 462 * {@link org.apache.wiki.auth.authorize.Role}, 463 * <code>false</code> otherwise 464 */ 465 public static boolean isRolePrincipal( final Principal principal ) { 466 return principal instanceof Role || principal instanceof GroupPrincipal; 467 } 468 469 /** 470 * Determines whether the supplied Principal is a "user principal". 471 * @param principal the principal to test 472 * @return <code>false</code> if the Principal is of type 473 * {@link GroupPrincipal} or 474 * {@link org.apache.wiki.auth.authorize.Role}, 475 * <code>true</code> otherwise 476 */ 477 public static boolean isUserPrincipal( final Principal principal ) { 478 return !isRolePrincipal( principal ); 479 } 480 481 /** 482 * Instantiates and executes a single JAAS 483 * {@link javax.security.auth.spi.LoginModule}, and returns a Set of 484 * Principals that results from a successful login. The LoginModule is instantiated, 485 * then its {@link javax.security.auth.spi.LoginModule#initialize(Subject, CallbackHandler, Map, Map)} 486 * method is called. The parameters passed to <code>initialize</code> is a 487 * dummy Subject, an empty shared-state Map, and an options Map the caller supplies. 488 * 489 * @param clazz the LoginModule class to instantiate 490 * @param handler the callback handler to supply to the LoginModule 491 * @param options a Map of key/value strings for initializing the LoginModule 492 * @return the set of Principals returned by the JAAS method {@link Subject#getPrincipals()} 493 * @throws WikiSecurityException if the LoginModule could not be instantiated for any reason 494 */ 495 protected Set<Principal> doJAASLogin(Class<? extends LoginModule> clazz, CallbackHandler handler, Map<String,String> options) throws WikiSecurityException 496 { 497 // Instantiate the login module 498 final LoginModule loginModule; 499 try { 500 loginModule = clazz.getDeclaredConstructor().newInstance(); 501 } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { 502 throw new WikiSecurityException(e.getMessage(), e ); 503 } 504 505 // Initialize the LoginModule 506 Subject subject = new Subject(); 507 loginModule.initialize( subject, handler, EMPTY_MAP, options ); 508 509 // Try to log in: 510 boolean loginSucceeded = false; 511 boolean commitSucceeded = false; 512 try 513 { 514 loginSucceeded = loginModule.login(); 515 if (loginSucceeded) 516 { 517 commitSucceeded = loginModule.commit(); 518 } 519 } 520 catch (LoginException e) 521 { 522 // Login or commit failed! No principal for you! 523 } 524 525 // If we successfully logged in & committed, return all the principals 526 if (loginSucceeded && commitSucceeded) 527 { 528 return subject.getPrincipals(); 529 } 530 return NO_PRINCIPALS; 531 } 532 533 /** 534 * Looks up and obtains a configuration file inside the WEB-INF folder of a 535 * wiki webapp. 536 * @param engine the wiki engine 537 * @param name the file to obtain, <em>e.g.</em>, <code>jspwiki.policy</code> 538 * @return the URL to the file 539 */ 540 protected static URL findConfigFile( WikiEngine engine, String name ) 541 { 542 log.info( "looking for " + name + " inside WEB-INF " ); 543 // Try creating an absolute path first 544 File defaultFile = null; 545 if( engine.getRootPath() != null ) 546 { 547 defaultFile = new File( engine.getRootPath() + "/WEB-INF/" + name ); 548 } 549 if ( defaultFile != null && defaultFile.exists() ) 550 { 551 try 552 { 553 return defaultFile.toURI().toURL(); 554 } 555 catch ( MalformedURLException e) 556 { 557 // Shouldn't happen, but log it if it does 558 log.warn( "Malformed URL: " + e.getMessage() ); 559 } 560 561 } 562 563 564 // Ok, the absolute path didn't work; try other methods 565 566 URL path = null; 567 568 if( engine.getServletContext() != null ) { 569 final File tmpFile; 570 try { 571 tmpFile = File.createTempFile( "temp." + name, "" ); 572 } catch( final IOException e ) { 573 log.error( "unable to create a temp file to load onto the policy", e ); 574 return null; 575 } 576 tmpFile.deleteOnExit(); 577 log.info( "looking for /" + name + " on classpath" ); 578 // create a tmp file of the policy loaded as an InputStream and return the URL to it 579 try( final InputStream is = AuthenticationManager.class.getResourceAsStream( "/" + name ); 580 final OutputStream os = new FileOutputStream( tmpFile ) ) { 581 if( is == null ) { 582 throw new FileNotFoundException( name + " not found" ); 583 } 584 final URL url = engine.getServletContext().getResource( "/WEB-INF/" + name ); 585 if( url != null ) { 586 return url; 587 } 588 589 final byte[] buff = new byte[1024]; 590 int bytes; 591 while( ( bytes = is.read( buff ) ) != -1 ) { 592 os.write( buff, 0, bytes ); 593 } 594 595 path = tmpFile.toURI().toURL(); 596 } catch( final MalformedURLException e ) { 597 // This should never happen unless I screw up 598 log.fatal( "Your code is b0rked. You are a bad person.", e ); 599 } catch( final IOException e ) { 600 log.error( "failed to load security policy from file " + name + ",stacktrace follows", e ); 601 } 602 } 603 return path; 604 } 605 606 /** 607 * Returns the first Principal in a set that isn't a {@link org.apache.wiki.auth.authorize.Role} or 608 * {@link org.apache.wiki.auth.GroupPrincipal}. 609 * @param principals the principal set 610 * @return the login principal 611 */ 612 protected Principal getLoginPrincipal( final Set< Principal > principals ) { 613 for( final Principal principal : principals ) { 614 if ( isUserPrincipal( principal ) ) { 615 return principal; 616 } 617 } 618 return null; 619 } 620 621 // events processing ....................................................... 622 623 /** 624 * Registers a WikiEventListener with this instance. 625 * This is a convenience method. 626 * @param listener the event listener 627 */ 628 public synchronized void addWikiEventListener( final WikiEventListener listener ) { 629 WikiEventManager.addWikiEventListener( this, listener ); 630 } 631 632 /** 633 * Un-registers a WikiEventListener with this instance. 634 * This is a convenience method. 635 * @param listener the event listener 636 */ 637 public synchronized void removeWikiEventListener( final WikiEventListener listener ) { 638 WikiEventManager.removeWikiEventListener( this, listener ); 639 } 640 641 /** 642 * Fires a WikiSecurityEvent of the provided type, Principal and target Object 643 * to all registered listeners. 644 * 645 * @see org.apache.wiki.event.WikiSecurityEvent 646 * @param type the event type to be fired 647 * @param principal the subject of the event, which may be <code>null</code> 648 * @param target the changed Object, which may be <code>null</code> 649 */ 650 protected void fireEvent( final int type, final Principal principal, final Object target ) { 651 if ( WikiEventManager.isListening( this ) ) { 652 WikiEventManager.fireEvent( this, new WikiSecurityEvent( this, type, principal, target ) ); 653 } 654 } 655 656 /** 657 * Initializes the options Map supplied to the configured LoginModule every time it is invoked. 658 * The properties and values extracted from 659 * <code>jspwiki.properties</code> are of the form 660 * <code>jspwiki.loginModule.options.<var>param</var> = <var>value</var>, where 661 * <var>param</var> is the key name, and <var>value</var> is the value. 662 * @param props the properties used to initialize JSPWiki 663 * @throws IllegalArgumentException if any of the keys are duplicated 664 */ 665 private void initLoginModuleOptions(Properties props) 666 { 667 for ( Object key : props.keySet() ) 668 { 669 String propName = key.toString(); 670 if ( propName.startsWith( PREFIX_LOGIN_MODULE_OPTIONS ) ) 671 { 672 // Extract the option name and value 673 String optionKey = propName.substring( PREFIX_LOGIN_MODULE_OPTIONS.length() ).trim(); 674 if ( optionKey.length() > 0 ) 675 { 676 String optionValue = props.getProperty( propName ); 677 678 // Make sure the key is unique before stashing the key/value pair 679 if ( m_loginModuleOptions.containsKey( optionKey ) ) 680 { 681 throw new IllegalArgumentException( "JAAS LoginModule key " + propName + " cannot be specified twice!" ); 682 } 683 m_loginModuleOptions.put( optionKey, optionValue ); 684 } 685 } 686 } 687 } 688 689 /** 690 * After successful login, this method is called to inject authorized role Principals into the WikiSession. 691 * To determine which roles should be injected, the configured Authorizer 692 * is queried for the roles it knows about by calling {@link org.apache.wiki.auth.Authorizer#getRoles()}. 693 * Then, each role returned by the authorizer is tested by calling {@link org.apache.wiki.auth.Authorizer#isUserInRole(WikiSession, Principal)}. 694 * If this check fails, and the Authorizer is of type WebAuthorizer, the role is checked again by calling 695 * {@link org.apache.wiki.auth.authorize.WebAuthorizer#isUserInRole(javax.servlet.http.HttpServletRequest, Principal)}). 696 * Any roles that pass the test are injected into the Subject by firing appropriate authentication events. 697 * @param session the user's current WikiSession 698 * @param authorizer the WikiEngine's configured Authorizer 699 * @param request the user's HTTP session, which may be <code>null</code> 700 */ 701 private void injectAuthorizerRoles( WikiSession session, Authorizer authorizer, HttpServletRequest request ) 702 { 703 // Test each role the authorizer knows about 704 for ( Principal role : authorizer.getRoles() ) 705 { 706 // Test the Authorizer 707 if ( authorizer.isUserInRole( session, role ) ) 708 { 709 fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session ); 710 if ( log.isDebugEnabled() ) 711 { 712 log.debug("Added authorizer role " + role.getName() + "." ); 713 } 714 } 715 716 // If web authorizer, test the request.isInRole() method also 717 else if ( request != null && authorizer instanceof WebAuthorizer ) 718 { 719 WebAuthorizer wa = (WebAuthorizer)authorizer; 720 if ( wa.isUserInRole( request, role ) ) 721 { 722 fireEvent( WikiSecurityEvent.PRINCIPAL_ADD, role, session ); 723 if ( log.isDebugEnabled() ) 724 { 725 log.debug("Added container role " + role.getName() + "." ); 726 } 727 } 728 } 729 } 730 } 731 732}