From 6283c7add35fc8c25f0da630ad9aeedfbff444ad Mon Sep 17 00:00:00 2001 From: rmartinc Date: Fri, 20 Sep 2019 08:44:08 +0200 Subject: [PATCH] KEYCLOAK-10975: Clock skew configuration in keycloak-saml.xml can't be found in the keycloak-saml subsystem --- .../subsystem/saml/as7/AllowedClockSkew.java | 50 ++++ .../subsystem/saml/as7/Constants.java | 7 + .../saml/as7/IdentityProviderDefinition.java | 8 +- .../saml/as7/KeycloakSubsystemParser.java | 34 +++ .../saml/as7/LocalDescriptions.properties | 3 + .../schema/wildfly-keycloak-saml_1_2.xsd | 29 +++ ...systemParsingAllowedClockSkewTestCase.java | 199 +++++++++++++++ .../saml/extension/AllowedClockSkew.java | 53 ++++ .../adapter/saml/extension/Constants.java | 7 + .../extension/IdentityProviderDefinition.java | 8 +- .../extension/KeycloakSubsystemParser.java | 34 +++ .../extension/LocalDescriptions.properties | 3 + .../schema/wildfly-keycloak-saml_1_2.xsd | 29 +++ ...systemParsingAllowedClockSkewTestCase.java | 238 ++++++++++++++++++ 14 files changed, 700 insertions(+), 2 deletions(-) create mode 100644 adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/AllowedClockSkew.java create mode 100755 adapters/saml/as7-eap6/subsystem/src/test/java/org/keycloak/subsystem/saml/as7/SubsystemParsingAllowedClockSkewTestCase.java create mode 100644 adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/AllowedClockSkew.java create mode 100755 adapters/saml/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingAllowedClockSkewTestCase.java diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/AllowedClockSkew.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/AllowedClockSkew.java new file mode 100644 index 0000000000..8231eeeebf --- /dev/null +++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/AllowedClockSkew.java @@ -0,0 +1,50 @@ +/* + * 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.subsystem.saml.as7; + +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.operations.validation.EnumValidator; +import org.jboss.as.controller.operations.validation.IntRangeValidator; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +/** + * + * @author rmartinc + */ +abstract public class AllowedClockSkew { + + static final SimpleAttributeDefinition ALLOWED_CLOCK_SKEW_VALUE = + new SimpleAttributeDefinitionBuilder(Constants.Model.ALLOWED_CLOCK_SKEW_VALUE, ModelType.INT, false) + .setXmlName(Constants.XML.ALLOWED_CLOCK_SKEW) + .setAllowExpression(false) + .setValidator(new IntRangeValidator(1, Integer.MAX_VALUE, true, false)) + .build(); + + static private enum AllowedClockSkewUnits {MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS}; + + static final SimpleAttributeDefinition ALLOWED_CLOCK_SKEW_UNIT = + new SimpleAttributeDefinitionBuilder(Constants.Model.ALLOWED_CLOCK_SKEW_UNIT, ModelType.STRING, true) + .setXmlName(Constants.XML.ALLOWED_CLOCK_SKEW_UNIT) + .setAllowExpression(false) + .setDefaultValue(new ModelNode(AllowedClockSkewUnits.SECONDS.name())) + .setValidator(EnumValidator.create(AllowedClockSkewUnits.class, true, false)) + .build(); + + static final SimpleAttributeDefinition[] ATTRIBUTES = {ALLOWED_CLOCK_SKEW_UNIT, ALLOWED_CLOCK_SKEW_VALUE}; +} 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 d9fcd22fd8..a59daf1607 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 @@ -72,6 +72,10 @@ public class Constants { static final String ROLE_MAPPINGS_PROVIDER_ID = "roleMappingsProviderId"; static final String ROLE_MAPPINGS_PROVIDER_CONFIG = "roleMappingsProviderConfig"; + + static final String ALLOWED_CLOCK_SKEW = "AllowedClockSkew"; + static final String ALLOWED_CLOCK_SKEW_UNIT = "unit"; + static final String ALLOWED_CLOCK_SKEW_VALUE = "value"; } static class XML { @@ -133,5 +137,8 @@ public class Constants { static final String VALUE = "value"; static final String PROPERTY = "Property"; static final String ROLE_MAPPINGS_PROVIDER = "RoleMappingsProvider"; + + static final String ALLOWED_CLOCK_SKEW = "AllowedClockSkew"; + static final String ALLOWED_CLOCK_SKEW_UNIT = "unit"; } } diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java index a0f85ba322..aac3f2815c 100644 --- a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java +++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java @@ -63,9 +63,15 @@ public class IdentityProviderDefinition extends SimpleResourceDefinition { .setAllowNull(false) .build(); + static final ObjectTypeAttributeDefinition ALLOWED_CLOCK_SKEW = + ObjectTypeAttributeDefinition.Builder.of(Constants.Model.ALLOWED_CLOCK_SKEW, + AllowedClockSkew.ATTRIBUTES) + .setAllowNull(true) + .build(); + static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD}; - static final SimpleAttributeDefinition[] ALL_ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD, SINGLE_SIGN_ON, SINGLE_LOGOUT}; + static final SimpleAttributeDefinition[] ALL_ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD, SINGLE_SIGN_ON, SINGLE_LOGOUT, ALLOWED_CLOCK_SKEW}; static final HashMap ATTRIBUTE_MAP = new HashMap<>(); diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java index 1560008967..524825653f 100755 --- a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java +++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java @@ -161,6 +161,8 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • list, XMLExtendedStreamReader reader, PathAddress parentAddr) throws XMLStreamException { PathAddress addr = PathAddress.pathAddress(parentAddr, PathElement.pathElement(Constants.Model.KEY, "key-" + list.size())); @@ -460,6 +481,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • + + + This defines the allowed clock skew between IDP and SP in milliseconds. The default value is 0. + + @@ -336,4 +341,28 @@ + + + + The value is the allowed clock skew between the IDP and the SP. + + + + + + + + + + Time unit for the value of the clock skew. + + + + + + + + + + diff --git a/adapters/saml/as7-eap6/subsystem/src/test/java/org/keycloak/subsystem/saml/as7/SubsystemParsingAllowedClockSkewTestCase.java b/adapters/saml/as7-eap6/subsystem/src/test/java/org/keycloak/subsystem/saml/as7/SubsystemParsingAllowedClockSkewTestCase.java new file mode 100755 index 0000000000..5177f35c40 --- /dev/null +++ b/adapters/saml/as7-eap6/subsystem/src/test/java/org/keycloak/subsystem/saml/as7/SubsystemParsingAllowedClockSkewTestCase.java @@ -0,0 +1,199 @@ +/* + * 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.subsystem.saml.as7; + +import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; +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 javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import org.jboss.as.controller.ExpressionResolver; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.model.test.ModelTestUtils; +import org.jboss.as.subsystem.test.KernelServices; +import org.jboss.dmr.ModelNode; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Test case for AllowedClockSkew subsystem configuration. + * + * @author rmartinc + */ +public class SubsystemParsingAllowedClockSkewTestCase extends AbstractSubsystemBaseTest { + + private String subsystemXml = null; + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + public SubsystemParsingAllowedClockSkewTestCase() { + super(KeycloakSamlExtension.SUBSYSTEM_NAME, new KeycloakSamlExtension()); + } + + @Override + protected String getSubsystemXml() throws IOException { + return subsystemXml; + } + + private void setSubsystemXml(String value, String unit) throws IOException { + try { + String template = readResource("keycloak-saml-1.2.xml"); + if (value != null) { + // assign the AllowedClockSkew element using DOM + DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = db.parse(new InputSource(new StringReader(template))); + // create the skew element + Element allowedClockSkew = doc.createElement(Constants.XML.ALLOWED_CLOCK_SKEW); + if (unit != null) { + allowedClockSkew.setAttribute(Constants.XML.ALLOWED_CLOCK_SKEW_UNIT, unit); + } + allowedClockSkew.setTextContent(value); + // locate the IDP and insert the node + XPath xPath = XPathFactory.newInstance().newXPath(); + NodeList nodeList = (NodeList) xPath.compile("/subsystem/secure-deployment[1]/SP/IDP").evaluate(doc, XPathConstants.NODESET); + nodeList.item(0).appendChild(allowedClockSkew); + // transform again to XML + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + subsystemXml = writer.getBuffer().toString(); + } else { + subsystemXml = template; + } + } catch (DOMException | ParserConfigurationException | SAXException | TransformerException | XPathExpressionException e) { + throw new IOException(e); + } + } + + private PathAddress getIdpPath() { + return PathAddress.EMPTY_ADDRESS + .append(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakSamlExtension.SUBSYSTEM_NAME)) + .append(PathElement.pathElement(Constants.Model.SECURE_DEPLOYMENT, "my-app.war")) + .append(PathElement.pathElement(Constants.Model.SERVICE_PROVIDER, "http://localhost:8080/sales-post-enc/")) + .append(PathElement.pathElement(Constants.Model.IDENTITY_PROVIDER, "idp")); + } + + private void testSubsystem(String value, String unit, int realValue, String realUnit) throws Exception { + setSubsystemXml(value, unit); + // perform the common test + KernelServices s = super.standardSubsystemTest(null, true); + // get the values for the AllowedClockSkew parameters + ModelNode idp = ModelTestUtils.getSubModel(s.readWholeModel(), getIdpPath()); + ModelNode allowedClockSkew = idp.get(Constants.Model.ALLOWED_CLOCK_SKEW); + if (value != null) { + Assert.assertTrue(allowedClockSkew.isDefined()); + ModelNode allowedClockSkewValue = allowedClockSkew.get(Constants.Model.ALLOWED_CLOCK_SKEW_VALUE); + ModelNode allowedClockSkewUnit = allowedClockSkew.get(Constants.Model.ALLOWED_CLOCK_SKEW_UNIT); + allowedClockSkewValue = ExpressionResolver.TEST_RESOLVER.resolveExpressions(allowedClockSkewValue); + allowedClockSkewUnit = ExpressionResolver.TEST_RESOLVER.resolveExpressions(allowedClockSkewUnit); + Assert.assertEquals(realValue, allowedClockSkewValue.asInt()); + if (unit != null) { + Assert.assertEquals(realUnit, allowedClockSkewUnit.asString()); + } else { + Assert.assertFalse(allowedClockSkewUnit.isDefined()); + } + } else { + Assert.assertFalse(allowedClockSkew.isDefined()); + } + } + + private void testSubsystem(String value, String unit) throws Exception { + testSubsystem(value, unit, value == null? -1 : Integer.parseInt(value.trim()), unit); + } + + @Test + @Override + public void testSubsystem() throws Exception { + testSubsystem(null, null); + } + + @Test + public void testSubsystemAllowedClockSkewWithUnit() throws Exception { + testSubsystem("3500", "MILLISECONDS"); + } + + @Test + public void testSubsystemAllowedClockSkewWithoutUnit() throws Exception { + testSubsystem("1", null); + } + + @Test + public void testSubsystemAllowedClockSkewWithSpaces() throws Exception { + testSubsystem("\n 20 \n ", null); + } + + @Test + public void testErrorOnNonInteger() throws Exception { + exception.expect(NumberFormatException.class); + //exception.expectMessage("WFLYCTL0097"); + testSubsystem("invalid-value", null, -1, null); + } + + @Test + public void testErrorOnNonPositiveInteger() throws Exception { + exception.expect(XMLStreamException.class); + exception.expectMessage("JBAS014708"); + testSubsystem("0", null); + } + + @Test + public void testErrorNoValidUnit() throws Exception { + exception.expect(XMLStreamException.class); + exception.expectMessage("JBAS014839"); + testSubsystem("30", "invalid-unit"); + } + + // For the moment no expressions allowed as the rest of the subsystem doesn't resolve expressions + //@Test + //public void testExpression() throws Exception { + // System.setProperty("test.prop.SKEW_TIME", "30"); + // System.setProperty("test.prop.SKEW_UNIT", "MILLISECONDS"); + // try { + // testSubsystem("${test.prop.SKEW_TIME}", "${test.prop.SKEW_UNIT}", 30, "MILLISECONDS"); + // } finally { + // System.clearProperty("test.prop.SKEW_TIME"); + // System.clearProperty("test.prop.SKEW_UNIT"); + // } + //} +} diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/AllowedClockSkew.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/AllowedClockSkew.java new file mode 100644 index 0000000000..d557078e28 --- /dev/null +++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/AllowedClockSkew.java @@ -0,0 +1,53 @@ +/* + * 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.subsystem.adapter.saml.extension; + +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.operations.validation.EnumValidator; +import org.jboss.as.controller.operations.validation.IntRangeValidator; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +/** + * + * @author rmartinc + */ +abstract public class AllowedClockSkew { + + static final SimpleAttributeDefinition ALLOWED_CLOCK_SKEW_VALUE = + new SimpleAttributeDefinitionBuilder(Constants.Model.ALLOWED_CLOCK_SKEW_VALUE, ModelType.INT, false) + .setXmlName(Constants.XML.ALLOWED_CLOCK_SKEW) + .setAllowExpression(false) + .setValidator(new IntRangeValidator(1, Integer.MAX_VALUE, true, false)) + .build(); + + static private enum AllowedClockSkewUnits {MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS}; + + static final SimpleAttributeDefinition ALLOWED_CLOCK_SKEW_UNIT = + new SimpleAttributeDefinitionBuilder(Constants.Model.ALLOWED_CLOCK_SKEW_UNIT, ModelType.STRING, true) + .setXmlName(Constants.XML.ALLOWED_CLOCK_SKEW_UNIT) + .setAllowExpression(false) + .setDefaultValue(new ModelNode(AllowedClockSkewUnits.SECONDS.name())) + .setAllowedValues(AllowedClockSkewUnits.MINUTES.name(), AllowedClockSkewUnits.SECONDS.name(), + AllowedClockSkewUnits.MILLISECONDS.name(), AllowedClockSkewUnits.MICROSECONDS.name(), + AllowedClockSkewUnits.NANOSECONDS.name()) + .setValidator(EnumValidator.create(AllowedClockSkewUnits.class, true, false)) + .build(); + + static final SimpleAttributeDefinition[] ATTRIBUTES = {ALLOWED_CLOCK_SKEW_UNIT, ALLOWED_CLOCK_SKEW_VALUE}; +} 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 90f139c5b1..b32133c024 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 @@ -74,6 +74,10 @@ public class Constants { static final String ROLE_MAPPINGS_PROVIDER_ID = "roleMappingsProviderId"; static final String ROLE_MAPPINGS_PROVIDER_CONFIG = "roleMappingsProviderConfig"; + + static final String ALLOWED_CLOCK_SKEW = "AllowedClockSkew"; + static final String ALLOWED_CLOCK_SKEW_UNIT = "unit"; + static final String ALLOWED_CLOCK_SKEW_VALUE = "value"; } @@ -136,6 +140,9 @@ public class Constants { static final String VALUE = "value"; static final String PROPERTY = "Property"; static final String ROLE_MAPPINGS_PROVIDER = "RoleMappingsProvider"; + + static final String ALLOWED_CLOCK_SKEW = "AllowedClockSkew"; + static final String ALLOWED_CLOCK_SKEW_UNIT = "unit"; } } diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java index 0a514f77a6..2766f33ff2 100644 --- a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java +++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java @@ -63,9 +63,15 @@ public class IdentityProviderDefinition extends SimpleResourceDefinition { .setAllowNull(false) .build(); + static final ObjectTypeAttributeDefinition ALLOWED_CLOCK_SKEW = + ObjectTypeAttributeDefinition.Builder.of(Constants.Model.ALLOWED_CLOCK_SKEW, + AllowedClockSkew.ATTRIBUTES) + .setAllowNull(true) + .build(); + static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD}; - static final SimpleAttributeDefinition[] ALL_ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD, SINGLE_SIGN_ON, SINGLE_LOGOUT}; + static final SimpleAttributeDefinition[] ALL_ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD, SINGLE_SIGN_ON, SINGLE_LOGOUT, ALLOWED_CLOCK_SKEW}; static final HashMap ATTRIBUTE_MAP = new HashMap<>(); diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java index 153a42d078..25aae2ce3a 100755 --- a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java +++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java @@ -159,6 +159,8 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • list, XMLExtendedStreamReader reader, PathAddress parentAddr) throws XMLStreamException { PathAddress addr = PathAddress.pathAddress(parentAddr, PathElement.pathElement(Constants.Model.KEY, "key-" + list.size())); @@ -458,6 +479,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • + + + This defines the allowed clock skew between IDP and SP in milliseconds. The default value is 0. + + @@ -336,4 +341,28 @@ + + + + The value is the allowed clock skew between the IDP and the SP. + + + + + + + + + + Time unit for the value of the clock skew. + + + + + + + + + + diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingAllowedClockSkewTestCase.java b/adapters/saml/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingAllowedClockSkewTestCase.java new file mode 100755 index 0000000000..c4adfd272c --- /dev/null +++ b/adapters/saml/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingAllowedClockSkewTestCase.java @@ -0,0 +1,238 @@ +/* + * 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.subsystem.adapter.saml.extension; + +import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Properties; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; +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 javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import org.jboss.as.controller.ExpressionResolver; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.model.test.ModelTestUtils; +import org.jboss.as.subsystem.test.KernelServices; +import org.jboss.dmr.ModelNode; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Test case for AllowedClockSkew subsystem configuration. + * + * @author rmartinc + */ +public class SubsystemParsingAllowedClockSkewTestCase extends AbstractSubsystemBaseTest { + + private String subsystemXml = null; + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + public SubsystemParsingAllowedClockSkewTestCase() { + super(KeycloakSamlExtension.SUBSYSTEM_NAME, new KeycloakSamlExtension()); + } + + @Override + protected String getSubsystemXml() throws IOException { + return subsystemXml; + } + + @Override + protected String getSubsystemXsdPath() throws Exception { + return "schema/wildfly-keycloak-saml_1_2.xsd"; + } + + @Override + protected String[] getSubsystemTemplatePaths() throws IOException { + return new String[]{ + "/subsystem-templates/keycloak-saml-adapter.xml" + }; + } + + @Override + protected Properties getResolvedProperties() { + return System.getProperties(); + } + + private void setSubsystemXml(String value, String unit) throws IOException { + try { + String template = readResource("keycloak-saml-1.2.xml"); + if (value != null) { + // assign the AllowedClockSkew element using DOM + DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = db.parse(new InputSource(new StringReader(template))); + // create the skew element + Element allowedClockSkew = doc.createElement(Constants.XML.ALLOWED_CLOCK_SKEW); + if (unit != null) { + allowedClockSkew.setAttribute(Constants.XML.ALLOWED_CLOCK_SKEW_UNIT, unit); + } + allowedClockSkew.setTextContent(value); + // locate the IDP and insert the node + XPath xPath = XPathFactory.newInstance().newXPath(); + NodeList nodeList = (NodeList) xPath.compile("/subsystem/secure-deployment[1]/SP/IDP").evaluate(doc, XPathConstants.NODESET); + nodeList.item(0).appendChild(allowedClockSkew); + // transform again to XML + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + subsystemXml = writer.getBuffer().toString(); + } else { + subsystemXml = template; + } + } catch (DOMException | ParserConfigurationException | SAXException | TransformerException | XPathExpressionException e) { + throw new IOException(e); + } + } + + private PathAddress getIdpPath() { + return PathAddress.EMPTY_ADDRESS + .append(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakSamlExtension.SUBSYSTEM_NAME)) + .append(PathElement.pathElement(Constants.Model.SECURE_DEPLOYMENT, "my-app.war")) + .append(PathElement.pathElement(Constants.Model.SERVICE_PROVIDER, "http://localhost:8080/sales-post-enc/")) + .append(PathElement.pathElement(Constants.Model.IDENTITY_PROVIDER, "idp")); + } + + private void testSubsystem(String value, String unit, int realValue, String realUnit) throws Exception { + setSubsystemXml(value, unit); + // perform the common test + KernelServices s = super.standardSubsystemTest(null, true); + // get the values for the AllowedClockSkew parameters + ModelNode idp = ModelTestUtils.getSubModel(s.readWholeModel(), getIdpPath()); + ModelNode allowedClockSkew = idp.get(Constants.Model.ALLOWED_CLOCK_SKEW); + if (value != null) { + Assert.assertTrue(allowedClockSkew.isDefined()); + ModelNode allowedClockSkewValue = allowedClockSkew.get(Constants.Model.ALLOWED_CLOCK_SKEW_VALUE); + ModelNode allowedClockSkewUnit = allowedClockSkew.get(Constants.Model.ALLOWED_CLOCK_SKEW_UNIT); + allowedClockSkewValue = ExpressionResolver.TEST_RESOLVER.resolveExpressions(allowedClockSkewValue); + allowedClockSkewUnit = ExpressionResolver.TEST_RESOLVER.resolveExpressions(allowedClockSkewUnit); + Assert.assertEquals(realValue, allowedClockSkewValue.asInt()); + if (unit != null) { + Assert.assertEquals(realUnit, allowedClockSkewUnit.asString()); + } else { + Assert.assertFalse(allowedClockSkewUnit.isDefined()); + } + } else { + Assert.assertFalse(allowedClockSkew.isDefined()); + } + } + + private void testSubsystem(String value, String unit) throws Exception { + testSubsystem(value, unit, value == null? -1 : Integer.parseInt(value.trim()), unit); + } + + private void testSchema(String value, String unit) throws Exception { + setSubsystemXml(value, unit); + super.testSchema(); + } + + @Test + @Override + public void testSubsystem() throws Exception { + testSubsystem(null, null); + } + + @Test + @Override + public void testSchema() throws Exception { + testSchema(null, null); + } + + @Test + public void testSubsystemAllowedClockSkewWithUnit() throws Exception { + testSubsystem("3500", "MILLISECONDS"); + } + + @Test + public void testSchemaAllowedClockSkewWithUnit() throws Exception { + testSchema("3500", "MILLISECONDS"); + } + + @Test + public void testSubsystemAllowedClockSkewWithoutUnit() throws Exception { + testSubsystem("1", null); + } + + @Test + public void testSchemaAllowedClockSkewWithoutUnit() throws Exception { + testSchema("1", null); + } + + @Test + public void testSubsystemAllowedClockSkewWithSpaces() throws Exception { + testSubsystem("\n 20 \n ", null); + } + + @Test + public void testErrorOnNonInteger() throws Exception { + exception.expect(XMLStreamException.class); + exception.expectMessage("WFLYCTL0097"); + testSubsystem("invalid-value", null, -1, null); + } + + @Test + public void testErrorOnNonPositiveInteger() throws Exception { + exception.expect(XMLStreamException.class); + exception.expectMessage("WFLYCTL0117"); + testSubsystem("0", null); + } + + @Test + public void testErrorNoValidUnit() throws Exception { + exception.expect(XMLStreamException.class); + exception.expectMessage("WFLYCTL0248"); + testSubsystem("30", "invalid-unit"); + } + + // For the moment no expressions allowed as the rest of the subsystem doesn't resolve expressions + //@Test + //public void testExpression() throws Exception { + // System.setProperty("test.prop.SKEW_TIME", "30"); + // System.setProperty("test.prop.SKEW_UNIT", "MILLISECONDS"); + // try { + // testSubsystem("${test.prop.SKEW_TIME}", "${test.prop.SKEW_UNIT}", 30, "MILLISECONDS"); + // } finally { + // System.clearProperty("test.prop.SKEW_TIME"); + // System.clearProperty("test.prop.SKEW_UNIT"); + // } + //} +}