Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * JBoss, Home of Professional Open Source.
   * Copyright 2008, Red Hat Middleware LLC, and individual contributors
   * as indicated by the @author tags. See the copyright.txt file in the
   * distribution for a full listing of individual contributors.
   *
   * This is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as
   * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this software; if not, write to the Free
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
 package org.picketlink.identity.federation.bindings.jboss.auth;
 
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
 
 import  org.jboss.security.SecurityConstants;
 import  org.jboss.security.SimplePrincipal;
 import  org.jboss.security.auth.callback.ObjectCallback;

This LoginModule authenticates clients by validating their SAML assertions with an external security token service (such as PicketLinkSTS). If the supplied assertion contains roles, these roles are extracted and included in the Group returned by the getRoleSets method.

This module defines the following module options:

  • configFile - this property identifies the properties file that will be used to establish communication with the external security token service.
  • cache.invalidation: set it to true if you require invalidation of JBoss Auth Cache at SAML Principal expiration.
  • jboss.security.security_domain: name of the security domain where this login module is configured. This is only required if the cache.invalidation option is configured.
  • roleKey: a comma separated list of strings that define the attributes in SAML assertion for user roles
  • localValidation: if you want to validate the assertion locally for signature and expiry
  • localValidationSecurityDomain: the security domain for the trust store information (via the JaasSecurityDomain)
  • tokenEncodingType: encoding type of SAML token delivered via http request's header. Possible values are: base64 - content encoded as base64. In case of encoding will vary between base64 and gzip use base64 and LoginModule will detect gzipped data. gzip - gzipped content encoded as base64 none - content not encoded in any way
  • samlTokenHttpHeader - name of http request header to fetch SAML token from. For example: "Authorize"
  • samlTokenHttpHeaderRegEx - Java regular expression to be used to get SAML token from "samlTokenHttpHeader". Example: use: ."(.)".* to parse SAML token from header content like this: SAML_assertion="HHDHS=", at the same time set samlTokenHttpHeaderRegExGroup to 1.
  • samlTokenHttpHeaderRegExGroup - Group value to be used when parsing out value of http request header specified by "samlTokenHttpHeader" using "samlTokenHttpHeaderRegEx".

Any properties specified besides the above properties are assumed to be used to configure how the STSClient will connect to the STS. For example, the JBossWS StubExt.PROPERTY_SOCKET_FACTORY can be specified in order to inform the socket factory that must be used to connect to the STS. All properties will be set in the request context of the Dispatch instance used by the STSClient to send requests to the STS.

An example of a configFile can be seen bellow:

 serviceName=PicketLinkSTS
 portName=PicketLinkSTSPort
 endpointAddress=http://localhost:8080/picketlink-sts/PicketLinkSTS
 username=JBoss
 password=JBoss
 
The first three properties specify the STS endpoint URL, service name, and port name. The last two properties specify the username and password that are to be used by the application server to authenticate to the STS and have the SAML assertions validated.

NOTE: Sub-classes can use getSTSClient() method to customize the STSClient class to make calls to STS/

Author(s):
Stefan Guilhen
Anil.Saldhana@redhat.com
@SuppressWarnings("unchecked")
    
    protected String stsConfigurationFile;
    protected Principal principal;
    protected SamlCredential credential;
    protected AssertionType assertion;
    protected boolean enableCacheInvalidation = false;
    protected String securityDomain = null;
    protected boolean localValidation = false;
Maximal number of clients in the STS Client Pool.
    protected int maxClientsInPool = 0;
    
    
Number of clients initialized for in case pool is out of free clients.
    protected int initialNumberOfClients = 0;

    
    
Options that are computed by this login module. Few options are removed and the rest are set in the dispatch sts call
    protected Map<StringObjectoptions = new HashMap<StringObject>();

    
Original Options that are sent by the JDK JAAS Framework
    protected Map<StringObjectrawOptions = new HashMap<StringObject>();

    
This is an option that should identify the configuration file for WSTrustClient.
    public static final String STS_CONFIG_FILE = "configFile";

    
Key to specify the end point address
    public static final String ENDPOINT_ADDRESS = "endpointAddress";

    
Key to specify the port name
    public static final String PORT_NAME = "portName";

    
Key to specify the service name
    public static final String SERVICE_NAME = "serviceName";

    
Key to specify the username
    public static final String USERNAME_KEY = "username";

    
Key to specify the password
    public static final String PASSWORD_KEY = "password";
    // A variable used by the unit test to pass local validation
    protected boolean localTestingOnly = false;

    
Paramater name.
    public static final String MAX_CLIENTS_IN_POOL = "maxClientsInPool";

    
Paramater name.
    public static final String INITIAL_NUMBER_OF_CLIENTS = "initialNumberOfClients";
    /*
     * (non-Javadoc)
     *
     * @see org.jboss.security.auth.spi.AbstractServerLoginModule#initialize(javax.security.auth.Subject,
     * javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
     */
    @Override
    public void initialize(Subject subjectCallbackHandler callbackHandlerMap<String, ?> sharedStateMap<String, ?> options) {
        super.initialize(subjectcallbackHandlersharedStateoptions);
        this..putAll(options);
        this..putAll(options);
        if (.isTraceEnabled()) {
            .trace(options.toString());
        }
        // save the config file and cache validation options, removing them from the map - all remaining properties will
        // be set in the request context of the Dispatch instance used to send requests to the STS.
        this. = (Stringthis..remove();
        String cacheInvalidation = (Stringthis..remove("cache.invalidation");
        if (cacheInvalidation != null && !cacheInvalidation.isEmpty()) {
            this. = Boolean.parseBoolean(cacheInvalidation);
            this. = (Stringthis..remove(SecurityConstants.SECURITY_DOMAIN_OPTION);
            if (this. == null || this..isEmpty())
                throw .optionNotSet(SecurityConstants.SECURITY_DOMAIN_OPTION);
        }
        String roleKeyStr = (Stringoptions.get("roleKey");
        if (StringUtil.isNotNull(roleKeyStr)) {
             = roleKeyStr.trim();
        }
        String localValidationStr = (Stringoptions.get("localValidation");
        if (StringUtil.isNotNull(localValidationStr)) {
             = Boolean.parseBoolean(localValidationStr);
             = (Stringoptions.get("localValidationSecurityDomain");
            if ( == null) {
               throw .optionNotSet("localValidationSecurityDomain");
            }
            
            if (.startsWith("java:") == false)
                 = SecurityConstants.JAAS_CONTEXT_ROOT + "/" + ;
            String localTestingOnlyStr = (Stringoptions.get("localTestingOnly");
            if (StringUtil.isNotNull(localTestingOnlyStr)) {
                 = Boolean.valueOf(localTestingOnlyStr);
            }
        }
        String maxClientsString = (Stringoptions.get();
        if (StringUtil.isNotNull(maxClientsString)) {
            try {
                this. = Integer.parseInt(maxClientsString);
            } catch (Exception e) {
                .cannotParseParameterValue(e);
            }
        }
        
        String initialNumberOfClientsString = (Stringoptions.get();
        if (StringUtil.isNotNull(initialNumberOfClientsString)) {
            try {
                this. = Integer.parseInt(initialNumberOfClientsString);
            } catch (Exception e) {
                .cannotParseParameterValue(e);
            }
        }
        
        
    }
    /*
     * (non-Javadoc)
     *
     * @see org.jboss.security.auth.spi.AbstractServerLoginModule#login()
     */
    @Override
    public boolean login() throws LoginException {
        // if shared data exists, set our principal and assertion variables.
        if (super.login()) {
            Object sharedPrincipal = super..get("javax.security.auth.login.name");
            if (sharedPrincipal instanceof Principal)
                this. = (PrincipalsharedPrincipal;
            else {
                try {
                    this. = createIdentity(sharedPrincipal.toString());
                } catch (Exception e) {
                    throw .authFailedToCreatePrincipal(e);
                }
            }
            Object credential = super..get("javax.security.auth.login.password");
            if (credential instanceof SamlCredential)
                this. = (SamlCredentialcredential;
            else
                throw .authSharedCredentialIsNotSAMLCredential(credential.getClass().getName());
            return true;
        }
        // obtain the assertion from the callback handler.
        ObjectCallback callback = new ObjectCallback(null);
        Element assertionElement = null;
        try {
            if (getSamlTokenHttpHeader() != null) {
                this. = getCredentialFromHttpRequest();
            }
            else {
                super..handle(new Callback[] { callback });
                
                if (callback.getCredential() instanceof String) {
                    callback.setCredential(new SamlCredential(DocumentUtil.getDocument(callback.getCredential().toString()).getDocumentElement()));
                }
                
                if (callback.getCredential() instanceof SamlCredential == false)
                    throw .authSharedCredentialIsNotSAMLCredential(callback.getCredential().getClass().getName());
                this. = (SamlCredentialcallback.getCredential();
            }
            assertionElement = this..getAssertionAsElement();
        } catch (Exception e) {
            throw .authErrorHandlingCallback(e);
        }
    
        
        // if there is no shared data, validate the assertion using the STS.
        if () {
            .trace("Local Validation is being Performed");
            try {
                boolean isValid = localValidation(assertionElement);
                if (isValid) {
                    .trace("Local Validation passed.");
                }
            } catch (Exception e) {
                LoginException le = new LoginException();
                le.initCause(e);
                throw le;
            }
        } else {
            .trace("Local Validation is disabled. Verifying with STS");
            // sts config file has to be present to call STS (using sts client)
            if (this. == null)
                throw .authSTSConfigFileNotFound();
            // send the assertion to the STS for validation.
            STSClient client = this.getSTSClient();
            try {
                boolean isValid = client.validateToken(assertionElement);
                // if the STS says the assertion is invalid, throw an exception to signal that authentication has failed.
                if (isValid == false)
                    throw .authInvalidSAMLAssertionBySTS();
            } catch (WSTrustException we) {
                throw .authAssertionValidationError(we);
            }
        }
        // if the assertion is valid, create a principal containing the assertion subject.
        try {
            this. = SAMLUtil.fromElement(assertionElement);
            SubjectType subject = .getSubject();
            if (subject != null) {
                BaseIDAbstractType baseID = subject.getSubType().getBaseID();
                if (baseID instanceof NameIDType) {
                    NameIDType nameID = (NameIDTypebaseID;
                    this. = new PicketLinkPrincipal(nameID.getValue());
                    // If the user has configured cache invalidation of subject based on saml token expiry
                    if () {
                        TimeCacheExpiry cacheExpiry = this.getCacheExpiry();
                        XMLGregorianCalendar expiry = AssertionUtil.getExpiration();
                        if (expiry != null) {
                            Date expiryDate = expiry.toGregorianCalendar().getTime();
                            .trace("Creating Cache Entry for JBoss at [" + new Date() + "] , with expiration set to SAML expiry = " + expiryDate);
                            cacheExpiry.register(expiryDate);
                        } else {
                            .samlAssertionWithoutExpiration(.getID());
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw .authFailedToParseSAMLAssertion(e);
        }
        // if password-stacking has been configured, set the principal and the assertion in the shared map.
        if (getUseFirstPass()) {
            super..put("javax.security.auth.login.name"this.);
            super..put("javax.security.auth.login.password"this.);
        }
        return (super. = true);
    }
    
    
    /* (non-Javadoc)
     * @see org.jboss.security.auth.spi.AbstractServerLoginModule#commit()
     */
    @Override
    public boolean commit() throws LoginException {
        if (super.commit()) {
            final boolean added = subject.getPublicCredentials().add(this.);
            if (added && .isTraceEnabled())
                .trace("Added Credential " + this.);
            return true;
        } else {
            return false;
        }
    }

    
Called if the overall authentication failed (phase 2).
    @Override
    public boolean abort() throws LoginException {
        clearState();
        super.abort();
        return true;
    }
    @Override
    public boolean logout() throws LoginException {
        clearState();
        super.logout();
        return true;
    }
    private void clearState() {
        AbstractSTSLoginModule.removeAllSamlCredentials(subject);
         = null;
    }
    /*
     * (non-Javadoc)
     *
     * @see org.jboss.security.auth.spi.AbstractServerLoginModule#getIdentity()
     */
    @Override
    protected Principal getIdentity() {
        return this.;
    }
    /*
     * (non-Javadoc)
     *
     * @see org.jboss.security.auth.spi.AbstractServerLoginModule#getRoleSets()
     */
    @Override
    protected Group[] getRoleSets() throws LoginException {
        if (this. == null) {
            try {
                this. = SAMLUtil.fromElement(this..getAssertionAsElement());
            } catch (Exception e) {
                throw .authFailedToParseSAMLAssertion(e);
            }
        }
        if (.isTraceEnabled()) {
            try {
                .trace("Assertion from where roles will be sought = " + AssertionUtil.asString());
            } catch (ProcessingException ignore) {
            }
        }
        List<StringroleKeys = new ArrayList<String>();
        if (StringUtil.isNotNull()) {
            roleKeys.addAll(StringUtil.tokenize());
        }
        String groupName = SecurityConstants.ROLES_IDENTIFIER;
        Group rolesGroup = new PicketLinkGroup(groupName);
        List<Stringroles = AssertionUtil.getRoles(roleKeys);
        for (String role : roles) {
            rolesGroup.addMember(new SimplePrincipal(role));
        }
        return new Group[] { rolesGroup };
    }

    
Get the STSClient object with which we can make calls to the STS

Returns:
    protected STSClient getSTSClient() {
        /*
         * Builder builder = new Builder(this.stsConfigurationFile); STSClient client = new STSClient(builder.build());
         */
        Builder builder = null;
        STSClient client = null;
        if (.containsKey()) {
            builder = new Builder(this.);
            client = STSClientFactory.getInstance().create(builder.build());
        } else {
            builder = new Builder();
            builder.endpointAddress((String.get());
            String passwordString = (String.get();
            if (passwordString != null && passwordString.startsWith(.)) {
                // password is masked
                String salt = (String.get(.);
                if (StringUtil.isNullOrEmpty(salt))
                    throw .optionNotSet("Salt");
                String iCount = (String.get(.);
                if (StringUtil.isNullOrEmpty(iCount))
                    throw .optionNotSet("Iteration Count");
                int iterationCount = Integer.parseInt(iCount);
                try {
                    builder.password(StringUtil.decode(passwordStringsaltiterationCount));
                } catch (Exception e) {
                    throw .unableToDecodePasswordError(passwordString);
                }
            }
            client = STSClientFactory.getInstance().create(builder.build());
        }
        // if the login module options map still contains any properties, assume they are for configuring the connection
        // to the STS and set them in the Dispatch request context.
        if (!this..isEmpty()) {
            Dispatch<Sourcedispatch = client.getDispatch();
            for (Map.Entry<String, ?> entry : this..entrySet())
                dispatch.getRequestContext().put(entry.getKey(), entry.getValue());
        }
        return client;
    }
    
    
Locally validate the SAML Assertion element

Parameters:
assertionElement
Returns:
Throws:
Exception
    protected abstract boolean localValidation(Element assertionElementthrows Exception;
    protected abstract TimeCacheExpiry getCacheExpiry() throws Exception;
New to GrepCode? Check out our FAQ X