diff --git a/oiosaml/src/main/java/dk/gov/oio/saml/service/validation/AssertionValidationService.java b/oiosaml/src/main/java/dk/gov/oio/saml/service/validation/AssertionValidationService.java index 75262b6..031fbf2 100644 --- a/oiosaml/src/main/java/dk/gov/oio/saml/service/validation/AssertionValidationService.java +++ b/oiosaml/src/main/java/dk/gov/oio/saml/service/validation/AssertionValidationService.java @@ -196,11 +196,14 @@ private void validateAssertion(Assertion assertion, AuthnRequestWrapper authnReq } private void validateAttributeStatement(Map attributes, boolean isProfessional) throws AssertionValidationException { - // SpecVer + // SpecVer is mandatory in the OIOSAML attribute profiles, but its value identifies the profile + // version the assertion was issued under and changes between profile versions, so only the + // presence of the attribute is required here String specVersion = attributes.get(Constants.SPEC_VER); - if (!Constants.SPEC_VER_VAL.equals(specVersion)) { - throw new AssertionValidationException("specVersion Was: " + specVersion + " Expected: " + Constants.SPEC_VER_VAL); + if (specVersion == null || specVersion.isEmpty()) { + throw new AssertionValidationException("Assertions MUST contain the specVersion attribute " + Constants.SPEC_VER); } + log.debug("Assertion issued under OIOSAML profile version '{}'", specVersion); // Professional if (isProfessional) { diff --git a/oiosaml/src/main/java/dk/gov/oio/saml/util/Constants.java b/oiosaml/src/main/java/dk/gov/oio/saml/util/Constants.java index c080683..6ac31bb 100644 --- a/oiosaml/src/main/java/dk/gov/oio/saml/util/Constants.java +++ b/oiosaml/src/main/java/dk/gov/oio/saml/util/Constants.java @@ -69,7 +69,6 @@ public class Constants { // SAML Attributes constants public static final String SPEC_VER = "https://data.gov.dk/model/core/specVersion"; - public static final String SPEC_VER_VAL = "OIO-SAML-3.0"; public static final String PRIVILEGE_ATTRIBUTE = "https://data.gov.dk/model/core/eid/privilegesIntermediate"; public static final String LOA = "https://data.gov.dk/concept/core/nsis/loa"; public static final String CVR_NUMBER = "https://data.gov.dk/model/core/eid/professional/cvr"; diff --git a/oiosaml/src/test/java/dk/gov/oio/saml/service/validation/AssertionValidationServiceTest.java b/oiosaml/src/test/java/dk/gov/oio/saml/service/validation/AssertionValidationServiceTest.java index 070fa10..9dded8c 100644 --- a/oiosaml/src/test/java/dk/gov/oio/saml/service/validation/AssertionValidationServiceTest.java +++ b/oiosaml/src/test/java/dk/gov/oio/saml/service/validation/AssertionValidationServiceTest.java @@ -58,6 +58,60 @@ public void testValidateCorrectAssertion() throws Exception { validationService.validate(request, messageContext, response, assertion, new AuthnRequestWrapper(authnRequest, NSISLevel.SUBSTANTIAL, "")); } + @DisplayName("Test that validator will pass an assertion issued under a newer OIOSAML profile version") + @Test + public void testValidateAssertionWithNewerSpecVersion() throws Exception { + AssertionValidationService validationService = new AssertionValidationService(); + + // Mock HttpServletRequest + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer(TestConstants.SP_ASSERTION_CONSUMER_URL)); + + // Create AuthnRequest + AuthnRequestService authnRequestService = AuthnRequestService.getInstance(); + AuthnRequest authnRequest = getAuthnRequest(authnRequestService); + String inResponseToId = authnRequest.getID(); + + // Create MessageContext, Response and Assertion, with the specVersion value used by OIOSAML 4.0.0 + String nameID = "https://data.gov.dk/model/core/eid/person/uuid/37a5a1aa-67ce-4f70-b7c0-b8e678d585f7"; + MessageContext messageContext = IdpUtil.createMessageWithAssertion(true, true, true, nameID, TestConstants.SP_ENTITY_ID, TestConstants.SP_ASSERTION_CONSUMER_URL, inResponseToId, TestConstants.SPEC_VERSION_OIOSAML_40); + Response response = (Response) messageContext.getMessage(); + + AssertionService assertionService = new AssertionService(); + Assertion assertion = assertionService.getAssertion(response); + + // Validate + validationService.validate(request, messageContext, response, assertion, new AuthnRequestWrapper(authnRequest, NSISLevel.SUBSTANTIAL, "")); + } + + @DisplayName("Test that validator will fail an assertion without a specVersion attribute") + @Test + public void testFailAssertionWithoutSpecVersion() throws Exception { + AssertionValidationService validationService = new AssertionValidationService(); + + // Mock HttpServletRequest + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer(TestConstants.SP_ASSERTION_CONSUMER_URL)); + + // Create AuthnRequest + AuthnRequestService authnRequestService = AuthnRequestService.getInstance(); + AuthnRequest authnRequest = getAuthnRequest(authnRequestService); + String inResponseToId = authnRequest.getID(); + + // Create MessageContext, Response and Assertion, without the mandatory specVersion attribute + String nameID = "https://data.gov.dk/model/core/eid/person/uuid/37a5a1aa-67ce-4f70-b7c0-b8e678d585f7"; + MessageContext messageContext = IdpUtil.createMessageWithAssertion(true, true, true, nameID, TestConstants.SP_ENTITY_ID, TestConstants.SP_ASSERTION_CONSUMER_URL, inResponseToId, null); + Response response = (Response) messageContext.getMessage(); + + AssertionService assertionService = new AssertionService(); + Assertion assertion = assertionService.getAssertion(response); + + // Validate, should fail, specVersion is mandatory + Assertions.assertThrows(AssertionValidationException.class, () -> { + validationService.validate(request, messageContext, response, assertion, new AuthnRequestWrapper(authnRequest, NSISLevel.SUBSTANTIAL, "")); + }); + } + @DisplayName("Test that validator will fail an assertion with the wrong destination") @Test public void testFailAssertionWithWrongDestination() throws Exception { diff --git a/oiosaml/src/test/java/dk/gov/oio/saml/util/IdpUtil.java b/oiosaml/src/test/java/dk/gov/oio/saml/util/IdpUtil.java index 427dcb7..c0246af 100644 --- a/oiosaml/src/test/java/dk/gov/oio/saml/util/IdpUtil.java +++ b/oiosaml/src/test/java/dk/gov/oio/saml/util/IdpUtil.java @@ -58,8 +58,20 @@ public static MessageContext createMessageWithAssertion( String recipientEntityId, String assertionConsumerUrl, String inResponseToId) throws Exception { + return createMessageWithAssertion(encrypted, validCert, validSignature, subjectNameID, recipientEntityId, assertionConsumerUrl, inResponseToId, TestConstants.SPEC_VERSION_OIOSAML_30); + } + + public static MessageContext createMessageWithAssertion( + boolean encrypted, + boolean validCert, + boolean validSignature, + String subjectNameID, + String recipientEntityId, + String assertionConsumerUrl, + String inResponseToId, + String specVersion) throws Exception { // Create proxy Response - Response response = createResponse(encrypted, validCert, validSignature, subjectNameID, recipientEntityId, assertionConsumerUrl, inResponseToId); + Response response = createResponse(encrypted, validCert, validSignature, subjectNameID, recipientEntityId, assertionConsumerUrl, inResponseToId, specVersion); // Build Proxy MessageContext and add response MessageContext messageContext = new MessageContext<>(); @@ -89,6 +101,18 @@ public static Response createResponse( String recipientEntityId, String assertionConsumerUrl, String inResponseToId) throws Exception { + return createResponse(encrypted, validCert, validSignature, subjectNameID, recipientEntityId, assertionConsumerUrl, inResponseToId, TestConstants.SPEC_VERSION_OIOSAML_30); + } + + public static Response createResponse( + boolean encrypted, + boolean validCert, + boolean validSignature, + String subjectNameID, + String recipientEntityId, + String assertionConsumerUrl, + String inResponseToId, + String specVersion) throws Exception { DateTime issueInstant = new DateTime(); @@ -110,7 +134,7 @@ public static Response createResponse( status.setStatusCode(statusCode); response.setStatus(status); - Assertion assertion = createAssertion(issueInstant, subjectNameID, recipientEntityId, assertionConsumerUrl); + Assertion assertion = createAssertion(issueInstant, subjectNameID, recipientEntityId, assertionConsumerUrl, specVersion); SignAssertion(assertion, validSignature); if (encrypted) { EncryptedAssertion encryptedAssertion = encryptAssertion(assertion, validCert); @@ -282,7 +306,7 @@ private static void SignAssertion(Assertion assertion, boolean validSignature) t Signer.signObject(signature); } - private static Assertion createAssertion(DateTime issueInstant, String subjectNameID, String recipientEntityId, String assertionConsumerUrl) { + private static Assertion createAssertion(DateTime issueInstant, String subjectNameID, String recipientEntityId, String assertionConsumerUrl, String specVersion) { RandomIdentifierGenerationStrategy secureRandomIdGenerator = new RandomIdentifierGenerationStrategy(); String id = secureRandomIdGenerator.generateIdentifier(); @@ -310,7 +334,9 @@ private static Assertion createAssertion(DateTime issueInstant, String subjectNa AttributeStatement attributeStatement = buildSAMLObject(AttributeStatement.class); List attributes = attributeStatement.getAttributes(); - attributes.add(createSimpleAttribute("https://data.gov.dk/model/core/specVersion", "OIO-SAML-3.0")); + if (specVersion != null) { + attributes.add(createSimpleAttribute(Constants.SPEC_VER, specVersion)); + } attributes.add(createSimpleAttribute("https://data.gov.dk/concept/core/nsis/loa", NSISLevel.SUBSTANTIAL.getName())); assertion.getAttributeStatements().add(attributeStatement); diff --git a/oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java b/oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java index 5424a13..0953299 100644 --- a/oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java +++ b/oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java @@ -147,6 +147,10 @@ public class TestConstants { " \n" + ""; public static final String BAD_SP_ASSERTION_CONSUMER_URL = "http://localhost:8080/sso"; + + // Value of the specVersion attribute in the OIOSAML Web SSO profiles, see [OIO-ALG-01] chapter 6 + public static final String SPEC_VERSION_OIOSAML_30 = "OIO-SAML-3.0"; + public static final String SPEC_VERSION_OIOSAML_40 = "https://data.gov.dk/saml/profile/oio/4.0.0/"; public static final String VALID_CERTIFICATE = "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";