package com.sap.security.core.server.saml.app.ssotest.dest;
import java.security.Principal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import com.sap.engine.interfaces.security.auth.AbstractLoginModule;
import com.sap.security.core.server.saml.jaas.SAMLAuthenticationData;
import com.sap.security.core.server.saml.jaas.SAMLAuthenticationDataCarrier;
import com.sap.security.core.server.saml.jaas.SAMLLoginModule;
import com.sap.tc.logging.Location;
/**
* This JAAS login module is used in the SAML SSO Test application to
* demonstrate that the data stored by the SAMLLoginModule can be read
* out and used for user-mapping.
*
* This code serves demonstration purposes only. It is not part of the
* SAP product maintenance process. Coding techniques used in this module
* must not be understood as SAP's recommendations for writing login modules.
*
* <p>Copyright (c) 2003 SAP AG.
*/
public class SAMLMappingModule extends AbstractLoginModule {
/** Location for tracing */
private static final Location LOCATION =
Location.getLocation(SAMLMappingModule.class);
/** The <code>Subject</code> passed from the login context */
private Subject _subject;
/** Callback handler needed for "change Password" callback */
private CallbackHandler _callbackHander;
/**
* The map in which the data from the <code>SAMLLoginModule</code> can
* be found.
*/
private Map _sharedState;
/** The map with the options for this module */
private Map _options;
/** Option "Mapping" */
private static final String OPTION_MAPPING = "Mapping";
/** Additional data from the login process */
private SAMLMappingModulePrincipal _newPrincipal = null;
/**
* Store the data passed from the login context.
*/
public void initialize(
Subject subject,
CallbackHandler callbackHandler,
Map sharedState,
Map options) {
_subject = subject;
_callbackHander = callbackHandler;
_sharedState = sharedState;
_options = options;
}
/**
* Login phase of JAAS.
*
* In this step, the module checks whether the <code>SAMLLoginModule</code>
* has stored some data in the shared state.
*
* If this is the case, the module checks whether it can find a
* mapping entry for this user and remembers the internal user name.
*
* @return
* <code>false</code>, if no data were found in the shared state. In this
* case, the mapping module can be ignored.
* <code>true</code>, if data were found and a mapping could be
* determined.
*
* @throws
* LoginException
* Error in the configuration of the mapping, or failure to find the
* incoming external user name.
*/
public boolean login() throws LoginException {
final String METHOD = "login()";
boolean result = false;
/* "try...catch...finally" for logging */
try {
LOCATION.entering(METHOD);
_newPrincipal = null;
/* Read the mapping from the options */
String mappingString = (String) _options.get(OPTION_MAPPING);
/* The option must be specified */
if ((mappingString == null) || ("".equals(mappingString.trim()))) {
throw new Exception(
"The SAMLMappingModule did not find the "
+ "required option \""
+ OPTION_MAPPING
+ "\"");
}
/* Evaluate the options value and create the mapping */
HashMap userMapping = new HashMap();
try {
StringTokenizer tokenizer =
new StringTokenizer(mappingString, ",", false);
while (tokenizer.hasMoreTokens()) {
String singleMapping = tokenizer.nextToken().trim();
/* Split into external and internal name */
StringTokenizer mapTokens =
new StringTokenizer(singleMapping, ":", false);
String from = null;
String to = null;
from = mapTokens.nextToken().trim();
to = mapTokens.nextToken().trim();
if (((from == null) || ("".equals(from)))
|| ((to == null) || ("".equals(to)))) {
throw new Exception();
}
userMapping.put(from, to);
}
} catch (Exception e) {
/* Any error in between --> wrong mapping format */
throw new Exception(
"The SAMLMappingModule found in option \""
+ OPTION_MAPPING
+ "\" a non-interpretable "
+ "mapping information \""
+ mappingString
+ "\". Specify mappings like this: "
+ "\"ext1 : int1, ext2 : int2\".");
}
/*
* After the check of correct configuration, we evaluate the
* shared state for the information that the SAMLLoginModule might
* have placed there.
*/
SAMLAuthenticationData authData;
try {
authData =
(SAMLAuthenticationData) _sharedState.get(
SAMLLoginModule.KEY_AUTH_DATA);
} catch (Exception e) {
/* Class cast of something like this */
throw new Exception(
"The SAMLMappingModule could not interpret "
+ "a data item in the "
+ "shared state: \""
+ e.getLocalizedMessage()
+ "\".");
}
/* If no data present, the module can be ignored. */
if (authData == null) {
result = false;
return result;
}
/*
* If the incoming user name is not known to the mapping, this is
* worth an exception.
*/
String nameIdentifier = authData.getNameIdentifier();
if (!userMapping.containsKey(nameIdentifier)) {
throw new Exception(
"The SAMLMappingModule received user name \""
+ nameIdentifier
+ "\" from the "
+ "SAMLLoginModule, but this user name is not present "
+ "in the mapping data.");
}
String newUserName = (String) userMapping.get(nameIdentifier);
/*
* Check user lock status.
* See documentation of "AbstractLoginModule" for details.
*
* This code accesses the user management of the engine
* which requires permissions to be granted in the
* "Security Provider" service.
* Domain: "SAP-J2EE-Engine", Resource "user-management". Grant
* "all" for action "ALL".
*
* As this is not done within this demo application, the code
* is left out here.
*/
// checkUserLockStatus(newUserName);
/*
* Check "user must change password" status.
* See documentation of "AbstractLoginModule" for details.
*
* See comment above why this code is now commented out.
*/
// changePasswordIfNeeded(newUserName, _callbackHander);
/* Prepare new principal to be appended in commit()
*
phase
*/
_newPrincipal =
new SAMLMappingModulePrincipal(newUserName, authData);
/*
* Write user name to shared state for a possibly following
* TicketCreator module.
*/
_sharedState.put(AbstractLoginModule.NAME, newUserName);
result = true;
return result;
} catch (Exception e) {
LoginException exceptionToBeThrown;
if (e instanceof LoginException) {
/*
* LoginExceptions are thrown only from the methods of the base
* class. The example code in AbstractLoginModule indicates
* that these exceptions need not to be wrapped, but are
* thrown directly.
*/
exceptionToBeThrown = (LoginException) e;
} else {
/* Other exceptions are traced from this class */
LOCATION.errorT(
METHOD,
"An error occurred in the SAMLMappingModule:\n{0}\n",
new Object[] { e.getMessage()});
/*
* The AbstractLoginModule offers a method to be used to
* indicate any exception in login phase. This method is not
* used directly but wrapped into the following try...catch
* block because the resulting exception shall be traced by
* the mapping module (annotation: the method from
* AbstractLoginModule also performs some tracing, but it
* probably is not the same location as this mapping module
* has, so the program flow information would be split.
*/
try {
/* For compiler happiness only */
exceptionToBeThrown = new LoginException(e.getMessage());
throwUserLoginException(e);
} catch (LoginException f) {
/* Will always be executed */
exceptionToBeThrown = f;
}
}
LOCATION.throwing(METHOD, exceptionToBeThrown);
throw exceptionToBeThrown;
} finally {
LOCATION.exiting(new Boolean(result));
}
}
/**
* If a user name was stored, append it to the subject.
*/
public boolean commit() throws LoginException {
final String METHOD = "commit()";
boolean result = false;
try {
LOCATION.entering(METHOD);
if (_newPrincipal != null) {
_subject.getPrincipals().add(_newPrincipal);
/* See documentation of "AbstractLoginModule" */
writeLogonStatistics(
true,
_newPrincipal.getName(),
new Date().getTime(),
_sharedState);
result = true;
return result;
} else {
result = false;
return result;
}
} catch (Exception e) {
LOCATION.throwing(METHOD, e);
_sharedState.remove(AbstractLoginModule.NAME);
throw new LoginException(e.getMessage());
} finally {
LOCATION.exiting(new Boolean(result));
}
}
/**
* Destroy user name.
*/
public boolean abort() throws LoginException {
String userName =
(_newPrincipal != null) ? _newPrincipal.getName() : "Unknown";
/* See documentation of "AbstractLoginModule" */
writeLogonStatistics(
false,
userName,
new Date().getTime(),
_sharedState);
_newPrincipal = null;
return true;
}
/**
* Remove stored principal.
*/
public boolean logout() throws LoginException {
if (_newPrincipal != null) {
_subject.getPrincipals().remove(_newPrincipal);
_newPrincipal = null;
return true;
} else {
return false;
}
}
/**
* Inner class for the principal that is appended by this login module.
*/
private static class SAMLMappingModulePrincipal
implements Principal, SAMLAuthenticationDataCarrier {
private String _name;
private SAMLAuthenticationData _authData;
/**
* Constructor for SAMLMappingModulePrincipal.
*/
public SAMLMappingModulePrincipal(
String name,
SAMLAuthenticationData authData) {
_name = name;
_authData = authData;
}
/**
* @see java.security.Principal#getName()
*/
public String getName() {
return _name;
}
/**
* @see SAMLAuthenticationDataCarrier#getSAMLAuthenticationData()
*/
public SAMLAuthenticationData getSAMLAuthenticationData() {
return _authData;
}
}
}
|