1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.harmony.javax.security.auth.login; 19 20 import java.io.IOException; 21 import java.security.AccessController; 22 import java.security.AccessControlContext; 23 import java.security.PrivilegedExceptionAction; 24 import java.security.PrivilegedActionException; 25 26 import java.security.Security; 27 import java.util.HashMap; 28 import java.util.Map; 29 30 import org.apache.harmony.javax.security.auth.Subject; 31 import org.apache.harmony.javax.security.auth.callback.CallbackHandler; 32 import org.apache.harmony.javax.security.auth.callback.Callback; 33 import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; 34 import org.apache.harmony.javax.security.auth.spi.LoginModule; 35 import org.apache.harmony.javax.security.auth.AuthPermission; 36 37 import org.apache.harmony.javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; 38 39 40 41 public class LoginContext { 42 43 private static final String DEFAULT_CALLBACK_HANDLER_PROPERTY = "auth.login.defaultCallbackHandler"; //$NON-NLS-1$ 44 45 /* 46 * Integer constants which serve as a replacement for the corresponding 47 * LoginModuleControlFlag.* constants. These integers are used later as 48 * index in the arrays - see loginImpl() and logoutImpl() methods 49 */ 50 private static final int OPTIONAL = 0; 51 52 private static final int REQUIRED = 1; 53 54 private static final int REQUISITE = 2; 55 56 private static final int SUFFICIENT = 3; 57 58 // Subject to be used for this LoginContext's operations 59 private Subject subject; 60 61 /* 62 * Shows whether the subject was specified by user (true) or was created by 63 * this LoginContext itself (false). 64 */ 65 private boolean userProvidedSubject; 66 67 // Shows whether we use installed or user-provided Configuration 68 private boolean userProvidedConfig; 69 70 // An user's AccessControlContext, used when user specifies 71 private AccessControlContext userContext; 72 73 /* 74 * Either a callback handler passed by the user or a wrapper for the user's 75 * specified handler - see init() below. 76 */ 77 private CallbackHandler callbackHandler; 78 79 /* 80 * An array which keeps the instantiated and init()-ialized login modules 81 * and their states 82 */ 83 private Module[] modules; 84 85 // Stores a shared state 86 private Map<String, ?> sharedState; 87 88 // A context class loader used to load [mainly] LoginModules 89 private ClassLoader contextClassLoader; 90 91 // Shows overall status - whether this LoginContext was successfully logged 92 private boolean loggedIn; 93 LoginContext(String name)94 public LoginContext(String name) throws LoginException { 95 super(); 96 init(name, null, null, null); 97 } 98 LoginContext(String name, CallbackHandler cbHandler)99 public LoginContext(String name, CallbackHandler cbHandler) throws LoginException { 100 super(); 101 if (cbHandler == null) { 102 throw new LoginException("auth.34"); //$NON-NLS-1$ 103 } 104 init(name, null, cbHandler, null); 105 } 106 LoginContext(String name, Subject subject)107 public LoginContext(String name, Subject subject) throws LoginException { 108 super(); 109 if (subject == null) { 110 throw new LoginException("auth.03"); //$NON-NLS-1$ 111 } 112 init(name, subject, null, null); 113 } 114 LoginContext(String name, Subject subject, CallbackHandler cbHandler)115 public LoginContext(String name, Subject subject, CallbackHandler cbHandler) 116 throws LoginException { 117 super(); 118 if (subject == null) { 119 throw new LoginException("auth.03"); //$NON-NLS-1$ 120 } 121 if (cbHandler == null) { 122 throw new LoginException("auth.34"); //$NON-NLS-1$ 123 } 124 init(name, subject, cbHandler, null); 125 } 126 LoginContext(String name, Subject subject, CallbackHandler cbHandler, Configuration config)127 public LoginContext(String name, Subject subject, CallbackHandler cbHandler, 128 Configuration config) throws LoginException { 129 super(); 130 init(name, subject, cbHandler, config); 131 } 132 133 // Does all the machinery needed for the initialization. init(String name, Subject subject, final CallbackHandler cbHandler, Configuration config)134 private void init(String name, Subject subject, final CallbackHandler cbHandler, 135 Configuration config) throws LoginException { 136 userProvidedSubject = (this.subject = subject) != null; 137 138 // 139 // Set config 140 // 141 if (name == null) { 142 throw new LoginException("auth.00"); //$NON-NLS-1$ 143 } 144 145 if (config == null) { 146 config = Configuration.getAccessibleConfiguration(); 147 } else { 148 userProvidedConfig = true; 149 } 150 151 SecurityManager sm = System.getSecurityManager(); 152 153 if (sm != null && !userProvidedConfig) { 154 sm.checkPermission(new AuthPermission("createLoginContext." + name));//$NON-NLS-1$ 155 } 156 157 AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name); 158 if (entries == null) { 159 if (sm != null && !userProvidedConfig) { 160 sm.checkPermission(new AuthPermission("createLoginContext.other")); //$NON-NLS-1$ 161 } 162 entries = config.getAppConfigurationEntry("other"); //$NON-NLS-1$ 163 if (entries == null) { 164 throw new LoginException("auth.35 " + name); //$NON-NLS-1$ 165 } 166 } 167 168 modules = new Module[entries.length]; 169 for (int i = 0; i < modules.length; i++) { 170 modules[i] = new Module(entries[i]); 171 } 172 // 173 // Set CallbackHandler and this.contextClassLoader 174 // 175 176 /* 177 * as some of the operations to be executed (i.e. get*ClassLoader, 178 * getProperty, class loading) are security-checked, then combine all of 179 * them into a single doPrivileged() call. 180 */ 181 try { 182 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { 183 public Void run() throws Exception { 184 // First, set the 'contextClassLoader' 185 contextClassLoader = Thread.currentThread().getContextClassLoader(); 186 if (contextClassLoader == null) { 187 contextClassLoader = ClassLoader.getSystemClassLoader(); 188 } 189 // then, checks whether the cbHandler is set 190 if (cbHandler == null) { 191 // well, let's try to find it 192 String klassName = Security 193 .getProperty(DEFAULT_CALLBACK_HANDLER_PROPERTY); 194 if (klassName == null || klassName.length() == 0) { 195 return null; 196 } 197 Class<?> klass = Class.forName(klassName, true, contextClassLoader); 198 callbackHandler = (CallbackHandler) klass.newInstance(); 199 } else { 200 callbackHandler = cbHandler; 201 } 202 return null; 203 } 204 }); 205 } catch (PrivilegedActionException ex) { 206 Throwable cause = ex.getCause(); 207 throw (LoginException) new LoginException("auth.36").initCause(cause);//$NON-NLS-1$ 208 } 209 210 if (userProvidedConfig) { 211 userContext = AccessController.getContext(); 212 } else if (callbackHandler != null) { 213 userContext = AccessController.getContext(); 214 callbackHandler = new ContextedCallbackHandler(callbackHandler); 215 } 216 } 217 getSubject()218 public Subject getSubject() { 219 if (userProvidedSubject || loggedIn) { 220 return subject; 221 } 222 return null; 223 } 224 225 /** 226 * Warning: calling the method more than once may result in undefined 227 * behaviour if logout() method is not invoked before. 228 */ login()229 public void login() throws LoginException { 230 PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() { 231 public Void run() throws LoginException { 232 loginImpl(); 233 return null; 234 } 235 }; 236 try { 237 if (userProvidedConfig) { 238 AccessController.doPrivileged(action, userContext); 239 } else { 240 AccessController.doPrivileged(action); 241 } 242 } catch (PrivilegedActionException ex) { 243 throw (LoginException) ex.getException(); 244 } 245 } 246 247 /** 248 * The real implementation of login() method whose calls are wrapped into 249 * appropriate doPrivileged calls in login(). 250 */ loginImpl()251 private void loginImpl() throws LoginException { 252 if (subject == null) { 253 subject = new Subject(); 254 } 255 256 if (sharedState == null) { 257 sharedState = new HashMap<String, Object>(); 258 } 259 260 // PHASE 1: Calling login()-s 261 Throwable firstProblem = null; 262 263 int[] logged = new int[4]; 264 int[] total = new int[4]; 265 266 for (Module module : modules) { 267 try { 268 // if a module fails during Class.forName(), then it breaks overall 269 // attempt - see catch() below 270 module.create(subject, callbackHandler, sharedState); 271 272 if (module.module.login()) { 273 ++total[module.getFlag()]; 274 ++logged[module.getFlag()]; 275 if (module.getFlag() == SUFFICIENT) { 276 break; 277 } 278 } 279 } catch (Throwable ex) { 280 if (firstProblem == null) { 281 firstProblem = ex; 282 } 283 if (module.klass == null) { 284 /* 285 * an exception occurred during class lookup - overall 286 * attempt must fail a little trick: increase the REQUIRED's 287 * number - this will look like a failed REQUIRED module 288 * later, so overall attempt will fail 289 */ 290 ++total[REQUIRED]; 291 break; 292 } 293 ++total[module.getFlag()]; 294 // something happened after the class was loaded 295 if (module.getFlag() == REQUISITE) { 296 // ... and no need to walk down anymore 297 break; 298 } 299 } 300 } 301 // end of PHASE1, 302 303 // Let's decide whether we have either overall success or a total failure 304 boolean fail = true; 305 306 /* 307 * Note: 'failed[xxx]!=0' is not enough to check. 308 * 309 * Use 'logged[xx] != total[xx]' instead. This is because some modules 310 * might not be counted as 'failed' if an exception occurred during 311 * preload()/Class.forName()-ing. But, such modules still get counted in 312 * the total[]. 313 */ 314 315 // if any REQ* module failed - then it's failure 316 if (logged[REQUIRED] != total[REQUIRED] || logged[REQUISITE] != total[REQUISITE]) { 317 // fail = true; 318 } else { 319 if (total[REQUIRED] == 0 && total[REQUISITE] == 0) { 320 // neither REQUIRED nor REQUISITE was configured. 321 // must have at least one SUFFICIENT or OPTIONAL 322 if (logged[OPTIONAL] != 0 || logged[SUFFICIENT] != 0) { 323 fail = false; 324 } 325 //else { fail = true; } 326 } else { 327 fail = false; 328 } 329 } 330 331 int commited[] = new int[4]; 332 // clear it 333 total[0] = total[1] = total[2] = total[3] = 0; 334 if (!fail) { 335 // PHASE 2: 336 337 for (Module module : modules) { 338 if (module.klass != null) { 339 ++total[module.getFlag()]; 340 try { 341 module.module.commit(); 342 ++commited[module.getFlag()]; 343 } catch (Throwable ex) { 344 if (firstProblem == null) { 345 firstProblem = ex; 346 } 347 } 348 } 349 } 350 } 351 352 // need to decide once again 353 fail = true; 354 if (commited[REQUIRED] != total[REQUIRED] || commited[REQUISITE] != total[REQUISITE]) { 355 //fail = true; 356 } else { 357 if (total[REQUIRED] == 0 && total[REQUISITE] == 0) { 358 /* 359 * neither REQUIRED nor REQUISITE was configured. must have at 360 * least one SUFFICIENT or OPTIONAL 361 */ 362 if (commited[OPTIONAL] != 0 || commited[SUFFICIENT] != 0) { 363 fail = false; 364 } else { 365 //fail = true; 366 } 367 } else { 368 fail = false; 369 } 370 } 371 372 if (fail) { 373 // either login() or commit() failed. aborting... 374 375 for (Module module : modules) { 376 try { 377 module.module.abort(); 378 } catch ( /*LoginException*/Throwable ex) { 379 if (firstProblem == null) { 380 firstProblem = ex; 381 } 382 } 383 } 384 if (firstProblem instanceof PrivilegedActionException 385 && firstProblem.getCause() != null) { 386 firstProblem = firstProblem.getCause(); 387 } 388 if (firstProblem instanceof LoginException) { 389 throw (LoginException) firstProblem; 390 } 391 throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$ 392 } 393 loggedIn = true; 394 } 395 logout()396 public void logout() throws LoginException { 397 PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() { 398 public Void run() throws LoginException { 399 logoutImpl(); 400 return null; 401 } 402 }; 403 try { 404 if (userProvidedConfig) { 405 AccessController.doPrivileged(action, userContext); 406 } else { 407 AccessController.doPrivileged(action); 408 } 409 } catch (PrivilegedActionException ex) { 410 throw (LoginException) ex.getException(); 411 } 412 } 413 414 /** 415 * The real implementation of logout() method whose calls are wrapped into 416 * appropriate doPrivileged calls in logout(). 417 */ logoutImpl()418 private void logoutImpl() throws LoginException { 419 if (subject == null) { 420 throw new LoginException("auth.38"); //$NON-NLS-1$ 421 } 422 loggedIn = false; 423 Throwable firstProblem = null; 424 int total = 0; 425 for (Module module : modules) { 426 try { 427 module.module.logout(); 428 ++total; 429 } catch (Throwable ex) { 430 if (firstProblem == null) { 431 firstProblem = ex; 432 } 433 } 434 } 435 if (firstProblem != null || total == 0) { 436 if (firstProblem instanceof PrivilegedActionException 437 && firstProblem.getCause() != null) { 438 firstProblem = firstProblem.getCause(); 439 } 440 if (firstProblem instanceof LoginException) { 441 throw (LoginException) firstProblem; 442 } 443 throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$ 444 } 445 } 446 447 /** 448 * <p>A class that servers as a wrapper for the CallbackHandler when we use 449 * installed Configuration, but not a passed one. See API docs on the 450 * LoginContext.</p> 451 * 452 * <p>Simply invokes the given handler with the given AccessControlContext.</p> 453 */ 454 private class ContextedCallbackHandler implements CallbackHandler { 455 private final CallbackHandler hiddenHandlerRef; 456 ContextedCallbackHandler(CallbackHandler handler)457 ContextedCallbackHandler(CallbackHandler handler) { 458 super(); 459 this.hiddenHandlerRef = handler; 460 } 461 handle(final Callback[] callbacks)462 public void handle(final Callback[] callbacks) throws IOException, 463 UnsupportedCallbackException { 464 try { 465 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { 466 public Void run() throws IOException, UnsupportedCallbackException { 467 hiddenHandlerRef.handle(callbacks); 468 return null; 469 } 470 }, userContext); 471 } catch (PrivilegedActionException ex) { 472 if (ex.getCause() instanceof UnsupportedCallbackException) { 473 throw (UnsupportedCallbackException) ex.getCause(); 474 } 475 throw (IOException) ex.getCause(); 476 } 477 } 478 } 479 480 /** 481 * A private class that stores an instantiated LoginModule. 482 */ 483 private final class Module { 484 485 // An initial info about the module to be used 486 AppConfigurationEntry entry; 487 488 // A mapping of LoginModuleControlFlag onto a simple int constant 489 int flag; 490 491 // The LoginModule itself 492 LoginModule module; 493 494 // A class of the module 495 Class<?> klass; 496 Module(AppConfigurationEntry entry)497 Module(AppConfigurationEntry entry) { 498 this.entry = entry; 499 LoginModuleControlFlag flg = entry.getControlFlag(); 500 if (flg == LoginModuleControlFlag.OPTIONAL) { 501 flag = OPTIONAL; 502 } else if (flg == LoginModuleControlFlag.REQUISITE) { 503 flag = REQUISITE; 504 } else if (flg == LoginModuleControlFlag.SUFFICIENT) { 505 flag = SUFFICIENT; 506 } else { 507 flag = REQUIRED; 508 //if(flg!=LoginModuleControlFlag.REQUIRED) throw new Error() 509 } 510 } 511 getFlag()512 int getFlag() { 513 return flag; 514 } 515 516 /** 517 * Loads class of the LoginModule, instantiates it and then calls 518 * initialize(). 519 */ create(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState)520 void create(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState) 521 throws LoginException { 522 String klassName = entry.getLoginModuleName(); 523 if (klass == null) { 524 try { 525 klass = Class.forName(klassName, false, contextClassLoader); 526 } catch (ClassNotFoundException ex) { 527 throw (LoginException) new LoginException( 528 "auth.39 " + klassName).initCause(ex); //$NON-NLS-1$ 529 } 530 } 531 532 if (module == null) { 533 try { 534 module = (LoginModule) klass.newInstance(); 535 } catch (IllegalAccessException ex) { 536 throw (LoginException) new LoginException( 537 "auth.3A " + klassName) //$NON-NLS-1$ 538 .initCause(ex); 539 } catch (InstantiationException ex) { 540 throw (LoginException) new LoginException( 541 "auth.3A" + klassName) //$NON-NLS-1$ 542 .initCause(ex); 543 } 544 module.initialize(subject, callbackHandler, sharedState, entry.getOptions()); 545 } 546 } 547 } 548 } 549