Skip to content

Commit

Permalink
KEYCLOAK-3864 Add support for SAML2 <Extensions> element in protocol …
Browse files Browse the repository at this point in the history
…messages
  • Loading branch information
hmlnarik committed Nov 4, 2016
1 parent 6baf9b8 commit 904a5c3
Show file tree
Hide file tree
Showing 25 changed files with 539 additions and 5 deletions.
12 changes: 12 additions & 0 deletions saml-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,19 @@
import org.w3c.dom.Document;

import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;

/**
* @author pedroigor
*/
public class SAML2AuthnRequestBuilder {
public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2AuthnRequestBuilder> {

private final AuthnRequestType authnRequestType;
protected String destination;
protected String issuer;
protected final List<NodeGenerator> extensions = new LinkedList<>();

public SAML2AuthnRequestBuilder destination(String destination) {
this.destination = destination;
Expand All @@ -45,6 +49,12 @@ public SAML2AuthnRequestBuilder issuer(String issuer) {
return this;
}

@Override
public SAML2AuthnRequestBuilder addExtension(NodeGenerator extension) {
this.extensions.add(extension);
return this;
}

public SAML2AuthnRequestBuilder() {
try {
this.authnRequestType = new AuthnRequestType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
Expand Down Expand Up @@ -90,6 +100,14 @@ public Document toDocument() {

authnRequestType.setDestination(URI.create(this.destination));

if (! this.extensions.isEmpty()) {
ExtensionsType extensionsType = new ExtensionsType();
for (NodeGenerator extension : this.extensions) {
extensionsType.addExtension(extension);
}
authnRequestType.setExtensions(extensionsType);
}

return new SAML2Request().convert(authnRequestType);
} catch (Exception e) {
throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@

package org.keycloak.saml;

import java.util.LinkedList;
import java.util.List;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ParsingException;
Expand All @@ -32,11 +35,12 @@
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SAML2ErrorResponseBuilder {
public class SAML2ErrorResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2ErrorResponseBuilder> {

protected String status;
protected String destination;
protected String issuer;
protected final List<NodeGenerator> extensions = new LinkedList<>();

public SAML2ErrorResponseBuilder status(String status) {
this.status = status;
Expand All @@ -53,6 +57,11 @@ public SAML2ErrorResponseBuilder issuer(String issuer) {
return this;
}

@Override
public SAML2ErrorResponseBuilder addExtension(NodeGenerator extension) {
this.extensions.add(extension);
return this;
}

public Document buildDocument() throws ProcessingException {

Expand All @@ -66,6 +75,14 @@ public Document buildDocument() throws ProcessingException {
statusResponse.setIssuer(issuer);
statusResponse.setDestination(destination);

if (! this.extensions.isEmpty()) {
ExtensionsType extensionsType = new ExtensionsType();
for (NodeGenerator extension : this.extensions) {
extensionsType.addExtension(extension);
}
statusResponse.setExtensions(extensionsType);
}

SAML2Response saml2Response = new SAML2Response();
return saml2Response.convert(statusResponse);
} catch (ConfigurationException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
import org.w3c.dom.Document;

import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;

import static org.keycloak.saml.common.util.StringUtil.isNotNull;

Expand All @@ -49,7 +52,7 @@
*
* @author bburke@redhat.com
*/
public class SAML2LoginResponseBuilder {
public class SAML2LoginResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LoginResponseBuilder> {
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();

protected String destination;
Expand All @@ -64,6 +67,7 @@ public class SAML2LoginResponseBuilder {
protected String authMethod;
protected String requestIssuer;
protected String sessionIndex;
protected final List<NodeGenerator> extensions = new LinkedList<>();


public SAML2LoginResponseBuilder sessionIndex(String sessionIndex) {
Expand Down Expand Up @@ -136,6 +140,12 @@ public SAML2LoginResponseBuilder disableAuthnStatement(boolean disableAuthnState
return this;
}

@Override
public SAML2LoginResponseBuilder addExtension(NodeGenerator extension) {
this.extensions.add(extension);
return this;
}

public Document buildDocument(ResponseType responseType) throws ConfigurationException, ProcessingException {
Document samlResponseDocument = null;

Expand Down Expand Up @@ -207,6 +217,14 @@ public ResponseType buildModel() throws ConfigurationException, ProcessingExcept
assertion.addStatement(authnStatement);
}

if (! this.extensions.isEmpty()) {
ExtensionsType extensionsType = new ExtensionsType();
for (NodeGenerator extension : this.extensions) {
extensionsType.addExtension(extension);
}
responseType.setExtensions(extensionsType);
}

return responseType;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,22 @@
import org.w3c.dom.Document;

import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SAML2LogoutRequestBuilder {
public class SAML2LogoutRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutRequestBuilder> {
protected String userPrincipal;
protected String userPrincipalFormat;
protected String sessionIndex;
protected long assertionExpiration;
protected String destination;
protected String issuer;
protected final List<NodeGenerator> extensions = new LinkedList<>();

public SAML2LogoutRequestBuilder destination(String destination) {
this.destination = destination;
Expand All @@ -50,6 +54,12 @@ public SAML2LogoutRequestBuilder issuer(String issuer) {
return this;
}

@Override
public SAML2LogoutRequestBuilder addExtension(NodeGenerator extension) {
this.extensions.add(extension);
return this;
}

/**
* Length of time in seconds the assertion is valid for
* See SAML core specification 2.5.1.2 NotOnOrAfter
Expand Down Expand Up @@ -99,6 +109,15 @@ private LogoutRequestType createLogoutRequest() throws ConfigurationException {

if (assertionExpiration > 0) lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionExpiration * 1000));
lort.setDestination(URI.create(destination));

if (! this.extensions.isEmpty()) {
ExtensionsType extensionsType = new ExtensionsType();
for (NodeGenerator extension : this.extensions) {
extensionsType.addExtension(extension);
}
lort.setExtensions(extensionsType);
}

return lort;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,20 @@
import org.w3c.dom.Document;

import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SAML2LogoutResponseBuilder {
public class SAML2LogoutResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutResponseBuilder> {

protected String logoutRequestID;
protected String destination;
protected String issuer;
protected final List<NodeGenerator> extensions = new LinkedList<>();

public SAML2LogoutResponseBuilder logoutRequestID(String logoutRequestID) {
this.logoutRequestID = logoutRequestID;
Expand All @@ -57,6 +61,11 @@ public SAML2LogoutResponseBuilder issuer(String issuer) {
return this;
}

@Override
public SAML2LogoutResponseBuilder addExtension(NodeGenerator extension) {
this.extensions.add(extension);
return this;
}

public Document buildDocument() throws ProcessingException {
Document samlResponse = null;
Expand All @@ -77,6 +86,14 @@ public Document buildDocument() throws ProcessingException {
statusResponse.setIssuer(issuer);
statusResponse.setDestination(destination);

if (! this.extensions.isEmpty()) {
ExtensionsType extensionsType = new ExtensionsType();
for (NodeGenerator extension : this.extensions) {
extensionsType.addExtension(extension);
}
statusResponse.setExtensions(extensionsType);
}

SAML2Response saml2Response = new SAML2Response();
samlResponse = saml2Response.convert(statusResponse);
} catch (ConfigurationException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.keycloak.saml;

import javax.xml.stream.XMLStreamWriter;
import org.keycloak.saml.common.exceptions.ProcessingException;

/**
* Implementations of this interface are builders that can register &lt;samlp:Extensions&gt;
* content providers.
*
* @author hmlnarik
*/
public interface SamlProtocolExtensionsAwareBuilder<T> {

public interface NodeGenerator {
/**
* Generate contents of the &lt;samlp:Extensions&gt; tag. When this method is invoked,
* the writer has already emitted the &lt;samlp:Extensions&gt; start tag.
*
* @param writer Writer to use for producing XML output
* @throws ProcessingException If any exception fails
*/
void write(XMLStreamWriter writer) throws ProcessingException;
}

/**
* Adds a given node subtree as a SAML protocol extension into the SAML protocol message.
*
* @param extension
* @return
*/
T addExtension(NodeGenerator extension);
}
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,11 @@ public RuntimeException parserExpectedTag(String tag, String foundElementTag) {
return new RuntimeException(ErrorCodes.EXPECTED_TAG + tag + ">. Found <" + foundElementTag + ">");
}

@Override
public RuntimeException parserExpectedNamespace(String ns, String foundElementNs) {
return new RuntimeException(ErrorCodes.EXPECTED_NAMESPACE + ns + ">. Found <" + foundElementNs + ">");
}

/*
*(non-Javadoc)
*
Expand Down Expand Up @@ -2378,4 +2383,10 @@ public ProcessingException samlAssertionWrongAudience(String serviceURL) {
return new ProcessingException("Wrong audience [" + serviceURL + "].");
}

@Override
public ProcessingException samlExtensionUnknownChild(Class<?> clazz) {
return new ProcessingException("Unknown child type specified for extension: "
+ (clazz == null ? "<null>" : clazz.getSimpleName())
+ ".");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public interface ErrorCodes {

String EXPECTED_TAG = "PL00066: Parser : Expected start tag:";

String EXPECTED_NAMESPACE = "PL00107: Parser : Expected start element namespace:";

String EXPECTED_TEXT_VALUE = "PL00071: Parser: Expected text value:";

String EXPECTED_END_TAG = "PL00066: Parser : Expected end tag:";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,14 @@ public interface PicketLinkLogger {
*/
RuntimeException parserExpectedTag(String tag, String foundElementTag);

/**
* @param ns
* @param foundElementNs
*
* @return
*/
RuntimeException parserExpectedNamespace(String ns, String foundElementNs);

/**
* @param elementName
*
Expand Down Expand Up @@ -1219,4 +1227,6 @@ public interface PicketLinkLogger {
RuntimeException parserFeatureNotSupported(String feature);

ProcessingException samlAssertionWrongAudience(String serviceURL);

ProcessingException samlExtensionUnknownChild(Class<?> clazz);
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
} else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
continue;
} else
throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
+ startElement.getLocation());
Expand Down

0 comments on commit 904a5c3

Please sign in to comment.