keycloak-aplcache
Changes
integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java 4(+2 -2)
integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettyAdapterSessionStore.java 15(+7 -8)
integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java 3(+2 -1)
integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettyAdapterSessionStore.java 16(+7 -9)
integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java 3(+2 -1)
integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettyAdapterSessionStore.java 15(+7 -8)
integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java 3(+2 -1)
integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java 6(+6 -0)
integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyUserSessionManagement.java 0(+0 -0)
integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/WrappingSessionHandler.java 0(+0 -0)
integration/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/JettySessionTokenStore.java 20(+16 -4)
integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java 5(+5 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java 5(+5 -0)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java 5(+2 -3)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java 5(+5 -0)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagement.java 0(+0 -0)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagementWrapper.java 0(+0 -0)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimpleGroup.java 0(+0 -0)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimplePrincipal.java 0(+0 -0)
integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java 32(+32 -0)
integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java 20(+2 -18)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java 2(+1 -1)
integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java 39(+39 -0)
integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java 0(+0 -0)
integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java 7(+6 -1)
integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java 0(+0 -0)
pom.xml 5(+5 -0)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java 409(+407 -2)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java 13(+13 -0)
saml/client-adapter/pom.xml 1(+1 -0)
saml/client-adapter/undertow/pom.xml 87(+87 -0)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java 142(+142 -0)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java 25(+25 -0)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java 140(+140 -0)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java 43(+43 -0)
Details
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 309337f..8e4d163 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -10,7 +10,6 @@ import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
-import org.keycloak.dom.saml.v2.assertion.EncryptedAssertionType;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
@@ -30,26 +29,17 @@ import org.keycloak.saml.SAMLRequestParser;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.SamlProtocolUtils;
import org.keycloak.saml.common.constants.GeneralConstants;
-import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ProcessingException;
-import org.keycloak.saml.common.util.DocumentUtil;
-import org.keycloak.saml.common.util.StaxParserUtil;
-import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
-import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
-import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
-import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
+import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
import org.keycloak.saml.processing.web.util.PostBindingUtil;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
@@ -62,9 +52,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
-import javax.xml.namespace.QName;
import java.io.IOException;
-import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
@@ -156,7 +144,7 @@ public class SAMLEndpoint {
}
protected abstract String getBindingType();
- protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
+ protected abstract void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException;
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
protected PublicKey getIDPKey() {
@@ -189,7 +177,7 @@ public class SAMLEndpoint {
}
if (config.isValidateSignature()) {
try {
- verifySignature(holder);
+ verifySignature(GeneralConstants.SAML_REQUEST_KEY, holder);
} catch (VerificationException e) {
logger.error("validation failed", e);
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
@@ -276,7 +264,7 @@ public class SAMLEndpoint {
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
try {
- AssertionType assertion = getAssertion(responseType);
+ AssertionType assertion = AssertionUtil.getAssertion(responseType, realm.getPrivateKey());
SubjectType subject = assertion.getSubject();
SubjectType.STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
@@ -336,22 +324,6 @@ public class SAMLEndpoint {
- private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
- List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
-
- if (assertions.isEmpty()) {
- throw new IdentityBrokerException("No assertion from response.");
- }
-
- ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
- EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
-
- if (encryptedAssertion != null) {
- decryptAssertion(responseType, realm.getPrivateKey());
-
- }
- return responseType.getAssertions().get(0).getAssertion();
- }
public Response handleSamlResponse(String samlResponse, String relayState) {
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
@@ -365,7 +337,7 @@ public class SAMLEndpoint {
}
if (config.isValidateSignature()) {
try {
- verifySignature(holder);
+ verifySignature(GeneralConstants.SAML_RESPONSE_KEY, holder);
} catch (VerificationException e) {
logger.error("validation failed", e);
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
@@ -408,43 +380,14 @@ public class SAMLEndpoint {
}
- protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
- SAML2Response saml2Response = new SAML2Response();
-
- try {
- Document doc = saml2Response.convert(responseType);
- Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
-
- if (enc == null) {
- throw new IdentityBrokerException("No encrypted assertion found.");
- }
-
- String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
- Document newDoc = DocumentUtil.createDocument();
- Node importedNode = newDoc.importNode(enc, true);
- newDoc.appendChild(importedNode);
-
- Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
- SAMLParser parser = new SAMLParser();
-
- JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
- AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
- .getNodeAsStream(decryptedDocumentElement)));
- responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
-
- return responseType;
- } catch (Exception e) {
- throw new IdentityBrokerException("Could not decrypt assertion.", e);
- }
- }
}
protected class PostBinding extends Binding {
@Override
- protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
+ protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
}
@@ -467,9 +410,9 @@ public class SAMLEndpoint {
protected class RedirectBinding extends Binding {
@Override
- protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
+ protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
PublicKey publicKey = getIDPKey();
- SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
+ SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, key);
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
index d07bffa..066a32a 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
@@ -5,7 +5,7 @@ package org.keycloak.adapters;
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public interface AdapterTokenStore {
+public interface AdapterTokenStore extends AdapterSessionStore {
/**
* Impl can validate if current token exists and perform refreshing if it exists and is expired
@@ -39,6 +39,4 @@ public interface AdapterTokenStore {
*/
void refreshCallback(RefreshableKeycloakSecurityContext securityContext);
- void saveRequest();
- boolean restoreRequest();
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index fa33363..1dfe41d 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -26,7 +26,7 @@ public class OAuthRequestAuthenticator {
protected KeycloakDeployment deployment;
protected RequestAuthenticator reqAuthenticator;
protected int sslRedirectPort;
- protected AdapterTokenStore tokenStore;
+ protected AdapterSessionStore tokenStore;
protected String tokenString;
protected String idTokenString;
protected IDToken idToken;
@@ -36,7 +36,7 @@ public class OAuthRequestAuthenticator {
protected String refreshToken;
protected String strippedOauthParametersRequestUri;
- public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort, AdapterTokenStore tokenStore) {
+ public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort, AdapterSessionStore tokenStore) {
this.reqAuthenticator = requestAuthenticator;
this.facade = facade;
this.deployment = deployment;
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/AdapterSessionStore.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AdapterSessionStore.java
new file mode 100755
index 0000000..8c5f2e2
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AdapterSessionStore.java
@@ -0,0 +1,10 @@
+package org.keycloak.adapters;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface AdapterSessionStore {
+ void saveRequest();
+ boolean restoreRequest();
+}
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthOutcome.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthOutcome.java
index 47eeee1..a4c55cb 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthOutcome.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/AuthOutcome.java
@@ -7,5 +7,6 @@ package org.keycloak.adapters;
public enum AuthOutcome {
NOT_ATTEMPTED,
FAILED,
- AUTHENTICATED
+ AUTHENTICATED,
+ LOGGED_OUT
}
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/HttpFacade.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/HttpFacade.java
index 90c2dc7..49a39b4 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/HttpFacade.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/HttpFacade.java
@@ -33,6 +33,13 @@ public interface HttpFacade {
*/
boolean isSecure();
+ /**
+ * Get first query or form param
+ *
+ * @param param
+ * @return
+ */
+ String getFirstParam(String param);
String getQueryParamValue(String param);
Cookie getCookie(String cookieName);
String getHeader(String name);
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
new file mode 100755
index 0000000..8225ae2
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/InMemorySessionIdMapper.java
@@ -0,0 +1,67 @@
+package org.keycloak.adapters;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Maps external principal and SSO id to internal local http session id
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class InMemorySessionIdMapper implements SessionIdMaper {
+ ConcurrentHashMap<String, String> ssoToSession = new ConcurrentHashMap<>();
+ ConcurrentHashMap<String, String> sessionToSso = new ConcurrentHashMap<>();
+ ConcurrentHashMap<String, Set<String>> principalToSession = new ConcurrentHashMap<>();
+ ConcurrentHashMap<String, String> sessionToPrincipal = new ConcurrentHashMap<>();
+
+ @Override
+ public Set<String> getUserSessions(String principal) {
+ Set<String> lookup = principalToSession.get(principal);
+ if (lookup == null) return null;
+ Set<String> copy = new HashSet<>();
+ copy.addAll(lookup);
+ return copy;
+ }
+
+ @Override
+ public String getSessionFromSSO(String sso) {
+ return ssoToSession.get(sso);
+ }
+
+ @Override
+ public void map(String sso, String principal, String session) {
+ ssoToSession.put(sso, session);
+ sessionToSso.put(session, sso);
+ Set<String> userSessions = principalToSession.get(principal);
+ if (userSessions == null) {
+ final Set<String> tmp = Collections.synchronizedSet(new HashSet<String>());
+ userSessions = principalToSession.putIfAbsent(principal, tmp);
+ if (userSessions == null) {
+ userSessions = tmp;
+ }
+ }
+ userSessions.add(session);
+ sessionToPrincipal.put(session, principal);
+ }
+
+ @Override
+ public void removeSession(String session) {
+ String sso = sessionToSso.remove(session);
+ if (sso != null) {
+ ssoToSession.remove(sso);
+ }
+ String principal = sessionToPrincipal.remove(session);
+ if (principal != null) {
+ Set<String> sessions = principalToSession.get(principal);
+ sessions.remove(session);
+ if (sessions.isEmpty()) {
+ principalToSession.remove(principal, sessions);
+ }
+ }
+ }
+
+
+}
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMaper.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMaper.java
new file mode 100755
index 0000000..8e86b1e
--- /dev/null
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/SessionIdMaper.java
@@ -0,0 +1,17 @@
+package org.keycloak.adapters;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SessionIdMaper {
+ Set<String> getUserSessions(String principal);
+
+ String getSessionFromSSO(String sso);
+
+ void map(String sso, String principal, String session);
+
+ void removeSession(String session);
+}
diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
index 2ff677f..490d42d 100755
--- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
+++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsHttpFacade.java
@@ -34,6 +34,11 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
protected class RequestFacade implements OIDCHttpFacade.Request {
@Override
+ public String getFirstParam(String param) {
+ throw new RuntimeException("NOT IMPLEMENTED");
+ }
+
+ @Override
public String getMethod() {
return requestContext.getMethod();
}
diff --git a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
index 243de1c..2bc1d8c 100755
--- a/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
@@ -7,6 +7,7 @@ import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
+import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import javax.servlet.ServletRequest;
@@ -23,7 +24,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
- return new JettySessionTokenStore(request, resolvedDeployment);
+ return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
diff --git a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
index 0d1bf3b..a319266 100755
--- a/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
@@ -7,6 +7,7 @@ import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
+import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import javax.servlet.ServletRequest;
@@ -23,7 +24,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
- return new JettySessionTokenStore(request, resolvedDeployment);
+ return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
diff --git a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
index 90b3c6a..2aec5ef 100755
--- a/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
+++ b/integration/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java
@@ -7,6 +7,7 @@ import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
+import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import javax.servlet.ServletRequest;
@@ -38,6 +39,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
- return new JettySessionTokenStore(request, resolvedDeployment);
+ return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
}
diff --git a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java
index 8ffae79..145f5ef 100755
--- a/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java
+++ b/integration/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/core/JettyHttpFacade.java
@@ -18,6 +18,7 @@ import java.util.List;
* @version $Revision: 1 $
*/
public class JettyHttpFacade implements HttpFacade {
+ public final static String __J_METHOD = "org.eclipse.jetty.security.HTTP_METHOD";
protected org.eclipse.jetty.server.Request request;
protected HttpServletResponse response;
protected RequestFacade requestFacade = new RequestFacade();
@@ -59,6 +60,11 @@ public class JettyHttpFacade implements HttpFacade {
}
@Override
+ public String getFirstParam(String param) {
+ return request.getParameter(param);
+ }
+
+ @Override
public boolean isSecure() {
return request.isSecure();
}
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index e2afb94..ed8744e 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -183,6 +183,11 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
return new Request() {
@Override
+ public String getFirstParam(String param) {
+ return servletRequest.getParameter(param);
+ }
+
+ @Override
public String getMethod() {
return servletRequest.getMethod();
}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
old mode 100644
new mode 100755
index 478f182..e2e5ba4
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
@@ -33,6 +33,11 @@ class WrappedHttpServletRequest implements Request {
}
@Override
+ public String getFirstParam(String param) {
+ return request.getParameter(param);
+ }
+
+ @Override
public String getMethod() {
return request.getMethod();
}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
old mode 100644
new mode 100755
index 602f8ca..a5f5b07
--- a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
@@ -2,10 +2,9 @@ package org.keycloak.adapters.springsecurity.token;
import org.junit.Before;
import org.junit.Test;
-import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterSessionStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import javax.servlet.http.HttpServletRequest;
@@ -32,7 +31,7 @@ public class SpringSecurityAdapterTokenStoreFactoryTest {
@Test
public void testCreateAdapterTokenStore() throws Exception {
- AdapterTokenStore store = factory.createAdapterTokenStore(deployment, request);
+ AdapterSessionStore store = factory.createAdapterTokenStore(deployment, request);
assertNotNull(store);
assertTrue(store instanceof SpringSecurityTokenStore);
}
diff --git a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
index a72ad5d..b195f39 100755
--- a/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
+++ b/integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java
@@ -65,6 +65,11 @@ public class CatalinaHttpFacade implements HttpFacade {
}
@Override
+ public String getFirstParam(String param) {
+ return request.getParameter(param);
+ }
+
+ @Override
public String getQueryParamValue(String paramName) {
if (queryParameters == null) {
queryParameters = UriUtils.decodeQueryString(request.getQueryString());
diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java
new file mode 100755
index 0000000..f1ffbf0
--- /dev/null
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java
@@ -0,0 +1,32 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.connector.Request;
+import org.keycloak.adapters.AdapterSessionStore;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaAdapterSessionStore implements AdapterSessionStore {
+ protected Request request;
+ protected AbstractKeycloakAuthenticatorValve valve;
+
+ public CatalinaAdapterSessionStore(Request request, AbstractKeycloakAuthenticatorValve valve) {
+ this.request = request;
+ this.valve = valve;
+ }
+
+ public void saveRequest() {
+ try {
+ valve.keycloakSaveRequest(request);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean restoreRequest() {
+ return valve.keycloakRestoreRequest(request);
+ }
+}
diff --git a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
index a91ede0..e0d3ca6 100755
--- a/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
+++ b/integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
@@ -19,26 +19,23 @@ import java.util.logging.Logger;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class CatalinaSessionTokenStore implements AdapterTokenStore {
+public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore implements AdapterTokenStore {
private static final Logger log = Logger.getLogger("" + CatalinaSessionTokenStore.class);
- private Request request;
private KeycloakDeployment deployment;
private CatalinaUserSessionManagement sessionManagement;
protected GenericPrincipalFactory principalFactory;
- protected AbstractKeycloakAuthenticatorValve valve;
public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment,
CatalinaUserSessionManagement sessionManagement,
GenericPrincipalFactory principalFactory,
AbstractKeycloakAuthenticatorValve valve) {
- this.request = request;
+ super(request, valve);
this.deployment = deployment;
this.sessionManagement = sessionManagement;
this.principalFactory = principalFactory;
- this.valve = valve;
}
@Override
@@ -169,17 +166,4 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
// no-op
}
- @Override
- public void saveRequest() {
- try {
- valve.keycloakSaveRequest(request);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public boolean restoreRequest() {
- return valve.keycloakRestoreRequest(request);
- }
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
index 71382ff..790078f 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
@@ -60,7 +60,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
Integer code = servePage(exchange, errorPage);
return new ChallengeResult(true, code);
}
- UndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
+ UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
if (challenge.challenge(facade)) {
return new ChallengeResult(true, exchange.getResponseCode());
}
diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
new file mode 100755
index 0000000..60bfaa4
--- /dev/null
+++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
@@ -0,0 +1,39 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.keycloak.adapters.HttpFacade;
+
+import javax.security.cert.X509Certificate;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletHttpFacade extends UndertowHttpFacade {
+ protected HttpServletRequest request;
+ protected HttpServletResponse response;
+
+ public ServletHttpFacade(HttpServerExchange exchange) {
+ super(exchange);
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ request = (HttpServletRequest)servletRequestContext.getServletRequest();
+ }
+
+ protected class RequestFacade extends UndertowHttpFacade.RequestFacade {
+ @Override
+ public String getFirstParam(String param) {
+ return request.getParameter(param);
+ }
+
+ }
+
+ @Override
+ public Request getRequest() {
+ return new RequestFacade();
+ }
+}
diff --git a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
index d2c4b44..1abb86c 100755
--- a/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
+++ b/integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -21,7 +21,7 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public abstract class UndertowHttpFacade implements HttpFacade {
+public class UndertowHttpFacade implements HttpFacade {
protected HttpServerExchange exchange;
protected RequestFacade requestFacade = new RequestFacade();
protected ResponseFacade responseFacade = new ResponseFacade();
@@ -67,6 +67,11 @@ public abstract class UndertowHttpFacade implements HttpFacade {
}
@Override
+ public String getFirstParam(String param) {
+ throw new RuntimeException("Not implemented yet");
+ }
+
+ @Override
public String getQueryParamValue(String param) {
Map<String,Deque<String>> queryParameters = exchange.getQueryParameters();
if (queryParameters == null) return null;
pom.xml 5(+5 -0)
diff --git a/pom.xml b/pom.xml
index a35d56f..861f0f1 100755
--- a/pom.xml
+++ b/pom.xml
@@ -987,6 +987,11 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${project.version}</version>
</dependency>
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
new file mode 100755
index 0000000..d5f86d7
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/InitiateLogin.java
@@ -0,0 +1,86 @@
+package org.keycloak.adapters.saml;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AuthChallenge;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2AuthnRequestBuilder;
+import org.keycloak.saml.SAML2NameIDPolicyBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.w3c.dom.Document;
+
+import java.security.KeyPair;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class InitiateLogin implements AuthChallenge {
+ protected static Logger log = Logger.getLogger(InitiateLogin.class);
+
+ protected SamlDeployment deployment;
+ protected SamlSessionStore sessionStore;
+
+ public InitiateLogin(SamlDeployment deployment, SamlSessionStore sessionStore) {
+ this.deployment = deployment;
+ this.sessionStore = sessionStore;
+ }
+
+ @Override
+ public boolean errorPage() {
+ return true;
+ }
+
+ @Override
+ public boolean challenge(HttpFacade httpFacade) {
+ try {
+ String issuerURL = deployment.getIssuer();
+ String actionUrl = deployment.getSingleSignOnServiceUrl();
+ String destinationUrl = actionUrl;
+ String nameIDPolicyFormat = deployment.getNameIDPolicyFormat();
+
+ if (nameIDPolicyFormat == null) {
+ nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
+ }
+
+ String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
+
+ if (deployment.getResponseBinding() == SamlDeployment.Binding.POST) {
+ protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
+ }
+
+ SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
+ .assertionConsumerUrl(deployment.getAssertionConsumerServiceUrl())
+ .destination(destinationUrl)
+ .issuer(issuerURL)
+ .forceAuthn(deployment.isForceAuthentication())
+ .protocolBinding(protocolBinding)
+ .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
+ BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+
+ if (deployment.isRequestsSigned()) {
+
+
+ KeyPair keypair = deployment.getSigningKeyPair();
+ if (keypair == null) {
+ throw new RuntimeException("Signing keys not configured");
+ }
+ if (deployment.getSignatureCanonicalizationMethod() != null) {
+ binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
+ }
+
+ binding.signWith(keypair);
+ binding.signDocument();
+ }
+ sessionStore.saveRequest();
+
+ Document document = authnRequestBuilder.toDocument();
+ SamlDeployment.Binding samlBinding = deployment.getRequestBinding();
+ SamlUtil.sendSaml(httpFacade, actionUrl, binding, document, samlBinding);
+ } catch (Exception e) {
+ throw new RuntimeException("Could not create authentication request.", e);
+ }
+ return true;
+ }
+
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
index eb5db0f..8c393bd 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
@@ -1,25 +1,430 @@
package org.keycloak.adapters.saml;
import org.jboss.logging.Logger;
+import org.keycloak.VerificationException;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
import org.keycloak.adapters.HttpFacade;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
+import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
+import org.keycloak.dom.saml.v2.assertion.SubjectType;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2LogoutRequestBuilder;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+import org.keycloak.saml.SAMLRequestParser;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+import org.keycloak.saml.processing.web.util.RedirectBindingUtil;
+import org.keycloak.util.KeycloakUriBuilder;
+import org.keycloak.util.MultivaluedHashMap;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.IOException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class SamlAuthenticator {
+public abstract class SamlAuthenticator {
protected static Logger log = Logger.getLogger(SamlAuthenticator.class);
protected HttpFacade facade;
protected AuthChallenge challenge;
+ protected SamlDeployment deployment;
+ protected SamlSessionStore sessionStore;
+
+ public SamlAuthenticator(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ this.facade = facade;
+ this.deployment = deployment;
+ this.sessionStore = sessionStore;
+ }
public AuthChallenge getChallenge() {
return challenge;
}
public AuthOutcome authenticate() {
- return null;
+
+
+ String samlRequest = facade.getRequest().getFirstParam(GeneralConstants.SAML_REQUEST_KEY);
+ String samlResponse = facade.getRequest().getFirstParam(GeneralConstants.SAML_RESPONSE_KEY);
+ String relayState = facade.getRequest().getFirstParam(GeneralConstants.RELAY_STATE);
+ boolean globalLogout = "true".equals(facade.getRequest().getQueryParamValue("GLO"));
+ if (samlRequest != null) {
+ return handleSamlRequest(samlRequest, relayState);
+ } else if (samlResponse != null) {
+ return handleSamlResponse(samlResponse, relayState);
+ } else if (sessionStore.isLoggedIn()) {
+ if (globalLogout) {
+ return globalLogout();
+ }
+ if (verifySSL()) return AuthOutcome.FAILED;
+ log.debug("AUTHENTICATED: was cached");
+ return AuthOutcome.AUTHENTICATED;
+ }
+ return initiateLogin();
+ }
+
+ protected AuthOutcome globalLogout() {
+ SamlSession account = sessionStore.getAccount();
+ SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
+ .assertionExpiration(30)
+ .issuer(deployment.getIssuer())
+ .sessionIndex(account.getSessionIndex())
+ .userPrincipal(account.getPrincipal().getSamlSubject(), account.getPrincipal().getNameIDFormat())
+ .destination(deployment.getSingleLogoutServiceUrl());
+ BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+ if (deployment.isRequestsSigned()) {
+ binding.signWith(deployment.getSigningKeyPair())
+ .signDocument();
+ }
+
+ binding.relayState("logout");
+
+ try {
+ SamlUtil.sendSaml(facade, deployment.getSingleLogoutServiceUrl(), binding, logoutBuilder.buildDocument(), deployment.getRequestBinding());
+ } catch (ProcessingException e) {
+ throw new RuntimeException(e);
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (ParsingException e) {
+ throw new RuntimeException(e);
+ }
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
+
+ protected AuthOutcome handleSamlRequest(String samlRequest, String relayState) {
+ SAMLDocumentHolder holder = null;
+ boolean postBinding = false;
+ if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
+ holder = SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
+ } else {
+ postBinding = true;
+ holder = SAMLRequestParser.parseRequestPostBinding(samlRequest);
+ }
+ RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
+ if (!facade.getRequest().getURI().toString().equals(requestAbstractType.getDestination())) {
+ throw new RuntimeException("destination not equal to request");
+ }
+ validateSamlSignature(holder, postBinding, GeneralConstants.SAML_REQUEST_KEY);
+
+ if (requestAbstractType instanceof LogoutRequestType) {
+ LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
+ return logoutRequest(logout, relayState);
+
+ } else {
+ throw new RuntimeException("unknown SAML request type");
+ }
+ }
+
+ protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) {
+ if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
+ sessionStore.logoutByPrincipal(request.getNameID().getValue());
+ } else {
+ sessionStore.logoutBySsoId(request.getSessionIndex());
+ }
+
+ String issuerURL = deployment.getIssuer();
+ SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+ builder.logoutRequestID(request.getID());
+ builder.destination(deployment.getSingleLogoutServiceUrl());
+ builder.issuer(issuerURL);
+ BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder().relayState(relayState);
+ if (deployment.isRequestsSigned()) {
+ binding.signWith(deployment.getSigningKeyPair())
+ .signDocument();
+ }
+
+
+ try {
+ SamlUtil.sendSaml(facade, deployment.getSingleLogoutServiceUrl(), binding, builder.buildDocument(),
+ deployment.getResponseBinding());
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ } catch (ProcessingException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return AuthOutcome.NOT_ATTEMPTED;
+
+ }
+
+
+ protected AuthOutcome handleSamlResponse(String samlResponse, String relayState) {
+ SAMLDocumentHolder holder = null;
+ boolean postBinding = false;
+ if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) {
+ holder = extractRedirectBindingResponse(samlResponse);
+ } else {
+ postBinding = true;
+ holder = extractPostBindingResponse(samlResponse);
+ }
+ StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
+ // validate destination
+ if (!facade.getRequest().getURI().toString().equals(statusResponse.getDestination())) {
+ throw new RuntimeException("destination not equal to request");
+ }
+ validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY);
+ if (statusResponse instanceof ResponseType) {
+ return handleLoginResponse((ResponseType)statusResponse);
+
+ } else {
+ // todo need to check that it is actually a LogoutResponse
+ return handleLogoutResponse(holder, statusResponse, relayState);
+ }
+
+ }
+
+ private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) {
+ if (deployment.isValidateSignatures()) {
+ try {
+ if (postBinding) {
+ verifyPostBindingSignature(holder.getSamlDocument(), deployment.getSignatureValidationKey());
+ } else {
+ verifyRedirectBindingSignature(deployment.getSignatureValidationKey(), paramKey);
+ }
+ } catch (VerificationException e) {
+ log.error("validation failed", e);
+ throw new RuntimeException("invalid document signature");
+ }
+ }
+ }
+
+ protected AuthOutcome handleLoginResponse(ResponseType responseType) {
+ AssertionType assertion = null;
+ try {
+ assertion = AssertionUtil.getAssertion(responseType, deployment.getAssertionDecryptionKey());
+ if (AssertionUtil.hasExpired(assertion)) {
+ return initiateLogin();
+ }
+ } catch (ParsingException e) {
+ throw new RuntimeException(e);
+ } catch (ProcessingException e) {
+ throw new RuntimeException(e);
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+
+ SubjectType subject = assertion.getSubject();
+ SubjectType.STSubType subType = subject.getSubType();
+ NameIDType subjectNameID = (NameIDType) subType.getBaseID();
+ String principalName = subjectNameID.getValue();
+
+ final Set<String> roles = new HashSet<>();
+ MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+ MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>();
+
+ Set<StatementAbstractType> statements = assertion.getStatements();
+ for (StatementAbstractType statement : statements) {
+ if (statement instanceof AttributeStatementType) {
+ AttributeStatementType attributeStatement = (AttributeStatementType) statement;
+ List<AttributeStatementType.ASTChoiceType> attList = attributeStatement.getAttributes();
+ for (AttributeStatementType.ASTChoiceType obj : attList) {
+ AttributeType attr = obj.getAttribute();
+ if (isRole(attr)) {
+ List<Object> attributeValues = attr.getAttributeValue();
+ if (attributeValues != null) {
+ for (Object attrValue : attributeValues) {
+ String role = getAttributeValue(attrValue);
+ roles.add(role);
+ }
+ }
+ } else {
+ List<Object> attributeValues = attr.getAttributeValue();
+ if (attributeValues != null) {
+ for (Object attrValue : attributeValues) {
+ String value = getAttributeValue(attrValue);
+ if (attr.getName() != null) {
+ attributes.add(attr.getName(), value);
+ }
+ if (attr.getFriendlyName() != null) {
+ attributes.add(attr.getFriendlyName(), value);
+ }
+ }
+ }
+ }
+
+ }
+ }
+ }
+ if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_ATTRIBUTE_NAME) {
+ if (deployment.getPrincipalAttributeName() != null) {
+ String attribute = attributes.getFirst(deployment.getPrincipalAttributeName());
+ if (attribute != null) principalName = attribute;
+ }
+ } else if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_FRIENDLY_ATTRIBUTE_NAME) {
+ if (deployment.getPrincipalAttributeName() != null) {
+ String attribute = friendlyAttributes.getFirst(deployment.getPrincipalAttributeName());
+ if (attribute != null) principalName = attribute;
+ }
+ }
+
+ AuthnStatementType authn = null;
+ for (Object statement : assertion.getStatements()) {
+ if (statement instanceof AuthnStatementType) {
+ authn = (AuthnStatementType)statement;
+ break;
+ }
+ }
+
+
+ final SamlPrincipal principal = new SamlPrincipal(principalName, principalName, subjectNameID.getFormat().toString(), attributes, friendlyAttributes);
+ String index = authn == null ? null : authn.getSessionIndex();
+ final String sessionIndex = index;
+ SamlSession account = new SamlSession() {
+ @Override
+ public SamlPrincipal getPrincipal() {
+ return principal;
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return roles;
+ }
+
+ @Override
+ public String getSessionIndex() {
+ return sessionIndex;
+ }
+ };
+ sessionStore.saveAccount(account);
+ completeAuthentication(account);
+
+ // redirect to original request, it will be restored
+ facade.getResponse().setHeader("Location", sessionStore.getRedirectUri());
+ facade.getResponse().setStatus(302);
+ facade.getResponse().end();
+
+
+ return AuthOutcome.AUTHENTICATED;
+ }
+
+ protected abstract void completeAuthentication(SamlSession account);
+
+ private String getAttributeValue(Object attrValue) {
+ String value;
+ if (attrValue instanceof String) {
+ value = (String)attrValue;
+ } else if (attrValue instanceof Node) {
+ Node roleNode = (Node) attrValue;
+ value = roleNode.getFirstChild().getNodeValue();
+ } else if (attrValue instanceof NameIDType) {
+ NameIDType nameIdType = (NameIDType) attrValue;
+ value = nameIdType.getValue();
+ } else
+ throw new RuntimeException("Unknown attribute value type: " + attrValue.getClass().getName());
+ return value;
+ }
+
+ protected boolean isRole(AttributeType attribute) {
+ return deployment.getRoleAttributeNames().contains(attribute.getName()) || deployment.getRoleAttributeFriendlyNames().contains(attribute.getFriendlyName());
+ }
+
+ protected AuthOutcome handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
+ boolean loggedIn = sessionStore.isLoggedIn();
+ if (!loggedIn || !"logout".equals(relayState)) {
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
+ sessionStore.logoutAccount();
+ return AuthOutcome.LOGGED_OUT;
+ }
+
+ protected SAMLDocumentHolder extractRedirectBindingResponse(String response) {
+ return SAMLRequestParser.parseRequestRedirectBinding(response);
+ }
+ protected SAMLDocumentHolder extractPostBindingResponse(String response) {
+ byte[] samlBytes = PostBindingUtil.base64Decode(response);
+ String xml = new String(samlBytes);
+ return SAMLRequestParser.parseResponseDocument(samlBytes);
+ }
+
+
+
+ protected AuthOutcome initiateLogin() {
+ challenge = new InitiateLogin(deployment, sessionStore);
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
+
+ protected boolean verifySSL() {
+ if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
+ log.warn("SSL is required to authenticate");
+ return true;
+ }
+ return false;
}
+
+ public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
+ SAML2Signature saml2Signature = new SAML2Signature();
+ try {
+ if (!saml2Signature.validate(document, publicKey)) {
+ throw new VerificationException("Invalid signature on document");
+ }
+ } catch (ProcessingException e) {
+ throw new VerificationException("Error validating signature", e);
+ }
+ }
+
+ public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
+ String request = facade.getRequest().getQueryParamValue(paramKey);
+ String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+ String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
+ String decodedAlgorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+
+ if (request == null) throw new VerificationException("SAM was null");
+ if (algorithm == null) throw new VerificationException("SigAlg was null");
+ if (signature == null) throw new VerificationException("Signature was null");
+
+ // Shibboleth doesn't sign the document for redirect binding.
+ // todo maybe a flag?
+
+ String relayState = facade.getRequest().getQueryParamValue(GeneralConstants.RELAY_STATE);
+ KeycloakUriBuilder builder = KeycloakUriBuilder.fromPath("/")
+ .queryParam(paramKey, request);
+ if (relayState != null) {
+ builder.queryParam(GeneralConstants.RELAY_STATE, relayState);
+ }
+ builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
+ String rawQuery = builder.build().getRawQuery();
+
+ try {
+ byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
+
+ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
+ Signature validator = signatureAlgorithm.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);
+ }
+ }
+
+
+
}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
new file mode 100755
index 0000000..1de9d72
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -0,0 +1,50 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.enums.SslRequired;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlDeployment {
+ enum Binding {
+ POST,
+ REDIRECT
+ }
+
+ public boolean isConfigured();
+ SslRequired getSslRequired();
+ String getSingleSignOnServiceUrl();
+ String getSingleLogoutServiceUrl();
+ String getIssuer();
+ String getNameIDPolicyFormat();
+ String getAssertionConsumerServiceUrl();
+ Binding getRequestBinding();
+ Binding getResponseBinding();
+ KeyPair getSigningKeyPair();
+ String getSignatureCanonicalizationMethod();
+ boolean isForceAuthentication();
+ boolean isRequestsSigned();
+
+ boolean isValidateSignatures();
+ PublicKey getSignatureValidationKey();
+ PrivateKey getAssertionDecryptionKey();
+
+ Set<String> getRoleAttributeNames();
+ Set<String> getRoleAttributeFriendlyNames();
+
+ enum PrincipalNamePolicy {
+ FROM_NAME_ID,
+ FROM_ATTRIBUTE_NAME,
+ FROM_FRIENDLY_ATTRIBUTE_NAME
+ }
+ PrincipalNamePolicy getPrincipalNamePolicy();
+ String getPrincipalAttributeName();
+
+
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java
new file mode 100755
index 0000000..b8ea94d
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java
@@ -0,0 +1,13 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.HttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlDeploymentContext {
+ public SamlDeployment resolveDeployment(HttpFacade facade) {
+ return null;
+ }
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
new file mode 100755
index 0000000..f336c92
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
@@ -0,0 +1,84 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.util.MultivaluedHashMap;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlPrincipal implements Serializable, Principal {
+ private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+ private MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>();
+ private String name;
+ private String samlSubject;
+ private String nameIDFormat;
+
+ public SamlPrincipal(String name, String samlSubject, String nameIDFormat, MultivaluedHashMap<String, String> attributes, MultivaluedHashMap<String, String> friendlyAttributes) {
+ this.name = name;
+ this.attributes = attributes;
+ this.friendlyAttributes = friendlyAttributes;
+ this.samlSubject = samlSubject;
+ this.nameIDFormat = nameIDFormat;
+ }
+
+ public SamlPrincipal() {
+ }
+
+ public String getSamlSubject() {
+ return samlSubject;
+ }
+
+ public String getNameIDFormat() {
+ return nameIDFormat;
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+
+ public List<String> getAttributes(String name) {
+ List<String> list = attributes.get(name);
+ if (list != null) {
+ return Collections.unmodifiableList(list);
+ } else {
+ return Collections.emptyList();
+ }
+
+ }
+ public List<String> getFriendlyAttributes(String friendlyName) {
+ List<String> list = friendlyAttributes.get(name);
+ if (list != null) {
+ return Collections.unmodifiableList(list);
+ } else {
+ return Collections.emptyList();
+ }
+
+ }
+
+ public String getAttribute(String name) {
+ return attributes.getFirst(name);
+ }
+
+ public String getFriendlyAttribute(String friendlyName) {
+ return friendlyAttributes.getFirst(friendlyName);
+ }
+
+ public Set<String> getAttributeNames() {
+ return Collections.unmodifiableSet(attributes.keySet());
+
+ }
+
+ public Set<String> getFriendlyNames() {
+ return Collections.unmodifiableSet(friendlyAttributes.keySet());
+
+ }
+
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java
new file mode 100755
index 0000000..b3a1833
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java
@@ -0,0 +1,15 @@
+package org.keycloak.adapters.saml;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlSession extends Serializable {
+ SamlPrincipal getPrincipal();
+ Set<String> getRoles();
+ String getSessionIndex();
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
new file mode 100755
index 0000000..6b6a83a
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
@@ -0,0 +1,20 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.AdapterSessionStore;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlSessionStore extends AdapterSessionStore {
+ boolean isLoggedIn();
+ SamlSession getAccount();
+ void saveAccount(SamlSession account);
+ String getRedirectUri();
+ void logoutAccount();
+ void logoutByPrincipal(String principal);
+ void logoutBySsoId(List<String> ssoIds);
+
+}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java
new file mode 100755
index 0000000..b0981db
--- /dev/null
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java
@@ -0,0 +1,31 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.w3c.dom.Document;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlUtil {
+ public static void sendSaml(HttpFacade httpFacade, String actionUrl, BaseSAML2BindingBuilder binding, Document document, SamlDeployment.Binding samlBinding) throws ProcessingException, ConfigurationException, IOException {
+ if (samlBinding == SamlDeployment.Binding.POST) {
+ String html = binding.postBinding(document).getHtmlRequest(actionUrl);
+ httpFacade.getResponse().setStatus(200);
+ httpFacade.getResponse().setHeader("Content-Type", "text/html");
+ httpFacade.getResponse().setHeader("Pragma", "no-cache");
+ httpFacade.getResponse().setHeader("Cache-Control", "no-cache, no-store");
+ httpFacade.getResponse().getOutputStream().write(html.getBytes());
+ httpFacade.getResponse().end();
+ } else {
+ String uri = binding.redirectBinding(document).requestURI(actionUrl).toString();
+ httpFacade.getResponse().setStatus(302);
+ httpFacade.getResponse().setHeader("Location", uri);
+ }
+ }
+}
saml/client-adapter/pom.xml 1(+1 -0)
diff --git a/saml/client-adapter/pom.xml b/saml/client-adapter/pom.xml
index 5242ff6..6ca4df9 100755
--- a/saml/client-adapter/pom.xml
+++ b/saml/client-adapter/pom.xml
@@ -15,5 +15,6 @@
<modules>
<module>core</module>
+ <module>undertow</module>
</modules>
</project>
saml/client-adapter/undertow/pom.xml 87(+87 -0)
diff --git a/saml/client-adapter/undertow/pom.xml b/saml/client-adapter/undertow/pom.xml
new file mode 100755
index 0000000..6049517
--- /dev/null
+++ b/saml/client-adapter/undertow/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.5.0.Final-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-undertow-saml-adapter</artifactId>
+ <name>Keycloak Undertow SAML Adapter</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter-spi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
new file mode 100755
index 0000000..6a21c5e
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.NotificationReceiver;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.api.SecurityNotification;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.Headers;
+import io.undertow.util.StatusCodes;
+import org.keycloak.adapters.AuthChallenge;
+import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.UndertowHttpFacade;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * Abstract base class for a Keycloak-enabled Undertow AuthenticationMechanism.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
+ public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
+ protected SamlDeploymentContext deploymentContext;
+ protected UndertowUserSessionManagement sessionManagement;
+ protected String logoutPage;
+ protected String errorPage;
+
+ public AbstractSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
+ String logoutPage,
+ String errorPage) {
+ this.deploymentContext = deploymentContext;
+ this.sessionManagement = sessionManagement;
+ this.errorPage = errorPage;
+ this.logoutPage = logoutPage;
+ }
+
+ @Override
+ public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
+ AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
+ if (challenge != null) {
+ if (challenge.errorPage() && errorPage != null) {
+ Integer code = servePage(exchange, errorPage);
+ return new ChallengeResult(true, code);
+ }
+ UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+ if (challenge.challenge(facade)) {
+ return new ChallengeResult(true, exchange.getResponseCode());
+ }
+ }
+ return new ChallengeResult(false);
+ }
+
+ protected Integer servePage(final HttpServerExchange exchange, final String location) {
+ sendRedirect(exchange, location);
+ return StatusCodes.TEMPORARY_REDIRECT;
+ }
+
+ static void sendRedirect(final HttpServerExchange exchange, final String location) {
+ // TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better handle this.
+ String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + location;
+ exchange.getResponseHeaders().put(Headers.LOCATION, loc);
+ }
+
+
+
+ protected void registerNotifications(final SecurityContext securityContext) {
+
+ final NotificationReceiver logoutReceiver = new NotificationReceiver() {
+ @Override
+ public void handleNotification(SecurityNotification notification) {
+ if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
+
+ HttpServerExchange exchange = notification.getExchange();
+ UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ SamlSessionStore sessionStore = getTokenStore(exchange, facade, deployment, securityContext);
+ sessionStore.logoutAccount();
+ }
+ };
+
+ securityContext.registerNotificationReceiver(logoutReceiver);
+ }
+
+ /**
+ * Call this inside your authenticate method.
+ */
+ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
+ UndertowHttpFacade facade = new UndertowHttpFacade(exchange);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (!deployment.isConfigured()) {
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+ SamlSessionStore sessionStore = getTokenStore(exchange, facade, deployment, securityContext);
+ UndertowSamlAuthenticator authenticator = new UndertowSamlAuthenticator(securityContext, facade,
+ deploymentContext.resolveDeployment(facade), sessionStore);
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ registerNotifications(securityContext);
+ return AuthenticationMechanismOutcome.AUTHENTICATED;
+ }
+ if (outcome == AuthOutcome.LOGGED_OUT) {
+ securityContext.logout();
+ if (logoutPage != null) {
+ sendRedirect(exchange, logoutPage);
+ exchange.setResponseCode(302);
+ exchange.endExchange();
+ }
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, challenge);
+ }
+
+ if (outcome == AuthOutcome.FAILED) {
+ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ protected abstract SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext);
+}
\ No newline at end of file
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java
new file mode 100755
index 0000000..ec2444a
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java
@@ -0,0 +1,25 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletSamlAuthMech extends AbstractSamlAuthMech {
+ public ServletSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
+ String logoutPage, String errorPage) {
+ super(deploymentContext, sessionManagement, logoutPage, errorPage);
+ }
+
+ @Override
+ protected SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext) {
+ return new ServletSamlSessionStore(exchange, sessionManagement, securityContext);
+ }
+}
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
new file mode 100755
index 0000000..696a29c
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
@@ -0,0 +1,140 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.idm.Account;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.servlet.spec.HttpSessionImpl;
+import io.undertow.servlet.util.SavedRequest;
+import io.undertow.util.Sessions;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+import org.keycloak.util.KeycloakUriBuilder;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.security.Principal;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletSamlSessionStore implements SamlSessionStore {
+ protected static Logger log = Logger.getLogger(SamlSessionStore.class);
+ public static final String SAML_REDIRECT_URI = "SAML_REDIRECT_URI";
+
+ private final HttpServerExchange exchange;
+ private final UndertowUserSessionManagement sessionManagement;
+ private final SecurityContext securityContext;
+
+ public ServletSamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement,
+ SecurityContext securityContext) {
+ this.exchange = exchange;
+ this.sessionManagement = sessionManagement;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public void logoutAccount() {
+ HttpSession session = getSession(false);
+ if (session != null) {
+ session.removeAttribute(SamlSession.class.getName());
+ session.removeAttribute(SAML_REDIRECT_URI);
+ }
+ }
+
+ @Override
+ public void logoutByPrincipal(String principal) {
+
+ }
+
+ @Override
+ public void logoutBySsoId(List<String> ssoIds) {
+
+ }
+
+ @Override
+ public boolean isLoggedIn() {
+ HttpSession session = getSession(false);
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ if (samlSession == null) {
+ log.debug("SamlSession was not in session, returning null");
+ return false;
+ }
+
+ Account undertowAccount = new Account() {
+ @Override
+ public Principal getPrincipal() {
+ return samlSession.getPrincipal();
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return samlSession.getRoles();
+ }
+ };
+ securityContext.authenticationComplete(undertowAccount, "KEYCLOAK-SAML", false);
+ restoreRequest();
+ return true;
+ }
+
+ @Override
+ public void saveAccount(SamlSession account) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSession session = getSession(true);
+ session.setAttribute(SamlSession.class.getName(), account);
+ sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
+
+ }
+
+ @Override
+ public SamlSession getAccount() {
+ HttpSession session = getSession(true);
+ return (SamlSession)session.getAttribute(SamlSession.class.getName());
+ }
+
+ @Override
+ public String getRedirectUri() {
+ final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true);
+ return (String)session.getAttribute(SAML_REDIRECT_URI);
+ }
+
+ @Override
+ public void saveRequest() {
+ SavedRequest.trySaveRequest(exchange);
+ final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true);
+ KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
+ .replaceQuery(exchange.getQueryString());
+ if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
+ String uri = uriBuilder.build().toString();
+
+ session.setAttribute(SAML_REDIRECT_URI, uri);
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ HttpSession session = getSession(false);
+ if (session == null) return false;
+ SavedRequest.tryRestoreRequest(exchange, session);
+ session.removeAttribute(SAML_REDIRECT_URI);
+ return false;
+ }
+
+ protected HttpSession getSession(boolean create) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ return req.getSession(create);
+ }
+}
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java
new file mode 100755
index 0000000..dfb2843
--- /dev/null
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java
@@ -0,0 +1,43 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.idm.Account;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.saml.SamlAuthenticator;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+
+import java.security.Principal;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowSamlAuthenticator extends SamlAuthenticator {
+ protected SecurityContext securityContext;
+
+ public UndertowSamlAuthenticator(SecurityContext securityContext, HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ super(facade, deployment, sessionStore);
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ protected void completeAuthentication(final SamlSession samlSession) {
+ Account undertowAccount = new Account() {
+ @Override
+ public Principal getPrincipal() {
+ return samlSession.getPrincipal();
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return samlSession.getRoles();
+ }
+ };
+ securityContext.authenticationComplete(undertowAccount, "KEYCLOAK-SAML", false);
+
+ }
+}
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java b/saml/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
index a39adf3..63b5f5e 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
@@ -164,7 +164,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
public Document getDocument() {
return document;
}
- public URI responseUri(String redirectUri, boolean asRequest) throws ConfigurationException, ProcessingException, IOException {
+ public URI generateURI(String redirectUri, boolean asRequest) throws ConfigurationException, ProcessingException, IOException {
String samlParameterName = GeneralConstants.SAML_RESPONSE_KEY;
if (asRequest) {
@@ -173,6 +173,23 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
return builder.generateRedirectUri(samlParameterName, redirectUri, document);
}
+
+ public URI requestURI(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
+ return builder.generateRedirectUri(GeneralConstants.SAML_REQUEST_KEY, actionUrl, document);
+ }
+ public URI responseURI(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
+ return builder.generateRedirectUri(GeneralConstants.SAML_RESPONSE_KEY, actionUrl, document);
+ }
+ }
+
+ public BaseRedirectBindingBuilder redirectBinding(Document document) throws ProcessingException {
+ return new BaseRedirectBindingBuilder(this, document);
+
+ }
+
+ public BasePostBindingBuilder postBinding(Document document) throws ProcessingException {
+ return new BasePostBindingBuilder(this, document);
+
}
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
index 0f6f5ed..eaf7d8c 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
@@ -21,15 +21,22 @@
*/
package org.keycloak.saml.processing.core.saml.v2.util;
+import org.keycloak.dom.saml.v2.assertion.EncryptedAssertionType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.saml.common.ErrorCodes;
import org.keycloak.saml.common.PicketLinkLogger;
import org.keycloak.saml.common.PicketLinkLoggerFactory;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.exceptions.fed.IssueInstantMissingException;
import org.keycloak.saml.common.util.DocumentUtil;
+import org.keycloak.saml.common.util.StaxParserUtil;
import org.keycloak.saml.common.util.StaxUtil;
+import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
+import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
import org.keycloak.dom.saml.v1.assertion.SAML11AssertionType;
import org.keycloak.dom.saml.v1.assertion.SAML11AttributeStatementType;
@@ -45,13 +52,17 @@ import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.assertion.SubjectType.STSubType;
+import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
+import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.xml.datatype.XMLGregorianCalendar;
+import javax.xml.namespace.QName;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
@@ -510,4 +521,48 @@ public class AssertionUtil {
}
return roles;
}
+
+ public static AssertionType getAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
+ List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
+
+ if (assertions.isEmpty()) {
+ throw new ProcessingException("No assertion from response.");
+ }
+
+ ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
+ EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
+
+ if (encryptedAssertion != null) {
+ decryptAssertion(responseType, privateKey);
+
+ }
+ return responseType.getAssertions().get(0).getAssertion();
+ }
+
+ public static ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
+ SAML2Response saml2Response = new SAML2Response();
+
+ Document doc = saml2Response.convert(responseType);
+ Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
+
+ if (enc == null) {
+ throw new ProcessingException("No encrypted assertion found.");
+ }
+
+ String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
+ Document newDoc = DocumentUtil.createDocument();
+ Node importedNode = newDoc.importNode(enc, true);
+ newDoc.appendChild(importedNode);
+
+ Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
+ SAMLParser parser = new SAMLParser();
+
+ JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
+ AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
+ .getNodeAsStream(decryptedDocumentElement)));
+
+ responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
+
+ return responseType;
+ }
}
\ No newline at end of file
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
index 36e5804..3afe813 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
@@ -29,8 +29,6 @@ public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2
protected Response buildResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
String str = builder.buildHtmlPostResponse(responseDoc, actionUrl, asRequest);
- 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();
@@ -53,7 +51,7 @@ public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2
}
private Response response(String redirectUri, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
- URI uri = responseUri(redirectUri, asRequest);
+ URI uri = generateURI(redirectUri, asRequest);
if (logger.isDebugEnabled()) logger.trace("redirect-binding uri: " + uri.toString());
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
index bc18f3d..d9d937e 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -14,7 +14,6 @@ import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.security.PublicKey;
-import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.Certificate;
@@ -64,23 +63,23 @@ public class SamlProtocolUtils {
return cert.getPublicKey();
}
- public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation) throws VerificationException {
+ public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation, String paramKey) throws VerificationException {
MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
- String request = encodedParams.getFirst(GeneralConstants.SAML_REQUEST_KEY);
+ String request = encodedParams.getFirst(paramKey);
String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
String decodedAlgorithm = uriInformation.getQueryParameters(true).getFirst(GeneralConstants.SAML_SIG_ALG_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");
+ if (request == null) throw new VerificationException("SAM was null");
+ if (algorithm == null) throw new VerificationException("SigAlg was null");
+ if (signature == null) throw new VerificationException("Signature was null");
// Shibboleth doesn't sign the document for redirect binding.
// todo maybe a flag?
UriBuilder builder = UriBuilder.fromPath("/")
- .queryParam(GeneralConstants.SAML_REQUEST_KEY, request);
+ .queryParam(paramKey, request);
if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
builder.queryParam(GeneralConstants.RELAY_STATE, encodedParams.getFirst(GeneralConstants.RELAY_STATE));
}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 0780e94..9521e0c 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -460,7 +460,7 @@ public class SamlService {
return;
}
PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
- SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
+ SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
}