diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java
index 21f2608694..d9fcd22fd8 100755
--- a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java
@@ -29,6 +29,7 @@ public class Constants {
static final String NAME_ID_POLICY_FORMAT = "nameIDPolicyFormat";
static final String LOGOUT_PAGE = "logoutPage";
static final String FORCE_AUTHENTICATION = "forceAuthentication";
+ static final String KEEP_DOM_ASSERTION = "keepDOMAssertion";
static final String IS_PASSIVE = "isPassive";
static final String TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN = "turnOffChangeSessionIdOnLogin";
static final String ROLE_ATTRIBUTES = "RoleIdentifiers";
@@ -83,6 +84,7 @@ public class Constants {
static final String NAME_ID_POLICY_FORMAT = "nameIDPolicyFormat";
static final String LOGOUT_PAGE = "logoutPage";
static final String FORCE_AUTHENTICATION = "forceAuthentication";
+ static final String KEEP_DOM_ASSERTION = "keepDOMAssertion";
static final String ROLE_IDENTIFIERS = "RoleIdentifiers";
static final String SIGNING = "signing";
static final String ENCRYPTION = "encryption";
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java
index aff84ed6f0..cf5f15a8cc 100755
--- a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java
@@ -60,6 +60,11 @@ public class ServiceProviderDefinition extends SimpleResourceDefinition {
.setXmlName(Constants.XML.FORCE_AUTHENTICATION)
.build();
+ static final SimpleAttributeDefinition KEEP_DOM_ASSERTION =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.KEEP_DOM_ASSERTION, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.KEEP_DOM_ASSERTION)
+ .build();
+
static final SimpleAttributeDefinition IS_PASSIVE =
new SimpleAttributeDefinitionBuilder(Constants.Model.IS_PASSIVE, ModelType.BOOLEAN, true)
.setXmlName(Constants.XML.IS_PASSIVE)
@@ -96,7 +101,7 @@ public class ServiceProviderDefinition extends SimpleResourceDefinition {
.build();
static final SimpleAttributeDefinition[] ATTRIBUTES = {SSL_POLICY, NAME_ID_POLICY_FORMAT, LOGOUT_PAGE, FORCE_AUTHENTICATION,
- IS_PASSIVE, TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN};
+ IS_PASSIVE, TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN, KEEP_DOM_ASSERTION};
static final AttributeDefinition[] ELEMENTS = {PRINCIPAL_NAME_MAPPING_POLICY, PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, ROLE_ATTRIBUTES,
ROLE_MAPPINGS_PROVIDER_ID, ROLE_MAPPINGS_PROVIDER_CONFIG};
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties b/adapters/saml/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties
index a247ee7858..e901b12c03 100755
--- a/adapters/saml/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties
+++ b/adapters/saml/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties
@@ -32,6 +32,7 @@ keycloak-saml.SP.sslPolicy=SSL Policy to use
keycloak-saml.SP.nameIDPolicyFormat=Name ID policy format URN
keycloak-saml.SP.logoutPage=URI to a logout page
keycloak-saml.SP.forceAuthentication=Redirected unauthenticated request to a login page
+keycloak-saml.SP.keepDOMAssertion=Attribute to inject the DOM representation of the assertion into the SamlPrincipal (respecting the original syntax)
keycloak-saml.SP.isPassive=If user isn't logged in just return with an error. Used to check if a user is already logged in or not
keycloak-saml.SP.turnOffChangeSessionIdOnLogin=The session id is changed by default on a successful login. Change this to true if you want to turn this off
keycloak-saml.SP.RoleIdentifiers=Role identifiers
@@ -84,4 +85,4 @@ keycloak-saml.IDP.SingleLogoutService.requestBinding=HTTP method to use for requ
keycloak-saml.IDP.SingleLogoutService.responseBinding=HTTP method to use for response
keycloak-saml.IDP.SingleLogoutService.postBindingUrl=Endpoint URL for posting
keycloak-saml.IDP.SingleLogoutService.redirectBindingUrl=Endpoint URL for redirects
-keycloak-saml.IDP.Key=Key definition for identity provider
\ No newline at end of file
+keycloak-saml.IDP.Key=Key definition for identity provider
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_2.xsd b/adapters/saml/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_2.xsd
index 5eca1ac311..ff69122b39 100755
--- a/adapters/saml/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_2.xsd
+++ b/adapters/saml/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_2.xsd
@@ -1,7 +1,7 @@
-
+
@@ -65,4 +66,4 @@
-
\ No newline at end of file
+
diff --git a/adapters/saml/core-public/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java b/adapters/saml/core-public/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
index 136557ffac..6eb8571f78 100755
--- a/adapters/saml/core-public/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
+++ b/adapters/saml/core-public/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
@@ -28,6 +28,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.w3c.dom.Document;
/**
* @author Bill Burke
@@ -43,14 +44,20 @@ public class SamlPrincipal implements Serializable, Principal {
private String samlSubject;
private String nameIDFormat;
private AssertionType assertion;
+ private Document assertionDocument;
public SamlPrincipal(AssertionType assertion, String name, String samlSubject, String nameIDFormat, MultivaluedHashMap attributes, MultivaluedHashMap friendlyAttributes) {
+ this(assertion, null, name, samlSubject, nameIDFormat, attributes, friendlyAttributes);
+ }
+
+ public SamlPrincipal(AssertionType assertion, Document assertionDocument, String name, String samlSubject, String nameIDFormat, MultivaluedHashMap attributes, MultivaluedHashMap friendlyAttributes) {
this.name = name;
this.attributes = attributes;
this.friendlyAttributes = friendlyAttributes;
this.samlSubject = samlSubject;
this.nameIDFormat = nameIDFormat;
this.assertion = assertion;
+ this.assertionDocument = assertionDocument;
}
public SamlPrincipal() {
@@ -104,6 +111,16 @@ public class SamlPrincipal implements Serializable, Principal {
return res;
}
+ /*
+ * The assertion element in DOM format, to respect the original syntax.
+ * It's only available if option keepDOMAssertion is set to true.
+ *
+ * @return The document assertion or null
+ */
+ public Document getAssertionDocument() {
+ return assertionDocument;
+ }
+
@Override
public String getName() {
return name;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
index 85aa952564..2194d8cda5 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -315,6 +315,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
private SignatureAlgorithm signatureAlgorithm;
private String signatureCanonicalizationMethod;
private boolean autodetectBearerOnly;
+ private boolean keepDOMAssertion;
@Override
public boolean turnOffChangeSessionIdOnLogin() {
@@ -478,4 +479,13 @@ public class DefaultSamlDeployment implements SamlDeployment {
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
this.autodetectBearerOnly = autodetectBearerOnly;
}
+
+ @Override
+ public boolean isKeepDOMAssertion() {
+ return keepDOMAssertion;
+ }
+
+ public void setKeepDOMAssertion(Boolean keepDOMAssertion) {
+ this.keepDOMAssertion = keepDOMAssertion != null && keepDOMAssertion;
+ }
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
index 492e92a681..5aeee06d26 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -183,4 +183,6 @@ public interface SamlDeployment {
String getPrincipalAttributeName();
boolean isAutodetectBearerOnly();
+ boolean isKeepDOMAssertion();
+
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
index 1e3347ea8e..e6644430bc 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
@@ -90,6 +90,7 @@ public class SP implements Serializable {
private RoleMappingsProviderConfig roleMappingsProviderConfig;
private IDP idp;
private boolean autodetectBearerOnly;
+ private boolean keepDOMAssertion;
public String getEntityID() {
return entityID;
@@ -131,6 +132,14 @@ public class SP implements Serializable {
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin != null && turnOffChangeSessionIdOnLogin;
}
+ public boolean isKeepDOMAssertion() {
+ return keepDOMAssertion;
+ }
+
+ public void setKeepDOMAssertion(Boolean keepDOMAssertion) {
+ this.keepDOMAssertion = keepDOMAssertion != null && keepDOMAssertion;
+ }
+
public List getKeys() {
return keys;
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
index 91a6d34e37..33045ad0d4 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -80,6 +80,7 @@ public class DeploymentBuilder {
IDP idp = sp.getIdp();
deployment.setSignatureCanonicalizationMethod(idp.getSignatureCanonicalizationMethod());
deployment.setAutodetectBearerOnly(sp.isAutodetectBearerOnly());
+ deployment.setKeepDOMAssertion(sp.isKeepDOMAssertion());
deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
if (idp.getSignatureAlgorithm() != null) {
deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(idp.getSignatureAlgorithm()));
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterV1QNames.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterV1QNames.java
index 478a9f2fe6..37df459f61 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterV1QNames.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterV1QNames.java
@@ -90,6 +90,7 @@ public enum KeycloakSamlAdapterV1QNames implements HasQName {
ATTR_VALIDATE_REQUEST_SIGNATURE(null, "validateRequestSignature"),
ATTR_VALIDATE_RESPONSE_SIGNATURE(null, "validateResponseSignature"),
ATTR_VALUE(null, "value"),
+ ATTR_KEEP_DOM_ASSERTION(null, "keepDOMAssertion"),
UNKNOWN_ELEMENT("")
;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SpParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SpParser.java
index 29c6252c8e..e1859a64d3 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SpParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SpParser.java
@@ -53,6 +53,7 @@ public class SpParser extends AbstractKeycloakSamlAdapterV1Parser {
sp.setIsPassive(StaxParserUtil.getBooleanAttributeValueRP(element, KeycloakSamlAdapterV1QNames.ATTR_IS_PASSIVE));
sp.setAutodetectBearerOnly(StaxParserUtil.getBooleanAttributeValueRP(element, KeycloakSamlAdapterV1QNames.ATTR_AUTODETECT_BEARER_ONLY));
sp.setTurnOffChangeSessionIdOnLogin(StaxParserUtil.getBooleanAttributeValueRP(element, KeycloakSamlAdapterV1QNames.ATTR_TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN));
+ sp.setKeepDOMAssertion(StaxParserUtil.getBooleanAttributeValueRP(element, KeycloakSamlAdapterV1QNames.ATTR_KEEP_DOM_ASSERTION));
return sp;
}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index 0195d869cf..c96bed25fd 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -375,9 +375,11 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return AuthOutcome.FAILED;
}
+ Element assertionElement = null;
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
try {
- if (!AssertionUtil.isSignatureValid(getAssertionFromResponse(responseHolder), deployment.getIDP().getSignatureValidationKeyLocator())) {
+ assertionElement = getAssertionFromResponse(responseHolder);
+ if (!AssertionUtil.isSignatureValid(assertionElement, deployment.getIDP().getSignatureValidationKeyLocator())) {
log.error("Failed to verify saml assertion signature");
challenge = new AuthChallenge() {
@@ -493,7 +495,13 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
URI nameFormat = subjectNameID == null ? null : subjectNameID.getFormat();
String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
- final SamlPrincipal principal = new SamlPrincipal(assertion, principalName, principalName, nameFormatString, attributes, friendlyAttributes);
+ if (deployment.isKeepDOMAssertion() && assertionElement == null) {
+ // obtain the assertion from the response to add the DOM document to the principal
+ assertionElement = getAssertionFromResponseNoException(responseHolder);
+ }
+ final SamlPrincipal principal = new SamlPrincipal(assertion,
+ deployment.isKeepDOMAssertion()? getAssertionDocumentFromElement(assertionElement) : null,
+ principalName, principalName, nameFormatString, attributes, friendlyAttributes);
final String sessionIndex = authn == null ? null : authn.getSessionIndex();
final XMLGregorianCalendar sessionNotOnOrAfter = authn == null ? null : authn.getSessionNotOnOrAfter();
SamlSession account = new SamlSession(principal, roles, sessionIndex, sessionNotOnOrAfter);
@@ -534,6 +542,30 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
}
+ private Element getAssertionFromResponseNoException(final SAMLDocumentHolder responseHolder) {
+ try {
+ return getAssertionFromResponse(responseHolder);
+ } catch (ConfigurationException|ProcessingException e) {
+ log.warn("Cannot obtain DOM assertion element", e);
+ return null;
+ }
+ }
+
+ private Document getAssertionDocumentFromElement(final Element assertionElement) {
+ if (assertionElement == null) {
+ return null;
+ }
+ try {
+ Document assertionDoc = DocumentUtil.createDocument();
+ assertionDoc.adoptNode(assertionElement);
+ assertionDoc.appendChild(assertionElement);
+ return assertionDoc;
+ } catch (ConfigurationException e) {
+ log.warn("Cannot obtain DOM assertion document", e);
+ return null;
+ }
+ }
+
private String getAttributeValue(Object attrValue) {
String value = null;
if (attrValue instanceof String) {
diff --git a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_12.xsd b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_12.xsd
index 4b7b9573fd..8f682e9d81 100644
--- a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_12.xsd
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_12.xsd
@@ -97,6 +97,11 @@
SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.
+
+
+ Attribute to inject the DOM representation of the assertion into the SamlPrincipal (respecting the original syntax). Default value is false
+
+
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 true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
index b7613e0fe1..e5115f65b2 100755
--- a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
@@ -86,6 +86,17 @@ public class KeycloakSamlAdapterXMLParserTest {
testValidationValid("keycloak-saml-with-role-mappings-provider.xml");
}
+ @Test
+ public void testValidationWithKeepDOMAssertion() throws Exception {
+ testValidationValid("keycloak-saml-keepdomassertion.xml");
+ // check keep dom assertion is TRUE
+ KeycloakSamlAdapter config = parseKeycloakSamlAdapterConfig("keycloak-saml-keepdomassertion.xml", KeycloakSamlAdapter.class);
+ assertNotNull(config);
+ assertEquals(1, config.getSps().size());
+ SP sp = config.getSps().get(0);
+ assertTrue(sp.isKeepDOMAssertion());
+ }
+
@Test
public void testValidationKeyInvalid() throws Exception {
InputStream schemaIs = KeycloakSamlAdapterV1Parser.class.getResourceAsStream(CURRENT_XSD_LOCATION);
@@ -115,6 +126,7 @@ public class KeycloakSamlAdapterXMLParserTest {
assertTrue(sp.isForceAuthentication());
assertTrue(sp.isIsPassive());
assertFalse(sp.isAutodetectBearerOnly());
+ assertFalse(sp.isKeepDOMAssertion());
assertEquals(2, sp.getKeys().size());
Key signing = sp.getKeys().get(0);
assertTrue(signing.isSigning());
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-keepdomassertion.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-keepdomassertion.xml
new file mode 100755
index 0000000000..f7e99b0ff3
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-keepdomassertion.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ private pem
+
+
+ public pem
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ cert pem
+
+
+
+
+
+
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java
index 1d671c188e..90f139c5b1 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java
@@ -30,6 +30,7 @@ public class Constants {
static final String NAME_ID_POLICY_FORMAT = "nameIDPolicyFormat";
static final String LOGOUT_PAGE = "logoutPage";
static final String FORCE_AUTHENTICATION = "forceAuthentication";
+ static final String KEEP_DOM_ASSERTION = "keepDOMAssertion";
static final String IS_PASSIVE = "isPassive";
static final String TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN = "turnOffChangeSessionIdOnLogin";
static final String ROLE_ATTRIBUTES = "RoleIdentifiers";
@@ -86,6 +87,7 @@ public class Constants {
static final String NAME_ID_POLICY_FORMAT = "nameIDPolicyFormat";
static final String LOGOUT_PAGE = "logoutPage";
static final String FORCE_AUTHENTICATION = "forceAuthentication";
+ static final String KEEP_DOM_ASSERTION = "keepDOMAssertion";
static final String ROLE_IDENTIFIERS = "RoleIdentifiers";
static final String SIGNING = "signing";
static final String ENCRYPTION = "encryption";
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java
index caa5aa5b4b..5d859d941e 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java
@@ -62,6 +62,11 @@ public class ServiceProviderDefinition extends SimpleResourceDefinition {
.setXmlName(Constants.XML.FORCE_AUTHENTICATION)
.build();
+ static final SimpleAttributeDefinition KEEP_DOM_ASSERTION =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.KEEP_DOM_ASSERTION, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.KEEP_DOM_ASSERTION)
+ .build();
+
static final SimpleAttributeDefinition IS_PASSIVE =
new SimpleAttributeDefinitionBuilder(Constants.Model.IS_PASSIVE, ModelType.BOOLEAN, true)
.setXmlName(Constants.XML.IS_PASSIVE)
@@ -97,7 +102,7 @@ public class ServiceProviderDefinition extends SimpleResourceDefinition {
.build();
static final SimpleAttributeDefinition[] ATTRIBUTES = {SSL_POLICY, NAME_ID_POLICY_FORMAT, LOGOUT_PAGE, FORCE_AUTHENTICATION,
- IS_PASSIVE, TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN};
+ IS_PASSIVE, TURN_OFF_CHANGE_SESSSION_ID_ON_LOGIN, KEEP_DOM_ASSERTION};
static final AttributeDefinition[] ELEMENTS = {PRINCIPAL_NAME_MAPPING_POLICY, PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, ROLE_ATTRIBUTES,
ROLE_MAPPINGS_PROVIDER_ID, ROLE_MAPPINGS_PROVIDER_CONFIG};
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties
index 23e604dd9d..a5484a648f 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties
@@ -32,6 +32,7 @@ keycloak-saml.SP.sslPolicy=SSL Policy to use
keycloak-saml.SP.nameIDPolicyFormat=Name ID policy format URN
keycloak-saml.SP.logoutPage=URI to a logout page
keycloak-saml.SP.forceAuthentication=Redirected unauthenticated request to a login page
+keycloak-saml.SP.keepDOMAssertion=Attribute to inject the DOM representation of the assertion into the SamlPrincipal (respecting the original syntax)
keycloak-saml.SP.isPassive=If user isn't logged in just return with an error. Used to check if a user is already logged in or not
keycloak-saml.SP.turnOffChangeSessionIdOnLogin=The session id is changed by default on a successful login. Change this to true if you want to turn this off
keycloak-saml.SP.RoleIdentifiers=Role identifiers
@@ -83,4 +84,4 @@ keycloak-saml.IDP.SingleLogoutService.requestBinding=HTTP method to use for requ
keycloak-saml.IDP.SingleLogoutService.responseBinding=HTTP method to use for response
keycloak-saml.IDP.SingleLogoutService.postBindingUrl=Endpoint URL for posting
keycloak-saml.IDP.SingleLogoutService.redirectBindingUrl=Endpoint URL for redirects
-keycloak-saml.IDP.Key=Key definition for identity provider
\ No newline at end of file
+keycloak-saml.IDP.Key=Key definition for identity provider
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_2.xsd b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_2.xsd
index baa10c6ac6..ff69122b39 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_2.xsd
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_2.xsd
@@ -84,6 +84,11 @@
Redirected unauthenticated request to a login page
+
+
+ Attribute to inject the DOM representation of the assertion into the SamlPrincipal (respecting the original syntax). Default value is false
+
+
If user isn't logged in just return with an error. Used to check if a user is already logged in or not
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml b/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml
deleted file mode 100755
index bb3bce855a..0000000000
--- a/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
-
- my_key.pem
- my_key.pub
- cert.cer
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml b/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.2-err.xml
similarity index 97%
rename from adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml
rename to adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.2-err.xml
index 5afd0bf721..6733125e55 100644
--- a/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.2-err.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
-
\ No newline at end of file
+
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.2.xml b/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.2.xml
index 71f400a30b..bb0fc98985 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.2.xml
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.2.xml
@@ -21,6 +21,7 @@
sslPolicy="EXTERNAL"
nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
logoutPage="/logout.jsp"
+ keepDOMAssertion="false"
forceAuthentication="false"
isPassive="true"
turnOffChangeSessionIdOnLogin="true">
@@ -72,4 +73,4 @@
-
\ No newline at end of file
+
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
index bde0ccd3d9..be44455fd0 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
@@ -58,7 +58,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
@Override
public String getHelpText() {
- return "Keycloak SAML adapter Wildfly/JBoss subsystem xml. Put this element of your standalone.xml file.";
+ return "Keycloak SAML adapter Wildfly/JBoss subsystem xml. Put this element of your standalone.xml file.";
}
@Override
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
index 7ff56ce7cf..2b59000295 100755
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
@@ -39,10 +39,19 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.datatype.XMLGregorianCalendar;
import java.io.IOException;
+import java.io.StringWriter;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.w3c.dom.Document;
/**
* @author Bill Burke
@@ -94,6 +103,25 @@ public class SendUsernameServlet {
}
+ @GET
+ @Path("getAssertionFromDocument")
+ public Response getAssertionFromDocument() throws IOException, TransformerException {
+ sentPrincipal = httpServletRequest.getUserPrincipal();
+ DocumentBuilderFactory domFact = DocumentBuilderFactory.newInstance();
+ Document doc = ((SamlPrincipal) sentPrincipal).getAssertionDocument();
+ String xml = "";
+ if (doc != null) {
+ DOMSource domSource = new DOMSource(doc);
+ StringWriter writer = new StringWriter();
+ StreamResult result = new StreamResult(writer);
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.transform(domSource, result);
+ xml = writer.toString();
+ }
+ return Response.ok(xml).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_TYPE + ";charset=UTF-8").build();
+ }
+
@GET
@Path("{path}")
public Response doGetElseWhere(@PathParam("path") String path, @QueryParam("checkRoles") boolean checkRolesFlag) throws IOException {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeDomServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeDomServlet.java
new file mode 100644
index 0000000000..f690ddf39b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeDomServlet.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.testsuite.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+
+import java.net.URL;
+
+/**
+ * @author rmartinc
+ */
+public class EmployeeDomServlet extends SAMLServlet {
+ public static final String DEPLOYMENT_NAME = "employee-dom";
+
+ @ArquillianResource
+ @OperateOnDeployment(DEPLOYMENT_NAME)
+ private URL url;
+
+ @Override
+ public URL getInjectedUrl() {
+ return url;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/SAMLServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/SAMLServletAdapterTest.java
index b0bfd038d1..a7068b0fb3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/SAMLServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/SAMLServletAdapterTest.java
@@ -39,10 +39,13 @@ import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.io.StringReader;
import java.net.URI;
import java.net.URL;
import java.security.KeyPair;
import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -132,6 +135,7 @@ import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
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.util.AssertionUtil;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.adapter.page.*;
import org.keycloak.testsuite.admin.ApiUtil;
@@ -189,6 +193,9 @@ public class SAMLServletAdapterTest extends AbstractSAMLServletAdapterTest {
@Page
protected Employee2Servlet employee2ServletPage;
+ @Page
+ protected EmployeeDomServlet employeeDomServletPage;
+
@Page
protected EmployeeSigServlet employeeSigServletPage;
@@ -307,6 +314,11 @@ public class SAMLServletAdapterTest extends AbstractSAMLServletAdapterTest {
return samlServletDeployment(Employee2Servlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
}
+ @Deployment(name = EmployeeDomServlet.DEPLOYMENT_NAME)
+ protected static WebArchive employeedom() {
+ return samlServletDeployment(EmployeeDomServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+ }
+
@Deployment(name = EmployeeSigServlet.DEPLOYMENT_NAME)
protected static WebArchive employeeSig() {
return samlServletDeployment(EmployeeSigServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
@@ -1421,6 +1433,10 @@ public class SAMLServletAdapterTest extends AbstractSAMLServletAdapterTest {
waitUntilElement(By.xpath("//body")).text().contains("phone: 617");
waitUntilElement(By.xpath("//body")).text().contains("friendlyAttribute phone: null");
+ driver.navigate().to(employee2ServletPage.getUriBuilder().clone().path("getAssertionFromDocument").build().toURL());
+ waitForPageToLoad();
+ Assert.assertEquals("", driver.getPageSource());
+
employee2ServletPage.logout();
checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage);
@@ -1483,6 +1499,25 @@ public class SAMLServletAdapterTest extends AbstractSAMLServletAdapterTest {
validateXMLWithSchema(driver.getPageSource(), "/adapter-test/keycloak-saml/metadata-schema/saml-schema-metadata-2.0.xsd");
}
+ @Test
+ public void testDOMAssertion() throws Exception {
+ assertSuccessfulLogin(employeeDomServletPage, bburkeUser, testRealmSAMLPostLoginPage, "principal=bburke");
+ assertSuccessfullyLoggedIn(employeeDomServletPage, "principal=bburke");
+
+ driver.navigate().to(employeeDomServletPage.getUriBuilder().clone().path("getAssertionFromDocument").build().toURL());
+ waitForPageToLoad();
+ String xml = driver.getPageSource();
+ Assert.assertNotEquals("", xml);
+ Document doc = DocumentUtil.getDocument(new StringReader(xml));
+ String certBase64 = DocumentUtil.getElement(doc, new QName("http://www.w3.org/2000/09/xmldsig#", "X509Certificate")).getTextContent();
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ Certificate cert = cf.generateCertificate(new ByteArrayInputStream(Base64.decode(certBase64)));
+ PublicKey pubkey = cert.getPublicKey();
+ Assert.assertTrue(AssertionUtil.isSignatureValid(doc.getDocumentElement(), pubkey));
+
+ employeeDomServletPage.logout();
+ checkLoggedOut(employeeDomServletPage, testRealmSAMLPostLoginPage);
+ }
@Test
public void spMetadataValidation() throws Exception {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-dom/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-dom/WEB-INF/keycloak-saml.xml
new file mode 100755
index 0000000000..79543164c3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-dom/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-dom/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-dom/WEB-INF/keystore.jks
new file mode 100755
index 0000000000..e02b800093
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-dom/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
index 3bc1d70dba..604c32b39b 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
@@ -625,6 +625,61 @@
}
]
},
+ {
+ "clientId": "http://localhost:8280/employee-dom/",
+ "enabled": true,
+ "protocol": "saml",
+ "fullScopeAllowed": true,
+ "baseUrl": "http://localhost:8080/employee-dom",
+ "redirectUris": [
+ "http://localhost:8080/employee-dom/*"
+ ],
+ "adminUrl": "http://localhost:8080/employee-dom",
+ "attributes": {
+ "saml.assertion.signature": "true",
+ "saml.server.signature": "true",
+ "saml.client.signature": "true",
+ "saml.signature.algorithm": "RSA_SHA256",
+ "saml.authnstatement": "true",
+ "saml.signing.certificate": "MIIC+zCCAeOgAwIBAgIEcFrChjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtZG9tLzAeFw0xOTA3MDMwOTE1NDlaFw00NjExMTgwOTE1NDlaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1kb20vMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmEbjaKmKCh2MXTVLMUXdbjKSdmXAOenuE2bDD0AlEaJmnJ5zU2JY6UuFflH3332n2YktaaCyTznwX1Zcf7GH3bm7xhV1HSmlbFpIY17M8QUOIGZEzvKSbT9gjRJSPIdE1JvZuqgzuXpRlRfC4eoH1VgS0Vmu4gwIRFnUUgqc5hW11AQVkGZs7TkEYbVEYneKMbQOKa1OzW+FAb7C13Yn19gSvGr3THE+7FGwxEJM6N6kr4xnxg4VpaXcsW4ijGI3CHPJA06MZ6LzXxCmz+8TOSLo5pV7GKgME9QR1lBSC2Cp0yDtHjqK6QCqApyHhP2xN8qzJhMIhffSSHq4GokhjwIDAQABoyEwHzAdBgNVHQ4EFgQUOVG/h7cr+T6LJ4dQIVALBknwF/AwDQYJKoZIhvcNAQELBQADggEBAI5Y1MPMHPsDRJBQke/+tkRO4PALbsAQtfvYDNmpBGzUNo2xU3n7PNzbWrcqubjLN0nqXloBTaeeHtrFGAejMCS5X8UOGLyXbKBm7hHJs5ZZASrm0FkUzyuJexWCbSAg0p7Z6wWw03dnV/A9LDFwTdGIYsnSzZ59/v3BUH89mavOwVuVJB5O2PysUob3urcv1tmv9eL5jAMc764ID1gLkydcNrmICa+aZ/FojfReyTtwWX0DoPflPvF/Xllp3jLg1HwSlD6fD2wO/MKawgBbE6xrAkg5bF01B25RadJJffx3hEtgxBzlo1EL4Ir+lJmM1vzuTq4c1wDYKku4Y0Qg5o0="
+ },
+ "protocolMappers": [
+ {
+ "name": "email",
+ "protocol": "saml",
+ "protocolMapper": "saml-user-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "email",
+ "friendly.name": "email",
+ "attribute.name": "urn:oid:1.2.840.113549.1.9.1",
+ "attribute.nameformat": "URI Reference"
+ }
+ },
+ {
+ "name": "phone",
+ "protocol": "saml",
+ "protocolMapper": "saml-user-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "phone",
+ "attribute.name": "phone",
+ "attribute.nameformat": "Basic"
+ }
+ },
+ {
+ "name": "role-list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "attribute.name": "Role",
+ "attribute.nameformat": "Basic",
+ "single": "false"
+ }
+ }
+ ]
+ },
{
"clientId": "http://localhost:8280/employee-sig-front/",
"enabled": true,