keycloak-uncached
KEYCLOAK-2107 - support IsPassive mode in SAML SP adapter library KEYCLOAK-2075 …
Changes
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java 7(+3 -4)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java 1(+1 -0)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java 42(+23 -19)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java 29(+17 -12)
saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java 67(+40 -27)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java 35(+19 -16)
testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java 6(+6 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java 31(+31 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java 10(+6 -4)
Details
diff --git a/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter-config.xml b/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter-config.xml
index cce3faf..5335362 100755
--- a/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter-config.xml
+++ b/docbook/saml-adapter-docs/reference/en/en-US/modules/adapter-config.xml
@@ -11,7 +11,8 @@
sslPolicy="EXTERNAL"
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
logoutPage="/logout.jsp"
- forceAuthentication="false">
+ forceAuthentication="false"
+ isPassive="false">
<Keys>
<Key signing="true" >
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
@@ -63,7 +64,8 @@
<SP entityID="sp"
sslPolicy="ssl"
nameIDPolicyFormat="format"
- forceAuthentication="true">
+ forceAuthentication="true"
+ isPassive="false">
...
</SP>]]></programlisting>
<para>
@@ -106,12 +108,23 @@
<listitem>
<para>
SAML clients can request that a user is re-authenticated even if
- they are already logged in at the IDP. Set this to true if you
+ they are already logged in at the IDP. Set this to <literal>true</literal> if you
want this.
<emphasis>OPTIONAL.</emphasis>. Set to <literal>false</literal> by default.
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>isPassive</term>
+ <listitem>
+ <para>
+ SAML clients can request that a user is never asked to authenticate even if
+ they are not logged in at the IDP. Set this to <literal>true</literal> if you want this.
+ Do not use together with <literal>forceAuthentication</literal> as they are opposite.
+ <emphasis>OPTIONAL.</emphasis>. Set to <literal>false</literal> by default.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</para>
</section>
diff --git a/docbook/saml-adapter-docs/reference/en/en-US/modules/tomcat-adapter.xml b/docbook/saml-adapter-docs/reference/en/en-US/modules/tomcat-adapter.xml
index f6db1b0..37ffbe1 100755
--- a/docbook/saml-adapter-docs/reference/en/en-US/modules/tomcat-adapter.xml
+++ b/docbook/saml-adapter-docs/reference/en/en-US/modules/tomcat-adapter.xml
@@ -1,5 +1,5 @@
<chapter id="tomcat-adapter">
- <title>Tomcat 6, 7 and 8 SAML dapters</title>
+ <title>Tomcat 6, 7 and 8 SAML adapters</title>
<para>
To be able to secure WAR apps deployed on Tomcat 6, 7 and 8 you must install the Keycloak Tomcat 6, 7 or 8 SAML adapter
into your Tomcat installation. You then have to provide some extra configuration in each WAR you deploy to
diff --git a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthOutcome.java b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthOutcome.java
index 60a34b2..2479884 100755
--- a/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthOutcome.java
+++ b/integration/adapter-spi/src/main/java/org/keycloak/adapters/spi/AuthOutcome.java
@@ -5,8 +5,5 @@ package org.keycloak.adapters.spi;
* @version $Revision: 1 $
*/
public enum AuthOutcome {
- NOT_ATTEMPTED,
- FAILED,
- AUTHENTICATED,
- LOGGED_OUT
+ NOT_ATTEMPTED, FAILED, AUTHENTICATED, NOT_AUTHENTICATED, LOGGED_OUT
}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
index 404fc58..91a016c 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -5,17 +5,17 @@ package org.keycloak.adapters.saml.config.parsers;
* @version $Revision: 1 $
*/
public class ConfigXmlConstants {
- public static final String KEYCLOAK_SAML_ADAPTER ="keycloak-saml-adapter";
- public static final String SP_ELEMENT="SP";
+ public static final String KEYCLOAK_SAML_ADAPTER = "keycloak-saml-adapter";
+ public static final String SP_ELEMENT = "SP";
public static final String ENTITY_ID_ATTR = "entityID";
public static final String SSL_POLICY_ATTR = "sslPolicy";
public static final String NAME_ID_POLICY_FORMAT_ATTR = "nameIDPolicyFormat";
public static final String FORCE_AUTHENTICATION_ATTR = "forceAuthentication";
+ public static final String IS_PASSIVE_ATTR = "isPassive";
public static final String SIGNATURE_ALGORITHM_ATTR = "signatureAlgorithm";
public static final String SIGNATURE_CANONICALIZATION_METHOD_ATTR = "signatureCanonicalizationMethod";
public static final String LOGOUT_PAGE_ATTR = "logoutPage";
-
public static final String KEYS_ELEMENT = "Keys";
public static final String KEY_ELEMENT = "Key";
public static final String SIGNING_ATTR = "signing";
@@ -36,7 +36,6 @@ public class ConfigXmlConstants {
public static final String POLICY_ATTR = "policy";
public static final String ATTRIBUTE_ATTR = "attribute";
-
public static final String ROLE_IDENTIFIERS_ELEMENT = "RoleIdentifiers";
public static final String ATTRIBUTE_ELEMENT = "Attribute";
public static final String NAME_ATTR = "name";
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
index b200b61..5de2072 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -41,6 +41,7 @@ public class DeploymentBuilder {
deployment.setConfigured(true);
deployment.setEntityID(sp.getEntityID());
deployment.setForceAuthentication(sp.isForceAuthentication());
+ deployment.setIsPassive(sp.isIsPassive());
deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat());
deployment.setLogoutPage(sp.getLogoutPage());
deployment.setSignatureCanonicalizationMethod(sp.getIdp().getSignatureCanonicalizationMethod());
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
index 3446f20..14b04e1 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -1,21 +1,22 @@
package org.keycloak.adapters.saml.config.parsers;
-import org.keycloak.adapters.saml.config.IDP;
-import org.keycloak.adapters.saml.config.Key;
-import org.keycloak.adapters.saml.config.SP;
-import org.keycloak.saml.common.exceptions.ParsingException;
-import org.keycloak.saml.common.parsers.AbstractParser;
-import org.keycloak.saml.common.util.StaxParserUtil;
-import org.keycloak.common.util.StringPropertyReplacer;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.common.util.StringPropertyReplacer;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -25,13 +26,16 @@ public class SPXmlParser extends AbstractParser {
public static String getAttributeValue(StartElement startElement, String tag) {
String str = StaxParserUtil.getAttributeValue(startElement, tag);
- if (str != null) return StringPropertyReplacer.replaceProperties(str);
- else return str;
+ if (str != null)
+ return StringPropertyReplacer.replaceProperties(str);
+ else
+ return str;
}
public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
String result = getAttributeValue(startElement, tag);
- if (result == null) return defaultValue;
+ if (result == null)
+ return defaultValue;
return Boolean.valueOf(result);
}
@@ -41,11 +45,11 @@ public class SPXmlParser extends AbstractParser {
public static String getElementText(XMLEventReader xmlEventReader) throws ParsingException {
String result = StaxParserUtil.getElementText(xmlEventReader);
- if (result != null) result = StringPropertyReplacer.replaceProperties(result);
+ if (result != null)
+ result = StringPropertyReplacer.replaceProperties(result);
return result;
}
-
@Override
public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
@@ -61,6 +65,7 @@ public class SPXmlParser extends AbstractParser {
sp.setLogoutPage(getAttributeValue(startElement, ConfigXmlConstants.LOGOUT_PAGE_ATTR));
sp.setNameIDPolicyFormat(getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
sp.setForceAuthentication(getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
+ sp.setIsPassive(getBooleanAttributeValue(startElement, ConfigXmlConstants.IS_PASSIVE_ATTR));
while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
if (xmlEvent == null)
@@ -79,7 +84,7 @@ public class SPXmlParser extends AbstractParser {
String tag = StaxParserUtil.getStartElementName(startElement);
if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
KeysXmlParser parser = new KeysXmlParser();
- List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
+ List<Key> keys = (List<Key>) parser.parse(xmlEventReader);
sp.setKeys(keys);
} else if (tag.equals(ConfigXmlConstants.PRINCIPAL_NAME_MAPPING_ELEMENT)) {
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
@@ -98,7 +103,7 @@ public class SPXmlParser extends AbstractParser {
parseRoleMapping(xmlEventReader, sp);
} else if (tag.equals(ConfigXmlConstants.IDP_ELEMENT)) {
IDPXmlParser parser = new IDPXmlParser();
- IDP idp = (IDP)parser.parse(xmlEventReader);
+ IDP idp = (IDP) parser.parse(xmlEventReader);
sp.setIdp(idp);
} else {
StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
@@ -108,7 +113,7 @@ public class SPXmlParser extends AbstractParser {
return sp;
}
- protected void parseRoleMapping(XMLEventReader xmlEventReader, SP sp) throws ParsingException {
+ protected void parseRoleMapping(XMLEventReader xmlEventReader, SP sp) throws ParsingException {
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.validate(startElement, ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT);
Set<String> roleAttributes = new HashSet<>();
@@ -144,7 +149,6 @@ public class SPXmlParser extends AbstractParser {
sp.setRoleAttributes(roleAttributes);
}
-
@Override
public boolean supports(QName qname) {
return false;
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/SP.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
index f37f930..5203b13 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
@@ -33,6 +33,7 @@ public class SP implements Serializable {
private String entityID;
private String sslPolicy;
private boolean forceAuthentication;
+ private boolean isPassive;
private String logoutPage;
private List<Key> keys;
private String nameIDPolicyFormat;
@@ -64,6 +65,14 @@ public class SP implements Serializable {
this.forceAuthentication = forceAuthentication;
}
+ public boolean isIsPassive() {
+ return isPassive;
+ }
+
+ public void setIsPassive(boolean isPassive) {
+ this.isPassive = isPassive;
+ }
+
public List<Key> getKeys() {
return keys;
}
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
index 26fad92..7aab095 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -1,13 +1,13 @@
package org.keycloak.adapters.saml;
-import org.keycloak.common.enums.SslRequired;
-import org.keycloak.saml.SignatureAlgorithm;
-
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Set;
+import org.keycloak.common.enums.SslRequired;
+import org.keycloak.saml.SignatureAlgorithm;
+
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@@ -31,7 +31,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
return validateResponseSignature;
}
- @Override
+ @Override
public Binding getRequestBinding() {
return requestBinding;
}
@@ -97,7 +97,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
return signResponse;
}
- @Override
+ @Override
public Binding getRequestBinding() {
return requestBinding;
}
@@ -150,12 +150,8 @@ public class DefaultSamlDeployment implements SamlDeployment {
}
}
-
-
public static class DefaultIDP implements IDP {
-
-
private String entityID;
private PublicKey signatureValidationKey;
private SingleSignOnService singleSignOnService;
@@ -204,6 +200,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
private String entityID;
private String nameIDPolicyFormat;
private boolean forceAuthentication;
+ private boolean isPassive;
private PrivateKey decryptionKey;
private KeyPair signingKeyPair;
private String assertionConsumerServiceUrl;
@@ -214,7 +211,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
private SignatureAlgorithm signatureAlgorithm;
private String signatureCanonicalizationMethod;
-
@Override
public IDP getIDP() {
return idp;
@@ -244,6 +240,11 @@ public class DefaultSamlDeployment implements SamlDeployment {
public boolean isForceAuthentication() {
return forceAuthentication;
}
+
+ @Override
+ public boolean isIsPassive() {
+ return isPassive;
+ }
@Override
public PrivateKey getDecryptionKey() {
@@ -265,7 +266,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
return roleAttributeNames;
}
- @Override
+ @Override
public PrincipalNamePolicy getPrincipalNamePolicy() {
return principalNamePolicy;
}
@@ -298,6 +299,10 @@ public class DefaultSamlDeployment implements SamlDeployment {
public void setForceAuthentication(boolean forceAuthentication) {
this.forceAuthentication = forceAuthentication;
}
+
+ public void setIsPassive(boolean isPassive){
+ this.isPassive = isPassive;
+ }
public void setDecryptionKey(PrivateKey decryptionKey) {
this.decryptionKey = decryptionKey;
@@ -332,7 +337,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
this.logoutPage = logoutPage;
}
- @Override
+ @Override
public String getSignatureCanonicalizationMethod() {
return signatureCanonicalizationMethod;
}
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
index 158bae8..4c7cbab 100755
--- 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
@@ -48,7 +48,7 @@ public class InitiateLogin implements AuthChallenge {
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
.destination(destinationUrl)
.issuer(issuerURL)
- .forceAuthn(deployment.isForceAuthentication())
+ .forceAuthn(deployment.isForceAuthentication()).isPassive(deployment.isIsPassive())
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
if (deployment.getIDP().getSingleSignOnService().getResponseBinding() != null) {
String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
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 1d289d7..919ec35 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
@@ -17,7 +17,9 @@ 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.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
import org.keycloak.saml.BaseSAML2BindingBuilder;
import org.keycloak.saml.SAML2LogoutRequestBuilder;
import org.keycloak.saml.SAML2LogoutResponseBuilder;
@@ -27,6 +29,7 @@ import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.Base64;
+import org.keycloak.saml.common.util.StringUtil;
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;
@@ -207,7 +210,26 @@ public abstract class SamlAuthenticator {
log.error("Request URI does not match SAML request destination");
return AuthOutcome.FAILED;
}
+
if (statusResponse instanceof ResponseType) {
+
+ //validate status
+ StatusType status = statusResponse.getStatus();
+ if(status == null){
+ log.error("Missing Status in SAML response");
+ return AuthOutcome.FAILED;
+ }
+ if(!checkStatusCodeValue(status.getStatusCode(), JBossSAMLURIConstants.STATUS_SUCCESS.get())){
+ if(checkStatusCodeValue(status.getStatusCode(), JBossSAMLURIConstants.STATUS_RESPONDER.get()) && checkStatusCodeValue(status.getStatusCode().getStatusCode(), JBossSAMLURIConstants.STATUS_NO_PASSIVE.get())){
+ // KEYCLOAK-2107 - handle user not authenticated due passive mode
+ log.debug("Not authenticated due passive mode Status found in SAML response: " + status.toString());
+ return AuthOutcome.NOT_AUTHENTICATED;
+ }
+ log.error("Error Status found in SAML response: " + status.toString());
+ return AuthOutcome.FAILED;
+
+ }
+
try {
if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) {
try {
@@ -287,7 +309,16 @@ public abstract class SamlAuthenticator {
}
}
+ private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
+ if(statusCode != null && statusCode.getValue()!=null){
+ String v = statusCode.getValue().toString();
+ return expectedValue.equals(v);
+ }
+ return false;
+ }
+
protected AuthOutcome handleLoginResponse(ResponseType responseType) {
+
AssertionType assertion = null;
try {
assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey());
@@ -295,7 +326,7 @@ public abstract class SamlAuthenticator {
return initiateLogin();
}
} catch (Exception e) {
- log.error("Error extracting SAML assertion, e");
+ log.error("Error extracting SAML assertion: " + e.getMessage());
challenge = new AuthChallenge() {
@Override
public boolean challenge(HttpFacade exchange) {
@@ -434,9 +465,9 @@ public abstract class SamlAuthenticator {
return SAMLRequestParser.parseRequestRedirectBinding(response);
}
+
protected SAMLDocumentHolder extractPostBindingResponse(String response) {
byte[] samlBytes = PostBindingUtil.base64Decode(response);
- String xml = new String(samlBytes);
return SAMLRequestParser.parseResponseDocument(samlBytes);
}
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
index 0728447..8c53236 100755
--- 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
@@ -56,6 +56,7 @@ public interface SamlDeployment {
String getEntityID();
String getNameIDPolicyFormat();
boolean isForceAuthentication();
+ boolean isIsPassive();
PrivateKey getDecryptionKey();
KeyPair getSigningKeyPair();
String getSignatureCanonicalizationMethod();
diff --git a/saml/client-adapter/core/src/main/resources/schema/keycloak_saml_adapter_1_6.xsd b/saml/client-adapter/core/src/main/resources/schema/keycloak_saml_adapter_1_6.xsd
index 534c9ae..d3e55f9 100755
--- a/saml/client-adapter/core/src/main/resources/schema/keycloak_saml_adapter_1_6.xsd
+++ b/saml/client-adapter/core/src/main/resources/schema/keycloak_saml_adapter_1_6.xsd
@@ -33,6 +33,7 @@
<xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional"/>
<xs:attribute name="logoutPage" type="xs:string" use="optional"/>
<xs:attribute name="forceAuthentication" type="xs:boolean" use="optional"/>
+ <xs:attribute name="isPassive" type="xs:boolean" use="optional"/>
</xs:complexType>
<xs:complexType name="keys-type">
diff --git a/saml/client-adapter/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java b/saml/client-adapter/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java
index c92fec6..5a6e037 100755
--- a/saml/client-adapter/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java
+++ b/saml/client-adapter/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java
@@ -68,6 +68,7 @@ public class XmlParserTest {
Assert.assertEquals("ssl", sp.getSslPolicy());
Assert.assertEquals("format", sp.getNameIDPolicyFormat());
Assert.assertTrue(sp.isForceAuthentication());
+ Assert.assertTrue(sp.isIsPassive());
Assert.assertEquals(2, sp.getKeys().size());
Key signing = sp.getKeys().get(0);
Assert.assertTrue(signing.isSigning());
diff --git a/saml/client-adapter/core/src/test/resources/keycloak-saml.xml b/saml/client-adapter/core/src/test/resources/keycloak-saml.xml
index ef910dc..ae8c96d 100755
--- a/saml/client-adapter/core/src/test/resources/keycloak-saml.xml
+++ b/saml/client-adapter/core/src/test/resources/keycloak-saml.xml
@@ -2,7 +2,8 @@
<SP entityID="sp"
sslPolicy="ssl"
nameIDPolicyFormat="format"
- forceAuthentication="true">
+ forceAuthentication="true"
+ isPassive="true">
<Keys>
<Key signing="true" >
<KeyStore file="file" resource="cp" password="pw">
diff --git a/saml/client-adapter/core/src/test/resources/keycloak-saml2.xml b/saml/client-adapter/core/src/test/resources/keycloak-saml2.xml
index ee6388d..7662e61 100755
--- a/saml/client-adapter/core/src/test/resources/keycloak-saml2.xml
+++ b/saml/client-adapter/core/src/test/resources/keycloak-saml2.xml
@@ -4,7 +4,8 @@
nameIDPolicyFormat="format"
signatureAlgorithm=""
signatureCanonicalizationMethod=""
- forceAuthentication="true">
+ forceAuthentication="true"
+ isPassive="true">
<Keys>
<Key signing="true" >
<KeyStore file="file" resource="cp" password="pw">
diff --git a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
index b2a0ded..ac95784 100755
--- a/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
+++ b/saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
@@ -1,18 +1,11 @@
package org.keycloak.adapters.saml.servlet;
-import org.keycloak.adapters.spi.AuthChallenge;
-import org.keycloak.adapters.spi.AuthOutcome;
-import org.keycloak.adapters.spi.InMemorySessionIdMapper;
-import org.keycloak.adapters.spi.SessionIdMapper;
-import org.keycloak.adapters.saml.DefaultSamlDeployment;
-import org.keycloak.adapters.saml.SamlAuthenticator;
-import org.keycloak.adapters.saml.SamlDeployment;
-import org.keycloak.adapters.saml.SamlDeploymentContext;
-import org.keycloak.adapters.saml.SamlSession;
-import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
-import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
-import org.keycloak.adapters.servlet.ServletHttpFacade;
-import org.keycloak.saml.common.exceptions.ParsingException;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -24,12 +17,20 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+
+import org.keycloak.adapters.saml.DefaultSamlDeployment;
+import org.keycloak.adapters.saml.SamlAuthenticator;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
+import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
+import org.keycloak.adapters.servlet.ServletHttpFacade;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.InMemorySessionIdMapper;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.saml.common.exceptions.ParsingException;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -38,7 +39,7 @@ import java.util.logging.Logger;
public class SamlFilter implements Filter {
protected SamlDeploymentContext deploymentContext;
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
- private final static Logger log = Logger.getLogger(""+SamlFilter.class);
+ private final static Logger log = Logger.getLogger("" + SamlFilter.class);
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
@@ -46,12 +47,14 @@ public class SamlFilter implements Filter {
if (configResolverClass != null) {
try {
throw new RuntimeException("Not implemented yet");
- //KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
- //deploymentContext = new SamlDeploymentContext(configResolver);
- //log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
+ // KeycloakConfigResolver configResolver = (KeycloakConfigResolver)
+ // context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
+ // deploymentContext = new SamlDeploymentContext(configResolver);
+ // log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.",
+ // configResolverClass);
} catch (Exception ex) {
- log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
- //deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+ log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[] { configResolverClass, ex.getMessage() });
+ // deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
}
} else {
String fp = filterConfig.getInitParameter("keycloak.config.file");
@@ -65,7 +68,8 @@ public class SamlFilter implements Filter {
} else {
String path = "/WEB-INF/keycloak-saml.xml";
String pathParam = filterConfig.getInitParameter("keycloak.config.path");
- if (pathParam != null) path = pathParam;
+ if (pathParam != null)
+ path = pathParam;
is = filterConfig.getServletContext().getResourceAsStream(path);
}
final SamlDeployment deployment;
@@ -105,7 +109,6 @@ public class SamlFilter implements Filter {
}
FilterSamlSessionStore tokenStore = new FilterSamlSessionStore(request, facade, 100000, idMapper);
-
SamlAuthenticator authenticator = new SamlAuthenticator(facade, deployment, tokenStore) {
@Override
protected void completeAuthentication(SamlSession account) {
@@ -139,6 +142,16 @@ public class SamlFilter implements Filter {
challenge.challenge(facade);
return;
}
+
+ if (deployment.isIsPassive() && outcome == AuthOutcome.NOT_AUTHENTICATED) {
+ log.fine("PASSIVE_NOT_AUTHENTICATED");
+ if (facade.isEnded()) {
+ return;
+ }
+ chain.doFilter(req, res);
+ return;
+ }
+
if (!facade.isEnded()) {
response.sendError(403);
}
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
index 8f1929a..3d632dd 100755
--- 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
@@ -16,6 +16,15 @@
*/
package org.keycloak.adapters.saml.undertow;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.undertow.UndertowHttpFacade;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.NotificationReceiver;
import io.undertow.security.api.SecurityContext;
@@ -24,14 +33,6 @@ import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;
import io.undertow.util.Headers;
import io.undertow.util.StatusCodes;
-import org.keycloak.adapters.spi.AuthChallenge;
-import org.keycloak.adapters.spi.AuthOutcome;
-import org.keycloak.adapters.spi.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.
@@ -44,8 +45,7 @@ public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
protected UndertowUserSessionManagement sessionManagement;
protected String errorPage;
- public AbstractSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
- String errorPage) {
+ public AbstractSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement, String errorPage) {
this.deploymentContext = deploymentContext;
this.sessionManagement = sessionManagement;
this.errorPage = errorPage;
@@ -69,19 +69,19 @@ public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
}
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.
+ // 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;
+ if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT)
+ return;
HttpServerExchange exchange = notification.getExchange();
UndertowHttpFacade facade = createFacade(exchange);
@@ -104,13 +104,16 @@ public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
}
SamlSessionStore sessionStore = getTokenStore(exchange, facade, deployment, securityContext);
- UndertowSamlAuthenticator authenticator = new UndertowSamlAuthenticator(securityContext, facade,
- deploymentContext.resolveDeployment(facade), sessionStore);
+ 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.NOT_AUTHENTICATED) {
+ // we are in passive mode and user is not authenticated, let app server to try another auth mechanism
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
if (outcome == AuthOutcome.LOGGED_OUT) {
securityContext.logout();
if (deployment.getLogoutPage() != null) {
diff --git a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java
index 482834b..826928a 100755
--- a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java
+++ b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusCodeType.java
@@ -80,4 +80,10 @@ public class StatusCodeType implements Serializable {
public void setValue(URI value) {
this.value = value;
}
+
+ @Override
+ public String toString() {
+ return "StatusCodeType [value=" + value + ", statusCode=" + statusCode + "]";
+ }
+
}
\ No newline at end of file
diff --git a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java
index 2e2eab9..ec92dd4 100755
--- a/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java
+++ b/saml/saml-core/src/main/java/org/keycloak/dom/saml/v2/protocol/StatusType.java
@@ -100,4 +100,9 @@ public class StatusType implements Serializable {
this.statusDetail = value;
}
+ @Override
+ public String toString() {
+ return "StatusType [statusCode=" + statusCode + ", statusMessage=" + statusMessage + ", statusDetail=" + statusDetail + "]";
+ }
+
}
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java b/saml/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
index 40db78c..b043a12 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
@@ -17,16 +17,16 @@
*/
package org.keycloak.saml;
+import java.net.URI;
+
+import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator;
import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
-import org.keycloak.dom.saml.v2.assertion.NameIDType;
-import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.w3c.dom.Document;
-import java.net.URI;
-
/**
* @author pedroigor
*/
@@ -64,6 +64,11 @@ public class SAML2AuthnRequestBuilder {
return this;
}
+ public SAML2AuthnRequestBuilder isPassive(boolean isPassive) {
+ this.authnRequestType.setIsPassive(isPassive);
+ return this;
+ }
+
public SAML2AuthnRequestBuilder nameIdPolicy(SAML2NameIDPolicyBuilder nameIDPolicy) {
this.authnRequestType.setNameIDPolicy(nameIDPolicy.build());
return this;
@@ -74,7 +79,7 @@ public class SAML2AuthnRequestBuilder {
return this;
}
- public Document toDocument() {
+ public Document toDocument() {
try {
AuthnRequestType authnRequestType = this.authnRequestType;
diff --git a/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java b/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
index 2373656..a3d0800 100755
--- a/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
+++ b/saml/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
@@ -1,21 +1,28 @@
package org.keycloak.saml;
+<<<<<<< Upstream, based on keycloak/master
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.dom.saml.v2.protocol.StatusType;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+=======
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+>>>>>>> 9408d08 KEYCLOAK-2107 - support IsPassive mode in SAML SP adapter library KEYCLOAK-2075 - added integration tests for both server and adapter side
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.response.SAML2Response;
import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator;
import org.keycloak.saml.processing.core.saml.v2.factories.JBossSAMLAuthnResponseFactory;
+<<<<<<< Upstream, based on keycloak/master
import org.keycloak.saml.processing.core.saml.v2.holders.IDPInfoHolder;
import org.keycloak.saml.processing.core.saml.v2.holders.IssuerInfoHolder;
import org.keycloak.saml.processing.core.saml.v2.holders.SPInfoHolder;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
+=======
+>>>>>>> 9408d08 KEYCLOAK-2107 - support IsPassive mode in SAML SP adapter library KEYCLOAK-2075 - added integration tests for both server and adapter side
import org.w3c.dom.Document;
import java.net.URI;
@@ -45,7 +52,6 @@ public class SAML2ErrorResponseBuilder {
return this;
}
-
public Document buildDocument() throws ProcessingException {
try {
@@ -65,8 +71,6 @@ public class SAML2ErrorResponseBuilder {
} catch (ParsingException e) {
throw new ProcessingException(e);
}
-
}
-
}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 6729c90..3a30c2e 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -147,33 +147,36 @@ public class SamlProtocol implements LoginProtocol {
@Override
public Response sendError(ClientSessionModel clientSession, Error error) {
- RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
- session.sessions().removeClientSession(realm, clientSession);
- if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
- if (error == Error.CANCELLED_BY_USER) {
- UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
- Map<String, String> params = new HashMap<>();
- params.put("realm", realm.getName());
- params.put("protocol", LOGIN_PROTOCOL);
- params.put("client", clientSession.getClient().getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
- URI redirect = builder.buildFromMap(params);
- return Response.status(302).location(redirect).build();
- } else {
- return ErrorPage.error(session, translateErrorToIdpInitiatedErrorMessage(error));
- }
- } else {
- SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(clientSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get());
- try {
- JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(clientSession.getNote(GeneralConstants.RELAY_STATE));
- Document document = builder.buildDocument();
- if (isPostBinding(clientSession)) {
- return binding.postBinding(document).response(clientSession.getRedirectUri());
+ try {
+ if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
+ if (error == Error.CANCELLED_BY_USER) {
+ UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
+ Map<String, String> params = new HashMap<>();
+ params.put("realm", realm.getName());
+ params.put("protocol", LOGIN_PROTOCOL);
+ params.put("client", clientSession.getClient().getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
+ URI redirect = builder.buildFromMap(params);
+ return Response.status(302).location(redirect).build();
} else {
- return binding.redirectBinding(document).response(clientSession.getRedirectUri());
+ return ErrorPage.error(session, translateErrorToIdpInitiatedErrorMessage(error));
+ }
+ } else {
+ SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(clientSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get());
+ try {
+ JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(clientSession.getNote(GeneralConstants.RELAY_STATE));
+ Document document = builder.buildDocument();
+ if (isPostBinding(clientSession)) {
+ return binding.postBinding(document).response(clientSession.getRedirectUri());
+ } else {
+ return binding.redirectBinding(document).response(clientSession.getRedirectUri());
+ }
+ } catch (Exception e) {
+ return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
}
- } catch (Exception e) {
- return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
}
+ } finally {
+ RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
+ session.sessions().removeClientSession(realm, clientSession);
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java
index 0d20f7a..9f23b70 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTest.java
@@ -19,6 +19,7 @@ public class SamlAdapterTest {
ClassLoader classLoader = SamlAdapterTest.class.getClassLoader();
initializeSamlSecuredWar("/keycloak-saml/simple-post", "/sales-post", "post.war", classLoader);
+ initializeSamlSecuredWar("/keycloak-saml/simple-post-passive", "/sales-post-passive", "post-passive.war", classLoader);
initializeSamlSecuredWar("/keycloak-saml/signed-post", "/sales-post-sig", "post-sig.war", classLoader);
initializeSamlSecuredWar("/keycloak-saml/signed-post-email", "/sales-post-sig-email", "post-sig-email.war", classLoader);
initializeSamlSecuredWar("/keycloak-saml/signed-post-transient", "/sales-post-sig-transient", "post-sig-transient.war", classLoader);
@@ -97,6 +98,11 @@ public class SamlAdapterTest {
}
@Test
+ public void testPostPassiveLoginLogout() {
+ testStrategy.testPostPassiveLoginLogout(true);
+ }
+
+ @Test
public void testPostSignedLoginLogoutTransientNameID() {
testStrategy.testPostSignedLoginLogoutTransientNameID();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
index b63c960..383c3fc 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
@@ -139,6 +139,37 @@ public class SamlAdapterTestStrategy extends ExternalResource {
checkLoggedOut(APP_SERVER_BASE_URL + "/sales-post/");
}
+ public void testPostPassiveLoginLogout(boolean forbiddenIfNotauthenticated) {
+ // first request on passive app - no login page shown, user not logged in as we are in passive mode
+ driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-passive/");
+ assertEquals(APP_SERVER_BASE_URL + "/sales-post-passive/", driver.getCurrentUrl());
+ System.out.println(driver.getPageSource());
+ if (forbiddenIfNotauthenticated) {
+ Assert.assertTrue(driver.getPageSource().contains("Forbidden"));
+ } else {
+ Assert.assertTrue(driver.getPageSource().contains("principal=null"));
+ }
+
+ // login user by asking login from other app
+ driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
+ loginPage.login("bburke", "password");
+
+ // navigate to the passive app again, we have to be logged in now
+ driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-passive/");
+ assertEquals(APP_SERVER_BASE_URL + "/sales-post-passive/", driver.getCurrentUrl());
+ System.out.println(driver.getPageSource());
+ Assert.assertTrue(driver.getPageSource().contains("bburke"));
+
+ // logout from both app
+ driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-passive?GLO=true");
+ driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post?GLO=true");
+
+ // refresh passive app page, not logged in again as we are in passive mode
+ driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post-passive/");
+ assertEquals(APP_SERVER_BASE_URL + "/sales-post-passive/", driver.getCurrentUrl());
+ Assert.assertFalse(driver.getPageSource().contains("bburke"));
+ }
+
public void testPostSimpleUnauthorized(CheckAuthError error) {
driver.navigate().to(APP_SERVER_BASE_URL + "/sales-post/");
assertEquals(driver.getCurrentUrl(), AUTH_SERVER_URL + "/realms/demo/protocol/saml");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
index 019bfea..7878843 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
@@ -1,35 +1,24 @@
package org.keycloak.testsuite.saml;
import org.apache.commons.io.IOUtils;
-import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
-import org.keycloak.Config;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
-import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
import org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper;
import org.keycloak.protocol.saml.mappers.HardcodedRole;
import org.keycloak.protocol.saml.mappers.RoleListMapper;
import org.keycloak.protocol.saml.mappers.RoleNameMapper;
-import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.managers.RealmManager;
-import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.testsuite.pages.LoginPage;
-import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
@@ -47,19 +36,10 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.client.ClientRequestContext;
-import javax.ws.rs.client.ClientRequestFilter;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.WebTarget;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
import static org.junit.Assert.assertEquals;
@@ -166,6 +146,7 @@ public class SamlBindingTest {
driver.navigate().to("http://localhost:8081/sales-post?GLO=true");
checkLoggedOut("http://localhost:8081/sales-post/");
}
+
@Test
public void testPostSimpleLoginLogoutIdpInitiated() {
driver.navigate().to("http://localhost:8081/auth/realms/demo/protocol/saml/clients/sales-post");
@@ -188,6 +169,7 @@ public class SamlBindingTest {
checkLoggedOut("http://localhost:8081/sales-post-sig/");
}
+
@Test
public void testPostSignedLoginLogoutTransientNameID() {
driver.navigate().to("http://localhost:8081/sales-post-sig-transient/");
@@ -452,23 +434,10 @@ public class SamlBindingTest {
Assert.assertTrue(driver.getPageSource().contains("null"));
}
- private static String createToken() {
- KeycloakSession session = keycloakRule.startSession();
- try {
- RealmManager manager = new RealmManager(session);
-
- RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
- ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
- TokenManager tm = new TokenManager();
- UserModel admin = session.users().getUserByUsername("admin", adminRealm);
- ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
- clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/master");
- UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
- AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, true, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
- return tm.encodeToken(adminRealm, token);
- } finally {
- keycloakRule.stopSession(session, true);
- }
+ @Test
+ public void testPassiveMode() {
+ // KEYCLOAK-2075 test SAML IsPassive handling - PicketLink SP client library doesn't support this option unfortunately.
+ // But the test of server side is included in test of SAML Keycloak adapter
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java
index 9a47b44..d0c5d21 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/samlfilter/SamlAdapterTest.java
@@ -5,7 +5,6 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.testsuite.keycloaksaml.SamlAdapterTestStrategy;
-import org.keycloak.testsuite.keycloaksaml.SamlSPFacade;
import org.keycloak.testsuite.keycloaksaml.SendUsernameServlet;
import org.openqa.selenium.WebDriver;
@@ -25,6 +24,7 @@ public class SamlAdapterTest {
ClassLoader classLoader = SamlAdapterTest.class.getClassLoader();
initializeSamlSecuredWar("/keycloak-saml/simple-post", "/sales-post", "post.war", classLoader);
+ initializeSamlSecuredWar("/keycloak-saml/simple-post-passive", "/sales-post-passive", "post-passive.war", classLoader);
initializeSamlSecuredWar("/keycloak-saml/signed-post", "/sales-post-sig", "post-sig.war", classLoader);
initializeSamlSecuredWar("/keycloak-saml/signed-post-email", "/sales-post-sig-email", "post-sig-email.war", classLoader);
initializeSamlSecuredWar("/keycloak-saml/signed-post-transient", "/sales-post-sig-transient", "post-sig-transient.war", classLoader);
@@ -37,9 +37,6 @@ public class SamlAdapterTest {
initializeSamlSecuredWar("/keycloak-saml/bad-realm-signed-post", "/bad-realm-sales-post-sig", "bad-realm-post-sig.war", classLoader);
initializeSamlSecuredWar("/keycloak-saml/encrypted-post", "/sales-post-enc", "post-enc.war", classLoader);
SamlAdapterTestStrategy.uploadSP("http://localhost:8081/auth");
-
-
-
}
@Override
@@ -106,6 +103,11 @@ public class SamlAdapterTest {
}
@Test
+ public void testPostPassiveLoginLogout() {
+ testStrategy.testPostPassiveLoginLogout(false);
+ }
+
+ @Test
public void testPostSignedLoginLogoutTransientNameID() {
testStrategy.testPostSignedLoginLogoutTransientNameID();
}
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/simple-post-passive/WEB-INF/keycloak-saml.xml b/testsuite/integration/src/test/resources/keycloak-saml/simple-post-passive/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000..3154627
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keycloak-saml/simple-post-passive/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,25 @@
+<keycloak-saml-adapter>
+ <SP entityID="http://localhost:8081/sales-post-passive/"
+ sslPolicy="EXTERNAL"
+ nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ logoutPage="/logout.jsp"
+ forceAuthentication="false"
+ isPassive="true">
+ <PrincipalNameMapping policy="FROM_NAME_ID"/>
+ <RoleIdentifiers>
+ <Attribute name="Role"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp">
+ <SingleSignOnService requestBinding="POST"
+ bindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+ />
+
+ <SingleLogoutService
+ requestBinding="POST"
+ responseBinding="POST"
+ postBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+ redirectBindingUrl="http://localhost:8081/auth/realms/demo/protocol/saml"
+ />
+ </IDP>
+ </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json b/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json
index 95b9fb9..7a50a91 100755
--- a/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json
+++ b/testsuite/integration/src/test/resources/keycloak-saml/testsaml.json
@@ -86,6 +86,24 @@
}
},
{
+ "name": "http://localhost:8081/sales-post-passive/",
+ "enabled": true,
+ "fullScopeAllowed": true,
+ "protocol": "saml",
+ "baseUrl": "http://localhost:8081/sales-post-passive",
+ "redirectUris": [
+ "http://localhost:8081/sales-post-passive/*"
+ ],
+ "attributes": {
+ "saml.authnstatement": "true",
+ "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-passive/",
+ "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-passive/",
+ "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-passive/",
+ "saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-passive/",
+ "saml_idp_initiated_sso_url_name": "sales-post-passive"
+ }
+ },
+ {
"name": "http://localhost:8081/sales-post-sig/",
"enabled": true,
"protocol": "saml",