Package org.jboss.identity.seam.federation

Source Code of org.jboss.identity.seam.federation.SamlAuthenticationFilter$AuthenticatedUser

/*
* 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.jboss.identity.seam.federation;

import static org.jboss.seam.ScopeType.APPLICATION;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.security.auth.login.LoginException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;

import org.jboss.identity.federation.api.saml.v2.common.IDGenerator;
import org.jboss.identity.federation.api.saml.v2.request.SAML2Request;
import org.jboss.identity.federation.api.saml.v2.response.SAML2Response;
import org.jboss.identity.federation.web.util.HTTPRedirectUtil;
import org.jboss.identity.federation.web.util.PostBindingUtil;
import org.jboss.identity.federation.web.util.RedirectBindingSignatureUtil;
import org.jboss.identity.federation.web.util.RedirectBindingUtil;
import org.jboss.identity.federation.core.exceptions.ConfigurationException;
import org.jboss.identity.federation.core.exceptions.ParsingException;
import org.jboss.identity.federation.core.saml.v2.constants.JBossSAMLURIConstants;
import org.jboss.identity.federation.core.saml.v2.holders.DestinationInfoHolder;
import org.jboss.identity.federation.core.saml.v2.util.AssertionUtil;
import org.jboss.identity.federation.saml.v2.assertion.AssertionType;
import org.jboss.identity.federation.saml.v2.assertion.AttributeStatementType;
import org.jboss.identity.federation.saml.v2.assertion.AttributeType;
import org.jboss.identity.federation.saml.v2.assertion.NameIDType;
import org.jboss.identity.federation.saml.v2.assertion.StatementAbstractType;
import org.jboss.identity.federation.saml.v2.protocol.AuthnRequestType;
import org.jboss.identity.federation.saml.v2.protocol.ResponseType;
import org.jboss.identity.federation.saml.v2.protocol.StatusType;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.web.Filter;
import org.jboss.seam.contexts.Context;
import org.jboss.seam.contexts.SessionContext;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Credentials;
import org.jboss.seam.security.Identity;
import org.jboss.seam.servlet.ContextualHttpServletRequest;
import org.jboss.seam.servlet.ServletRequestSessionMap;
import org.jboss.seam.util.Base64;
import org.jboss.seam.web.AbstractFilter;
import org.xml.sax.SAXException;

/**
* Seam Servlet Filter supporting SAMLv2 authentication. It implements the Web Browser SSO
* Profile. For outgoing authentication requests it can use either HTTP Post or HTTP Redirect
* binding. For the responses, it uses HTTP Post binding, with signature validation.
*
* Properties that configure this component:
*
* <dl>
* <dt>identityProviderURL</dt>
* <dd>URL of the identity provider.</dd>
* <dt>singleSignOnServiceURL</dt>
* <dd>URL of the SSO Service of the identity provider.</dd>
* <dt>keyStoreURL</dt>
* <dd>URL of the keystore.</dd>
* <dt>keyStorePass</dt>
* <dd>Password that gives access to the keystore.</dd>
* <dt>idpCertificateAlias</dt>
* <dd>The alias of the keystore entry that contains the certificate of the IDP.</dd>
* <dt>binding</dt>
* <dd>Method for sending the authentication request: HTTP_Redirect or HTTP_Post. Default: HTTP_Post.</dd>
* <dt>signatureRequired</dt>
* <dd>Specifies whether IDP responses are required to have a valid signature. Default: true.</dd>
* </dl>
*
* @author Marcel Kolsteren
* @author Anil Saldhana
*/
@Scope(APPLICATION)
@Name("org.jboss.identity.seam.federation.samlAuthenticationFilter")
@BypassInterceptors
@Filter(within = "org.jboss.seam.web.exceptionFilter")
public class SamlAuthenticationFilter extends AbstractFilter
{
   enum Binding {
      HTTP_Redirect, HTTP_Post
   };

   private String identityProviderURL;

   private String singleSignOnServiceURL;

   private String keyStoreURL;

   private String keyStorePass;

   private String idpCertificateAlias;

   private PublicKey publicKeyOfIDP;

   private Binding binding = Binding.HTTP_Post;

   private boolean signatureRequired = true;

   protected class AuthenticatedUser
   {
      String userName;

      Map<String, List<String>> attributes = new HashMap<String, List<String>>();
   }

   @Logger
   private Log log;

   @Override
   public void init(FilterConfig filterConfig) throws ServletException
   {
      super.init(filterConfig);
      if (signatureRequired)
      {
         publicKeyOfIDP = getPublicKeyOfIDP();
      }
   }

   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
         ServletException
   {
      if (!(request instanceof HttpServletRequest))
      {
         throw new ServletException("This filter can only process HttpServletRequest requests");
      }

      HttpServletRequest httpRequest = (HttpServletRequest) request;
      HttpServletResponse httpResponse = (HttpServletResponse) response;

      if (request.getParameter("SAMLResponse") != null)
      {
         // Received an authentication response from the IDP.

         AuthenticatedUser user = processIDPResponse((HttpServletRequest) request);
         if (user != null)
         {
            // Login the user. This ends with a redirect to the URL that was requested by the user.
            loginUser(httpRequest, httpResponse, user);
         }
      }
      else if (request.getParameter("newRelayState") != null)
      {
         // User requested a page for which login is required. Return a page that instructs the browser to post an
         // authentication request to the IDP.
         sendRequestToIDP(httpRequest, httpResponse);
      }
      else
      {
         // Request is not related to SAMLv2 authentication. Pass it on to the next chain.
         chain.doFilter(request, response);
      }
   }

   private void loginUser(HttpServletRequest httpRequest, HttpServletResponse httpResponse, AuthenticatedUser user)
         throws ServletException, IOException
   {
      // Force session creation
      httpRequest.getSession();

      Context ctx = new SessionContext(new ServletRequestSessionMap(httpRequest));

      // Only reauthenticate if username doesn't match Identity.username
      // and user isn't authenticated
      Credentials credentials = (Credentials) ctx.get(Credentials.class);
      Identity identity = (Identity) ctx.get(Identity.class);

      if (identity.isLoggedIn())
      {
         throw new RuntimeException("User is already logged in.");
      }

      credentials.setPassword("");
      authenticate(httpRequest, user);
      RelayStates relayStates = (RelayStates) ctx.get(RelayStates.class);
      String relayState = httpRequest.getParameter("RelayState");
      if (relayState == null)
      {
         throw new RuntimeException("RelayState parameter is missing");
      }
      relayStates.restoreState(Integer.parseInt(relayState), httpResponse);
   }

   private void authenticate(HttpServletRequest request, final AuthenticatedUser user) throws ServletException,
         IOException
   {
      new ContextualHttpServletRequest(request)
      {
         @Override
         public void process() throws ServletException, IOException, LoginException
         {
            SamlIdentity identity = (SamlIdentity) Identity.instance();
            identity.getCredentials().setUsername(user.userName);
            identity.setAttributes(user.attributes);
            identity.authenticate();
         }
      }.run();
   }

   private AuthenticatedUser processIDPResponse(HttpServletRequest request)
   {
      String samlResponse = request.getParameter("SAMLResponse");

      if (signatureRequired && !validateSignature(request))
      {
         log.error("Invalid signature");
         throw new RuntimeException("Validity Checks failed");
      }

      // deal with SAML response from IDP
      byte[] base64DecodedResponse = Base64.decode(samlResponse);
      InputStream is = new ByteArrayInputStream(base64DecodedResponse);

      SAML2Response saml2Response = new SAML2Response();

      ResponseType responseType;
      try
      {
         responseType = saml2Response.getResponseType(is);
      }
      catch (ParsingException e)
      {
         throw new RuntimeException(e);
      }
      catch (ConfigurationException e)
      {
         throw new RuntimeException(e);
      }

      StatusType statusType = responseType.getStatus();
      if (statusType == null)
      {
         throw new RuntimeException("Status Type from the IDP is null");
      }

      String statusValue = statusType.getStatusCode().getValue();
      if (JBossSAMLURIConstants.STATUS_SUCCESS.get().equals(statusValue) == false)
      {
         throw new RuntimeException("IDP forbid the user");
      }

      List<Object> assertions = responseType.getAssertionOrEncryptedAssertion();
      if (assertions.size() == 0)
      {
         throw new RuntimeException("IDP response does not contain assertions");
      }

      AuthenticatedUser user = null;

      for (Object assertion : responseType.getAssertionOrEncryptedAssertion())
      {
         if (assertion instanceof AssertionType)
         {
            AuthenticatedUser userInAssertion = handleAssertion((AssertionType) assertion);
            if (user == null)
            {
               user = userInAssertion;
            }
            else
            {
               log.warn("Multiple authenticated users found in assertions. Using the first one.");
            }
         }
         else
         {
            /* assertion instanceof EncryptedElementType */
            log.warn("Encountered encrypted assertion. Skipping it because decryption is not yet supported.");
         }
      }
      if (user == null)
      {
         log.warn("No authenticated users found in assertions.");
      }

      return user;
   }

   private AuthenticatedUser handleAssertion(AssertionType assertion)
   {
      try
      {
         if (AssertionUtil.hasExpired(assertion))
         {
            log.warn("Received assertion will not be processed because it has expired.");
            return null;
         }
      }
      catch (ConfigurationException e)
      {
         throw new RuntimeException(e);
      }

      AuthenticatedUser user = null;

      for (JAXBElement<?> contentElement : assertion.getSubject().getContent())
      {
         if (contentElement.getName().getLocalPart().equals("NameID"))
         {
            user = new AuthenticatedUser();
            user.userName = ((NameIDType) contentElement.getValue()).getValue();
         }
      }

      if (user != null)
      {
         for (StatementAbstractType statement : assertion.getStatementOrAuthnStatementOrAuthzDecisionStatement())
         {
            if (statement instanceof AttributeStatementType)
            {
               AttributeStatementType attributeStatement = (AttributeStatementType) statement;
               for (Object object : attributeStatement.getAttributeOrEncryptedAttribute())
               {
                  if (object instanceof AttributeType)
                  {
                     AttributeType attr = (AttributeType) object;
                     List<String> values = user.attributes.get(attr.getName());
                     if (values == null)
                     {
                        values = new LinkedList<String>();
                     }
                     for (Object value : attr.getAttributeValue())
                     {
                        values.add((String) value);
                     }
                     user.attributes.put(attr.getName(), values);
                  }
                  else
                  {
                     log.warn("Encrypted attributes are not supported. Ignoring the attribute.");
                  }
               }
            }
         }
      }
      else
      {
         log.warn("Subject is not specified using the NameID element. Ignoring the assertion.");
      }

      return user;
   }

   private boolean validateSignature(HttpServletRequest request)
   { 
      // Check if there is a signature
      String signature = request.getParameter("Signature");
      if (signature == null || signature.length() == 0)
      {
         log.error("Signature Value missing in response from IDP");
         return false;
      }
      String sigAlg = request.getParameter("sigAlg");
      if (sigAlg == null || sigAlg.length() == 0)
      {
         log.error("Signature Algorithm missing in the response from IDP");
         return false;
      }

      try
      {
         if("GET".equalsIgnoreCase(request.getMethod()))
         {
            String queryString = request.getQueryString();
            byte[] sigValue = RedirectBindingSignatureUtil.getSignatureValueFromSignedURL(queryString);
           
            return RedirectBindingSignatureUtil.validateSignature(queryString, this.publicKeyOfIDP, sigValue);
         }
         return true;
      }
      catch (UnsupportedEncodingException e)
      {
         throw new RuntimeException(e);
      }
      catch (GeneralSecurityException e)
      {
         throw new RuntimeException(e);
      }
      catch (IOException e)
      {
         throw new RuntimeException(e);
      }
   }

   private PublicKey getPublicKeyOfIDP()
   {
      try
      {
         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
         keyStore.load(new URL(keyStoreURL).openStream(), keyStorePass != null ? keyStorePass.toCharArray() : null);
         return keyStore.getCertificate(idpCertificateAlias).getPublicKey();
      }
      catch (KeyStoreException e)
      {
         throw new RuntimeException(e);
      }
      catch (NoSuchAlgorithmException e)
      {
         throw new RuntimeException(e);
      }
      catch (CertificateException e)
      {
         throw new RuntimeException(e);
      }
      catch (MalformedURLException e)
      {
         throw new RuntimeException(e);
      }
      catch (IOException e)
      {
         throw new RuntimeException(e);
      }
   }

   private void sendRequestToIDP(HttpServletRequest request, HttpServletResponse response)
   {
      Integer relayState = Integer.parseInt(request.getParameter("newRelayState"));

      try
      {
         /* Derive the service provider URL from the current request URL. Replace the last part with a place holder,
          * because we do not want the IDP to know what page the user requested. */
         String serviceProviderURL = request.getScheme() + "://" + request.getServerName() + ":"
               + request.getServerPort() + request.getContextPath() + "/SamlAuthenticationFilter.seam";

         AuthnRequestType authnRequest = createSAMLRequest(serviceProviderURL, identityProviderURL);

         SAML2Request saml2Request = new SAML2Request();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         saml2Request.marshall(authnRequest, baos);

         String samlMessage = PostBindingUtil.base64Encode(baos.toString());
         if (binding == Binding.HTTP_Redirect)
         {
            String deflatedRequest = RedirectBindingUtil.deflateBase64URLEncode(baos.toByteArray());
            StringBuilder sb = new StringBuilder();
            sb.append("?SAMLRequest=").append(deflatedRequest);
            sb.append("&RelayState=").append(relayState);
            HTTPRedirectUtil.sendRedirectForRequestor(singleSignOnServiceURL + sb.toString(), response);
         }
         else
         {
            DestinationInfoHolder destinationInfoHolder = new DestinationInfoHolder(singleSignOnServiceURL,
                  samlMessage, Integer.toString(relayState));
            PostBindingUtil.sendPost(destinationInfoHolder, response, true);
         }
      }
      catch (ConfigurationException e)
      {
         throw new RuntimeException();
      }
      catch (SAXException e)
      {
         throw new RuntimeException(e);
      }
      catch (JAXBException e)
      {
         throw new RuntimeException(e);
      }
      catch (IOException e)
      {
         throw new RuntimeException(e);
      }
   }
  
   private AuthnRequestType createSAMLRequest(String serviceURL, String identityURL) throws ConfigurationException
   {
      if(serviceURL == null)
         throw new IllegalArgumentException("serviceURL is null");
      if(identityURL == null)
         throw new IllegalArgumentException("identityURL is null");
     
      SAML2Request saml2Request = new SAML2Request();
      String id = IDGenerator.create("ID_");
      return saml2Request.createAuthnRequestType(id, serviceURL, identityURL, serviceURL);
   }
}
TOP

Related Classes of org.jboss.identity.seam.federation.SamlAuthenticationFilter$AuthenticatedUser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.