blob: 7d4627878abe1a8c559ad23212d947d1516e86bd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.harmony.javax.security.auth.login;
import java.io.IOException;
import java.security.AccessController;
import java.security.AccessControlContext;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
import org.apache.harmony.javax.security.auth.Subject;
import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
import org.apache.harmony.javax.security.auth.callback.Callback;
import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.harmony.javax.security.auth.spi.LoginModule;
import org.apache.harmony.javax.security.auth.AuthPermission;
import org.apache.harmony.javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
public class LoginContext {
private static final String DEFAULT_CALLBACK_HANDLER_PROPERTY = "auth.login.defaultCallbackHandler"; //$NON-NLS-1$
/*
* Integer constants which serve as a replacement for the corresponding
* LoginModuleControlFlag.* constants. These integers are used later as
* index in the arrays - see loginImpl() and logoutImpl() methods
*/
private static final int OPTIONAL = 0;
private static final int REQUIRED = 1;
private static final int REQUISITE = 2;
private static final int SUFFICIENT = 3;
// Subject to be used for this LoginContext's operations
private Subject subject;
/*
* Shows whether the subject was specified by user (true) or was created by
* this LoginContext itself (false).
*/
private boolean userProvidedSubject;
// Shows whether we use installed or user-provided Configuration
private boolean userProvidedConfig;
// An user's AccessControlContext, used when user specifies
private AccessControlContext userContext;
/*
* Either a callback handler passed by the user or a wrapper for the user's
* specified handler - see init() below.
*/
private CallbackHandler callbackHandler;
/*
* An array which keeps the instantiated and init()-ialized login modules
* and their states
*/
private Module[] modules;
// Stores a shared state
private Map<String, ?> sharedState;
// A context class loader used to load [mainly] LoginModules
private ClassLoader contextClassLoader;
// Shows overall status - whether this LoginContext was successfully logged
private boolean loggedIn;
public LoginContext(String name) throws LoginException {
super();
init(name, null, null, null);
}
public LoginContext(String name, CallbackHandler cbHandler) throws LoginException {
super();
if (cbHandler == null) {
throw new LoginException("auth.34"); //$NON-NLS-1$
}
init(name, null, cbHandler, null);
}
public LoginContext(String name, Subject subject) throws LoginException {
super();
if (subject == null) {
throw new LoginException("auth.03"); //$NON-NLS-1$
}
init(name, subject, null, null);
}
public LoginContext(String name, Subject subject, CallbackHandler cbHandler)
throws LoginException {
super();
if (subject == null) {
throw new LoginException("auth.03"); //$NON-NLS-1$
}
if (cbHandler == null) {
throw new LoginException("auth.34"); //$NON-NLS-1$
}
init(name, subject, cbHandler, null);
}
public LoginContext(String name, Subject subject, CallbackHandler cbHandler,
Configuration config) throws LoginException {
super();
init(name, subject, cbHandler, config);
}
// Does all the machinery needed for the initialization.
private void init(String name, Subject subject, final CallbackHandler cbHandler,
Configuration config) throws LoginException {
userProvidedSubject = (this.subject = subject) != null;
//
// Set config
//
if (name == null) {
throw new LoginException("auth.00"); //$NON-NLS-1$
}
if (config == null) {
config = Configuration.getAccessibleConfiguration();
} else {
userProvidedConfig = true;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null && !userProvidedConfig) {
sm.checkPermission(new AuthPermission("createLoginContext." + name));//$NON-NLS-1$
}
AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name);
if (entries == null) {
if (sm != null && !userProvidedConfig) {
sm.checkPermission(new AuthPermission("createLoginContext.other")); //$NON-NLS-1$
}
entries = config.getAppConfigurationEntry("other"); //$NON-NLS-1$
if (entries == null) {
throw new LoginException("auth.35 " + name); //$NON-NLS-1$
}
}
modules = new Module[entries.length];
for (int i = 0; i < modules.length; i++) {
modules[i] = new Module(entries[i]);
}
//
// Set CallbackHandler and this.contextClassLoader
//
/*
* as some of the operations to be executed (i.e. get*ClassLoader,
* getProperty, class loading) are security-checked, then combine all of
* them into a single doPrivileged() call.
*/
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
// First, set the 'contextClassLoader'
contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader == null) {
contextClassLoader = ClassLoader.getSystemClassLoader();
}
// then, checks whether the cbHandler is set
if (cbHandler == null) {
// well, let's try to find it
String klassName = Security
.getProperty(DEFAULT_CALLBACK_HANDLER_PROPERTY);
if (klassName == null || klassName.length() == 0) {
return null;
}
Class<?> klass = Class.forName(klassName, true, contextClassLoader);
callbackHandler = (CallbackHandler) klass.newInstance();
} else {
callbackHandler = cbHandler;
}
return null;
}
});
} catch (PrivilegedActionException ex) {
Throwable cause = ex.getCause();
throw (LoginException) new LoginException("auth.36").initCause(cause);//$NON-NLS-1$
}
if (userProvidedConfig) {
userContext = AccessController.getContext();
} else if (callbackHandler != null) {
userContext = AccessController.getContext();
callbackHandler = new ContextedCallbackHandler(callbackHandler);
}
}
public Subject getSubject() {
if (userProvidedSubject || loggedIn) {
return subject;
}
return null;
}
/**
* Warning: calling the method more than once may result in undefined
* behaviour if logout() method is not invoked before.
*/
public void login() throws LoginException {
PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
public Void run() throws LoginException {
loginImpl();
return null;
}
};
try {
if (userProvidedConfig) {
AccessController.doPrivileged(action, userContext);
} else {
AccessController.doPrivileged(action);
}
} catch (PrivilegedActionException ex) {
throw (LoginException) ex.getException();
}
}
/**
* The real implementation of login() method whose calls are wrapped into
* appropriate doPrivileged calls in login().
*/
private void loginImpl() throws LoginException {
if (subject == null) {
subject = new Subject();
}
if (sharedState == null) {
sharedState = new HashMap<String, Object>();
}
// PHASE 1: Calling login()-s
Throwable firstProblem = null;
int[] logged = new int[4];
int[] total = new int[4];
for (Module module : modules) {
try {
// if a module fails during Class.forName(), then it breaks overall
// attempt - see catch() below
module.create(subject, callbackHandler, sharedState);
if (module.module.login()) {
++total[module.getFlag()];
++logged[module.getFlag()];
if (module.getFlag() == SUFFICIENT) {
break;
}
}
} catch (Throwable ex) {
if (firstProblem == null) {
firstProblem = ex;
}
if (module.klass == null) {
/*
* an exception occurred during class lookup - overall
* attempt must fail a little trick: increase the REQUIRED's
* number - this will look like a failed REQUIRED module
* later, so overall attempt will fail
*/
++total[REQUIRED];
break;
}
++total[module.getFlag()];
// something happened after the class was loaded
if (module.getFlag() == REQUISITE) {
// ... and no need to walk down anymore
break;
}
}
}
// end of PHASE1,
// Let's decide whether we have either overall success or a total failure
boolean fail = true;
/*
* Note: 'failed[xxx]!=0' is not enough to check.
*
* Use 'logged[xx] != total[xx]' instead. This is because some modules
* might not be counted as 'failed' if an exception occurred during
* preload()/Class.forName()-ing. But, such modules still get counted in
* the total[].
*/
// if any REQ* module failed - then it's failure
if (logged[REQUIRED] != total[REQUIRED] || logged[REQUISITE] != total[REQUISITE]) {
// fail = true;
} else {
if (total[REQUIRED] == 0 && total[REQUISITE] == 0) {
// neither REQUIRED nor REQUISITE was configured.
// must have at least one SUFFICIENT or OPTIONAL
if (logged[OPTIONAL] != 0 || logged[SUFFICIENT] != 0) {
fail = false;
}
//else { fail = true; }
} else {
fail = false;
}
}
int commited[] = new int[4];
// clear it
total[0] = total[1] = total[2] = total[3] = 0;
if (!fail) {
// PHASE 2:
for (Module module : modules) {
if (module.klass != null) {
++total[module.getFlag()];
try {
module.module.commit();
++commited[module.getFlag()];
} catch (Throwable ex) {
if (firstProblem == null) {
firstProblem = ex;
}
}
}
}
}
// need to decide once again
fail = true;
if (commited[REQUIRED] != total[REQUIRED] || commited[REQUISITE] != total[REQUISITE]) {
//fail = true;
} else {
if (total[REQUIRED] == 0 && total[REQUISITE] == 0) {
/*
* neither REQUIRED nor REQUISITE was configured. must have at
* least one SUFFICIENT or OPTIONAL
*/
if (commited[OPTIONAL] != 0 || commited[SUFFICIENT] != 0) {
fail = false;
} else {
//fail = true;
}
} else {
fail = false;
}
}
if (fail) {
// either login() or commit() failed. aborting...
for (Module module : modules) {
try {
module.module.abort();
} catch ( /*LoginException*/Throwable ex) {
if (firstProblem == null) {
firstProblem = ex;
}
}
}
if (firstProblem instanceof PrivilegedActionException
&& firstProblem.getCause() != null) {
firstProblem = firstProblem.getCause();
}
if (firstProblem instanceof LoginException) {
throw (LoginException) firstProblem;
}
throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$
}
loggedIn = true;
}
public void logout() throws LoginException {
PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
public Void run() throws LoginException {
logoutImpl();
return null;
}
};
try {
if (userProvidedConfig) {
AccessController.doPrivileged(action, userContext);
} else {
AccessController.doPrivileged(action);
}
} catch (PrivilegedActionException ex) {
throw (LoginException) ex.getException();
}
}
/**
* The real implementation of logout() method whose calls are wrapped into
* appropriate doPrivileged calls in logout().
*/
private void logoutImpl() throws LoginException {
if (subject == null) {
throw new LoginException("auth.38"); //$NON-NLS-1$
}
loggedIn = false;
Throwable firstProblem = null;
int total = 0;
for (Module module : modules) {
try {
module.module.logout();
++total;
} catch (Throwable ex) {
if (firstProblem == null) {
firstProblem = ex;
}
}
}
if (firstProblem != null || total == 0) {
if (firstProblem instanceof PrivilegedActionException
&& firstProblem.getCause() != null) {
firstProblem = firstProblem.getCause();
}
if (firstProblem instanceof LoginException) {
throw (LoginException) firstProblem;
}
throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$
}
}
/**
* <p>A class that servers as a wrapper for the CallbackHandler when we use
* installed Configuration, but not a passed one. See API docs on the
* LoginContext.</p>
*
* <p>Simply invokes the given handler with the given AccessControlContext.</p>
*/
private class ContextedCallbackHandler implements CallbackHandler {
private final CallbackHandler hiddenHandlerRef;
ContextedCallbackHandler(CallbackHandler handler) {
super();
this.hiddenHandlerRef = handler;
}
public void handle(final Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws IOException, UnsupportedCallbackException {
hiddenHandlerRef.handle(callbacks);
return null;
}
}, userContext);
} catch (PrivilegedActionException ex) {
if (ex.getCause() instanceof UnsupportedCallbackException) {
throw (UnsupportedCallbackException) ex.getCause();
}
throw (IOException) ex.getCause();
}
}
}
/**
* A private class that stores an instantiated LoginModule.
*/
private final class Module {
// An initial info about the module to be used
AppConfigurationEntry entry;
// A mapping of LoginModuleControlFlag onto a simple int constant
int flag;
// The LoginModule itself
LoginModule module;
// A class of the module
Class<?> klass;
Module(AppConfigurationEntry entry) {
this.entry = entry;
LoginModuleControlFlag flg = entry.getControlFlag();
if (flg == LoginModuleControlFlag.OPTIONAL) {
flag = OPTIONAL;
} else if (flg == LoginModuleControlFlag.REQUISITE) {
flag = REQUISITE;
} else if (flg == LoginModuleControlFlag.SUFFICIENT) {
flag = SUFFICIENT;
} else {
flag = REQUIRED;
//if(flg!=LoginModuleControlFlag.REQUIRED) throw new Error()
}
}
int getFlag() {
return flag;
}
/**
* Loads class of the LoginModule, instantiates it and then calls
* initialize().
*/
void create(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState)
throws LoginException {
String klassName = entry.getLoginModuleName();
if (klass == null) {
try {
klass = Class.forName(klassName, false, contextClassLoader);
} catch (ClassNotFoundException ex) {
throw (LoginException) new LoginException(
"auth.39 " + klassName).initCause(ex); //$NON-NLS-1$
}
}
if (module == null) {
try {
module = (LoginModule) klass.newInstance();
} catch (IllegalAccessException ex) {
throw (LoginException) new LoginException(
"auth.3A " + klassName) //$NON-NLS-1$
.initCause(ex);
} catch (InstantiationException ex) {
throw (LoginException) new LoginException(
"auth.3A" + klassName) //$NON-NLS-1$
.initCause(ex);
}
module.initialize(subject, callbackHandler, sharedState, entry.getOptions());
}
}
}
}