saml signatures
This commit is contained in:
parent
4e41041469
commit
4750b22b6d
12 changed files with 430 additions and 248 deletions
|
@ -8,7 +8,6 @@ import org.picketlink.common.exceptions.ConfigurationException;
|
||||||
import org.picketlink.common.exceptions.ProcessingException;
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
import org.picketlink.common.util.DocumentUtil;
|
import org.picketlink.common.util.DocumentUtil;
|
||||||
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.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.factories.JBossSAMLAuthnResponseFactory;
|
||||||
import org.picketlink.identity.federation.core.saml.v2.holders.IDPInfoHolder;
|
import org.picketlink.identity.federation.core.saml.v2.holders.IDPInfoHolder;
|
||||||
|
@ -43,7 +42,7 @@ import static org.picketlink.common.util.StringUtil.isNotNull;
|
||||||
* @author Anil.Saldhana@redhat.com
|
* @author Anil.Saldhana@redhat.com
|
||||||
* @author bburke@redhat.com
|
* @author bburke@redhat.com
|
||||||
*/
|
*/
|
||||||
public class SAML2PostBindingResponseBuilder {
|
public class SALM2PostBindingLoginResponseBuilder extends SAML2PostBindingBuilder<SALM2PostBindingLoginResponseBuilder> {
|
||||||
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
|
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
|
||||||
|
|
||||||
protected List<String> roles = new LinkedList<String>();
|
protected List<String> roles = new LinkedList<String>();
|
||||||
|
@ -51,188 +50,68 @@ public class SAML2PostBindingResponseBuilder {
|
||||||
protected boolean multiValuedRoles;
|
protected boolean multiValuedRoles;
|
||||||
protected boolean disableAuthnStatement;
|
protected boolean disableAuthnStatement;
|
||||||
protected String requestID;
|
protected String requestID;
|
||||||
protected String responseIssuer;
|
|
||||||
protected String authMethod;
|
protected String authMethod;
|
||||||
protected String relayState;
|
|
||||||
protected String destination;
|
|
||||||
protected String requestIssuer;
|
protected String requestIssuer;
|
||||||
protected Map<String, Object> attributes = new HashMap<String, Object>();
|
protected Map<String, Object> attributes = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder attributes(Map<String, Object> attributes) {
|
public SALM2PostBindingLoginResponseBuilder attributes(Map<String, Object> attributes) {
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder attribute(String name, Object value) {
|
public SALM2PostBindingLoginResponseBuilder attribute(String name, Object value) {
|
||||||
this.attributes.put(name, value);
|
this.attributes.put(name, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder requestID(String requestID) {
|
public SALM2PostBindingLoginResponseBuilder requestID(String requestID) {
|
||||||
this.requestID =requestID;
|
this.requestID =requestID;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder requestIssuer(String requestIssuer) {
|
public SALM2PostBindingLoginResponseBuilder requestIssuer(String requestIssuer) {
|
||||||
this.requestIssuer =requestIssuer;
|
this.requestIssuer =requestIssuer;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder responseIssuer(String issuer) {
|
public SALM2PostBindingLoginResponseBuilder roles(List<String> roles) {
|
||||||
this.responseIssuer = issuer;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder roles(List<String> roles) {
|
|
||||||
this.roles = roles;
|
this.roles = roles;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder roles(String... roles) {
|
public SALM2PostBindingLoginResponseBuilder roles(String... roles) {
|
||||||
for (String role : roles) {
|
for (String role : roles) {
|
||||||
this.roles.add(role);
|
this.roles.add(role);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder authMethod(String authMethod) {
|
public SALM2PostBindingLoginResponseBuilder authMethod(String authMethod) {
|
||||||
this.authMethod = authMethod;
|
this.authMethod = authMethod;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder userPrincipal(String userPrincipal) {
|
public SALM2PostBindingLoginResponseBuilder userPrincipal(String userPrincipal) {
|
||||||
this.userPrincipal = userPrincipal;
|
this.userPrincipal = userPrincipal;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder relayState(String relayState) {
|
public SALM2PostBindingLoginResponseBuilder multiValuedRoles(boolean multiValuedRoles) {
|
||||||
this.relayState = relayState;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder destination(String destination) {
|
|
||||||
this.destination = destination;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder multiValuedRoles(boolean multiValuedRoles) {
|
|
||||||
this.multiValuedRoles = multiValuedRoles;
|
this.multiValuedRoles = multiValuedRoles;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SAML2PostBindingResponseBuilder disableAuthnStatement(boolean disableAuthnStatement) {
|
public SALM2PostBindingLoginResponseBuilder disableAuthnStatement(boolean disableAuthnStatement) {
|
||||||
this.disableAuthnStatement = disableAuthnStatement;
|
this.disableAuthnStatement = disableAuthnStatement;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response buildErrorResponse(String status) throws ConfigurationException, ProcessingException, IOException {
|
|
||||||
Document doc = getErrorResponse(status);
|
|
||||||
return buildResponse(doc);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Document getErrorResponse(String status) {
|
|
||||||
Document samlResponse = null;
|
|
||||||
ResponseType responseType = null;
|
|
||||||
|
|
||||||
SAML2Response saml2Response = new SAML2Response();
|
|
||||||
|
|
||||||
// Create a response type
|
|
||||||
String id = IDGenerator.create("ID_");
|
|
||||||
|
|
||||||
IssuerInfoHolder issuerHolder = new IssuerInfoHolder(responseIssuer);
|
|
||||||
issuerHolder.setStatusCode(status);
|
|
||||||
|
|
||||||
IDPInfoHolder idp = new IDPInfoHolder();
|
|
||||||
idp.setNameIDFormatValue(null);
|
|
||||||
idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
|
|
||||||
|
|
||||||
SPInfoHolder sp = new SPInfoHolder();
|
|
||||||
sp.setResponseDestinationURI(destination);
|
|
||||||
|
|
||||||
responseType = saml2Response.createResponseType(id);
|
|
||||||
responseType.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status));
|
|
||||||
responseType.setDestination(destination);
|
|
||||||
|
|
||||||
// Lets see how the response looks like
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
try {
|
|
||||||
saml2Response.marshall(responseType, sw);
|
|
||||||
} catch (ProcessingException e) {
|
|
||||||
logger.trace(e);
|
|
||||||
}
|
|
||||||
logger.trace("SAML Response Document: " + sw.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (supportSignature) {
|
|
||||||
try {
|
|
||||||
SAML2Signature ss = new SAML2Signature();
|
|
||||||
samlResponse = ss.sign(responseType, keyManager.getSigningKeyPair());
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace(e);
|
|
||||||
throw new RuntimeException(logger.signatureError(e));
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
try {
|
|
||||||
samlResponse = saml2Response.convert(responseType);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace(e);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return samlResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response buildLoginResponse() throws ConfigurationException, ProcessingException, IOException {
|
public Response buildLoginResponse() throws ConfigurationException, ProcessingException, IOException {
|
||||||
Document responseDoc = getResponse();
|
Document responseDoc = getResponse();
|
||||||
return buildResponse(responseDoc);
|
return buildResponse(responseDoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response buildResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
|
|
||||||
byte[] responseBytes = DocumentUtil.getDocumentAsString(responseDoc).getBytes("UTF-8");
|
|
||||||
String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes));
|
|
||||||
|
|
||||||
if (destination == null) {
|
|
||||||
throw logger.nullValueError("Destination is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
String key = GeneralConstants.SAML_RESPONSE_KEY;
|
|
||||||
builder.append("<HTML>");
|
|
||||||
builder.append("<HEAD>");
|
|
||||||
|
|
||||||
builder.append("<TITLE>HTTP Post Binding Response (Response)</TITLE>");
|
|
||||||
builder.append("</HEAD>");
|
|
||||||
builder.append("<BODY Onload=\"document.forms[0].submit()\">");
|
|
||||||
|
|
||||||
builder.append("<FORM METHOD=\"POST\" ACTION=\"" + destination + "\">");
|
|
||||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + key + "\"" + " VALUE=\"" + samlResponse + "\"/>");
|
|
||||||
|
|
||||||
if (isNotNull(relayState)) {
|
|
||||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"" + relayState + "\"/>");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append("<NOSCRIPT>");
|
|
||||||
builder.append("<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.</P>");
|
|
||||||
builder.append("<INPUT TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
|
|
||||||
builder.append("</NOSCRIPT>");
|
|
||||||
|
|
||||||
builder.append("</FORM></BODY></HTML>");
|
|
||||||
|
|
||||||
String str = builder.toString();
|
|
||||||
|
|
||||||
CacheControl cacheControl = new CacheControl();
|
|
||||||
cacheControl.setNoCache(true);
|
|
||||||
return Response.ok(str, MediaType.TEXT_HTML_TYPE)
|
|
||||||
.header("Pragma", "no-cache")
|
|
||||||
.header("Cache-Control", "no-cache, no-store").build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Document getResponse() throws ConfigurationException, ProcessingException {
|
public Document getResponse() throws ConfigurationException, ProcessingException {
|
||||||
|
|
||||||
Document samlResponseDocument = null;
|
Document samlResponseDocument = null;
|
||||||
|
@ -295,6 +174,10 @@ public class SAML2PostBindingResponseBuilder {
|
||||||
throw logger.samlAssertionMarshallError(e);
|
throw logger.samlAssertionMarshallError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (signed) {
|
||||||
|
signDocument(samlResponseDocument);
|
||||||
|
}
|
||||||
return samlResponseDocument;
|
return samlResponseDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
|
import org.picketlink.common.constants.GeneralConstants;
|
||||||
|
import org.picketlink.common.exceptions.ConfigurationException;
|
||||||
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
|
import org.picketlink.common.util.DocumentUtil;
|
||||||
|
import org.picketlink.identity.federation.web.util.PostBindingUtil;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.CacheControl;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import static org.picketlink.common.util.StringUtil.isNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class SAML2PostBindingBuilder<T extends SAML2PostBindingBuilder> {
|
||||||
|
protected KeyPair signingKeyPair;
|
||||||
|
protected X509Certificate signingCertificate;
|
||||||
|
protected boolean signed;
|
||||||
|
protected String signatureDigestMethod;
|
||||||
|
protected String signatureMethod;
|
||||||
|
protected String relayState;
|
||||||
|
protected String destination;
|
||||||
|
protected String responseIssuer;
|
||||||
|
|
||||||
|
public T sign(KeyPair keyPair) {
|
||||||
|
this.signingKeyPair = keyPair;
|
||||||
|
this.signed = true;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T sign(PrivateKey privateKey, PublicKey publicKey) {
|
||||||
|
this.signingKeyPair = new KeyPair(publicKey, privateKey);
|
||||||
|
this.signed = true;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T sign(KeyPair keyPair, X509Certificate cert) {
|
||||||
|
this.signingKeyPair = keyPair;
|
||||||
|
this.signingCertificate = cert;
|
||||||
|
this.signed = true;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T signatureDigestMethod(String method) {
|
||||||
|
this.signatureDigestMethod = method;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T signatureMethod(String method) {
|
||||||
|
this.signatureMethod = method;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T sign(PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) {
|
||||||
|
this.signingKeyPair = new KeyPair(publicKey, privateKey);
|
||||||
|
this.signingCertificate = cert;
|
||||||
|
this.signed = true;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T destination(String destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T responseIssuer(String issuer) {
|
||||||
|
this.responseIssuer = issuer;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T relayState(String relayState) {
|
||||||
|
this.relayState = relayState;
|
||||||
|
return (T)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected void signDocument(Document samlDocument) throws ProcessingException {
|
||||||
|
SamlProtocolUtils.signDocument(samlDocument, signingKeyPair, signatureMethod, signatureDigestMethod, signingCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response buildResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
|
||||||
|
byte[] responseBytes = DocumentUtil.getDocumentAsString(responseDoc).getBytes("UTF-8");
|
||||||
|
String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes));
|
||||||
|
|
||||||
|
if (destination == null) {
|
||||||
|
throw SALM2PostBindingLoginResponseBuilder.logger.nullValueError("Destination is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
String key = GeneralConstants.SAML_RESPONSE_KEY;
|
||||||
|
builder.append("<HTML>");
|
||||||
|
builder.append("<HEAD>");
|
||||||
|
|
||||||
|
builder.append("<TITLE>HTTP Post Binding Response (Response)</TITLE>");
|
||||||
|
builder.append("</HEAD>");
|
||||||
|
builder.append("<BODY Onload=\"document.forms[0].submit()\">");
|
||||||
|
|
||||||
|
builder.append("<FORM METHOD=\"POST\" ACTION=\"" + destination + "\">");
|
||||||
|
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + key + "\"" + " VALUE=\"" + samlResponse + "\"/>");
|
||||||
|
|
||||||
|
if (isNotNull(relayState)) {
|
||||||
|
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"" + relayState + "\"/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("<NOSCRIPT>");
|
||||||
|
builder.append("<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.</P>");
|
||||||
|
builder.append("<INPUT TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
|
||||||
|
builder.append("</NOSCRIPT>");
|
||||||
|
|
||||||
|
builder.append("</FORM></BODY></HTML>");
|
||||||
|
|
||||||
|
String str = builder.toString();
|
||||||
|
|
||||||
|
CacheControl cacheControl = new CacheControl();
|
||||||
|
cacheControl.setNoCache(true);
|
||||||
|
return Response.ok(str, MediaType.TEXT_HTML_TYPE)
|
||||||
|
.header("Pragma", "no-cache")
|
||||||
|
.header("Cache-Control", "no-cache, no-store").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
|
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||||
|
import org.picketlink.common.exceptions.ConfigurationException;
|
||||||
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
|
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.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.saml.v2.protocol.ResponseType;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class SAML2PostBindingErrorResponseBuilder extends SAML2PostBindingBuilder<SAML2PostBindingErrorResponseBuilder> {
|
||||||
|
|
||||||
|
public Document getErrorResponse(String status) throws ProcessingException {
|
||||||
|
Document samlResponse = null;
|
||||||
|
ResponseType responseType = null;
|
||||||
|
|
||||||
|
SAML2Response saml2Response = new SAML2Response();
|
||||||
|
|
||||||
|
// Create a response type
|
||||||
|
String id = IDGenerator.create("ID_");
|
||||||
|
|
||||||
|
IssuerInfoHolder issuerHolder = new IssuerInfoHolder(responseIssuer);
|
||||||
|
issuerHolder.setStatusCode(status);
|
||||||
|
|
||||||
|
IDPInfoHolder idp = new IDPInfoHolder();
|
||||||
|
idp.setNameIDFormatValue(null);
|
||||||
|
idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
|
||||||
|
|
||||||
|
SPInfoHolder sp = new SPInfoHolder();
|
||||||
|
sp.setResponseDestinationURI(destination);
|
||||||
|
|
||||||
|
responseType = saml2Response.createResponseType(id);
|
||||||
|
responseType.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status));
|
||||||
|
responseType.setDestination(destination);
|
||||||
|
|
||||||
|
if (signed) {
|
||||||
|
signDocument(samlResponse);
|
||||||
|
}
|
||||||
|
return samlResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response buildErrorResponse(String status) throws ConfigurationException, ProcessingException, IOException {
|
||||||
|
Document doc = getErrorResponse(status);
|
||||||
|
return buildResponse(doc);
|
||||||
|
}}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
|
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||||
|
import org.picketlink.common.exceptions.ConfigurationException;
|
||||||
|
import org.picketlink.common.exceptions.ParsingException;
|
||||||
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
|
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.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.DocumentUtil;
|
||||||
|
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
|
||||||
|
import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
|
||||||
|
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.ResponseType;
|
||||||
|
import org.picketlink.identity.federation.web.util.PostBindingUtil;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class SAML2PostBindingLogoutResponseBuilder extends SAML2PostBindingBuilder<SAML2PostBindingLogoutResponseBuilder> {
|
||||||
|
protected String userPrincipal;
|
||||||
|
|
||||||
|
public SAML2PostBindingLogoutResponseBuilder userPrincipal(String userPrincipal) {
|
||||||
|
this.userPrincipal = userPrincipal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildRequestString() {
|
||||||
|
try {
|
||||||
|
Document logoutRequestDocument = new SAML2Request().convert(createLogoutRequest());
|
||||||
|
if (signed) {
|
||||||
|
signDocument(logoutRequestDocument);
|
||||||
|
}
|
||||||
|
byte[] responseBytes = DocumentUtil.getDocumentAsString(logoutRequestDocument).getBytes("UTF-8");
|
||||||
|
return PostBindingUtil.base64Encode(new String(responseBytes));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LogoutRequestType createLogoutRequest() throws ConfigurationException {
|
||||||
|
LogoutRequestType lort = new SAML2Request().createLogoutRequest(responseIssuer);
|
||||||
|
|
||||||
|
NameIDType nameID = new NameIDType();
|
||||||
|
nameID.setValue(userPrincipal);
|
||||||
|
//Deal with NameID Format
|
||||||
|
String nameIDFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
|
||||||
|
nameID.setFormat(URI.create(nameIDFormat));
|
||||||
|
lort.setNameID(nameID);
|
||||||
|
|
||||||
|
long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
|
||||||
|
|
||||||
|
lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionValidity));
|
||||||
|
lort.setDestination(URI.create(destination));
|
||||||
|
return lort;
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,6 +43,5 @@ public class SAMLRequestParser {
|
||||||
logger.samlBase64DecodingError(e);
|
logger.samlBase64DecodingError(e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,34 +20,19 @@ import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.picketlink.common.constants.GeneralConstants;
|
import org.picketlink.common.constants.GeneralConstants;
|
||||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||||
import org.picketlink.common.exceptions.ConfigurationException;
|
|
||||||
import org.picketlink.common.exceptions.ParsingException;
|
|
||||||
import org.picketlink.common.exceptions.ProcessingException;
|
|
||||||
import org.picketlink.common.util.StringUtil;
|
|
||||||
import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
|
|
||||||
import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants;
|
import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||||
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.sts.PicketLinkCoreSTS;
|
|
||||||
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
|
||||||
import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler;
|
import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler;
|
||||||
import org.picketlink.identity.federation.web.util.PostBindingUtil;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class SamlLogin implements LoginProtocol {
|
public class SalmProtocol implements LoginProtocol {
|
||||||
protected static final Logger logger = Logger.getLogger(SamlLogin.class);
|
protected static final Logger logger = Logger.getLogger(SalmProtocol.class);
|
||||||
public static final String LOGIN_PROTOCOL = "saml";
|
public static final String LOGIN_PROTOCOL = "saml";
|
||||||
public static final String SAML_BINDING = "saml_binding";
|
public static final String SAML_BINDING = "saml_binding";
|
||||||
public static final String SAML_POST_BINDING = "post";
|
public static final String SAML_POST_BINDING = "post";
|
||||||
|
@ -61,19 +46,19 @@ public class SamlLogin implements LoginProtocol {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SamlLogin setSession(KeycloakSession session) {
|
public SalmProtocol setSession(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SamlLogin setRealm(RealmModel realm) {
|
public SalmProtocol setRealm(RealmModel realm) {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SamlLogin setUriInfo(UriInfo uriInfo) {
|
public SalmProtocol setUriInfo(UriInfo uriInfo) {
|
||||||
this.uriInfo = uriInfo;
|
this.uriInfo = uriInfo;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -93,15 +78,11 @@ public class SamlLogin implements LoginProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response getErrorResponse(ClientSessionModel clientSession, String status) {
|
protected Response getErrorResponse(ClientSessionModel clientSession, String status) {
|
||||||
String relayState = clientSession.getNote(GeneralConstants.RELAY_STATE);
|
SAML2PostBindingErrorResponseBuilder builder = new SAML2PostBindingErrorResponseBuilder()
|
||||||
String redirectUri = clientSession.getRedirectUri();
|
.relayState(clientSession.getNote(GeneralConstants.RELAY_STATE))
|
||||||
SAML2PostBindingResponseBuilder builder = new SAML2PostBindingResponseBuilder();
|
.destination(clientSession.getRedirectUri())
|
||||||
String responseIssuer = getResponseIssuer(realm);
|
.responseIssuer(getResponseIssuer(realm));
|
||||||
builder .relayState(relayState)
|
try {
|
||||||
.destination(redirectUri)
|
|
||||||
.responseIssuer(responseIssuer)
|
|
||||||
.requestIssuer(clientSession.getClient().getClientId());
|
|
||||||
try {
|
|
||||||
return builder.buildErrorResponse(status);
|
return builder.buildErrorResponse(status);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
|
||||||
|
@ -111,7 +92,7 @@ public class SamlLogin implements LoginProtocol {
|
||||||
@Override
|
@Override
|
||||||
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
|
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
|
||||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||||
if (SamlLogin.SAML_POST_BINDING.equals(clientSession.getNote(SamlLogin.SAML_BINDING))) {
|
if (SalmProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SalmProtocol.SAML_BINDING))) {
|
||||||
return postBinding(userSession, clientSession);
|
return postBinding(userSession, clientSession);
|
||||||
}
|
}
|
||||||
throw new RuntimeException("still need to implement redirect binding");
|
throw new RuntimeException("still need to implement redirect binding");
|
||||||
|
@ -123,7 +104,7 @@ public class SamlLogin implements LoginProtocol {
|
||||||
String redirectUri = clientSession.getRedirectUri();
|
String redirectUri = clientSession.getRedirectUri();
|
||||||
String responseIssuer = getResponseIssuer(realm);
|
String responseIssuer = getResponseIssuer(realm);
|
||||||
|
|
||||||
SAML2PostBindingResponseBuilder builder = new SAML2PostBindingResponseBuilder();
|
SALM2PostBindingLoginResponseBuilder builder = new SALM2PostBindingLoginResponseBuilder();
|
||||||
builder.requestID(requestID)
|
builder.requestID(requestID)
|
||||||
.relayState(relayState)
|
.relayState(relayState)
|
||||||
.destination(redirectUri)
|
.destination(redirectUri)
|
||||||
|
@ -140,7 +121,10 @@ public class SamlLogin implements LoginProtocol {
|
||||||
builder.roles(roleModel.getName());
|
builder.roles(roleModel.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ClientModel client = clientSession.getClient();
|
||||||
|
if (requiresRealmSignature(client)) {
|
||||||
|
builder.sign(realm.getPrivateKey(), realm.getPublicKey());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return builder.buildLoginResponse();
|
return builder.buildLoginResponse();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -149,7 +133,11 @@ public class SamlLogin implements LoginProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initClaims(SAML2PostBindingResponseBuilder builder, ClientModel model, UserModel user) {
|
private boolean requiresRealmSignature(ClientModel client) {
|
||||||
|
return "true".equals(client.getAttribute("samlServerSignature"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initClaims(SALM2PostBindingLoginResponseBuilder builder, ClientModel model, UserModel user) {
|
||||||
if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) {
|
if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) {
|
||||||
builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail());
|
builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail());
|
||||||
}
|
}
|
||||||
|
@ -172,15 +160,18 @@ public class SamlLogin implements LoginProtocol {
|
||||||
ApplicationModel app = (ApplicationModel)client;
|
ApplicationModel app = (ApplicationModel)client;
|
||||||
if (app.getManagementUrl() == null) return;
|
if (app.getManagementUrl() == null) return;
|
||||||
|
|
||||||
|
SAML2PostBindingLogoutResponseBuilder logoutBuilder = new SAML2PostBindingLogoutResponseBuilder()
|
||||||
|
.userPrincipal(userSession.getUser().getUsername())
|
||||||
|
.destination(client.getClientId());
|
||||||
|
if (requiresRealmSignature(client)) {
|
||||||
|
logoutBuilder.sign(realm.getPrivateKey(), realm.getPublicKey());
|
||||||
|
}
|
||||||
String logoutRequestString = null;
|
String logoutRequestString = null;
|
||||||
try {
|
try {
|
||||||
LogoutRequestType logoutRequest = createLogoutRequest(userSession.getUser(), client);
|
logoutRequestString = logoutBuilder.buildRequestString();
|
||||||
Document logoutRequestDocument = new SAML2Request().convert(logoutRequest);
|
|
||||||
|
|
||||||
byte[] responseBytes = DocumentUtil.getDocumentAsString(logoutRequestDocument).getBytes("UTF-8");
|
|
||||||
logoutRequestString = PostBindingUtil.base64Encode(new String(responseBytes));
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("failed to send saml logout", e);
|
logger.warn("failed to send saml logout", e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -219,24 +210,6 @@ public class SamlLogin implements LoginProtocol {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LogoutRequestType createLogoutRequest(UserModel user, ClientModel client) throws ConfigurationException, ProcessingException {
|
|
||||||
LogoutRequestType lort = new SAML2Request().createLogoutRequest(getResponseIssuer(realm));
|
|
||||||
|
|
||||||
NameIDType nameID = new NameIDType();
|
|
||||||
nameID.setValue(user.getUsername());
|
|
||||||
//Deal with NameID Format
|
|
||||||
String nameIDFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
|
|
||||||
nameID.setFormat(URI.create(nameIDFormat));
|
|
||||||
lort.setNameID(nameID);
|
|
||||||
|
|
||||||
long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
|
|
||||||
|
|
||||||
lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionValidity));
|
|
||||||
lort.setDestination(URI.create(client.getClientId()));
|
|
||||||
return lort;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class SamlLoginFactory implements LoginProtocolFactory {
|
public class SamlProtocolFactory implements LoginProtocolFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
|
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
|
||||||
|
@ -22,7 +22,7 @@ public class SamlLoginFactory implements LoginProtocolFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginProtocol create(KeycloakSession session) {
|
public LoginProtocol create(KeycloakSession session) {
|
||||||
return new SamlLogin().setSession(session);
|
return new SalmProtocol().setSession(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
|
import org.keycloak.VerificationException;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.util.PemUtils;
|
||||||
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
|
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class SamlProtocolUtils {
|
||||||
|
|
||||||
|
public static void verifyPostBindingSignature(ClientModel client, Document document) throws VerificationException {
|
||||||
|
if (!"true".equals(client.getAttribute("samlClientSignature"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SAML2Signature saml2Signature = new SAML2Signature();
|
||||||
|
String publicKeyPem = client.getAttribute(ClientModel.PUBLIC_KEY);
|
||||||
|
if (publicKeyPem == null) throw new VerificationException("Client does not have a public key.");
|
||||||
|
PublicKey publicKey = null;
|
||||||
|
try {
|
||||||
|
publicKey = PemUtils.decodePublicKey(publicKeyPem);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new VerificationException("Could not decode public key", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!saml2Signature.validate(document, publicKey)) {
|
||||||
|
throw new VerificationException("Invalid signature on document");
|
||||||
|
}
|
||||||
|
} catch (ProcessingException e) {
|
||||||
|
throw new VerificationException("Error validating signature", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void signDocument(Document samlDocument, KeyPair signingKeyPair, String signatureMethod, String signatureDigestMethod, X509Certificate signingCertificate) throws ProcessingException {
|
||||||
|
SAML2Signature samlSignature = new SAML2Signature();
|
||||||
|
|
||||||
|
if (signatureMethod != null) {
|
||||||
|
samlSignature.setSignatureMethod(signatureMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signatureDigestMethod != null) {
|
||||||
|
samlSignature.setDigestMethod(signatureDigestMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node nextSibling = samlSignature.getNextSiblingOfIssuer(samlDocument);
|
||||||
|
|
||||||
|
samlSignature.setNextSibling(nextSibling);
|
||||||
|
|
||||||
|
if (signingCertificate != null) {
|
||||||
|
samlSignature.setX509Certificate(signingCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
samlSignature.signSAMLDocument(samlDocument, signingKeyPair);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,13 +1,11 @@
|
||||||
package org.keycloak.protocol.saml;
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
|
||||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.jboss.resteasy.spi.HttpResponse;
|
import org.jboss.resteasy.spi.HttpResponse;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.VerificationException;
|
||||||
import org.keycloak.events.Details;
|
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
@ -27,15 +25,14 @@ import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder
|
||||||
import org.picketlink.identity.federation.saml.v2.SAML2Object;
|
import org.picketlink.identity.federation.saml.v2.SAML2Object;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
|
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
|
||||||
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.protocol.RequestAbstractType;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.FormParam;
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.Cookie;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
@ -45,8 +42,6 @@ import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import javax.ws.rs.ext.Providers;
|
import javax.ws.rs.ext.Providers;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resource class for the oauth/openid connect token service
|
* Resource class for the oauth/openid connect token service
|
||||||
|
@ -137,24 +132,7 @@ public class SamlService {
|
||||||
|
|
||||||
SAML2Object samlObject = documentHolder.getSamlObject();
|
SAML2Object samlObject = documentHolder.getSamlObject();
|
||||||
|
|
||||||
if (samlObject instanceof AuthnRequestType) {
|
RequestAbstractType requestAbstractType = (RequestAbstractType)samlObject;
|
||||||
event.event(EventType.LOGIN);
|
|
||||||
// Get the SAML Request Message
|
|
||||||
AuthnRequestType requestAbstractType = (AuthnRequestType) samlObject;
|
|
||||||
return loginRequest(relayState, requestAbstractType);
|
|
||||||
} else if (samlObject instanceof LogoutRequestType) {
|
|
||||||
event.event(EventType.LOGOUT);
|
|
||||||
LogoutRequestType requestAbstractType = (LogoutRequestType) samlObject;
|
|
||||||
return logoutRequest(relayState, requestAbstractType);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
event.event(EventType.LOGIN_ERROR);
|
|
||||||
event.error(Errors.INVALID_TOKEN);
|
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType) {
|
|
||||||
String issuer = requestAbstractType.getIssuer().getValue();
|
String issuer = requestAbstractType.getIssuer().getValue();
|
||||||
ClientModel client = realm.findClient(issuer);
|
ClientModel client = realm.findClient(issuer);
|
||||||
|
|
||||||
|
@ -176,6 +154,32 @@ public class SamlService {
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SamlProtocolUtils.verifyPostBindingSignature(client, documentHolder.getSamlDocument());
|
||||||
|
} catch (VerificationException e) {
|
||||||
|
logger.error("request validation failed", e);
|
||||||
|
event.error(Errors.INVALID_CLIENT);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid requester.");
|
||||||
|
}
|
||||||
|
if (samlObject instanceof AuthnRequestType) {
|
||||||
|
event.event(EventType.LOGIN);
|
||||||
|
// Get the SAML Request Message
|
||||||
|
AuthnRequestType authn = (AuthnRequestType) samlObject;
|
||||||
|
return loginRequest(relayState, authn, client);
|
||||||
|
} else if (samlObject instanceof LogoutRequestType) {
|
||||||
|
event.event(EventType.LOGOUT);
|
||||||
|
LogoutRequestType logout = (LogoutRequestType) samlObject;
|
||||||
|
return logoutRequest(logout, client);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
event.event(EventType.LOGIN_ERROR);
|
||||||
|
event.error(Errors.INVALID_TOKEN);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
|
||||||
|
|
||||||
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
|
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
|
||||||
String redirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
|
String redirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
|
||||||
|
|
||||||
|
@ -186,10 +190,10 @@ public class SamlService {
|
||||||
|
|
||||||
|
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||||
clientSession.setAuthMethod(SamlLogin.LOGIN_PROTOCOL);
|
clientSession.setAuthMethod(SalmProtocol.LOGIN_PROTOCOL);
|
||||||
clientSession.setRedirectUri(redirect);
|
clientSession.setRedirectUri(redirect);
|
||||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||||
clientSession.setNote(SamlLogin.SAML_BINDING, SamlLogin.SAML_POST_BINDING);
|
clientSession.setNote(SalmProtocol.SAML_BINDING, SalmProtocol.SAML_POST_BINDING);
|
||||||
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
||||||
clientSession.setNote("REQUEST_ID", requestAbstractType.getID());
|
clientSession.setNote("REQUEST_ID", requestAbstractType.getID());
|
||||||
|
|
||||||
|
@ -212,28 +216,7 @@ public class SamlService {
|
||||||
return forms.createLogin();
|
return forms.createLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response logoutRequest(String relayState, LogoutRequestType requestAbstractType) {
|
protected Response logoutRequest(LogoutRequestType requestAbstractType, ClientModel client) {
|
||||||
String issuer = requestAbstractType.getIssuer().getValue();
|
|
||||||
ClientModel client = realm.findClient(issuer);
|
|
||||||
|
|
||||||
if (client == null) {
|
|
||||||
event.error(Errors.CLIENT_NOT_FOUND);
|
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!client.isEnabled()) {
|
|
||||||
event.error(Errors.CLIENT_DISABLED);
|
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
|
||||||
}
|
|
||||||
if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
|
|
||||||
event.error(Errors.NOT_ALLOWED);
|
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login");
|
|
||||||
}
|
|
||||||
if (client.isDirectGrantsOnly()) {
|
|
||||||
event.error(Errors.NOT_ALLOWED);
|
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
|
|
||||||
}
|
|
||||||
|
|
||||||
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
|
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
|
||||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
|
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
|
||||||
if (authResult != null) {
|
if (authResult != null) {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
org.keycloak.protocol.saml.SamlLoginFactory
|
org.keycloak.protocol.saml.SamlProtocolFactory
|
|
@ -157,11 +157,11 @@ public class AccountTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
@Test
|
||||||
// @Ignore
|
@Ignore
|
||||||
// public void runit() throws Exception {
|
public void runit() throws Exception {
|
||||||
// Thread.sleep(10000000);
|
Thread.sleep(10000000);
|
||||||
// }
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void returnToAppFromQueryParam() {
|
public void returnToAppFromQueryParam() {
|
||||||
|
|
|
@ -32,12 +32,31 @@
|
||||||
"name": "http://localhost:8080/sales-post/",
|
"name": "http://localhost:8080/sales-post/",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
|
"protocol": "saml",
|
||||||
"baseUrl": "http://localhost:8080/sales-post",
|
"baseUrl": "http://localhost:8080/sales-post",
|
||||||
"adminUrl": "http://localhost:8080/sales-post",
|
"adminUrl": "http://localhost:8080/sales-post",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8080/sales-post/*"
|
"http://localhost:8080/sales-post/*"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"name": "http://localhost:8080/sales-post-sig/",
|
||||||
|
"enabled": true,
|
||||||
|
"protocol": "saml",
|
||||||
|
"fullScopeAllowed": true,
|
||||||
|
"baseUrl": "http://localhost:8080/sales-post-sig",
|
||||||
|
"adminUrl": "http://localhost:8080/sales-post-sig",
|
||||||
|
"redirectUris": [
|
||||||
|
"http://localhost:8080/sales-post-sig/*"
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"samlServerSignature": "true",
|
||||||
|
"samlClientSignature": "true",
|
||||||
|
"privateKey": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
|
||||||
|
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQAB",
|
||||||
|
"X509Certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"roles" : {
|
"roles" : {
|
||||||
"realm" : [
|
"realm" : [
|
||||||
|
|
Loading…
Reference in a new issue