KEYCLOAK-10975: Clock skew configuration in keycloak-saml.xml can't be found in the keycloak-saml subsystem

This commit is contained in:
rmartinc 2019-09-20 08:44:08 +02:00 committed by Hynek Mlnařík
parent 1cdc5e1969
commit 6283c7add3
14 changed files with 700 additions and 2 deletions

View file

@ -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};
}

View file

@ -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";
}
}

View file

@ -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<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();

View file

@ -161,6 +161,8 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
readSingleLogout(addIdentityProvider, reader);
} else if (Constants.XML.KEYS.equals(tagName)) {
readKeys(list, reader, addr);
} else if (Constants.XML.ALLOWED_CLOCK_SKEW.equals(tagName)) {
readAllowedClockSkew(addIdentityProvider, reader);
} else {
throw ParseUtils.unexpectedElement(reader);
}
@ -210,6 +212,25 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
list.addAll(keyList);
}
void readAllowedClockSkew(ModelNode addIdentityProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
ModelNode allowedClockSkew = addIdentityProvider.get(Constants.Model.ALLOWED_CLOCK_SKEW);
for (int i = 0; i < reader.getAttributeCount(); i++) {
String name = reader.getAttributeLocalName(i);
String value = reader.getAttributeValue(i);
if (Constants.XML.ALLOWED_CLOCK_SKEW_UNIT.equals(name)) {
SimpleAttributeDefinition attr = AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT;
attr.parseAndSetParameter(value, allowedClockSkew, reader);
} else {
throw ParseUtils.unexpectedAttribute(reader, i);
}
}
// the real value is the content
String value = reader.getElementText();
SimpleAttributeDefinition attr = AllowedClockSkew.ALLOWED_CLOCK_SKEW_VALUE;
attr.parseAndSetParameter(value, allowedClockSkew, reader);
}
void readKey(List<ModelNode> 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<Li
writeSingleSignOn(writer, idpAttributes.get(Constants.Model.SINGLE_SIGN_ON));
writeSingleLogout(writer, idpAttributes.get(Constants.Model.SINGLE_LOGOUT));
writeKeys(writer, idpAttributes.get(Constants.Model.KEY));
writeAllowedClockSkew(writer, idpAttributes.get(Constants.Model.ALLOWED_CLOCK_SKEW));
}
writer.writeEndElement();
}
@ -514,6 +536,18 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
}
}
void writeAllowedClockSkew(XMLExtendedStreamWriter writer, ModelNode allowedClockSkew) throws XMLStreamException {
if (!allowedClockSkew.isDefined()) {
return;
}
writer.writeStartElement(Constants.XML.ALLOWED_CLOCK_SKEW);
AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT.getAttributeMarshaller().marshallAsAttribute(AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT, allowedClockSkew, false, writer);
ModelNode allowedClockSkewValue = allowedClockSkew.get(Constants.Model.ALLOWED_CLOCK_SKEW_VALUE);
char[] chars = allowedClockSkewValue.asString().toCharArray();
writer.writeCharacters(chars, 0, chars.length);
writer.writeEndElement();
}
void writeKeyStore(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
if (!model.isDefined()) {
return;

View file

@ -86,3 +86,6 @@ keycloak-saml.IDP.SingleLogoutService.responseBinding=HTTP method to use for res
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
keycloak-saml.IDP.AllowedClockSkew=Allowed clock skew between the IDP and the SP
keycloak-saml.IDP.AllowedClockSkew.value=Allowed clock skew value between the IDP and the SP
keycloak-saml.IDP.AllowedClockSkew.unit=Time unit for the value of the clock skew. Values: MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS

View file

@ -105,6 +105,11 @@
<xs:element name="SingleSignOnService" minOccurs="1" maxOccurs="1" type="single-signon-type"/>
<xs:element name="SingleLogoutService" minOccurs="0" maxOccurs="1" type="single-logout-type"/>
<xs:element name="Keys" minOccurs="0" maxOccurs="1" type="keys-type"/>
<xs:element name="AllowedClockSkew" type="allowed-clock-skew-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>This defines the allowed clock skew between IDP and SP in milliseconds. The default value is 0.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="entityID" type="xs:string" use="required">
<xs:annotation>
@ -336,4 +341,28 @@
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="allowed-clock-skew-type">
<xs:annotation>
<xs:documentation>The value is the allowed clock skew between the IDP and the SP.</xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:positiveInteger">
<xs:attribute name="unit" type="clock-skew-unit-type"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="clock-skew-unit-type">
<xs:annotation>
<xs:documentation>Time unit for the value of the clock skew.</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:enumeration value="MINUTES" />
<xs:enumeration value="SECONDS" />
<xs:enumeration value="MILLISECONDS" />
<xs:enumeration value="MICROSECONDS" />
<xs:enumeration value="NANOSECONDS" />
</xs:restriction>
</xs:simpleType>
</xs:schema>

View file

@ -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");
// }
//}
}

View file

@ -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};
}

View file

@ -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";
}
}

View file

@ -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<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();

View file

@ -159,6 +159,8 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
readSingleLogout(addIdentityProvider, reader);
} else if (Constants.XML.KEYS.equals(tagName)) {
readKeys(list, reader, addr);
} else if (Constants.XML.ALLOWED_CLOCK_SKEW.equals(tagName)) {
readAllowedClockSkew(addIdentityProvider, reader);
} else {
throw ParseUtils.unexpectedElement(reader);
}
@ -208,6 +210,25 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
list.addAll(keyList);
}
void readAllowedClockSkew(ModelNode addIdentityProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
ModelNode allowedClockSkew = addIdentityProvider.get(Constants.Model.ALLOWED_CLOCK_SKEW);
for (int i = 0; i < reader.getAttributeCount(); i++) {
String name = reader.getAttributeLocalName(i);
String value = reader.getAttributeValue(i);
if (Constants.XML.ALLOWED_CLOCK_SKEW_UNIT.equals(name)) {
SimpleAttributeDefinition attr = AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT;
attr.parseAndSetParameter(value, allowedClockSkew, reader);
} else {
throw ParseUtils.unexpectedAttribute(reader, i);
}
}
// the real value is the content
String value = reader.getElementText();
SimpleAttributeDefinition attr = AllowedClockSkew.ALLOWED_CLOCK_SKEW_VALUE;
attr.parseAndSetParameter(value, allowedClockSkew, reader);
}
void readKey(List<ModelNode> 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<Li
writeSingleSignOn(writer, idpAttributes.get(Constants.Model.SINGLE_SIGN_ON));
writeSingleLogout(writer, idpAttributes.get(Constants.Model.SINGLE_LOGOUT));
writeKeys(writer, idpAttributes.get(Constants.Model.KEY));
writeAllowedClockSkew(writer, idpAttributes.get(Constants.Model.ALLOWED_CLOCK_SKEW));
}
writer.writeEndElement();
}
@ -512,6 +534,18 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
}
}
void writeAllowedClockSkew(XMLExtendedStreamWriter writer, ModelNode allowedClockSkew) throws XMLStreamException {
if (!allowedClockSkew.isDefined()) {
return;
}
writer.writeStartElement(Constants.XML.ALLOWED_CLOCK_SKEW);
AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT.getAttributeMarshaller().marshallAsAttribute(AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT, allowedClockSkew, false, writer);
ModelNode allowedClockSkewValue = allowedClockSkew.get(Constants.Model.ALLOWED_CLOCK_SKEW_VALUE);
char[] chars = allowedClockSkewValue.asString().toCharArray();
writer.writeCharacters(chars, 0, chars.length);
writer.writeEndElement();
}
void writeKeyStore(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
if (!model.isDefined()) {
return;

View file

@ -85,3 +85,6 @@ keycloak-saml.IDP.SingleLogoutService.responseBinding=HTTP method to use for res
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
keycloak-saml.IDP.AllowedClockSkew=Allowed clock skew between the IDP and the SP
keycloak-saml.IDP.AllowedClockSkew.value=Allowed clock skew value between the IDP and the SP
keycloak-saml.IDP.AllowedClockSkew.unit=Time unit for the value of the clock skew. Values: MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS

View file

@ -105,6 +105,11 @@
<xs:element name="SingleSignOnService" minOccurs="1" maxOccurs="1" type="single-signon-type"/>
<xs:element name="SingleLogoutService" minOccurs="0" maxOccurs="1" type="single-logout-type"/>
<xs:element name="Keys" minOccurs="0" maxOccurs="1" type="keys-type"/>
<xs:element name="AllowedClockSkew" type="allowed-clock-skew-type" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>This defines the allowed clock skew between IDP and SP in milliseconds. The default value is 0.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="entityID" type="xs:string" use="required">
<xs:annotation>
@ -336,4 +341,28 @@
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="allowed-clock-skew-type">
<xs:annotation>
<xs:documentation>The value is the allowed clock skew between the IDP and the SP.</xs:documentation>
</xs:annotation>
<xs:simpleContent>
<xs:extension base="xs:positiveInteger">
<xs:attribute name="unit" type="clock-skew-unit-type"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="clock-skew-unit-type">
<xs:annotation>
<xs:documentation>Time unit for the value of the clock skew.</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:enumeration value="MINUTES" />
<xs:enumeration value="SECONDS" />
<xs:enumeration value="MILLISECONDS" />
<xs:enumeration value="MICROSECONDS" />
<xs:enumeration value="NANOSECONDS" />
</xs:restriction>
</xs:simpleType>
</xs:schema>

View file

@ -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");
// }
//}
}