package com.sap.security.core.server.saml.app.ssotest.dest;
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.engine.lib.security.Principal;
import com.sap.tc.logging.Category;
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.
*
* This code might not reflect the most current rules for login modules.
* It's main purpose is to demonstrate how to obtain SAML authentication data
* from the shared state of the login module stack.
*
* Check the documentation in SAP Library
* http://help.sap.com/saphelp_nw70/helpdata/en/46/3ce9402f3f8031e10000000a1550b0/frameset.htm
* to get detailed instructions how to create a login module.
*
* <p>Copyright (c) 2006 SAP AG.
*/
public class SAMLMappingModule extends AbstractLoginModule {
/** Location for tracing */
private static final Location LOCATION =
Location.getLocation(SAMLMappingModule.class);
/** Category for logging */
private static final Category CATEGORY = Category.SYS_SECURITY;
/** 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 Principal _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.
*/
SAMLAuthenticationDataAccessor authData;
try {
authData = new SAMLAuthenticationDataAccessor(_sharedState);
} catch (IllegalStateException e) {
/* If no data present, the module can be ignored. */
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);
/* Prepare new pricipal to be appended in commit() phase */
_newPrincipal = new Principal(newUserName);
/*
* 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 */
CATEGORY.errorT(
LOCATION,
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);
_sharedState.put(AbstractLoginModule.PRINCIPAL, _newPrincipal);
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 {
_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;
}
}
/**
* Accessor method for the data in the shared state from the SAMLLoginModule.
* This class works around the defect that the archives delivered to customers
* for login module development do not contain the class that is stored by the
* SAMLLoginModule due to missing delivery.
*/
private static class SAMLAuthenticationDataAccessor {
/* Lookup key in shared state */
private static final String KEY_AUTH_DATA = "SAMLAuthenticationData";
/* Data attributes */
private final Date _authenticationInstant;
private final String _authenticationMethod;
private final String _issuer;
private final String _nameIdentifier;
private final String _nameFormat;
private final String _nameQualifier;
private final String _partnerKey;
/**
* Creates an instance of this class using the data found in the shared state
* of the login module stack.
*
* @param
* sharedState
* The shared state received by the mapping module from the framework.
*
* @throws
* IllegalStateException
* No SAML authentication data found in shared state. THis might be an
* expected situation.
*
* @throws
* Exception
* Any other issue, which is a technical problem (e.g. error during
* access to authentication data via reflection, or wrong class in state).
*/
public SAMLAuthenticationDataAccessor(Map sharedState)
throws IllegalStateException, Exception {
Object dataObject = sharedState.get(KEY_AUTH_DATA);
if (dataObject == null) {
throw new IllegalStateException("No SAML authentication data in shared state");
}
Class clazz = dataObject.getClass();
_authenticationInstant =
(Date) (clazz
.getMethod("getAuthenticationInstant", null)
.invoke(dataObject, null));
_authenticationMethod =
(String) (clazz
.getMethod("getAuthenticationMethod", null)
.invoke(dataObject, null));
_issuer =
(String) (clazz
.getMethod("getIssuer", null)
.invoke(dataObject, null));
_nameIdentifier =
(String) (clazz
.getMethod("getNameIdentifier", null)
.invoke(dataObject, null));
_nameFormat =
(String) (clazz
.getMethod("getNameFormat", null)
.invoke(dataObject, null));
_nameQualifier =
(String) (clazz
.getMethod("getNameQualifier", null)
.invoke(dataObject, null));
_partnerKey =
(String) (clazz
.getMethod("getPartnerKey", null)
.invoke(dataObject, null));
} // constructor
/**
* Return the authentication instant of the authentication process
* that occurred at the external system.
*/
public Date getAuthenticationInstant() {
return _authenticationInstant;
}
/**
* Return the authentication method of the authentication process
* that occurred at the external system.
* The possible values are defined in the SAML specification.
* Example: Password authentication will be represented by
* "urn:oasis:names:tc:SAML:1.0:am:password".
*/
public String getAuthenticationMethod() {
return _authenticationMethod;
}
/**
* Return the issuer name of the assertion that contained the
* authentication statement.
*/
public String getIssuer() {
return _issuer;
}
/**
* Return the name of the user currently logged on to the external system.
*/
public String getNameIdentifier() {
return _nameIdentifier;
}
/**
* Return the user name format, or <code>null</code> if not set.
*/
public String getNameFormat() {
return _nameFormat;
}
/**
* Return the user name qualifier, or <code>null</code> if not set
*/
public String getNameQualifier() {
return _nameQualifier;
}
/**
* Return partner key of the configuration entry of the issuer in the
* customizing of the own system.
* Note that this information cannot be influenced by the issuer, but
* <code>getIssuer()</code> returns data that are filled with a value
* at the discretion of the external system.
*/
public String getPartnerKey() {
return _partnerKey;
}
} // class SAMLAuthenticationDataAccessor
}
|