[KEYCLOAK-883] - More SAML configuration. Using SAML builders to create AuthnRequest.
This commit is contained in:
parent
da240b2b36
commit
b9a7594113
14 changed files with 283 additions and 71 deletions
|
@ -54,4 +54,7 @@ public class AuthenticationResponse {
|
||||||
return new AuthenticationResponse(Response.temporaryRedirect(url).build());
|
return new AuthenticationResponse(Response.temporaryRedirect(url).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static AuthenticationResponse fromResponse(Response response) {
|
||||||
|
return new AuthenticationResponse(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,12 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-saml-protocol</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.picketlink</groupId>
|
<groupId>org.picketlink</groupId>
|
||||||
<artifactId>picketlink-federation</artifactId>
|
<artifactId>picketlink-federation</artifactId>
|
||||||
|
|
|
@ -22,6 +22,8 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||||
import org.keycloak.broker.provider.AuthenticationResponse;
|
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||||
import org.keycloak.broker.provider.FederatedIdentity;
|
import org.keycloak.broker.provider.FederatedIdentity;
|
||||||
|
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
|
||||||
|
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
|
||||||
import org.picketlink.common.constants.JBossSAMLConstants;
|
import org.picketlink.common.constants.JBossSAMLConstants;
|
||||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||||
import org.picketlink.common.exceptions.ProcessingException;
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
|
@ -31,7 +33,6 @@ import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
|
||||||
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
||||||
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
|
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
|
||||||
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
|
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
|
||||||
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
|
|
||||||
import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
|
import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
|
||||||
import org.picketlink.identity.federation.core.util.JAXPValidationUtil;
|
import org.picketlink.identity.federation.core.util.JAXPValidationUtil;
|
||||||
import org.picketlink.identity.federation.core.util.XMLEncryptionUtil;
|
import org.picketlink.identity.federation.core.util.XMLEncryptionUtil;
|
||||||
|
@ -41,7 +42,6 @@ import org.picketlink.identity.federation.saml.v2.assertion.EncryptedAssertionTy
|
||||||
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
||||||
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType;
|
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType;
|
||||||
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType.STSubType;
|
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType.STSubType;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
|
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
|
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType.RTChoiceType;
|
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType.RTChoiceType;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.StatusCodeType;
|
import org.picketlink.identity.federation.saml.v2.protocol.StatusCodeType;
|
||||||
|
@ -53,10 +53,10 @@ import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
@ -85,22 +85,26 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
UriInfo uriInfo = request.getUriInfo();
|
UriInfo uriInfo = request.getUriInfo();
|
||||||
String issuerURL = UriBuilder.fromUri(uriInfo.getBaseUri()).build().toString();
|
String issuerURL = UriBuilder.fromUri(uriInfo.getBaseUri()).build().toString();
|
||||||
String destinationUrl = getConfig().getSingleSignOnServiceUrl();
|
String destinationUrl = getConfig().getSingleSignOnServiceUrl();
|
||||||
SAML2Request samlRequest = new SAML2Request();
|
|
||||||
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
||||||
|
|
||||||
if (nameIDPolicyFormat == null) {
|
if (nameIDPolicyFormat == null) {
|
||||||
nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
|
nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
samlRequest.setNameIDFormat(nameIDPolicyFormat);
|
String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
|
||||||
|
|
||||||
AuthnRequestType authn = samlRequest
|
if (getConfig().isPostBindingResponse()) {
|
||||||
.createAuthnRequestType(IDGenerator.create("ID_"), request.getRedirectUri(), destinationUrl, issuerURL);
|
protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
||||||
|
}
|
||||||
|
|
||||||
authn.setProtocolBinding(URI.create(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()));
|
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
|
||||||
authn.setForceAuthn(getConfig().isForceAuthn());
|
.assertionConsumerUrl(request.getRedirectUri())
|
||||||
|
.destination(destinationUrl)
|
||||||
Document authnDoc = samlRequest.convert(authn);
|
.issuer(issuerURL)
|
||||||
|
.forceAuthn(getConfig().isForceAuthn())
|
||||||
|
.protocolBinding(protocolBinding)
|
||||||
|
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat))
|
||||||
|
.relayState(request.getState());
|
||||||
|
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
PrivateKey privateKey = request.getRealm().getPrivateKey();
|
PrivateKey privateKey = request.getRealm().getPrivateKey();
|
||||||
|
@ -116,16 +120,14 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
|
|
||||||
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
||||||
|
|
||||||
this.saml2Signature.signSAMLDocument(authnDoc, keypair);
|
authnRequestBuilder.signWith(keypair);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] responseBytes = DocumentUtil.getDocumentAsString(authnDoc).getBytes("UTF-8");
|
if (getConfig().isPostBindingAuthnRequest()) {
|
||||||
String urlEncodedResponse = RedirectBindingUtil.deflateBase64URLEncode(responseBytes);
|
return AuthenticationResponse.fromResponse(authnRequestBuilder.postBinding().request());
|
||||||
URI redirectUri = UriBuilder.fromPath(destinationUrl)
|
} else {
|
||||||
.queryParam(SAML_REQUEST_PARAMETER, urlEncodedResponse)
|
return AuthenticationResponse.fromResponse(authnRequestBuilder.redirectBinding().request());
|
||||||
.queryParam(RELAY_STATE_PARAMETER, request.getState()).build();
|
}
|
||||||
|
|
||||||
return AuthenticationResponse.temporaryRedirect(redirectUri);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Could not create authentication request.", e);
|
throw new RuntimeException("Could not create authentication request.", e);
|
||||||
}
|
}
|
||||||
|
@ -133,44 +135,48 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRelayState(AuthenticationRequest request) {
|
public String getRelayState(AuthenticationRequest request) {
|
||||||
HttpRequest httpRequest = request.getHttpRequest();
|
return getRequestParameter(request, RELAY_STATE_PARAMETER);
|
||||||
return httpRequest.getFormParameters().getFirst(RELAY_STATE_PARAMETER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationResponse handleResponse(AuthenticationRequest request) {
|
public AuthenticationResponse handleResponse(AuthenticationRequest request) {
|
||||||
HttpRequest httpRequest = request.getHttpRequest();
|
|
||||||
String samlResponse = httpRequest.getFormParameters().getFirst(SAML_RESPONSE_PARAMETER);
|
|
||||||
|
|
||||||
if (samlResponse == null) {
|
|
||||||
throw new RuntimeException("No response from SAML identity provider.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SAML2Request saml2Request = new SAML2Request();
|
AssertionType assertion = getAssertion(request);
|
||||||
ResponseType responseType = (ResponseType) saml2Request
|
|
||||||
.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(samlResponse, "UTF-8")));
|
|
||||||
AssertionType assertion = getAssertion(request, saml2Request, responseType);
|
|
||||||
|
|
||||||
SubjectType subject = assertion.getSubject();
|
SubjectType subject = assertion.getSubject();
|
||||||
STSubType subType = subject.getSubType();
|
STSubType subType = subject.getSubType();
|
||||||
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||||
|
FederatedIdentity identity = new FederatedIdentity(subjectNameID.getValue());
|
||||||
|
|
||||||
FederatedIdentity user = new FederatedIdentity(subjectNameID.getValue());
|
identity.setUsername(subjectNameID.getValue());
|
||||||
|
|
||||||
user.setUsername(subjectNameID.getValue());
|
|
||||||
|
|
||||||
if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
||||||
user.setEmail(subjectNameID.getValue());
|
identity.setEmail(subjectNameID.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return AuthenticationResponse.end(user);
|
return AuthenticationResponse.end(identity);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Could not process response from SAML identity provider.", e);
|
throw new RuntimeException("Could not process response from SAML identity provider.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AssertionType getAssertion(AuthenticationRequest request, SAML2Request saml2Request, ResponseType responseType) throws ProcessingException {
|
private AssertionType getAssertion(AuthenticationRequest request) throws Exception {
|
||||||
|
String samlResponse = getRequestParameter(request, SAML_RESPONSE_PARAMETER);
|
||||||
|
|
||||||
|
if (samlResponse == null) {
|
||||||
|
throw new RuntimeException("No response from SAML identity provider.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAML2Request saml2Request = new SAML2Request();
|
||||||
|
ResponseType responseType;
|
||||||
|
|
||||||
|
if (getConfig().isPostBindingResponse()) {
|
||||||
|
responseType = (ResponseType) saml2Request
|
||||||
|
.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(samlResponse, "UTF-8")));
|
||||||
|
} else {
|
||||||
|
responseType = (ResponseType) saml2Request
|
||||||
|
.getSAML2ObjectFromStream(RedirectBindingUtil.base64DeflateDecode((samlResponse)));
|
||||||
|
}
|
||||||
|
|
||||||
validateStatusResponse(responseType);
|
validateStatusResponse(responseType);
|
||||||
validateSignature(saml2Request);
|
validateSignature(saml2Request);
|
||||||
|
|
||||||
|
@ -252,4 +258,17 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getRequestParameter(AuthenticationRequest request, String parameterName) {
|
||||||
|
MultivaluedMap<String, String> requestParameters;
|
||||||
|
|
||||||
|
if (getConfig().isPostBindingResponse()) {
|
||||||
|
HttpRequest httpRequest = request.getHttpRequest();
|
||||||
|
requestParameters = httpRequest.getFormParameters();
|
||||||
|
} else {
|
||||||
|
UriInfo uriInfo = request.getUriInfo();
|
||||||
|
requestParameters = uriInfo.getQueryParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestParameters.getFirst(parameterName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,4 +89,20 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||||
public void setEncryptionPublicKey(String encryptionPublicKey) {
|
public void setEncryptionPublicKey(String encryptionPublicKey) {
|
||||||
getConfig().put("encryptionPublicKey", encryptionPublicKey);
|
getConfig().put("encryptionPublicKey", encryptionPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPostBindingAuthnRequest() {
|
||||||
|
return Boolean.valueOf(getConfig().get("postBindingAuthnRequest"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostBindingAuthnRequest(boolean postBindingAuthnRequest) {
|
||||||
|
getConfig().put("postBindingAuthnRequest", String.valueOf(postBindingAuthnRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPostBindingResponse() {
|
||||||
|
return Boolean.valueOf(getConfig().get("postBindingResponse"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostBindingResponse(boolean postBindingResponse) {
|
||||||
|
getConfig().put("postBindingResponse", String.valueOf(postBindingResponse));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,20 @@
|
||||||
</div>
|
</div>
|
||||||
<span tooltip-placement="right" tooltip="Enable/disable signature validation of SAML responses." class="fa fa-info-circle"></span>
|
<span tooltip-placement="right" tooltip="Enable/disable signature validation of SAML responses." class="fa fa-info-circle"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label" for="postBindingResponse">HTTP-POST Binding Response</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input ng-model="identityProvider.config.postBindingResponse" id="postBindingResponse" onoffswitch />
|
||||||
|
</div>
|
||||||
|
<span tooltip-placement="right" tooltip="Indicates whether the identity provider must respond to the AuthnRequest using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used." class="fa fa-info-circle"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label" for="postBindingAuthnRequest">HTTP-POST Binding for AuthnRequest</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input ng-model="identityProvider.config.postBindingAuthnRequest" id="postBindingAuthnRequest" onoffswitch />
|
||||||
|
</div>
|
||||||
|
<span tooltip-placement="right" tooltip="Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used." class="fa fa-info-circle"></span>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
|
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
|
|
|
@ -127,7 +127,7 @@ public class SALM2LoginResponseBuilder extends SAML2BindingBuilder<SALM2LoginRes
|
||||||
// Create a response type
|
// Create a response type
|
||||||
String id = IDGenerator.create("ID_");
|
String id = IDGenerator.create("ID_");
|
||||||
|
|
||||||
IssuerInfoHolder issuerHolder = new IssuerInfoHolder(responseIssuer);
|
IssuerInfoHolder issuerHolder = new IssuerInfoHolder(issuer);
|
||||||
issuerHolder.setStatusCode(JBossSAMLURIConstants.STATUS_SUCCESS.get());
|
issuerHolder.setStatusCode(JBossSAMLURIConstants.STATUS_SUCCESS.get());
|
||||||
|
|
||||||
IDPInfoHolder idp = new IDPInfoHolder();
|
IDPInfoHolder idp = new IDPInfoHolder();
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source
|
||||||
|
*
|
||||||
|
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||||
|
*
|
||||||
|
* 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.protocol.saml;
|
||||||
|
|
||||||
|
import org.picketlink.common.exceptions.ConfigurationException;
|
||||||
|
import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
|
||||||
|
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
|
||||||
|
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author pedroigor
|
||||||
|
*/
|
||||||
|
public class SAML2AuthnRequestBuilder extends SAML2BindingBuilder<SAML2AuthnRequestBuilder> {
|
||||||
|
|
||||||
|
private final AuthnRequestType authnRequestType;
|
||||||
|
|
||||||
|
public SAML2AuthnRequestBuilder() {
|
||||||
|
try {
|
||||||
|
this.authnRequestType = new AuthnRequestType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
|
||||||
|
} catch (ConfigurationException e) {
|
||||||
|
throw new RuntimeException("Could not create SAML AuthnRequest builder.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SAML2AuthnRequestBuilder assertionConsumerUrl(String assertionConsumerUrl) {
|
||||||
|
this.authnRequestType.setAssertionConsumerServiceURL(URI.create(assertionConsumerUrl));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SAML2AuthnRequestBuilder forceAuthn(boolean forceAuthn) {
|
||||||
|
this.authnRequestType.setForceAuthn(forceAuthn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SAML2AuthnRequestBuilder nameIdPolicy(SAML2NameIDPolicyBuilder nameIDPolicy) {
|
||||||
|
this.authnRequestType.setNameIDPolicy(nameIDPolicy.build());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SAML2AuthnRequestBuilder protocolBinding(String protocolBinding) {
|
||||||
|
this.authnRequestType.setProtocolBinding(URI.create(protocolBinding));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedirectBindingBuilder redirectBinding() {
|
||||||
|
try {
|
||||||
|
return new RedirectBindingBuilder(toDocument());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not build authn request for post binding.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostBindingBuilder postBinding() {
|
||||||
|
try {
|
||||||
|
return new PostBindingBuilder(toDocument());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not build authn request for post binding.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Document toDocument() {
|
||||||
|
try {
|
||||||
|
AuthnRequestType authnRequestType = this.authnRequestType;
|
||||||
|
|
||||||
|
NameIDType nameIDType = new NameIDType();
|
||||||
|
|
||||||
|
nameIDType.setValue(this.issuer);
|
||||||
|
|
||||||
|
authnRequestType.setIssuer(nameIDType);
|
||||||
|
|
||||||
|
authnRequestType.setDestination(URI.create(this.destination));
|
||||||
|
|
||||||
|
return new SAML2Request().convert(authnRequestType);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,7 +44,7 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||||
protected SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSA_SHA1;
|
protected SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSA_SHA1;
|
||||||
protected String relayState;
|
protected String relayState;
|
||||||
protected String destination;
|
protected String destination;
|
||||||
protected String responseIssuer;
|
protected String issuer;
|
||||||
protected int encryptionKeySize = 128;
|
protected int encryptionKeySize = 128;
|
||||||
protected PublicKey encryptionPublicKey;
|
protected PublicKey encryptionPublicKey;
|
||||||
protected String encryptionAlgorithm = "AES";
|
protected String encryptionAlgorithm = "AES";
|
||||||
|
@ -108,8 +108,8 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T responseIssuer(String issuer) {
|
public T issuer(String issuer) {
|
||||||
this.responseIssuer = issuer;
|
this.issuer = issuer;
|
||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,14 +140,17 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String htmlResponse() throws ProcessingException, ConfigurationException, IOException {
|
public String htmlResponse() throws ProcessingException, ConfigurationException, IOException {
|
||||||
return buildHtml(encoded(), destination);
|
return buildHtml(encoded(), destination, false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
public Response request() throws ConfigurationException, ProcessingException, IOException {
|
||||||
|
return buildResponse(document, destination, true);
|
||||||
|
}
|
||||||
public Response response() throws ConfigurationException, ProcessingException, IOException {
|
public Response response() throws ConfigurationException, ProcessingException, IOException {
|
||||||
return buildResponse(document, destination);
|
return buildResponse(document, destination, false);
|
||||||
}
|
}
|
||||||
public Response response(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
|
public Response response(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
|
||||||
return buildResponse(document, actionUrl);
|
return buildResponse(document, actionUrl, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,15 +168,28 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||||
public Document getDocument() {
|
public Document getDocument() {
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
public URI responseUri(String redirectUri) throws ConfigurationException, ProcessingException, IOException {
|
public URI responseUri(String redirectUri, boolean asRequest) throws ConfigurationException, ProcessingException, IOException {
|
||||||
return generateRedirectUri("SAMLResponse", redirectUri, document);
|
String samlParameterName = GeneralConstants.SAML_RESPONSE_KEY;
|
||||||
|
|
||||||
|
if (asRequest) {
|
||||||
|
samlParameterName = GeneralConstants.SAML_REQUEST_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateRedirectUri(samlParameterName, redirectUri, document);
|
||||||
}
|
}
|
||||||
public Response response() throws ProcessingException, ConfigurationException, IOException {
|
public Response response() throws ProcessingException, ConfigurationException, IOException {
|
||||||
return response(destination);
|
return response(destination, false);
|
||||||
|
}
|
||||||
|
public Response response(String redirectUri) throws ProcessingException, ConfigurationException, IOException {
|
||||||
|
return response(destination, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response response(String redirectUri) throws ProcessingException, ConfigurationException, IOException {
|
public Response request() throws ProcessingException, ConfigurationException, IOException {
|
||||||
URI uri = responseUri(redirectUri);
|
return response(destination, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response response(String redirectUri, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
|
||||||
|
URI uri = responseUri(redirectUri, asRequest);
|
||||||
|
|
||||||
CacheControl cacheControl = new CacheControl();
|
CacheControl cacheControl = new CacheControl();
|
||||||
cacheControl.setNoCache(true);
|
cacheControl.setNoCache(true);
|
||||||
|
@ -266,8 +282,8 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Response buildResponse(Document responseDoc, String actionUrl) throws ProcessingException, ConfigurationException, IOException {
|
protected Response buildResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
|
||||||
String str = buildHtmlPostResponse(responseDoc, actionUrl);
|
String str = buildHtmlPostResponse(responseDoc, actionUrl, asRequest);
|
||||||
|
|
||||||
CacheControl cacheControl = new CacheControl();
|
CacheControl cacheControl = new CacheControl();
|
||||||
cacheControl.setNoCache(true);
|
cacheControl.setNoCache(true);
|
||||||
|
@ -276,14 +292,14 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||||
.header("Cache-Control", "no-cache, no-store").build();
|
.header("Cache-Control", "no-cache, no-store").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String buildHtmlPostResponse(Document responseDoc, String actionUrl) throws ProcessingException, ConfigurationException, IOException {
|
protected String buildHtmlPostResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
|
||||||
byte[] responseBytes = DocumentUtil.getDocumentAsString(responseDoc).getBytes("UTF-8");
|
byte[] responseBytes = DocumentUtil.getDocumentAsString(responseDoc).getBytes("UTF-8");
|
||||||
String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes));
|
String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes));
|
||||||
|
|
||||||
return buildHtml(samlResponse, actionUrl);
|
return buildHtml(samlResponse, actionUrl, asRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String buildHtml(String samlResponse, String actionUrl) {
|
protected String buildHtml(String samlResponse, String actionUrl, boolean asRequest) {
|
||||||
if (destination == null) {
|
if (destination == null) {
|
||||||
throw SALM2LoginResponseBuilder.logger.nullValueError("Destination is null");
|
throw SALM2LoginResponseBuilder.logger.nullValueError("Destination is null");
|
||||||
}
|
}
|
||||||
|
@ -291,6 +307,11 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
String key = GeneralConstants.SAML_RESPONSE_KEY;
|
String key = GeneralConstants.SAML_RESPONSE_KEY;
|
||||||
|
|
||||||
|
if (asRequest) {
|
||||||
|
key = GeneralConstants.SAML_REQUEST_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
builder.append("<HTML>");
|
builder.append("<HTML>");
|
||||||
builder.append("<HEAD>");
|
builder.append("<HEAD>");
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class SAML2ErrorResponseBuilder extends SAML2BindingBuilder<SAML2ErrorRes
|
||||||
// Create a response type
|
// Create a response type
|
||||||
String id = IDGenerator.create("ID_");
|
String id = IDGenerator.create("ID_");
|
||||||
|
|
||||||
IssuerInfoHolder issuerHolder = new IssuerInfoHolder(responseIssuer);
|
IssuerInfoHolder issuerHolder = new IssuerInfoHolder(issuer);
|
||||||
issuerHolder.setStatusCode(status);
|
issuerHolder.setStatusCode(status);
|
||||||
|
|
||||||
IDPInfoHolder idp = new IDPInfoHolder();
|
IDPInfoHolder idp = new IDPInfoHolder();
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
package org.keycloak.protocol.saml;
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
|
||||||
import org.picketlink.common.exceptions.ConfigurationException;
|
import org.picketlink.common.exceptions.ConfigurationException;
|
||||||
import org.picketlink.common.exceptions.ParsingException;
|
import org.picketlink.common.exceptions.ParsingException;
|
||||||
import org.picketlink.common.exceptions.ProcessingException;
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
|
import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
|
||||||
import org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil;
|
|
||||||
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
|
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
|
||||||
import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
|
import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
|
||||||
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
||||||
import org.picketlink.identity.federation.web.util.PostBindingUtil;
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +44,7 @@ public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRe
|
||||||
}
|
}
|
||||||
|
|
||||||
private LogoutRequestType createLogoutRequest() throws ConfigurationException {
|
private LogoutRequestType createLogoutRequest() throws ConfigurationException {
|
||||||
LogoutRequestType lort = new SAML2Request().createLogoutRequest(responseIssuer);
|
LogoutRequestType lort = new SAML2Request().createLogoutRequest(issuer);
|
||||||
|
|
||||||
NameIDType nameID = new NameIDType();
|
NameIDType nameID = new NameIDType();
|
||||||
nameID.setValue(userPrincipal);
|
nameID.setValue(userPrincipal);
|
||||||
|
|
|
@ -6,13 +6,8 @@ import org.picketlink.common.exceptions.ParsingException;
|
||||||
import org.picketlink.common.exceptions.ProcessingException;
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
||||||
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
|
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
|
||||||
import org.picketlink.identity.federation.core.saml.v2.factories.JBossSAMLAuthnResponseFactory;
|
|
||||||
import org.picketlink.identity.federation.core.saml.v2.holders.IDPInfoHolder;
|
|
||||||
import org.picketlink.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
|
|
||||||
import org.picketlink.identity.federation.core.saml.v2.holders.SPInfoHolder;
|
|
||||||
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
|
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
|
||||||
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
|
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.StatusCodeType;
|
import org.picketlink.identity.federation.saml.v2.protocol.StatusCodeType;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
|
import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.StatusType;
|
import org.picketlink.identity.federation.saml.v2.protocol.StatusType;
|
||||||
|
@ -60,7 +55,7 @@ public class SAML2LogoutResponseBuilder extends SAML2BindingBuilder<SAML2LogoutR
|
||||||
statusResponse.setStatus(statusType);
|
statusResponse.setStatus(statusType);
|
||||||
statusResponse.setInResponseTo(logoutRequestID);
|
statusResponse.setInResponseTo(logoutRequestID);
|
||||||
NameIDType issuer = new NameIDType();
|
NameIDType issuer = new NameIDType();
|
||||||
issuer.setValue(responseIssuer);
|
issuer.setValue(this.issuer);
|
||||||
|
|
||||||
statusResponse.setIssuer(issuer);
|
statusResponse.setIssuer(issuer);
|
||||||
statusResponse.setDestination(destination);
|
statusResponse.setDestination(destination);
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source
|
||||||
|
*
|
||||||
|
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||||
|
*
|
||||||
|
* 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.protocol.saml;
|
||||||
|
|
||||||
|
import org.picketlink.identity.federation.saml.v2.protocol.NameIDPolicyType;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author pedroigor
|
||||||
|
*/
|
||||||
|
public class SAML2NameIDPolicyBuilder {
|
||||||
|
|
||||||
|
private final NameIDPolicyType policyType;
|
||||||
|
|
||||||
|
private SAML2NameIDPolicyBuilder(String format) {
|
||||||
|
this.policyType = new NameIDPolicyType();
|
||||||
|
this.policyType.setFormat(URI.create(format));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SAML2NameIDPolicyBuilder format(String format) {
|
||||||
|
return new SAML2NameIDPolicyBuilder(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NameIDPolicyType build() {
|
||||||
|
this.policyType.setAllowCreate(Boolean.TRUE);
|
||||||
|
return this.policyType;
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,7 +114,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
|
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
|
||||||
.relayState(clientSession.getNote(GeneralConstants.RELAY_STATE))
|
.relayState(clientSession.getNote(GeneralConstants.RELAY_STATE))
|
||||||
.destination(clientSession.getRedirectUri())
|
.destination(clientSession.getRedirectUri())
|
||||||
.responseIssuer(getResponseIssuer(realm))
|
.issuer(getResponseIssuer(realm))
|
||||||
.status(status);
|
.status(status);
|
||||||
try {
|
try {
|
||||||
if (isPostBinding(clientSession)) {
|
if (isPostBinding(clientSession)) {
|
||||||
|
@ -191,7 +191,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
builder.requestID(requestID)
|
builder.requestID(requestID)
|
||||||
.relayState(relayState)
|
.relayState(relayState)
|
||||||
.destination(redirectUri)
|
.destination(redirectUri)
|
||||||
.responseIssuer(responseIssuer)
|
.issuer(responseIssuer)
|
||||||
.requestIssuer(clientSession.getClient().getClientId())
|
.requestIssuer(clientSession.getClient().getClientId())
|
||||||
.nameIdentifier(nameIdFormat, nameId)
|
.nameIdentifier(nameIdFormat, nameId)
|
||||||
.authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
|
.authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
|
||||||
|
|
|
@ -156,7 +156,7 @@ public class AuthenticationBrokerResource {
|
||||||
String relayState = provider.getRelayState(createAuthenticationRequest(providerId, null, realm, null));
|
String relayState = provider.getRelayState(createAuthenticationRequest(providerId, null, realm, null));
|
||||||
|
|
||||||
if (relayState == null) {
|
if (relayState == null) {
|
||||||
return redirectToErrorPage(realm, "No authorization code provided.");
|
return redirectToErrorPage(realm, "No relay state from identity provider.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientSessionCode clientCode = isValidAuthorizationCode(relayState, realm);
|
ClientSessionCode clientCode = isValidAuthorizationCode(relayState, realm);
|
||||||
|
|
Loading…
Reference in a new issue