Show TOC Start of Content Area

This graphic is explained in the accompanying text Example SAML Mapping Module Used by the SAML Test Application  Locate the document in its SAP Library structure

The following example shows the mapping module that is used by the SAML test application. It shows how to read the user data from the SAMLLoginModule and map it to a user on the SAP J2EE Engine.

Example SAML Mapping Module

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

}


 

 

End of Content Area