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
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}
549