Home | History | Annotate | Download | only in login
      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