blob: 7d4627878abe1a8c559ad23212d947d1516e86bd [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/*
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
18package org.apache.harmony.javax.security.auth.login;
19
20import java.io.IOException;
21import java.security.AccessController;
22import java.security.AccessControlContext;
23import java.security.PrivilegedExceptionAction;
24import java.security.PrivilegedActionException;
25
26import java.security.Security;
27import java.util.HashMap;
28import java.util.Map;
29
30import org.apache.harmony.javax.security.auth.Subject;
31import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
32import org.apache.harmony.javax.security.auth.callback.Callback;
33import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
34import org.apache.harmony.javax.security.auth.spi.LoginModule;
35import org.apache.harmony.javax.security.auth.AuthPermission;
36
37import org.apache.harmony.javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
38
39
40
41public 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}