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 94 public LoginContext(String name) throws LoginException { 95 super(); 96 init(name, null, null, null); 97 } 98 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 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 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 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. 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 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 */ 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 */ 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 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 */ 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 457 ContextedCallbackHandler(CallbackHandler handler) { 458 super(); 459 this.hiddenHandlerRef = handler; 460 } 461 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 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 512 int getFlag() { 513 return flag; 514 } 515 516 /** 517 * Loads class of the LoginModule, instantiates it and then calls 518 * initialize(). 519 */ 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