• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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