Skip to content

Commit

Permalink
Merge branch 'master' into renovate/configure
Browse files Browse the repository at this point in the history
  • Loading branch information
justinbleach authored Dec 21, 2024
2 parents 3f45e2b + 5024bd1 commit ad472e6
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 71 deletions.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[![Build Status](https://travis-ci.org/coveo/saml-client.svg?branch=master)](https://travis-ci.org/coveo/saml-client)
[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/coveo/saml-client/blob/master/LICENSE)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.coveo/saml-client/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.coveo/saml-client)

Expand All @@ -20,10 +19,29 @@ Add this dependency to your `pom.xml` to reference the library:
<dependency>
<groupId>com.coveo</groupId>
<artifactId>saml-client</artifactId>
<version>4.1.2</version>
<version>5.0.0</version>
</dependency>
```

[OpenSAML](https://shibboleth.atlassian.net/wiki/spaces/OSAML) latest versions are strictly hosted on Shibboleth repository. Therefore, to use this library, you need to add this to your `pom.xml` as well :
```xml
<repositories>
<repository>
<id>shibboleth</id>
<url>https://build.shibboleth.net/maven/releases</url>
</repository>
</repositories>
```
or with Gradle :
```asciidoc
repositories {
maven {
url 'https://build.shibboleth.net/maven/releases'
}
mavenCentral()
}
```

# Usage

## SAML authentication process overview
Expand Down
52 changes: 31 additions & 21 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

<groupId>com.coveo</groupId>
<artifactId>saml-client</artifactId>
<version>4.1.2</version>
<version>5.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>${project.groupId}:${project.artifactId}</name>

<description>Dead simple client for obtaining authentication from SAML identity provider</description>
<url>http://github.com/coveo/saml-client</url>
<url>https://github.com/coveo/saml-client</url>

<licenses>
<license>
Expand All @@ -23,14 +23,14 @@
<developer>
<name>Martin Laporte</name>
<organization>Coveo</organization>
<organizationUrl>http://github.com/coveo</organizationUrl>
<organizationUrl>https://github.com/coveooss</organizationUrl>
</developer>
</developers>

<scm>
<connection>scm:git:[email protected]:coveo/saml-client.git</connection>
<developerConnection>scm:git:[email protected]:coveo/saml-client.git</developerConnection>
<url>http://github.com/coveo/saml-client</url>
<connection>scm:git:[email protected]:coveooss/saml-client.git</connection>
<developerConnection>scm:git:[email protected]:coveooss/saml-client.git</developerConnection>
<url>https://github.com/coveooss/saml-client</url>
</scm>

<distributionManagement>
Expand All @@ -47,44 +47,54 @@
<repositories>
<repository>
<id>shibboleth</id>
<url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
<url>https://build.shibboleth.net/maven/releases</url>
</repository>
</repositories>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>

<opensaml.version>4.2.0</opensaml.version>
<opensaml.version>4.3.0</opensaml.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml-bom</artifactId>
<version>${opensaml.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml-core</artifactId>
<version>${opensaml.version}</version>
</dependency>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml-saml-api</artifactId>
<version>${opensaml.version}</version>
</dependency>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml-saml-impl</artifactId>
<version>${opensaml.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
Expand All @@ -94,17 +104,17 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
<version>1.16.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/com/coveo/saml/BrowserUtils.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.coveo.saml;

import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.text.StringEscapeUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
Expand All @@ -19,8 +19,16 @@ public static void postUsingBrowser(
String url, HttpServletResponse response, Map<String, String> values) throws IOException {

response.setContentType("text/html");
@SuppressWarnings("resource")
Writer writer = response.getWriter();
writeHtml(url, values, writer);
writer.flush();

response.setHeader("Cache-Control", "no-cache, no-store");
response.setHeader("Pragma", "no-cache");
}

private static void writeHtml(String url, Map<String, String> values, Writer writer)
throws IOException {
writer.write(
"<html><head></head><body><form id='TheForm' action='"
+ StringEscapeUtils.escapeHtml4(url)
Expand All @@ -41,9 +49,5 @@ public static void postUsingBrowser(

writer.write(
"</form><script type='text/javascript'>document.getElementById('TheForm').submit();</script></body></html>");
writer.flush();

response.setHeader("Cache-Control", "no-cache, no-store");
response.setHeader("Pragma", "no-cache");
}
}
178 changes: 178 additions & 0 deletions src/main/java/com/coveo/saml/MetadataUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package com.coveo.saml;

import java.io.StringWriter;
import java.security.cert.X509Certificate;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.opensaml.core.config.InitializationService;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.metadata.AssertionConsumerService;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
import org.opensaml.saml.saml2.metadata.NameIDFormat;
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml.saml2.metadata.SingleLogoutService;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.xmlsec.keyinfo.KeyInfoGenerator;
import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;


public class MetadataUtils {

private static final Logger logger = LoggerFactory.getLogger(SamlClient.class);

public static String generateSpMetadata(String entityId, String assertionConsumerServiceURL, String logoutServiceURL) {
return generateSpMetadata(entityId, assertionConsumerServiceURL, logoutServiceURL, null);
}

public static String generateSpMetadata(String entityId, String assertionConsumerServiceURL, String singleLogoutServiceURL, X509Certificate certificate) {
try {
InitializationService.initialize();

EntityDescriptor spEntityDescriptor = createSAMLObject(EntityDescriptor.class);
if (spEntityDescriptor == null) {
return null;
}
spEntityDescriptor.setEntityID(entityId);
SPSSODescriptor spSSODescriptor = createSAMLObject(SPSSODescriptor.class);
if (spSSODescriptor == null) {
return null;
}

spSSODescriptor.setWantAssertionsSigned(false);
spSSODescriptor.setAuthnRequestsSigned(false);

if (certificate != null) {

spSSODescriptor.setWantAssertionsSigned(true);
spSSODescriptor.setAuthnRequestsSigned(true);

X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory();
keyInfoGeneratorFactory.setEmitEntityCertificate(true);
KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance();

KeyDescriptor encKeyDescriptor = createSAMLObject(KeyDescriptor.class);
if (encKeyDescriptor == null) {
return null;
}

encKeyDescriptor.setUse(UsageType.ENCRYPTION);

Credential credential = new BasicX509Credential(certificate);

try {
encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential));
}
catch (Exception e) {
logger.error("Error while creating credentials", e);
}
spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor);

KeyDescriptor signKeyDescriptor = createSAMLObject(KeyDescriptor.class);
if (signKeyDescriptor == null) {
return null;
}

signKeyDescriptor.setUse(UsageType.SIGNING); // Set usage

try {
signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential));
}
catch (SecurityException e) {
logger.error("Error while creating credentials", e);
}
spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor);
}

SingleLogoutService singleLogoutService = createSAMLObject(SingleLogoutService.class);
if (singleLogoutService == null) {
return null;
}
singleLogoutService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
singleLogoutService.setLocation(singleLogoutServiceURL);
spSSODescriptor.getSingleLogoutServices().add(singleLogoutService);

NameIDFormat nameIDFormat = createSAMLObject(NameIDFormat.class);
if (nameIDFormat == null) {
return null;
}

nameIDFormat.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
spSSODescriptor.getNameIDFormats().add(nameIDFormat);

AssertionConsumerService assertionConsumerService = createSAMLObject(AssertionConsumerService.class);
if (assertionConsumerService == null) {
return null;
}
assertionConsumerService.setIndex(1);
assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);

assertionConsumerService.setLocation(assertionConsumerServiceURL);
spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService);

spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);

spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor);

DocumentBuilder builder;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

builder = factory.newDocumentBuilder();
Document document = builder.newDocument();
Marshaller out = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(spEntityDescriptor);
out.marshall(spEntityDescriptor, document);

TransformerFactory transformerfactory = TransformerFactory.newInstance();
transformerfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer transformer = transformerfactory.newTransformer();
StringWriter stringWriter = new StringWriter();
StreamResult streamResult = new StreamResult(stringWriter);
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.transform(source, streamResult);
stringWriter.close();

return stringWriter.toString();
}
catch (Exception e) {
logger.error("Error while generation SP metadata", e);
return null;
}

}

public static <T> T createSAMLObject(final Class<T> clazz) {
XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();

QName defaultElementName = null;
try {
defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null);
}
catch (Exception e) {
logger.error("Error while creating SAML object", e);
return null;
}
T object = (T) builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName);

return object;
}
}
Loading

0 comments on commit ad472e6

Please sign in to comment.