Merge pull request #775 from patriot1burke/master
saml redirect binding
This commit is contained in:
commit
8b05e2ebf8
12 changed files with 976 additions and 726 deletions
|
@ -24,6 +24,7 @@ public interface Errors {
|
|||
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
|
||||
String INVALID_CODE = "invalid_code";
|
||||
String INVALID_TOKEN = "invalid_token";
|
||||
String INVALID_SIGNATURE = "invalid_signature";
|
||||
String INVALID_REGISTRATION = "invalid_registration";
|
||||
String INVALID_FORM = "invalid_form";
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ public enum EventType {
|
|||
SEND_RESET_PASSWORD_ERROR,
|
||||
SOCIAL_LOGIN,
|
||||
SOCIAL_LOGIN_ERROR,
|
||||
|
||||
INVALID_SIGNATURE_ERROR,
|
||||
REGISTER_NODE,
|
||||
UNREGISTER_NODE
|
||||
}
|
||||
|
|
|
@ -2,14 +2,12 @@ package org.keycloak.protocol.saml;
|
|||
|
||||
import org.picketlink.common.PicketLinkLogger;
|
||||
import org.picketlink.common.PicketLinkLoggerFactory;
|
||||
import org.picketlink.common.constants.GeneralConstants;
|
||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||
import org.picketlink.common.exceptions.ConfigurationException;
|
||||
import org.picketlink.common.exceptions.ProcessingException;
|
||||
import org.picketlink.common.util.DocumentUtil;
|
||||
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;
|
||||
|
@ -19,14 +17,8 @@ import org.picketlink.identity.federation.saml.v2.assertion.AssertionType;
|
|||
import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
|
||||
import org.picketlink.identity.federation.saml.v2.assertion.AuthnStatementType;
|
||||
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.CacheControl;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -42,7 +34,7 @@ import static org.picketlink.common.util.StringUtil.isNotNull;
|
|||
* @author Anil.Saldhana@redhat.com
|
||||
* @author bburke@redhat.com
|
||||
*/
|
||||
public class SALM2PostBindingLoginResponseBuilder extends SAML2PostBindingBuilder<SALM2PostBindingLoginResponseBuilder> {
|
||||
public class SALM2LoginResponseBuilder extends SAML2BindingBuilder<SALM2LoginResponseBuilder> {
|
||||
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
|
||||
|
||||
protected List<String> roles = new LinkedList<String>();
|
||||
|
@ -55,65 +47,66 @@ public class SALM2PostBindingLoginResponseBuilder extends SAML2PostBindingBuilde
|
|||
protected Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder attributes(Map<String, Object> attributes) {
|
||||
public SALM2LoginResponseBuilder attributes(Map<String, Object> attributes) {
|
||||
this.attributes = attributes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder attribute(String name, Object value) {
|
||||
public SALM2LoginResponseBuilder attribute(String name, Object value) {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder requestID(String requestID) {
|
||||
public SALM2LoginResponseBuilder requestID(String requestID) {
|
||||
this.requestID =requestID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder requestIssuer(String requestIssuer) {
|
||||
public SALM2LoginResponseBuilder requestIssuer(String requestIssuer) {
|
||||
this.requestIssuer =requestIssuer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder roles(List<String> roles) {
|
||||
public SALM2LoginResponseBuilder roles(List<String> roles) {
|
||||
this.roles = roles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder roles(String... roles) {
|
||||
public SALM2LoginResponseBuilder roles(String... roles) {
|
||||
for (String role : roles) {
|
||||
this.roles.add(role);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder authMethod(String authMethod) {
|
||||
public SALM2LoginResponseBuilder authMethod(String authMethod) {
|
||||
this.authMethod = authMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder userPrincipal(String userPrincipal) {
|
||||
public SALM2LoginResponseBuilder userPrincipal(String userPrincipal) {
|
||||
this.userPrincipal = userPrincipal;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder multiValuedRoles(boolean multiValuedRoles) {
|
||||
public SALM2LoginResponseBuilder multiValuedRoles(boolean multiValuedRoles) {
|
||||
this.multiValuedRoles = multiValuedRoles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SALM2PostBindingLoginResponseBuilder disableAuthnStatement(boolean disableAuthnStatement) {
|
||||
public SALM2LoginResponseBuilder disableAuthnStatement(boolean disableAuthnStatement) {
|
||||
this.disableAuthnStatement = disableAuthnStatement;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Response buildLoginResponse() throws ConfigurationException, ProcessingException, IOException {
|
||||
Document responseDoc = getResponse();
|
||||
return buildResponse(responseDoc);
|
||||
public BindingBuilder binding() throws ConfigurationException, ProcessingException {
|
||||
|
||||
Document samlResponseDocument = buildDocument();
|
||||
|
||||
return new BindingBuilder(samlResponseDocument);
|
||||
}
|
||||
|
||||
public Document getResponse() throws ConfigurationException, ProcessingException {
|
||||
|
||||
public Document buildDocument() throws ConfigurationException, ProcessingException {
|
||||
Document samlResponseDocument = null;
|
||||
|
||||
ResponseType responseType = null;
|
||||
|
@ -175,7 +168,6 @@ public class SALM2PostBindingLoginResponseBuilder extends SAML2PostBindingBuilde
|
|||
}
|
||||
|
||||
encryptAndSign(samlResponseDocument);
|
||||
|
||||
return samlResponseDocument;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import org.picketlink.common.util.DocumentUtil;
|
|||
import org.picketlink.identity.federation.core.util.XMLEncryptionUtil;
|
||||
import org.picketlink.identity.federation.core.wstrust.WSTrustUtil;
|
||||
import org.picketlink.identity.federation.web.util.PostBindingUtil;
|
||||
import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
|
@ -17,11 +18,14 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
import javax.ws.rs.core.CacheControl;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.xml.namespace.QName;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static org.picketlink.common.util.StringUtil.isNotNull;
|
||||
|
@ -30,12 +34,11 @@ 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> {
|
||||
public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
|
||||
protected KeyPair signingKeyPair;
|
||||
protected X509Certificate signingCertificate;
|
||||
protected boolean signed;
|
||||
protected String signatureDigestMethod;
|
||||
protected String signatureMethod;
|
||||
protected SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RSA_SHA1;
|
||||
protected String relayState;
|
||||
protected String destination;
|
||||
protected String responseIssuer;
|
||||
|
@ -70,6 +73,11 @@ public class SAML2PostBindingBuilder<T extends SAML2PostBindingBuilder> {
|
|||
return (T)this;
|
||||
}
|
||||
|
||||
public T signatureAlgorithm(SignatureAlgorithm alg) {
|
||||
this.signatureAlgorithm = alg;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T encrypt(PublicKey publicKey) {
|
||||
encrypt = true;
|
||||
encryptionPublicKey = publicKey;
|
||||
|
@ -86,16 +94,6 @@ public class SAML2PostBindingBuilder<T extends SAML2PostBindingBuilder> {
|
|||
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 destination(String destination) {
|
||||
this.destination = destination;
|
||||
return (T)this;
|
||||
|
@ -111,6 +109,37 @@ public class SAML2PostBindingBuilder<T extends SAML2PostBindingBuilder> {
|
|||
return (T)this;
|
||||
}
|
||||
|
||||
public class BindingBuilder {
|
||||
protected Document document;
|
||||
|
||||
public BindingBuilder(Document document) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
public Document getDocument() {
|
||||
return document;
|
||||
}
|
||||
public Response postResponse() throws ConfigurationException, ProcessingException, IOException {
|
||||
return buildResponse(document);
|
||||
}
|
||||
|
||||
public URI redirectResponseUri() throws ConfigurationException, ProcessingException, IOException {
|
||||
return generateRedirectUri("SAMLResponse", document);
|
||||
}
|
||||
|
||||
public Response redirectResponse() throws ProcessingException, ConfigurationException, IOException {
|
||||
URI uri = redirectResponseUri();
|
||||
|
||||
CacheControl cacheControl = new CacheControl();
|
||||
cacheControl.setNoCache(true);
|
||||
return Response.status(302).location(uri)
|
||||
.header("Pragma", "no-cache")
|
||||
.header("Cache-Control", "no-cache, no-store").build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private String getSAMLNSPrefix(Document samlResponseDocument) {
|
||||
Node assertionElement = samlResponseDocument.getDocumentElement()
|
||||
.getElementsByTagNameNS(JBossSAMLURIConstants.ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0);
|
||||
|
@ -155,15 +184,25 @@ public class SAML2PostBindingBuilder<T extends SAML2PostBindingBuilder> {
|
|||
}
|
||||
|
||||
protected void signDocument(Document samlDocument) throws ProcessingException {
|
||||
SamlProtocolUtils.signDocument(samlDocument, signingKeyPair, signatureMethod, signatureDigestMethod, signingCertificate);
|
||||
SamlProtocolUtils.signDocument(samlDocument, signingKeyPair, signatureAlgorithm.getXmlSignatureMethod(), signatureAlgorithm.getXmlSignatureDigestMethod(), signingCertificate);
|
||||
}
|
||||
|
||||
protected Response buildResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
|
||||
String str = buildHtmlPostResponse(responseDoc);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
protected String buildHtmlPostResponse(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");
|
||||
throw SALM2LoginResponseBuilder.logger.nullValueError("Destination is null");
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
@ -190,13 +229,42 @@ public class SAML2PostBindingBuilder<T extends SAML2PostBindingBuilder> {
|
|||
|
||||
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();
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
protected String base64Encoded(Document document) throws ConfigurationException, ProcessingException, IOException {
|
||||
byte[] responseBytes = org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil.getDocumentAsString(document).getBytes("UTF-8");
|
||||
|
||||
return RedirectBindingUtil.deflateBase64URLEncode(responseBytes);
|
||||
}
|
||||
|
||||
|
||||
protected URI generateRedirectUri(String samlParameterName, Document document) throws ConfigurationException, ProcessingException, IOException {
|
||||
UriBuilder builder = UriBuilder.fromUri(destination)
|
||||
.replaceQuery(null)
|
||||
.queryParam(samlParameterName, base64Encoded(document));
|
||||
if (relayState != null) {
|
||||
builder.queryParam("RelayState", relayState);
|
||||
}
|
||||
|
||||
if (signed) {
|
||||
builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, signatureAlgorithm.getJavaSignatureAlgorithm());
|
||||
URI uri = builder.build();
|
||||
String rawQuery = uri.getRawQuery();
|
||||
Signature signature = signatureAlgorithm.createSignature();
|
||||
byte[] sig = new byte[0];
|
||||
try {
|
||||
signature.initSign(signingKeyPair.getPrivate());
|
||||
signature.update(rawQuery.getBytes("UTF-8"));
|
||||
sig = signature.sign();
|
||||
} catch (Exception e) {
|
||||
throw new ProcessingException(e);
|
||||
}
|
||||
String encodedSig = RedirectBindingUtil.base64URLEncode(sig);
|
||||
builder.queryParam(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY, encodedSig);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -12,17 +12,13 @@ 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 class SAML2ErrorResponseBuilder extends SAML2BindingBuilder<SAML2ErrorResponseBuilder> {
|
||||
|
||||
public Document getErrorResponse(String status) throws ProcessingException {
|
||||
public Document buildDocument(String status) throws ProcessingException {
|
||||
Document samlResponse = null;
|
||||
ResponseType responseType = null;
|
||||
|
||||
|
@ -49,7 +45,11 @@ public class SAML2PostBindingErrorResponseBuilder extends SAML2PostBindingBuilde
|
|||
return samlResponse;
|
||||
}
|
||||
|
||||
public Response buildErrorResponse(String status) throws ConfigurationException, ProcessingException, IOException {
|
||||
Document doc = getErrorResponse(status);
|
||||
return buildResponse(doc);
|
||||
}}
|
||||
public BindingBuilder binding(String status) throws ConfigurationException, ProcessingException {
|
||||
|
||||
Document samlResponseDocument = buildDocument(status);
|
||||
|
||||
return new BindingBuilder(samlResponseDocument);
|
||||
}
|
||||
|
||||
}
|
|
@ -2,37 +2,25 @@ 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> {
|
||||
public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRequestBuilder> {
|
||||
protected String userPrincipal;
|
||||
|
||||
public SAML2PostBindingLogoutResponseBuilder userPrincipal(String userPrincipal) {
|
||||
public SAML2LogoutRequestBuilder userPrincipal(String userPrincipal) {
|
||||
this.userPrincipal = userPrincipal;
|
||||
return this;
|
||||
}
|
|
@ -38,6 +38,7 @@ public class SalmProtocol implements LoginProtocol {
|
|||
public static final String LOGIN_PROTOCOL = "saml";
|
||||
public static final String SAML_BINDING = "saml_binding";
|
||||
public static final String SAML_POST_BINDING = "post";
|
||||
public static final String SAML_GET_BINDING = "get";
|
||||
|
||||
protected KeycloakSession session;
|
||||
|
||||
|
@ -80,33 +81,34 @@ public class SalmProtocol implements LoginProtocol {
|
|||
}
|
||||
|
||||
protected Response getErrorResponse(ClientSessionModel clientSession, String status) {
|
||||
SAML2PostBindingErrorResponseBuilder builder = new SAML2PostBindingErrorResponseBuilder()
|
||||
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
|
||||
.relayState(clientSession.getNote(GeneralConstants.RELAY_STATE))
|
||||
.destination(clientSession.getRedirectUri())
|
||||
.responseIssuer(getResponseIssuer(realm));
|
||||
try {
|
||||
return builder.buildErrorResponse(status);
|
||||
if (isPostBinding(clientSession)) {
|
||||
return builder.binding(status).postResponse();
|
||||
} else {
|
||||
return builder.binding(status).redirectResponse();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isPostBinding(ClientSessionModel clientSession) {
|
||||
return SalmProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SalmProtocol.SAML_BINDING));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
if (SalmProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SalmProtocol.SAML_BINDING))) {
|
||||
return postBinding(userSession, clientSession);
|
||||
}
|
||||
throw new RuntimeException("still need to implement redirect binding");
|
||||
}
|
||||
|
||||
protected Response postBinding(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||
String requestID = clientSession.getNote("REQUEST_ID");
|
||||
String relayState = clientSession.getNote(GeneralConstants.RELAY_STATE);
|
||||
String redirectUri = clientSession.getRedirectUri();
|
||||
String responseIssuer = getResponseIssuer(realm);
|
||||
|
||||
SALM2PostBindingLoginResponseBuilder builder = new SALM2PostBindingLoginResponseBuilder();
|
||||
SALM2LoginResponseBuilder builder = new SALM2LoginResponseBuilder();
|
||||
builder.requestID(requestID)
|
||||
.relayState(relayState)
|
||||
.destination(redirectUri)
|
||||
|
@ -138,7 +140,11 @@ public class SalmProtocol implements LoginProtocol {
|
|||
builder.encrypt(publicKey);
|
||||
}
|
||||
try {
|
||||
return builder.buildLoginResponse();
|
||||
if (isPostBinding(clientSession)) {
|
||||
return builder.binding().postResponse();
|
||||
} else {
|
||||
return builder.binding().redirectResponse();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("failed", e);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
|
||||
|
@ -153,7 +159,7 @@ public class SalmProtocol implements LoginProtocol {
|
|||
return "true".equals(client.getAttribute("samlEncrypt"));
|
||||
}
|
||||
|
||||
public void initClaims(SALM2PostBindingLoginResponseBuilder builder, ClientModel model, UserModel user) {
|
||||
public void initClaims(SALM2LoginResponseBuilder builder, ClientModel model, UserModel user) {
|
||||
if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) {
|
||||
builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail());
|
||||
}
|
||||
|
@ -176,7 +182,7 @@ public class SalmProtocol implements LoginProtocol {
|
|||
ApplicationModel app = (ApplicationModel)client;
|
||||
if (app.getManagementUrl() == null) return;
|
||||
|
||||
SAML2PostBindingLogoutResponseBuilder logoutBuilder = new SAML2PostBindingLogoutResponseBuilder()
|
||||
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
||||
.userPrincipal(userSession.getUser().getUsername())
|
||||
.destination(client.getClientId());
|
||||
if (requiresRealmSignature(client)) {
|
||||
|
|
|
@ -18,11 +18,22 @@ import java.security.cert.X509Certificate;
|
|||
*/
|
||||
public class SamlProtocolUtils {
|
||||
|
||||
public static void verifyPostBindingSignature(ClientModel client, Document document) throws VerificationException {
|
||||
public static void verifyDocumentSignature(ClientModel client, Document document) throws VerificationException {
|
||||
if (!"true".equals(client.getAttribute("samlClientSignature"))) {
|
||||
return;
|
||||
}
|
||||
SAML2Signature saml2Signature = new SAML2Signature();
|
||||
PublicKey publicKey = getPublicKey(client);
|
||||
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 PublicKey getPublicKey(ClientModel client) throws VerificationException {
|
||||
String publicKeyPem = client.getAttribute(ClientModel.PUBLIC_KEY);
|
||||
if (publicKeyPem == null) throw new VerificationException("Client does not have a public key.");
|
||||
PublicKey publicKey = null;
|
||||
|
@ -31,13 +42,7 @@ public class SamlProtocolUtils {
|
|||
} 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);
|
||||
}
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public static void signDocument(Document samlDocument, KeyPair signingKeyPair, String signatureMethod, String signatureDigestMethod, X509Certificate signingCertificate) throws ProcessingException {
|
||||
|
|
|
@ -26,12 +26,13 @@ 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.LogoutRequestType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.RequestAbstractType;
|
||||
import org.w3c.dom.Document;
|
||||
import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
@ -41,7 +42,11 @@ import javax.ws.rs.core.SecurityContext;
|
|||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.ext.Providers;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
|
||||
/**
|
||||
* Resource class for the oauth/openid connect token service
|
||||
|
@ -85,172 +90,292 @@ public class SamlService {
|
|||
this.authManager = authManager;
|
||||
}
|
||||
|
||||
public abstract class BindingProtocol {
|
||||
protected Response basicChecks(String samlRequest, String samlResponse) {
|
||||
if (!checkSsl()) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
}
|
||||
if (!realm.isEnabled()) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
|
||||
}
|
||||
|
||||
if (samlRequest == null && samlResponse == null) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Response handleSamlResponse(String samleResponse, String relayState) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
}
|
||||
|
||||
protected Response handleSamlRequest(String samlRequest, String relayState) {
|
||||
SAMLDocumentHolder documentHolder = extractDocument(samlRequest);
|
||||
if (documentHolder == null) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
}
|
||||
|
||||
SAML2Object samlObject = documentHolder.getSamlObject();
|
||||
|
||||
RequestAbstractType requestAbstractType = (RequestAbstractType)samlObject;
|
||||
String issuer = requestAbstractType.getIssuer().getValue();
|
||||
ClientModel client = realm.findClient(issuer);
|
||||
|
||||
if (client == null) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
||||
}
|
||||
if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
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.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
|
||||
}
|
||||
|
||||
try {
|
||||
verifySignature(documentHolder, client);
|
||||
} catch (VerificationException e) {
|
||||
SamlService.logger.error("request validation failed", e);
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_SIGNATURE);
|
||||
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 abstract void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException;
|
||||
|
||||
protected abstract SAMLDocumentHolder extractDocument(String samlRequest);
|
||||
|
||||
protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
|
||||
|
||||
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
|
||||
String redirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
|
||||
|
||||
if (redirect == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(SalmProtocol.LOGIN_PROTOCOL);
|
||||
clientSession.setRedirectUri(redirect);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
clientSession.setNote(SalmProtocol.SAML_BINDING, getBindingType());
|
||||
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
||||
clientSession.setNote("REQUEST_ID", requestAbstractType.getID());
|
||||
|
||||
Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
|
||||
if (response != null) return response;
|
||||
|
||||
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
|
||||
|
||||
String rememberMeUsername = AuthenticationManager.getRememberMeUsername(realm, headers);
|
||||
|
||||
if (rememberMeUsername != null) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
|
||||
formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
|
||||
formData.add("rememberMe", "on");
|
||||
|
||||
forms.setFormData(formData);
|
||||
}
|
||||
|
||||
return forms.createLogin();
|
||||
}
|
||||
|
||||
protected abstract String getBindingType();
|
||||
|
||||
protected Response logoutRequest(LogoutRequestType requestAbstractType, ClientModel client) {
|
||||
// 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);
|
||||
if (authResult != null) {
|
||||
logout(authResult.getSession());
|
||||
}
|
||||
|
||||
String redirectUri = null;
|
||||
|
||||
if (client instanceof ApplicationModel) {
|
||||
redirectUri = ((ApplicationModel)client).getBaseUrl();
|
||||
}
|
||||
|
||||
if (redirectUri != null) {
|
||||
String validatedRedirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri, realm, client);;
|
||||
if (validatedRedirect == null) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
|
||||
}
|
||||
return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
|
||||
} else {
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void logout(UserSessionModel userSession) {
|
||||
authManager.logout(session, realm, userSession, uriInfo, clientConnection);
|
||||
event.user(userSession.getUser()).session(userSession).success();
|
||||
}
|
||||
|
||||
private boolean checkSsl() {
|
||||
if (uriInfo.getBaseUri().getScheme().equals("https")) {
|
||||
return true;
|
||||
} else {
|
||||
return !realm.getSslRequired().isRequired(clientConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected class PostBindingProtocol extends BindingProtocol {
|
||||
|
||||
|
||||
@Override
|
||||
protected void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException {
|
||||
SamlProtocolUtils.verifyDocumentSignature(client, documentHolder.getSamlDocument());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SAMLDocumentHolder extractDocument(String samlRequest) {
|
||||
return SAMLRequestParser.parsePostBinding(samlRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBindingType() {
|
||||
return SalmProtocol.SAML_POST_BINDING;
|
||||
}
|
||||
|
||||
|
||||
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
||||
Response response = basicChecks(samlRequest, samlResponse);
|
||||
if (response != null) return response;
|
||||
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
||||
else return handleSamlResponse(samlResponse, relayState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected class RedirectBindingProtocol extends BindingProtocol {
|
||||
|
||||
@Override
|
||||
protected void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException {
|
||||
if (!"true".equals(client.getAttribute("samlClientSignature"))) {
|
||||
return;
|
||||
}
|
||||
MultivaluedMap<String, String> encodedParams = uriInfo.getQueryParameters(false);
|
||||
String request = encodedParams.getFirst(GeneralConstants.SAML_REQUEST_KEY);
|
||||
String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
|
||||
String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
|
||||
|
||||
if (request == null) throw new VerificationException("SAMLRequest as null");
|
||||
if (algorithm == null) throw new VerificationException("SigAlg as null");
|
||||
if (signature == null) throw new VerificationException("Signature as null");
|
||||
|
||||
SamlProtocolUtils.verifyDocumentSignature(client, documentHolder.getSamlDocument());
|
||||
|
||||
PublicKey publicKey = SamlProtocolUtils.getPublicKey(client);
|
||||
|
||||
|
||||
UriBuilder builder = UriBuilder.fromPath("/")
|
||||
.queryParam(GeneralConstants.SAML_REQUEST_KEY, request);
|
||||
if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
|
||||
builder.queryParam(GeneralConstants.RELAY_STATE, encodedParams.getFirst(GeneralConstants.RELAY_STATE));
|
||||
}
|
||||
builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
|
||||
String rawQuery = builder.build().getRawQuery();
|
||||
|
||||
try {
|
||||
byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
|
||||
|
||||
Signature validator = SignatureAlgorithm.RSA_SHA1.createSignature(); // todo plugin signature alg
|
||||
validator.initVerify(publicKey);
|
||||
validator.update(rawQuery.getBytes("UTF-8"));
|
||||
if (!validator.verify(decodedSignature)) {
|
||||
throw new VerificationException("Invalid query param signature");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new VerificationException(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SAMLDocumentHolder extractDocument(String samlRequest) {
|
||||
return SAMLRequestParser.parseRedirectBinding(samlRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBindingType() {
|
||||
return SalmProtocol.SAML_GET_BINDING;
|
||||
}
|
||||
|
||||
|
||||
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
||||
Response response = basicChecks(samlRequest, samlResponse);
|
||||
if (response != null) return response;
|
||||
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
||||
else return handleSamlResponse(samlResponse, relayState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
@GET
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||
return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
@Path("POST")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||
@FormParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||
if (!checkSsl()) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
|
||||
}
|
||||
if (!realm.isEnabled()) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
|
||||
}
|
||||
|
||||
if (samlRequest == null && samlResponse == null) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
|
||||
}
|
||||
|
||||
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
||||
else return handleSamlResponse(samlResponse, relayState);
|
||||
return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
||||
}
|
||||
|
||||
protected Response handleSamlResponse(String samleResponse, String relayState) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
}
|
||||
|
||||
|
||||
protected Response handleSamlRequest(String samlRequest, String relayState) {
|
||||
SAMLDocumentHolder documentHolder = SAMLRequestParser.parsePostBinding(samlRequest);
|
||||
if (documentHolder == null) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
}
|
||||
|
||||
SAML2Object samlObject = documentHolder.getSamlObject();
|
||||
|
||||
RequestAbstractType requestAbstractType = (RequestAbstractType)samlObject;
|
||||
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");
|
||||
}
|
||||
|
||||
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();
|
||||
String redirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
|
||||
|
||||
if (redirect == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
|
||||
}
|
||||
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(SalmProtocol.LOGIN_PROTOCOL);
|
||||
clientSession.setRedirectUri(redirect);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
clientSession.setNote(SalmProtocol.SAML_BINDING, SalmProtocol.SAML_POST_BINDING);
|
||||
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
||||
clientSession.setNote("REQUEST_ID", requestAbstractType.getID());
|
||||
|
||||
Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
|
||||
if (response != null) return response;
|
||||
|
||||
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
|
||||
|
||||
String rememberMeUsername = AuthenticationManager.getRememberMeUsername(realm, headers);
|
||||
|
||||
if (rememberMeUsername != null) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
|
||||
formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
|
||||
formData.add("rememberMe", "on");
|
||||
|
||||
forms.setFormData(formData);
|
||||
}
|
||||
|
||||
return forms.createLogin();
|
||||
}
|
||||
|
||||
protected Response logoutRequest(LogoutRequestType requestAbstractType, ClientModel client) {
|
||||
// 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);
|
||||
if (authResult != null) {
|
||||
logout(authResult.getSession());
|
||||
}
|
||||
|
||||
String redirectUri = null;
|
||||
|
||||
if (client instanceof ApplicationModel) {
|
||||
redirectUri = ((ApplicationModel)client).getBaseUrl();
|
||||
}
|
||||
|
||||
if (redirectUri != null) {
|
||||
String validatedRedirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri, realm, client);;
|
||||
if (validatedRedirect == null) {
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
|
||||
}
|
||||
return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
|
||||
} else {
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void logout(UserSessionModel userSession) {
|
||||
authManager.logout(session, realm, userSession, uriInfo, clientConnection);
|
||||
event.user(userSession.getUser()).session(userSession).success();
|
||||
}
|
||||
|
||||
private boolean checkSsl() {
|
||||
if (uriInfo.getBaseUri().getScheme().equals("https")) {
|
||||
return true;
|
||||
} else {
|
||||
return !realm.getSslRequired().isRequired(clientConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public enum SignatureAlgorithm {
|
||||
RSA_SHA1("http://www.w3.org/2000/09/xmldsig#rsa-sha1", "http://www.w3.org/2000/09/xmldsig#sha1", "SHA1withRSA"),
|
||||
RSA_SHA256("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "http://www.w3.org/2001/04/xmlenc#sha256", "SHA256withRSA"),
|
||||
RSA_SHA512("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", "http://www.w3.org/2001/04/xmlenc#sha512", "SHA512withRSA"),
|
||||
DSA_SHA1("http://www.w3.org/2000/09/xmldsig#dsa-sha1", "http://www.w3.org/2000/09/xmldsig#sha1", "SHA1withDSA")
|
||||
;
|
||||
private final String xmlSignatureMethod;
|
||||
private final String xmlSignatureDigestMethod;
|
||||
private final String javaSignatureAlgorithm;
|
||||
|
||||
SignatureAlgorithm(String xmlSignatureMethod, String xmlSignatureDigestMethod, String javaSignatureAlgorithm) {
|
||||
this.xmlSignatureMethod = xmlSignatureMethod;
|
||||
this.xmlSignatureDigestMethod = xmlSignatureDigestMethod;
|
||||
this.javaSignatureAlgorithm = javaSignatureAlgorithm;
|
||||
}
|
||||
|
||||
public String getXmlSignatureMethod() {
|
||||
return xmlSignatureMethod;
|
||||
}
|
||||
|
||||
public String getXmlSignatureDigestMethod() {
|
||||
return xmlSignatureDigestMethod;
|
||||
}
|
||||
|
||||
public String getJavaSignatureAlgorithm() {
|
||||
return javaSignatureAlgorithm;
|
||||
}
|
||||
|
||||
public Signature createSignature() {
|
||||
try {
|
||||
return Signature.getInstance(javaSignatureAlgorithm);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -157,11 +157,13 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
@Ignore
|
||||
public void runit() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void returnToAppFromQueryParam() {
|
||||
|
|
|
@ -75,6 +75,24 @@
|
|||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQAB",
|
||||
"X509Certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "http://localhost:8080/employee-sig/",
|
||||
"enabled": true,
|
||||
"protocol": "saml",
|
||||
"fullScopeAllowed": true,
|
||||
"baseUrl": "http://localhost:8080/employee-sig",
|
||||
"adminUrl": "http://localhost:8080/employee-sig",
|
||||
"redirectUris": [
|
||||
"http://localhost:8080/employee-sig/*"
|
||||
],
|
||||
"attributes": {
|
||||
"samlServerSignature": "true",
|
||||
"samlClientSignature": "true",
|
||||
"privateKey": "MIICXQIBAAKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABAoGANU1efgc6ojIvwn7Lsf8GAKN9z2D6uS0T3I9nw1k2CtI+xWhgKAUltEANx5lEfBRYIdYclidRpqrk8DYgzASrDYTHXzqVBJfAk1VrAGpqyRq+TNMLUHkXiTiSDOQ6WqhX93UGMmAgQm1RsLa6+fy1BO/B2y85+Yf2OUylsKS6avECQQDslRDiNFdtEjdvyOL20tQ7+W+eKVxVxKAyQ3gFjIIDizELZt+Jq1Wz6XV9NhK1JFtlVugeD1tlW/+K16fEmDYXAkEAzqKoN/JeGb20rfQldAUWdQbb0jrQAYlgoSU/9fYH9YVJT8vnkfhPBTwIw9H9euf1//lRP/jHltHd5ch4230YyQJBAN3rOkoltPiABPZbpuLGgwS7BwOCYrWlWmurtBLoaTCvyVKbrgXybNL1pBrOtR+rufvGWLeRyja65Gs1vY6BBQMCQQCTsNq/MjJj/522f7yNUl2cw4w2lOa7Um+IflFbAcDqkZu2ty0Kvgns2d4B6INeZ5ECpjaWnMA7YkFRzZnkd2NRAkB8lEY56ScnNigoZkkjtEUd2ejdhZPYuS9SKfv9zHwN+I+DE2vVFZz8GPq/iLcMx13PkZaYaJNQ4FtQY/hRLSn5",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQAB",
|
||||
"X509Certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
|
|
Loading…
Reference in a new issue