diff --git a/backend/build.gradle b/backend/build.gradle index 914ccc374..156829bf4 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -170,6 +170,8 @@ dependencies { // CSV file support compile 'com.opencsv:opencsv:4.4' + + testCompile 'org.skyscreamer:jsonassert:1.5.0' } def generatedSrcDir = new File(buildDir, 'generated/src/main/java') diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index adcabd2f0..ba3188daa 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -132,16 +132,10 @@ public ResponseEntity update(@RequestBody EntityDescriptorRepresentation edRe return new ResponseEntity(HttpStatus.CONFLICT); } - EntityDescriptor updatedEd = - EntityDescriptor.class.cast(entityDescriptorService.createDescriptorFromRepresentation(edRepresentation)); + entityDescriptorService.updateDescriptorFromRepresentation(existingEd, edRepresentation); + existingEd = entityDescriptorRepository.save(existingEd); - updatedEd.setAudId(existingEd.getAudId()); - updatedEd.setResourceId(existingEd.getResourceId()); - updatedEd.setCreatedDate(existingEd.getCreatedDate()); - - updatedEd = entityDescriptorRepository.save(updatedEd); - - return ResponseEntity.ok().body(entityDescriptorService.createRepresentationFromDescriptor(updatedEd)); + return ResponseEntity.ok().body(entityDescriptorService.createRepresentationFromDescriptor(existingEd)); } else { return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, "You are not authorized to perform the requested operation.")); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java index 498e46f32..2f53e5cc8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -171,6 +172,16 @@ public SPSSODescriptor getSPSSODescriptor(String s) { .orElse(null); } + @Transient + public Optional getOptionalSPSSODescriptor(String s) { + return Optional.ofNullable(this.getSPSSODescriptor(s)); + } + + @Transient + public Optional getOptionalSPSSODescriptor() { + return this.getOptionalSPSSODescriptor(""); + } + @Override public AuthnAuthorityDescriptor getAuthnAuthorityDescriptor(String s) { return authnAuthorityDescriptor; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Extensions.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Extensions.java index dd8521f70..c1538f463 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Extensions.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Extensions.java @@ -5,8 +5,10 @@ import javax.annotation.Nullable; import javax.persistence.Entity; +import javax.persistence.Transient; import java.util.Collections; import java.util.List; +import java.util.Optional; @Entity @@ -17,4 +19,14 @@ public class Extensions extends AbstractElementExtensibleXMLObject implements or public List getOrderedChildren() { return Collections.unmodifiableList(this.getUnknownXMLObjects()); } + + @Transient + public Optional getOptionalUIInfo() { + List uiinfos = this.getUnknownXMLObjects(UIInfo.DEFAULT_ELEMENT_NAME); + if (uiinfos.size() == 0) { + return Optional.empty(); + } else { + return Optional.of((UIInfo) uiinfos.get(0)); + } + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SPSSODescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SPSSODescriptor.java index cda00fe4f..379c2c928 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SPSSODescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SPSSODescriptor.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; @Entity @EqualsAndHashCode(callSuper = true) @@ -116,4 +117,9 @@ public List getOrderedChildren() { return Collections.unmodifiableList(children); } + + @Transient + public Optional getOptionalExtensions() { + return Optional.ofNullable(this.getExtensions()); + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java index bb175432c..a8d4fffaa 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; +import javax.persistence.Column; import javax.persistence.Entity; import javax.validation.constraints.NotNull; @@ -21,5 +22,6 @@ public RegexScheme() { } @NotNull + @Column(name = "match_regex") private String match; } 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 6bf5ac294..ba3680ee7 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 @@ -90,84 +90,109 @@ public JPAEntityDescriptorServiceImpl(OpenSamlObjects openSamlObjects, EntitySer this.userService = userService; } + @Override + public void updateDescriptorFromRepresentation(org.opensaml.saml.saml2.metadata.EntityDescriptor entityDescriptor, EntityDescriptorRepresentation representation) { + if (!(entityDescriptor instanceof EntityDescriptor)) { + throw new UnsupportedOperationException("not yet implemented"); + } + buildDescriptorFromRepresentation((EntityDescriptor) entityDescriptor, representation); + } + @Override public EntityDescriptor createDescriptorFromRepresentation(final EntityDescriptorRepresentation representation) { EntityDescriptor ed = openSamlObjects.buildDefaultInstanceOfType(EntityDescriptor.class); ed.setEntityID(representation.getEntityId()); - User user = userService.getCurrentUser(); - if (user != null) { - ed.setCreatedBy(user.getUsername()); - } else { - LOGGER.warn("Current user was null! Who is logged in?"); - } - - // setup SPSSODescriptor - if (representation.getServiceProviderSsoDescriptor() != null) { - SPSSODescriptor spssoDescriptor = getSPSSODescriptorFromEntityDescriptor(ed); - - if (!Strings.isNullOrEmpty(representation.getServiceProviderSsoDescriptor().getProtocolSupportEnum())) { - spssoDescriptor.setSupportedProtocols( - Arrays.stream(representation.getServiceProviderSsoDescriptor().getProtocolSupportEnum().split(",")).map(p -> MDDCConstants.PROTOCOL_BINDINGS.get(p.trim())).collect(Collectors.toList()) - ); - } - - - if (representation.getServiceProviderSsoDescriptor() != null && representation.getServiceProviderSsoDescriptor().getNameIdFormats() != null && representation.getServiceProviderSsoDescriptor().getNameIdFormats().size() > 0) { - for (String nameidFormat : representation.getServiceProviderSsoDescriptor().getNameIdFormats()) { - NameIDFormat nameIDFormat = openSamlObjects.buildDefaultInstanceOfType(NameIDFormat.class); - nameIDFormat.setFormat(nameidFormat); - - spssoDescriptor.getNameIDFormats().add(nameIDFormat); - } - } - } + return buildDescriptorFromRepresentation(ed, representation); + } + private EntityDescriptor buildDescriptorFromRepresentation(final EntityDescriptor ed, final EntityDescriptorRepresentation representation) { + setupSPSSODescriptor(ed, representation); ed.setServiceProviderName(representation.getServiceProviderName()); ed.setServiceEnabled(representation.isServiceEnabled()); + setupOrganization(ed, representation); + setupContacts(ed, representation); + setupUIInfo(ed, representation); + setupSecurity(ed, representation); + setupACSs(ed, representation); + setupLogout(ed, representation); + setupRelyingPartyOverrides(ed, representation); - // set up organization - if (representation.getOrganization() != null && representation.getOrganization().getName() != null && representation.getOrganization().getDisplayName() != null && representation.getOrganization().getUrl() != null) { - OrganizationRepresentation organizationRepresentation = representation.getOrganization(); - Organization organization = openSamlObjects.buildDefaultInstanceOfType(Organization.class); - - OrganizationName organizationName = openSamlObjects.buildDefaultInstanceOfType(OrganizationName.class); - organizationName.setXMLLang("en"); - organizationName.setValue(organizationRepresentation.getName()); - organization.getOrganizationNames().add(organizationName); - - OrganizationDisplayName organizationDisplayName = openSamlObjects.buildDefaultInstanceOfType(OrganizationDisplayName.class); - organizationDisplayName.setXMLLang("en"); - organizationDisplayName.setValue(organizationRepresentation.getDisplayName()); - organization.getDisplayNames().add(organizationDisplayName); - - OrganizationURL organizationURL = openSamlObjects.buildDefaultInstanceOfType(OrganizationURL.class); - organizationURL.setXMLLang("en"); - organizationURL.setValue(organizationRepresentation.getUrl()); - organization.getURLs().add(organizationURL); + return ed; + } - ed.setOrganization(organization); + void setupRelyingPartyOverrides(EntityDescriptor ed, EntityDescriptorRepresentation representation) { + if (representation.getRelyingPartyOverrides() != null || (representation.getAttributeRelease() != null && representation.getAttributeRelease().size() > 0)) { + // TODO: review if we need more than a naive implementation + getOptionalEntityAttributes(ed).ifPresent(entityAttributes -> entityAttributes.getAttributes().clear()); + getEntityAttributes(ed).getAttributes().addAll(entityService.getAttributeListFromEntityRepresentation(representation)); + } else { + getOptionalEntityAttributes(ed).ifPresent(entityAttributes -> entityAttributes.getAttributes().clear()); } + } - // set up contacts - if (representation.getContacts() != null && representation.getContacts().size() > 0) { - for (ContactRepresentation contactRepresentation : representation.getContacts()) { - ContactPerson contactPerson = ((ContactPersonBuilder) openSamlObjects.getBuilderFactory().getBuilder(ContactPerson.DEFAULT_ELEMENT_NAME)).buildObject(); - - contactPerson.setType(contactRepresentation.getType()); + void setupLogout(EntityDescriptor ed, EntityDescriptorRepresentation representation) { + // setup logout + if (representation.getLogoutEndpoints() != null && !representation.getLogoutEndpoints().isEmpty()) { + // TODO: review if we need more than a naive implementation + ed.getOptionalSPSSODescriptor().ifPresent(spssoDescriptor -> spssoDescriptor.getSingleLogoutServices().clear()); + for (LogoutEndpointRepresentation logoutEndpointRepresentation : representation.getLogoutEndpoints()) { + SingleLogoutService singleLogoutService = openSamlObjects.buildDefaultInstanceOfType(SingleLogoutService.class); + singleLogoutService.setBinding(logoutEndpointRepresentation.getBindingType()); + singleLogoutService.setLocation(logoutEndpointRepresentation.getUrl()); - GivenName givenName = openSamlObjects.buildDefaultInstanceOfType(GivenName.class); - givenName.setName(contactRepresentation.getName()); - contactPerson.setGivenName(givenName); + getSPSSODescriptorFromEntityDescriptor(ed).getSingleLogoutServices().add(singleLogoutService); + } + } else { + ed.getOptionalSPSSODescriptor().ifPresent(spssoDescriptor -> spssoDescriptor.getSingleLogoutServices().clear()); + } + } - EmailAddress emailAddress = openSamlObjects.buildDefaultInstanceOfType(EmailAddress.class); - emailAddress.setAddress(contactRepresentation.getEmailAddress()); - contactPerson.addEmailAddress(emailAddress); + void setupACSs(EntityDescriptor ed, EntityDescriptorRepresentation representation) { + // setup ACSs + if (representation.getAssertionConsumerServices() != null && representation.getAssertionConsumerServices().size() > 0) { + // TODO: review if we need more than a naive implementation + ed.getOptionalSPSSODescriptor().ifPresent(spssoDescriptor -> spssoDescriptor.getAssertionConsumerServices().clear()); + for (AssertionConsumerServiceRepresentation acsRepresentation : representation.getAssertionConsumerServices()) { + AssertionConsumerService assertionConsumerService = openSamlObjects.buildDefaultInstanceOfType(AssertionConsumerService.class); + getSPSSODescriptorFromEntityDescriptor(ed).getAssertionConsumerServices().add(assertionConsumerService); + if (acsRepresentation.isMakeDefault()) { + assertionConsumerService.setIsDefault(true); + } + assertionConsumerService.setBinding(acsRepresentation.getBinding()); + assertionConsumerService.setLocation(acsRepresentation.getLocationUrl()); + } + } else { + ed.getOptionalSPSSODescriptor().ifPresent(spssoDescriptor -> spssoDescriptor.getAssertionConsumerServices().clear()); + } + } - ed.addContactPerson(contactPerson); + void setupSecurity(EntityDescriptor ed, EntityDescriptorRepresentation representation) { + // setup security + if (representation.getSecurityInfo() != null) { + SecurityInfoRepresentation securityInfoRepresentation = representation.getSecurityInfo(); + if (securityInfoRepresentation.isAuthenticationRequestsSigned()) { + getSPSSODescriptorFromEntityDescriptor(ed).setAuthnRequestsSigned(true); + } + if (securityInfoRepresentation.isWantAssertionsSigned()) { + getSPSSODescriptorFromEntityDescriptor(ed).setWantAssertionsSigned(true); } + if (securityInfoRepresentation.isX509CertificateAvailable()) { + for (SecurityInfoRepresentation.X509CertificateRepresentation x509CertificateRepresentation : securityInfoRepresentation.getX509Certificates()) { + KeyDescriptor keyDescriptor = createKeyDescriptor(x509CertificateRepresentation.getName(), x509CertificateRepresentation.getType(), x509CertificateRepresentation.getValue()); + getSPSSODescriptorFromEntityDescriptor(ed).addKeyDescriptor(keyDescriptor); + } + } + } else { + ed.getOptionalSPSSODescriptor().ifPresent( spssoDescriptor -> { + spssoDescriptor.setAuthnRequestsSigned((Boolean) null); + spssoDescriptor.setWantAssertionsSigned((Boolean) null); + spssoDescriptor.getKeyDescriptors().clear(); + }); } + } + void setupUIInfo(EntityDescriptor ed, EntityDescriptorRepresentation representation) { // set up mdui if (representation.getMdui() != null) { MduiRepresentation mduiRepresentation = representation.getMdui(); @@ -177,6 +202,11 @@ public EntityDescriptor createDescriptorFromRepresentation(final EntityDescripto getUIInfo(ed).addDisplayName(displayName); displayName.setValue(mduiRepresentation.getDisplayName()); displayName.setXMLLang("en"); + } else { + ed.getOptionalSPSSODescriptor() + .flatMap(SPSSODescriptor::getOptionalExtensions) + .flatMap(Extensions::getOptionalUIInfo) + .ifPresent(u -> u.getXMLObjects().removeAll(u.getDisplayNames())); } if (!Strings.isNullOrEmpty(mduiRepresentation.getInformationUrl())) { @@ -184,6 +214,11 @@ public EntityDescriptor createDescriptorFromRepresentation(final EntityDescripto getUIInfo(ed).addInformationURL(informationURL); informationURL.setValue(mduiRepresentation.getInformationUrl()); informationURL.setXMLLang("en"); + } else { + ed.getOptionalSPSSODescriptor() + .flatMap(SPSSODescriptor::getOptionalExtensions) + .flatMap(Extensions::getOptionalUIInfo) + .ifPresent(u -> u.getXMLObjects().removeAll(u.getInformationURLs())); } if (!Strings.isNullOrEmpty(mduiRepresentation.getPrivacyStatementUrl())) { @@ -191,6 +226,11 @@ public EntityDescriptor createDescriptorFromRepresentation(final EntityDescripto getUIInfo(ed).addPrivacyStatementURL(privacyStatementURL); privacyStatementURL.setValue(mduiRepresentation.getPrivacyStatementUrl()); privacyStatementURL.setXMLLang("en"); + } else { + ed.getOptionalSPSSODescriptor() + .flatMap(SPSSODescriptor::getOptionalExtensions) + .flatMap(Extensions::getOptionalUIInfo) + .ifPresent(u -> u.getXMLObjects().removeAll(u.getPrivacyStatementURLs())); } if (!Strings.isNullOrEmpty(mduiRepresentation.getDescription())) { @@ -198,6 +238,11 @@ public EntityDescriptor createDescriptorFromRepresentation(final EntityDescripto getUIInfo(ed).addDescription(description); description.setValue(mduiRepresentation.getDescription()); description.setXMLLang("en"); + } else { + ed.getOptionalSPSSODescriptor() + .flatMap(SPSSODescriptor::getOptionalExtensions) + .flatMap(Extensions::getOptionalUIInfo) + .ifPresent(u -> u.getXMLObjects().removeAll(u.getDescriptions())); } if (!Strings.isNullOrEmpty(mduiRepresentation.getLogoUrl())) { @@ -207,70 +252,117 @@ public EntityDescriptor createDescriptorFromRepresentation(final EntityDescripto logo.setHeight(mduiRepresentation.getLogoHeight()); logo.setWidth(mduiRepresentation.getLogoWidth()); logo.setXMLLang("en"); + } else { + ed.getOptionalSPSSODescriptor() + .flatMap(SPSSODescriptor::getOptionalExtensions) + .flatMap(Extensions::getOptionalUIInfo) + .ifPresent(u -> u.getXMLObjects().removeAll(u.getLogos())); } + } else { + removeUIInfo(ed); } + } - // setup security - if (representation.getSecurityInfo() != null) { - SecurityInfoRepresentation securityInfoRepresentation = representation.getSecurityInfo(); - if (securityInfoRepresentation.isAuthenticationRequestsSigned()) { - getSPSSODescriptorFromEntityDescriptor(ed).setAuthnRequestsSigned(true); - } - if (securityInfoRepresentation.isWantAssertionsSigned()) { - getSPSSODescriptorFromEntityDescriptor(ed).setWantAssertionsSigned(true); - } - if (securityInfoRepresentation.isX509CertificateAvailable()) { - for (SecurityInfoRepresentation.X509CertificateRepresentation x509CertificateRepresentation : securityInfoRepresentation.getX509Certificates()) { - KeyDescriptor keyDescriptor = createKeyDescriptor(x509CertificateRepresentation.getName(), x509CertificateRepresentation.getType(), x509CertificateRepresentation.getValue()); - getSPSSODescriptorFromEntityDescriptor(ed).addKeyDescriptor(keyDescriptor); - } + void setupContacts(EntityDescriptor ed, EntityDescriptorRepresentation representation) { + // set up contacts + if (representation.getContacts() != null && representation.getContacts().size() > 0) { + for (ContactRepresentation contactRepresentation : representation.getContacts()) { + ContactPerson contactPerson = ((ContactPersonBuilder) openSamlObjects.getBuilderFactory().getBuilder(ContactPerson.DEFAULT_ELEMENT_NAME)).buildObject(); + + contactPerson.setType(contactRepresentation.getType()); + + GivenName givenName = openSamlObjects.buildDefaultInstanceOfType(GivenName.class); + givenName.setName(contactRepresentation.getName()); + contactPerson.setGivenName(givenName); + + EmailAddress emailAddress = openSamlObjects.buildDefaultInstanceOfType(EmailAddress.class); + emailAddress.setAddress(contactRepresentation.getEmailAddress()); + contactPerson.addEmailAddress(emailAddress); + + ed.addContactPerson(contactPerson); } + } else { + ed.getContactPersons().clear(); } + } - // setup ACSs - if (representation.getAssertionConsumerServices() != null && representation.getAssertionConsumerServices().size() > 0) { - for (AssertionConsumerServiceRepresentation acsRepresentation : representation.getAssertionConsumerServices()) { - AssertionConsumerService assertionConsumerService = openSamlObjects.buildDefaultInstanceOfType(AssertionConsumerService.class); - getSPSSODescriptorFromEntityDescriptor(ed).getAssertionConsumerServices().add(assertionConsumerService); - if (acsRepresentation.isMakeDefault()) { - assertionConsumerService.setIsDefault(true); - } - assertionConsumerService.setBinding(acsRepresentation.getBinding()); - assertionConsumerService.setLocation(acsRepresentation.getLocationUrl()); - } + void setupOrganization(EntityDescriptor ed, EntityDescriptorRepresentation representation) { + // set up organization + if (representation.getOrganization() != null && representation.getOrganization().getName() != null && representation.getOrganization().getDisplayName() != null && representation.getOrganization().getUrl() != null) { + OrganizationRepresentation organizationRepresentation = representation.getOrganization(); + Organization organization = openSamlObjects.buildDefaultInstanceOfType(Organization.class); + + OrganizationName organizationName = openSamlObjects.buildDefaultInstanceOfType(OrganizationName.class); + organizationName.setXMLLang("en"); + organizationName.setValue(organizationRepresentation.getName()); + organization.getOrganizationNames().add(organizationName); + + OrganizationDisplayName organizationDisplayName = openSamlObjects.buildDefaultInstanceOfType(OrganizationDisplayName.class); + organizationDisplayName.setXMLLang("en"); + organizationDisplayName.setValue(organizationRepresentation.getDisplayName()); + organization.getDisplayNames().add(organizationDisplayName); + + OrganizationURL organizationURL = openSamlObjects.buildDefaultInstanceOfType(OrganizationURL.class); + organizationURL.setXMLLang("en"); + organizationURL.setValue(organizationRepresentation.getUrl()); + organization.getURLs().add(organizationURL); + + ed.setOrganization(organization); + } else { + ed.setOrganization(null); } + } - // setup logout - if (representation.getLogoutEndpoints() != null && !representation.getLogoutEndpoints().isEmpty()) { - for (LogoutEndpointRepresentation logoutEndpointRepresentation : representation.getLogoutEndpoints()) { - SingleLogoutService singleLogoutService = openSamlObjects.buildDefaultInstanceOfType(SingleLogoutService.class); - singleLogoutService.setBinding(logoutEndpointRepresentation.getBindingType()); - singleLogoutService.setLocation(logoutEndpointRepresentation.getUrl()); + void setupSPSSODescriptor(EntityDescriptor ed, EntityDescriptorRepresentation representation) { + // setup SPSSODescriptor + if (representation.getServiceProviderSsoDescriptor() != null) { + SPSSODescriptor spssoDescriptor = getSPSSODescriptorFromEntityDescriptor(ed); - getSPSSODescriptorFromEntityDescriptor(ed).getSingleLogoutServices().add(singleLogoutService); + if (!Strings.isNullOrEmpty(representation.getServiceProviderSsoDescriptor().getProtocolSupportEnum())) { + spssoDescriptor.setSupportedProtocols( + Arrays.stream(representation.getServiceProviderSsoDescriptor().getProtocolSupportEnum().split(",")).map(p -> MDDCConstants.PROTOCOL_BINDINGS.get(p.trim())).collect(Collectors.toList()) + ); } - } - if (representation.getRelyingPartyOverrides() != null || (representation.getAttributeRelease() != null && representation.getAttributeRelease().size() > 0)) { - getEntityAttributes(ed).getAttributes().addAll(entityService.getAttributeListFromEntityRepresentation(representation)); + + if (representation.getServiceProviderSsoDescriptor() != null && representation.getServiceProviderSsoDescriptor().getNameIdFormats() != null && representation.getServiceProviderSsoDescriptor().getNameIdFormats().size() > 0) { + for (String nameidFormat : representation.getServiceProviderSsoDescriptor().getNameIdFormats()) { + NameIDFormat nameIDFormat = openSamlObjects.buildDefaultInstanceOfType(NameIDFormat.class); + + nameIDFormat.setFormat(nameidFormat); + + spssoDescriptor.getNameIDFormats().add(nameIDFormat); + } + } + } else { + ed.setRoleDescriptors(null); } - return ed; } - private SPSSODescriptor getSPSSODescriptorFromEntityDescriptor(EntityDescriptor entityDescriptor) { - if (entityDescriptor.getSPSSODescriptor("") == null) { + private SPSSODescriptor getSPSSODescriptorFromEntityDescriptor(EntityDescriptor entityDescriptor) { + return getSPSSODescriptorFromEntityDescriptor(entityDescriptor, true); + } + + private SPSSODescriptor getSPSSODescriptorFromEntityDescriptor(EntityDescriptor entityDescriptor, boolean create) { + if (entityDescriptor.getSPSSODescriptor("") == null && create) { SPSSODescriptor spssoDescriptor = openSamlObjects.buildDefaultInstanceOfType(SPSSODescriptor.class); entityDescriptor.getRoleDescriptors().add(spssoDescriptor); } return entityDescriptor.getSPSSODescriptor(""); } - private Attribute createAttributeWithBooleanValue(String name, String friendlyName, Boolean value) { + private Attribute createBaseAttribute(String name, String friendlyName) { Attribute attribute = ((AttributeBuilder) openSamlObjects.getBuilderFactory().getBuilder(Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); attribute.setName(name); attribute.setFriendlyName(friendlyName); attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"); + return attribute; + } + + private Attribute createAttributeWithBooleanValue(String name, String friendlyName, Boolean value) { + Attribute attribute = createBaseAttribute(name, friendlyName); + XSBoolean xsBoolean = (XSBoolean) openSamlObjects.getBuilderFactory().getBuilder(XSBoolean.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSBoolean.TYPE_NAME); xsBoolean.setValue(XSBooleanValue.valueOf(value.toString())); @@ -279,10 +371,7 @@ private Attribute createAttributeWithBooleanValue(String name, String friendlyNa } private Attribute createAttributeWithArbitraryValues(String name, String friendlyName, String... values) { - Attribute attribute = ((AttributeBuilder) openSamlObjects.getBuilderFactory().getBuilder(Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); - attribute.setName(name); - attribute.setFriendlyName(friendlyName); - attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"); + Attribute attribute = createBaseAttribute(name, friendlyName); for (String value : values) { XSAny xsAny = (XSAny) openSamlObjects.getBuilderFactory().getBuilder(XSAny.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME); @@ -322,18 +411,31 @@ private KeyDescriptor createKeyDescriptor(String name, String type, String value } private EntityAttributes getEntityAttributes(EntityDescriptor ed) { + return getEntityAttributes(ed, true); + } + + private Optional getOptionalEntityAttributes(EntityDescriptor ed) { + return Optional.ofNullable(getEntityAttributes(ed, false)); + } + + private EntityAttributes getEntityAttributes(EntityDescriptor ed, boolean create) { Extensions extensions = ed.getExtensions(); + if (extensions == null && !create) { + return null; + } if (extensions == null) { extensions = openSamlObjects.buildDefaultInstanceOfType(Extensions.class); ed.setExtensions(extensions); } - EntityAttributes entityAttributes; + EntityAttributes entityAttributes = null; if (extensions.getUnknownXMLObjects(EntityAttributes.DEFAULT_ELEMENT_NAME).size() > 0) { entityAttributes = (EntityAttributes) extensions.getUnknownXMLObjects(EntityAttributes.DEFAULT_ELEMENT_NAME).get(0); } else { - entityAttributes = ((EntityAttributesBuilder) openSamlObjects.getBuilderFactory().getBuilder(EntityAttributes.DEFAULT_ELEMENT_NAME)).buildObject(); - extensions.getUnknownXMLObjects().add(entityAttributes); + if (create) { + entityAttributes = ((EntityAttributesBuilder) openSamlObjects.getBuilderFactory().getBuilder(EntityAttributes.DEFAULT_ELEMENT_NAME)).buildObject(); + extensions.getUnknownXMLObjects().add(entityAttributes); + } } return entityAttributes; } @@ -355,6 +457,19 @@ private UIInfo getUIInfo(EntityDescriptor ed) { return uiInfo; } + private void removeUIInfo(EntityDescriptor ed) { + SPSSODescriptor spssoDescriptor = getSPSSODescriptorFromEntityDescriptor(ed, false); + if (spssoDescriptor != null) { + Extensions extensions = spssoDescriptor.getExtensions(); + if (extensions == null) { + return; + } + if (extensions.getUnknownXMLObjects(UIInfo.DEFAULT_ELEMENT_NAME).size() > 0) { + extensions.getUnknownXMLObjects().remove(extensions.getUnknownXMLObjects(UIInfo.DEFAULT_ELEMENT_NAME).get(0)); + } + } + } + //TODO: implement @Override public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.opensaml.saml.saml2.metadata.EntityDescriptor entityDescriptor) { @@ -582,12 +697,4 @@ public List getAttributeReleaseListFromAttributeList(List att public Map getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList) { return ModelRepresentationConversions.getRelyingPartyOverridesRepresentationFromAttributeList(attributeList); } - - - - @Override - public void updateDescriptorFromRepresentation(org.opensaml.saml.saml2.metadata.EntityDescriptor entityDescriptor, EntityDescriptorRepresentation representation) { - // TODO: implement - throw new UnsupportedOperationException("not yet implemented"); - } } 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 0c4057678..9ce206755 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 @@ -25,6 +25,8 @@ import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService 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 groovy.json.JsonOutput +import org.skyscreamer.jsonassert.JSONAssert import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.json.JacksonTester @@ -34,6 +36,7 @@ import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input import org.xmlunit.diff.DefaultNodeMatcher import org.xmlunit.diff.ElementSelectors +import spock.lang.Ignore import spock.lang.Specification @ContextConfiguration(classes=[CoreShibUiConfiguration, CustomPropertiesConfiguration]) @@ -55,6 +58,8 @@ class JPAEntityDescriptorServiceImplTests extends Specification { JacksonTester jacksonTester + ObjectMapper mapper + RandomGenerator generator @Autowired @@ -66,7 +71,8 @@ class JPAEntityDescriptorServiceImplTests extends Specification { def setup() { service = new JPAEntityDescriptorServiceImpl(openSamlObjects, new JPAEntityServiceImpl(openSamlObjects, new AttributeUtility(openSamlObjects), customPropertiesConfiguration), new UserService(roleRepository, userRepository)) - JacksonTester.initFields(this, new ObjectMapper()) + mapper = new ObjectMapper() + JacksonTester.initFields(this, mapper) generator = new RandomGenerator() testObjectGenerator = new TestObjectGenerator() } @@ -764,16 +770,25 @@ class JPAEntityDescriptorServiceImplTests extends Specification { testRunIndex << (1..5) } - def "updateDescriptorFromRepresentation throws expected exception"() { + def "updateDescriptorFromRepresentation updates descriptor properly"() { given: def randomEntityDescriptor = generateRandomEntityDescriptor() - def entityDescriptorRepresentation = service.createRepresentationFromDescriptor(randomEntityDescriptor) + def updatedEntityDescriptor = generateRandomEntityDescriptor() + + //copy values we don't care about asserting (id, entity id, ...) + updatedEntityDescriptor.entityID = randomEntityDescriptor.entityID + updatedEntityDescriptor.resourceId = randomEntityDescriptor.resourceId + updatedEntityDescriptor.elementLocalName = randomEntityDescriptor.elementLocalName + + def updatedEntityDescriptorRepresentation = service.createRepresentationFromDescriptor(updatedEntityDescriptor) when: - service.updateDescriptorFromRepresentation(randomEntityDescriptor, entityDescriptorRepresentation) + service.updateDescriptorFromRepresentation(randomEntityDescriptor, updatedEntityDescriptorRepresentation) then: - thrown UnsupportedOperationException + def expectedJson = mapper.writeValueAsString(updatedEntityDescriptorRepresentation) + def actualJson = mapper.writeValueAsString(service.createRepresentationFromDescriptor(randomEntityDescriptor)) + JSONAssert.assertEquals(expectedJson, actualJson, false) } def "createRepresentationFromDescriptor creates a representation containing a version that is a hash of the original object"() { @@ -849,6 +864,9 @@ class JPAEntityDescriptorServiceImplTests extends Specification { ed.setServiceProviderName(generator.randomString(10)) ed.setServiceEnabled(generator.randomBoolean()) ed.setResourceId(generator.randomId()) + ed.setElementLocalName(generator.randomString(10)) + + //TODO: Finish fleshing out this thing return ed } diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java new file mode 100644 index 000000000..0fe287c7e --- /dev/null +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java @@ -0,0 +1,18 @@ +package net.unicon.shibui.pac4j; + +import org.springframework.data.domain.AuditorAware; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Optional; + +public class Pac4jAuditorAware implements AuditorAware { + @Override + public Optional getCurrentAuditor() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return Optional.empty(); + } + return Optional.of(authentication.getName()); + } +} diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java index 120a45f36..6e08444a9 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java @@ -10,6 +10,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.data.domain.AuditorAware; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; @@ -93,4 +94,9 @@ public void configure(org.springframework.security.config.annotation.web.builder web.httpFirewall(firewall); } } + + @Bean + public AuditorAware defaultAuditorAware() { + return new Pac4jAuditorAware(); + } } diff --git a/pac4j-module/src/test/docker/conf/application.yml b/pac4j-module/src/test/docker/conf/application.yml index e5986c1c1..90ddce36a 100644 --- a/pac4j-module/src/test/docker/conf/application.yml +++ b/pac4j-module/src/test/docker/conf/application.yml @@ -1,6 +1,16 @@ spring: profiles: include: + datasource: + platform: mariadb + driver-class-name: org.mariadb.jdbc.Driver + url: jdbc:mariadb://db:3306/shibui + username: shibui + password: shibui + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.MariaDBDialect server: port: 8443 ssl: diff --git a/pac4j-module/src/test/docker/docker-compose.yml b/pac4j-module/src/test/docker/docker-compose.yml index ac3a781c2..f76e1b375 100644 --- a/pac4j-module/src/test/docker/docker-compose.yml +++ b/pac4j-module/src/test/docker/docker-compose.yml @@ -1,6 +1,18 @@ version: "3.7" services: + db: + image: mariadb + container_name: db + environment: + MYSQL_USER: shibui + MYSQL_PASSWORD: shibui + MYSQL_DATABASE: shibui + MYSQL_RANDOM_ROOT_PASSWORD: "yes" + networks: + - front + ports: + - 3306:3306 shibui: image: unicon/shibui-pac4j entrypoint: ["/usr/bin/java", "-Dspring.profiles.active=dev", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005", "-jar", "app.jar"] @@ -13,7 +25,8 @@ services: - ./conf/application.yml:/application.yml networks: - front - + depends_on: + - db mailhog: image: mailhog/mailhog:latest ports: