From 99ca8f0f43f6aa86cbca6875d2ef8b37d01a54ff Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Mon, 24 Sep 2018 15:15:51 -0700 Subject: [PATCH 1/3] [SHIBUI-855] Added backend support for forceAuthn to Relying Party Overrides. --- .../RelyingPartyOverridesRepresentation.java | 10 +++++ .../JPAEntityDescriptorServiceImpl.java | 3 ++ .../ui/service/JPAEntityServiceImpl.java | 8 ++++ .../shibboleth/admin/util/MDDCConstants.java | 3 ++ .../EntityDescriptorControllerTests.groovy | 6 ++- ...JPAEntityDescriptorServiceImplTests.groovy | 45 ++++++++++++++++++- .../src/test/resources/json/SHIBUI-855.json | 45 +++++++++++++++++++ 7 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 backend/src/test/resources/json/SHIBUI-855.json diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/RelyingPartyOverridesRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/RelyingPartyOverridesRepresentation.java index d3487b556..cac2a1d6a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/RelyingPartyOverridesRepresentation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/RelyingPartyOverridesRepresentation.java @@ -26,6 +26,8 @@ public class RelyingPartyOverridesRepresentation implements Serializable { private List authenticationMethods = new ArrayList<>(); + private boolean forceAuthn; + public boolean isSignAssertion() { return signAssertion; } @@ -97,4 +99,12 @@ public List getAuthenticationMethods() { public void setAuthenticationMethods(List authenticationMethods) { this.authenticationMethods = authenticationMethods; } + + public boolean isForceAuthn() { + return forceAuthn; + } + + public void setForceAuthn(boolean forceAuthn) { + this.forceAuthn = forceAuthn; + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java index 28fa8b3fd..a0ec7e1b6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java @@ -530,6 +530,9 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope case MDDCConstants.RELEASE_ATTRIBUTES: representation.setAttributeRelease(getStringListOfAttributeValues(attribute.getAttributeValues())); break; + case MDDCConstants.FORCE_AUTHN: + relyingPartyOverridesRepresentation.setForceAuthn(getBooleanValueOfAttribute(jpaAttribute)); + break; default: break; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImpl.java index 263f0c01e..e8b4ce590 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImpl.java @@ -26,6 +26,11 @@ public JPAEntityServiceImpl(OpenSamlObjects openSamlObjects) { this.openSamlObjects = openSamlObjects; } + public JPAEntityServiceImpl(OpenSamlObjects openSamlObjects, AttributeUtility attributeUtility) { + this.openSamlObjects = openSamlObjects; + this.attributeUtility = attributeUtility; + } + @Override public List getAttributeListFromEntityRepresentation(EntityDescriptorRepresentation entityDescriptorRepresentation) { List list = new ArrayList<>(); @@ -109,6 +114,9 @@ public List getAttributeListFromRelyingPartyOverridesRepresentation(R if (relyingPartyOverridesRepresentation.getAuthenticationMethods() != null && relyingPartyOverridesRepresentation.getAuthenticationMethods().size() > 0) { list.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.DEFAULT_AUTHENTICATION_METHODS, MDDCConstants.DEFAULT_AUTHENTICATION_METHODS_FN, relyingPartyOverridesRepresentation.getAuthenticationMethods())); } + if (relyingPartyOverridesRepresentation.isForceAuthn()) { + list.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.FORCE_AUTHN, MDDCConstants.FORCE_AUTHN_FN, true)); + } } return (List)(List)list; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/MDDCConstants.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/MDDCConstants.java index 2396eb30d..05953056a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/MDDCConstants.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/MDDCConstants.java @@ -36,6 +36,9 @@ public class MDDCConstants { public static final String DEFAULT_AUTHENTICATION_METHODS = "http://shibboleth.net/ns/profiles/defaultAuthenticationMethods"; public static final String DEFAULT_AUTHENTICATION_METHODS_FN = "defaultAuthenticationMethods"; + public static final String FORCE_AUTHN = "http://shibboleth.net/ns/profiles/forceAuthn"; + public static final String FORCE_AUTHN_FN = "forceAuthn"; + public static final Map PROTOCOL_BINDINGS; static { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy index 739243d24..bc3de95e5 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy @@ -457,7 +457,8 @@ class EntityDescriptorControllerTests extends Specification { "omitNotBefore": false, "responderId": null, "nameIdFormats": [], - "authenticationMethods": [] + "authenticationMethods": [], + "forceAuthn": false }, "attributeRelease": [ "givenName", @@ -585,7 +586,8 @@ class EntityDescriptorControllerTests extends Specification { "omitNotBefore": false, "responderId": null, "nameIdFormats": [], - "authenticationMethods": [] + "authenticationMethods": [], + "forceAuthn": false }, "attributeRelease": [ "givenName", diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy index 396b3df5b..679260897 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy @@ -10,11 +10,13 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRe import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.LogoutEndpointRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.MduiRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.OrganizationRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.SecurityInfoRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSsoDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator +import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import org.springframework.boot.test.json.JacksonTester import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input @@ -31,7 +33,8 @@ class JPAEntityDescriptorServiceImplTests extends Specification { it } - def service = new JPAEntityDescriptorServiceImpl(openSamlObjects, new JPAEntityServiceImpl(openSamlObjects)) + def service = new JPAEntityDescriptorServiceImpl(openSamlObjects, + new JPAEntityServiceImpl(openSamlObjects, new AttributeUtility(openSamlObjects))) JacksonTester jacksonTester @@ -447,6 +450,46 @@ class JPAEntityDescriptorServiceImplTests extends Specification { !diff.hasDifferences() } + def "SHIBUI-855, generate forceAuthn XML"() { + when: + + def expected = ''' + + + + + true + + + + +''' + + def test = openSamlObjects.marshalToXmlString(service.createDescriptorFromRepresentation(new EntityDescriptorRepresentation().with { + it.entityId = 'http://test.example.org/test1' + it.relyingPartyOverrides = new RelyingPartyOverridesRepresentation().with { + it.forceAuthn = true; + it + } + it + })) + + def diff = DiffBuilder.compare(Input.fromString(expected)).withTest(Input.fromString(test)).ignoreComments().ignoreWhitespace().build() + + then: + !diff.hasDifferences() + } + + + def "SHIBUI-855, read forceAuthn from json"() { + when: + def representation = new ObjectMapper().readValue(this.class.getResource('/json/SHIBUI-855.json').bytes, EntityDescriptorRepresentation) + def output = service.createRepresentationFromDescriptor(service.createDescriptorFromRepresentation(representation)) + + then: + assert output.relyingPartyOverrides?.forceAuthn == true + } + def "test ACS configuration"() { when: def expected = ''' diff --git a/backend/src/test/resources/json/SHIBUI-855.json b/backend/src/test/resources/json/SHIBUI-855.json new file mode 100644 index 000000000..14ff554b3 --- /dev/null +++ b/backend/src/test/resources/json/SHIBUI-855.json @@ -0,0 +1,45 @@ +{ + "id": "", + "entityId": "test", + "serviceProviderName": "test", + "organization": { + "name": null, + "displayName": null, + "url": null + }, + "contacts": [], + "mdui": { + "displayName": null, + "informationUrl": null, + "privacyStatementUrl": null, + "description": null, + "logoUrl": null, + "logoHeight": null, + "logoWidth": null + }, + "securityInfo": { + "x509CertificateAvailable": false, + "authenticationRequestsSigned": false, + "wantAssertionsSigned": false, + "x509Certificates": [] + }, + "assertionConsumerServices": [ + { + "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + "locationUrl": "test", + "makeDefault": true + } + ], + "serviceProviderSsoDescriptor": { + "protocolSupportEnum": null, + "nameIdFormats": [] + }, + "logoutEndpoints": [], + "serviceEnabled": false, + "relyingPartyOverrides": { + "nameIdFormats": [], + "authenticationMethods": [], + "forceAuthn": true + }, + "attributeRelease": [] +} \ No newline at end of file From b38c899a7aaf4bb24ed8131f0b29e7575a14859a Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 25 Sep 2018 14:38:08 -0700 Subject: [PATCH 2/3] SHIBUI-855 Added forceAuthN attribute --- .../main/resources/i18n/messages_en.properties | 2 ++ .../main/resources/i18n/messages_es.properties | 2 ++ .../forms/relying-party-form.component.html | 17 +++++++++++++++++ .../forms/relying-party-form.component.ts | 1 + .../schema/filter/entity-attributes.schema.json | 6 ++++++ 5 files changed, 28 insertions(+) diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 2dec2872d..8b20b2efc 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -322,6 +322,7 @@ label.attribute-eduPersonEntitlement=eduPersonEntitlement label.attribute-eduPersonAssurance=eduPersonAssurance label.attribute-eduPersonUniqueId=eduPersonUniqueId label.attribute-employeeNumber=employeeNumber +label.force-authn=Force AuthN message.must-be-unique=Must be unique. message.name-must-be-unique=Name must be unique. @@ -356,6 +357,7 @@ message.entity-id-min-unique=You must add at least one entity id target and they message.required-for-scripts=Required for Scripts message.required-for-regex=Required for Regex +tooltip.force-authn=Disallows use (or reuse) of authentication results and login flows that don\u0027t provide a real-time proof of user presence in the login process tooltip.service-provider-name-dashboard-display-only=Service Provider Name (Dashboard Display Only) tooltip.service-provider-entity-id=Service Provider Entity ID tooltip.organization-name=Organization Name diff --git a/backend/src/main/resources/i18n/messages_es.properties b/backend/src/main/resources/i18n/messages_es.properties index 449cd3af0..241aa5c49 100644 --- a/backend/src/main/resources/i18n/messages_es.properties +++ b/backend/src/main/resources/i18n/messages_es.properties @@ -322,6 +322,7 @@ label.attribute-eduPersonEntitlement=(es) eduPersonEntitlement label.attribute-eduPersonAssurance=(es) eduPersonAssurance label.attribute-eduPersonUniqueId=(es) eduPersonUniqueId label.attribute-employeeNumber=(es) employeeNumber +label.force-authn=(es) Force AuthN message.must-be-unique=(es) Must be unique. message.name-must-be-unique=(es) Name must be unique. @@ -356,6 +357,7 @@ message.entity-id-min-unique=(es) You must add at least one entity id target and message.required-for-scripts=(es) Required for Scripts message.required-for-regex=(es) Required for Regex +tooltip.force-authn=(es) Disallows use (or reuse) of authentication results and login flows that don\u0027t provide a real-time proof of user presence in the login process tooltip.service-provider-name-dashboard-display-only=(es) Service Provider Name (Dashboard Display Only) tooltip.service-provider-entity-id=(es) Service Provider Entity ID tooltip.organization-name=(es) Organization Name diff --git a/ui/src/app/metadata/domain/component/forms/relying-party-form.component.html b/ui/src/app/metadata/domain/component/forms/relying-party-form.component.html index 4fb9f2713..19f2f234f 100644 --- a/ui/src/app/metadata/domain/component/forms/relying-party-form.component.html +++ b/ui/src/app/metadata/domain/component/forms/relying-party-form.component.html @@ -147,6 +147,23 @@ [attr.aria-label]="'tooltip.instruction' | translate"> +
+
+
+ + +
+ + Disallows use (or reuse) of authentication results and login flows that don't provide a real-time proof of user + presence in the login process + + +
+
diff --git a/ui/src/app/metadata/domain/component/forms/relying-party-form.component.ts b/ui/src/app/metadata/domain/component/forms/relying-party-form.component.ts index 4aef6bff4..fe4c15038 100644 --- a/ui/src/app/metadata/domain/component/forms/relying-party-form.component.ts +++ b/ui/src/app/metadata/domain/component/forms/relying-party-form.component.ts @@ -46,6 +46,7 @@ export class RelyingPartyFormComponent extends ProviderFormFragmentComponent imp turnOffEncryption: false, useSha: false, ignoreAuthenticationMethod: false, + forceAuthn: false, omitNotBefore: false, responderId: '', nameIdFormats: this.nameIdFormatList, diff --git a/ui/src/assets/schema/filter/entity-attributes.schema.json b/ui/src/assets/schema/filter/entity-attributes.schema.json index 8d4e3cdd9..78d22fe27 100644 --- a/ui/src/assets/schema/filter/entity-attributes.schema.json +++ b/ui/src/assets/schema/filter/entity-attributes.schema.json @@ -123,6 +123,12 @@ "type": "boolean", "default": false }, + "forceAuthn": { + "title": "label.force-authn", + "description": "tooltip.force-authn", + "type": "boolean", + "default": false + }, "omitNotBefore": { "title": "label.omit-not-before-condition", "type": "boolean", From a0f6e0ad49926fdaa2bb45dc786aed0312e1e226 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 25 Sep 2018 15:45:01 -0700 Subject: [PATCH 3/3] [SHIBUI-899] Added forceAuthn changes to getRelyingPartyOverridesRepresentationFromAttributeList. This code looks eerily similar to JPAEntityServiceImpl... --- .../admin/util/ModelRepresentationConversions.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java index e68225b5a..d2fbda436 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java @@ -100,6 +100,9 @@ public static RelyingPartyOverridesRepresentation getRelyingPartyOverridesRepres case MDDCConstants.DEFAULT_AUTHENTICATION_METHODS: relyingPartyOverridesRepresentation.setAuthenticationMethods(getStringListValueOfAttribute(jpaAttribute)); break; + case MDDCConstants.FORCE_AUTHN: + relyingPartyOverridesRepresentation.setForceAuthn(getBooleanValueOfAttribute(jpaAttribute)); + break; default: break; } @@ -158,6 +161,9 @@ public static List getAttributeListFromA list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.DEFAULT_AUTHENTICATION_METHODS, MDDCConstants .DEFAULT_AUTHENTICATION_METHODS_FN, relyingPartyOverridesRepresentation.getAuthenticationMethods())); } + if (relyingPartyOverridesRepresentation.isForceAuthn()) { + list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.FORCE_AUTHN, MDDCConstants.FORCE_AUTHN_FN, true)); + } } return (List) (List) list;