Author: marcelkolsteren
Date: 2009-08-09 11:53:59 -0400 (Sun, 09 Aug 2009)
New Revision: 685
Added:
identity-federation/trunk/jboss-identity-seam/src/main/java/org/
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/RelayStates.java
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlAuthenticationFilter.java
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlEnabledPages.java
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlIdentity.java
identity-federation/trunk/jboss-identity-seam/src/main/resources/seam.properties
Modified:
identity-federation/trunk/jboss-identity-seam/pom.xml
Log:
JBID-161: Support for Seam Service Providers
Modified: identity-federation/trunk/jboss-identity-seam/pom.xml
===================================================================
--- identity-federation/trunk/jboss-identity-seam/pom.xml 2009-08-08 11:15:22 UTC (rev
684)
+++ identity-federation/trunk/jboss-identity-seam/pom.xml 2009-08-09 15:53:59 UTC (rev
685)
@@ -1,105 +1,131 @@
-<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
- <parent>
- <groupId>org.jboss.identity</groupId>
- <artifactId>jboss-identity-fed-parent</artifactId>
- <version>1.0.0.alpha5-SNAPSHOT</version>
- <relativePath>../parent</relativePath>
- </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>jboss-identity-seam</artifactId>
- <packaging>jar</packaging>
- <name>JBoss Identity Federation Bindings for Seam</name>
- <
url>http://labs.jboss.org/portal/jbossidentity/</url>
- <description>JBoss Identity Seam bindings contain the default bindings needed
for Seam web applications.</description>
- <licenses>
- <license>
- <name>lgpl</name>
- <
url>http://repository.jboss.com/licenses/lgpl.txt</url>
- </license>
- </licenses>
- <organization>
- <name>JBoss Inc.</name>
- <url>http://www.jboss.org</url>
- </organization>
-
- <build>
- <plugins>
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <version>2.4.3</version>
- <configuration>
- <printSummary>true</printSummary>
- <disableXmlReport>false</disableXmlReport>
- <testFailureIgnore>false</testFailureIgnore>
- <includes>
- <include>**/**TestCase.java</include>
- </includes>
- <forkMode>pertest</forkMode>
-
<argLine>-Djava.endorsed.dirs=${basedir}/src/test/resources/endorsed</argLine>
- <useFile>false</useFile>
- <trimStackTrace>false</trimStackTrace>
- </configuration>
- </plugin>
- </plugins>
- </build>
-
- <dependencies>
- <dependency>
- <groupId>org.jboss.identity</groupId>
- <artifactId>jboss-identity-fed-model</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.jboss.identity</groupId>
- <artifactId>jboss-identity-fed-api</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
- <version>2.4</version>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>sun-jaf</groupId>
- <artifactId>activation</artifactId>
- <version>1.1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
-
- <reporting>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
- <configuration>
- <doclet>org.jboss.apiviz.APIviz</doclet>
- <docletArtifact>
- <groupId>org.jboss.apiviz</groupId>
- <artifactId>apiviz</artifactId>
- <version>1.2.5.GA</version>
- </docletArtifact>
- <additionalparam>
- -charset UTF-8
- -docencoding UTF-8
- -version
- -author
- -breakiterator
- -windowtitle "${project.name} ${project.version} API Reference"
- -doctitle "${project.name} ${project.version} API Reference"
- -bottom "Copyright © ${project.inceptionYear}-Present
${project.organization.name}. All Rights Reserved."
- -link
http://java.sun.com/javase/6/docs/api/
- -sourceclasspath ${project.build.outputDirectory}
- </additionalparam>
- <encoding>UTF-8</encoding>
- </configuration>
- </plugin>
- </plugins>
- </reporting>
-</project>
+<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.jboss.identity</groupId>
+ <artifactId>jboss-identity-fed-parent
+ </artifactId>
+ <version>1.0.0.alpha5-SNAPSHOT</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>jboss-identity-seam</artifactId>
+ <packaging>jar</packaging>
+ <name>JBoss Identity Federation Bindings for Seam</name>
+ <
url>http://labs.jboss.org/portal/jbossidentity/
+ </url>
+ <description>JBoss Identity Seam bindings contain the default
+ bindings needed for Seam web applications.</description>
+ <licenses>
+ <license>
+ <name>lgpl</name>
+ <
url>http://repository.jboss.com/licenses/lgpl.txt
+ </url>
+ </license>
+ </licenses>
+ <organization>
+ <name>JBoss Inc.</name>
+ <url>http://www.jboss.org</url>
+ </organization>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.4.3</version>
+ <configuration>
+ <printSummary>true</printSummary>
+ <disableXmlReport>false</disableXmlReport>
+ <testFailureIgnore>false</testFailureIgnore>
+ <includes>
+ <include>**/**TestCase.java</include>
+ </includes>
+ <forkMode>pertest</forkMode>
+ <argLine>
+ -Djava.endorsed.dirs=${basedir}/src/test/resources/endorsed
+ </argLine>
+ <useFile>false</useFile>
+ <trimStackTrace>false</trimStackTrace>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.identity</groupId>
+ <artifactId>jboss-identity-fed-model
+ </artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.identity</groupId>
+ <artifactId>jboss-identity-fed-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.identity</groupId>
+ <artifactId>jboss-identity-bindings
+ </artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.seam</groupId>
+ <artifactId>jboss-seam</artifactId>
+ <version>2.1.2.GA</version>
+ <type>ejb</type>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>sun-jaf</groupId>
+ <artifactId>activation</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>sun-jaf</groupId>
+ <artifactId>activation</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.faces</groupId>
+ <artifactId>jsf-api</artifactId>
+ <version>1.2</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <doclet>org.jboss.apiviz.APIviz</doclet>
+ <docletArtifact>
+ <groupId>org.jboss.apiviz</groupId>
+ <artifactId>apiviz</artifactId>
+ <version>1.2.5.GA</version>
+ </docletArtifact>
+ <additionalparam> -charset UTF-8 -docencoding UTF-8 -version
+ -author -breakiterator -windowtitle "${project.name}
+ ${project.version} API Reference" -doctitle "${project.name}
+ ${project.version} API Reference" -bottom "Copyright ©
+ ${project.inceptionYear}-Present ${project.organization.name}. All
+ Rights Reserved." -link
http://java.sun.com/javase/6/docs/api/
+ -sourceclasspath ${project.build.outputDirectory}
+ </additionalparam>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
\ No newline at end of file
Added:
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/RelayStates.java
===================================================================
---
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/RelayStates.java
(rev 0)
+++
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/RelayStates.java 2009-08-09
15:53:59 UTC (rev 685)
@@ -0,0 +1,80 @@
+/*
+ * 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.SESSION;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.Startup;
+
+/**
+ * Session scoped component that stores relay states. Each relay state corresponds to an
uncompleted authorization request
+ * that has been sent to the IDP. The state is used to store the URL of the page that has
been requested by the user.
+ * Each state has an integer number that can be used as the RelayState parameter in the
SAMLv2 authentication protocol.
+ *
+ * @author Marcel Kolsteren
+ */
+@Scope(SESSION)
+(a)Name("org.jboss.identity.seam.federation.relayStates")
+@Startup
+public class RelayStates
+{
+ private Map<Integer, String> states = new HashMap<Integer, String>();
+
+ private int nextIndex = 0;
+
+ public int saveState(HttpServletRequest request)
+ {
+ int index = nextIndex++;
+
+ StringBuffer requestURL = request.getRequestURL();
+ if (request.getQueryString() != null)
+ {
+ requestURL.append("?" + request.getQueryString());
+ }
+
+ states.put(index, requestURL.toString());
+ return index;
+ }
+
+ public void restoreState(int index, HttpServletResponse response)
+ {
+ String requestURL = states.get(index);
+ try
+ {
+ response.sendRedirect(requestURL);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ states.remove(index);
+ }
+}
Added:
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlAuthenticationFilter.java
===================================================================
---
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlAuthenticationFilter.java
(rev 0)
+++
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlAuthenticationFilter.java 2009-08-09
15:53:59 UTC (rev 685)
@@ -0,0 +1,494 @@
+/*
+ * 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.request.SAML2Request;
+import org.jboss.identity.federation.api.saml.v2.response.SAML2Response;
+import org.jboss.identity.federation.bindings.tomcat.sp.SPUtil;
+import org.jboss.identity.federation.bindings.util.HTTPRedirectUtil;
+import org.jboss.identity.federation.bindings.util.PostBindingUtil;
+import org.jboss.identity.federation.bindings.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>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
+ */
+@Scope(APPLICATION)
+(a)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 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(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(ServletRequest 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(ServletRequest request)
+ {
+ String samlMessage = request.getParameter("SAMLResponse");
+
+ // 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
+ {
+ return
PostBindingUtil.validateSignature(samlMessage.getBytes("UTF-8"), signature,
publicKeyOfIDP);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private PublicKey getPublicKeyOfIDP()
+ {
+ try
+ {
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keyStore.load(new URL(keyStoreURL).openStream(), keyStorePass.toCharArray());
+ 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 = new
SPUtil().createSAMLRequest(serviceProviderURL, identityProviderURL);
+
+ SAML2Request saml2Request = new SAML2Request();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ saml2Request.marshall(authnRequest, baos);
+
+ String samlMessage = PostBindingUtil.base64Encode(baos.toString());
+ String destination = authnRequest.getDestination();
+ 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(destination + sb.toString(),
response);
+ }
+ else
+ {
+ DestinationInfoHolder destinationInfoHolder = new
DestinationInfoHolder(destination, samlMessage, Integer
+ .toString(relayState));
+ PostBindingUtil.sendPost(destinationInfoHolder, null, 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);
+ }
+ }
+}
Added:
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlEnabledPages.java
===================================================================
---
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlEnabledPages.java
(rev 0)
+++
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlEnabledPages.java 2009-08-09
15:53:59 UTC (rev 685)
@@ -0,0 +1,65 @@
+/*
+ * 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 javax.faces.context.FacesContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.jboss.seam.Component;
+import org.jboss.seam.ScopeType;
+import org.jboss.seam.annotations.Install;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.Startup;
+import org.jboss.seam.annotations.intercept.BypassInterceptors;
+import org.jboss.seam.faces.FacesManager;
+import org.jboss.seam.navigation.Pages;
+
+/**
+ * Override of Seam's Pages component. It replaces the login page redirection method
with a version
+ * that redirects to an URL that is filtered by the SamlAuthenticationFilter.
+ *
+ * @author Marcel Kolsteren
+ */
+(a)Scope(ScopeType.APPLICATION)
+@BypassInterceptors
+(a)Name("org.jboss.seam.navigation.pages")
+@Install(precedence = Install.FRAMEWORK, classDependencies =
"javax.faces.context.FacesContext")
+@Startup
+public class SamlEnabledPages extends Pages
+{
+ @Override
+ public void redirectToLoginView()
+ {
+ notLoggedIn();
+
+ HttpServletRequest httpRequest = (HttpServletRequest)
FacesContext.getCurrentInstance().getExternalContext()
+ .getRequest();
+
+ RelayStates relayStates = (RelayStates) Component.getInstance(RelayStates.class);
+ int relayState = relayStates.saveState(httpRequest);
+
+ String authenticationFilterURL = httpRequest.getScheme() + "://" +
httpRequest.getServerName() + ":"
+ + httpRequest.getServerPort() + httpRequest.getContextPath() +
"/SamlAuthenticationFilter.seam";
+ FacesManager.instance().redirectToExternalURL(authenticationFilterURL +
"?newRelayState=" + relayState);
+ }
+}
Added:
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlIdentity.java
===================================================================
---
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlIdentity.java
(rev 0)
+++
identity-federation/trunk/jboss-identity-seam/src/main/java/org/jboss/identity/seam/federation/SamlIdentity.java 2009-08-09
15:53:59 UTC (rev 685)
@@ -0,0 +1,72 @@
+/*
+ * 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.SESSION;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jboss.seam.annotations.Install;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.Startup;
+import org.jboss.seam.annotations.intercept.BypassInterceptors;
+import org.jboss.seam.security.Identity;
+
+/**
+ * Identity that has been establised using SAMLv2 authentication.
+ *
+ * @author Marcel Kolsteren
+ */
+(a)Name("org.jboss.seam.security.identity")
+@Scope(SESSION)
+@Install(precedence = Install.FRAMEWORK)
+@BypassInterceptors
+@Startup
+public class SamlIdentity extends Identity
+{
+ private static final long serialVersionUID = 7042249176714812268L;
+
+ private Map<String, List<String>> attributes = new HashMap<String,
List<String>>();
+
+ public Map<String, List<String>> getAttributes()
+ {
+ return attributes;
+ }
+
+ public void setAttributes(Map<String, List<String>> attributes)
+ {
+ this.attributes = attributes;
+ }
+
+ public String getAttributeValue(String attributeName)
+ {
+ return attributes.get(attributeName).get(0);
+ }
+
+ public List<String> getAttributeValues(String attributeName)
+ {
+ return attributes.get(attributeName);
+ }
+}
Added: identity-federation/trunk/jboss-identity-seam/src/main/resources/seam.properties
===================================================================