Skip to content

Commit

Permalink
Merge branch 'feature/shibui-2269' of bitbucket.org:unicon/shib-idp-u…
Browse files Browse the repository at this point in the history
…i into feature/SHIBUI-2269-external-filters
  • Loading branch information
rmathis committed Jun 28, 2022
2 parents c79e9f2 + e7980dd commit ba34b5e
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilF
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.SignatureValidationFilter
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.opensaml.OpenSamlNameIdFormatFilter
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ExternalMetadataResolver
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver
Expand Down Expand Up @@ -286,6 +287,12 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
}
}

void constructXmlNodeForResolver(ExternalMetadataResolver resolver, def markupBuilderDelegate, Closure childNodes) {
markupBuilderDelegate.MetadataFilters(providerRef: 'InCommonMD') {
childNodes()
}
}

void constructXmlNodeForResolver(FileBackedHttpMetadataResolver resolver, def markupBuilderDelegate, Closure childNodes) {
markupBuilderDelegate.MetadataProvider(id: resolver.xmlId,
'xsi:type': 'FileBackedHTTPMetadataProvider',
Expand Down Expand Up @@ -449,11 +456,11 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
'xsi:schemaLocation': 'urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd urn:mace:shibboleth:2.0:resource http://shibboleth.net/schema/idp/shibboleth-resource.xsd urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd urn:oasis:names:tc:SAML:2.0:assertion http://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd'
) {


resolversPositionOrderContainerService.allMetadataResolversInDefinedOrderOrUnordered.each {
edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver mr ->
//TODO: We do not currently marshall the internal incommon chaining resolver (with BaseMetadataResolver type)
if ((mr.type != 'BaseMetadataResolver') && (mr.enabled)) {
// We do not currently marshall the internal incommon chaining resolver (with BaseMetadataResolver type)
// We do not want to include the custom type: ExternalMetadataResolver
if ((mr.type != 'BaseMetadataResolver') && (mr.type != 'ExternalMetadataResolver') && (mr.enabled)) {
constructXmlNodeForResolver(mr, delegate) {
//TODO: enhance
def didNamespaceProtectionFilter = !(shibUIConfiguration.protectedAttributeNamespaces && shibUIConfiguration.protectedAttributeNamespaces.size() > 0)
Expand All @@ -478,6 +485,42 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
}
}

@Override
Document generateExternalMetadataFilterConfiguration() {
// TODO: this can probably be a better writer
new StringWriter().withCloseable { writer ->
def xml = new MarkupBuilder(writer)
xml.omitEmptyAttributes = true
xml.omitNullAttributes = true

// https://shibboleth.atlassian.net/wiki/spaces/IDP4/pages/1279033515/ByReferenceFilter
xml.MetadataFilter(
'xsi:type': 'ByReference',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation': 'urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd urn:oasis:names:tc:SAML:2.0:assertion http://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd',
'xmlns:md': 'urn:oasis:names:tc:SAML:2.0:metadata',
'xmlns': 'urn:mace:shibboleth:2.0:metadata',
'xmlns:security': 'urn:mace:shibboleth:2.0:security',
'xmlns:saml2': 'urn:oasis:names:tc:SAML:2.0:assertion'
) {
resolversPositionOrderContainerService.allMetadataResolversInDefinedOrderOrUnordered.each {
edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver mr ->
// Only include the custom type: ExternalMetadataResolver
if ((mr.type == 'ExternalMetadataResolver') && (mr.enabled)) {
constructXmlNodeForResolver(mr, delegate) {
mr.metadataFilters.each { edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter filter ->
if (filter.isFilterEnabled()) {
constructXmlNodeForFilter(filter, delegate)
}
}
}
}
}
}
return DOMBuilder.newInstance().parseText(writer.toString())
}
}

private String generateJavaScriptRegexScript(String regex) {
return """
"use strict";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolversPositionOrderContainerRepository;
import edu.internet2.tier.shibboleth.admin.ui.scheduled.EntityDescriptorFilesScheduledTasks;
import edu.internet2.tier.shibboleth.admin.ui.scheduled.ExternalMetadataProvidersScheduledTasks;
import edu.internet2.tier.shibboleth.admin.ui.scheduled.MetadataProvidersScheduledTasks;
import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdatedEntityListener;
import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener;
Expand Down Expand Up @@ -88,6 +89,14 @@ public MetadataProvidersScheduledTasks metadataProvidersScheduledTasks(
return new MetadataProvidersScheduledTasks(resource, metadataResolverService, fileWritingService());
}

@Bean
@ConditionalOnProperty(name = "shibui.external.metadataProviders.target")
public ExternalMetadataProvidersScheduledTasks externalMetadataProvidersScheduledTasks(
@Value("${shibui.external.metadataProviders.target}") final Resource resource,
final MetadataResolverService metadataResolverService) {
return new ExternalMetadataProvidersScheduledTasks(resource, metadataResolverService, fileWritingService());
}

@Bean
public EntityIdsSearchService entityIdsSearchService(LuceneUtility luceneUtility, Analyzer fullTokenAnalyzer) {
return new EntityIdsSearchServiceImpl(luceneUtility, fullTokenAnalyzer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ public ResponseEntity<?> getXml() throws IOException, TransformerException {
}
}

@GetMapping(value = "/MetadataResolvers/External", produces = "application/xml")
@Transactional(readOnly = true)
public ResponseEntity<?> getExternalXml() throws IOException, TransformerException {
// TODO: externalize
try (StringWriter writer = new StringWriter()) {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

transformer.transform(new DOMSource(metadataResolverService.generateExternalMetadataFilterConfiguration()), new StreamResult(writer));
return ResponseEntity.ok(writer.toString());
}
}

@GetMapping("/MetadataResolvers/{resourceId}")
@Transactional(readOnly = true)
public ResponseEntity<?> getOne(@PathVariable String resourceId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.envers.Audited;

import javax.persistence.Column;
import javax.persistence.Entity;

@Entity
@EqualsAndHashCode(callSuper = true)
@Getter
@Setter
@ToString
@Audited
public class ExternalMetadataResolver extends MetadataResolver {
@Column
private String description;

@Column(unique = true)
private String externalResolverId;

@Column
String name;

public ExternalMetadataResolver() {
type = "ExternalMetadataResolver";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
@JsonSubTypes.Type(value = FileBackedHttpMetadataResolver.class, name = "FileBackedHttpMetadataResolver"),
@JsonSubTypes.Type(value = DynamicHttpMetadataResolver.class, name = "DynamicHttpMetadataResolver"),
@JsonSubTypes.Type(value = FilesystemMetadataResolver.class, name = "FilesystemMetadataResolver"),
@JsonSubTypes.Type(value = ResourceBackedMetadataResolver.class, name = "ResourceBackedMetadataResolver")})
@JsonSubTypes.Type(value = ResourceBackedMetadataResolver.class, name = "ResourceBackedMetadataResolver"),
@JsonSubTypes.Type(value = ExternalMetadataResolver.class, name = "ExternalMetadataResolver")})
@Audited
@AuditOverride(forClass = AbstractAuditable.class)
public class MetadataResolver extends AbstractAuditable implements IActivatable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ public enum SchemaType {
FILE_BACKED_HTTP_METADATA_RESOLVER("FileBackedHttpMetadataResolver"),
FILESYSTEM_METADATA_RESOLVER("FilesystemMetadataResolver"),
LOCAL_DYNAMIC_METADATA_RESOLVER("LocalDynamicMetadataResolver"),
DYNAMIC_HTTP_METADATA_RESOLVER("DynamicHttpMetadataResolver");
DYNAMIC_HTTP_METADATA_RESOLVER("DynamicHttpMetadataResolver"),
EXTERNAL_METADATA_RESOLVER("ExternalMetadataResolver");

String jsonType;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package edu.internet2.tier.shibboleth.admin.ui.scheduled;

import edu.internet2.tier.shibboleth.admin.ui.service.FileWritingService;
import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.WritableResource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;

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 java.io.IOException;
import java.io.StringWriter;

@Configuration
@ConditionalOnProperty("shibui.external.metadataProviders.target")
public class ExternalMetadataProvidersScheduledTasks {
private static final Logger logger = LoggerFactory.getLogger(ExternalMetadataProvidersScheduledTasks.class);

private final Resource target;
private final MetadataResolverService metadataResolverService;
private final FileWritingService fileWritingService;

public ExternalMetadataProvidersScheduledTasks(Resource target, MetadataResolverService metadataResolverService, FileWritingService fileWritingService) {
this.target = target;
this.metadataResolverService = metadataResolverService;
this.fileWritingService = fileWritingService;
}

@Scheduled(fixedRateString = "${shibui.external.metadataProviders.taskRunRate:30000}")
@Transactional(readOnly = true)
public void generateMetadataProvidersFile() {
try (StringWriter os = new StringWriter()) {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");


transformer.transform(new DOMSource(metadataResolverService.generateExternalMetadataFilterConfiguration()), new StreamResult(os));
this.fileWritingService.write((WritableResource)this.target, os.toString());
} catch (IOException | TransformerException e) {
logger.error(e.getLocalizedMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException;
import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException;
import edu.internet2.tier.shibboleth.admin.ui.exception.InitializationException;
import org.w3c.dom.Node;

public interface MetadataResolverService {
public MetadataResolver findByResourceId(String resourceId) throws EntityNotFoundException;
Expand All @@ -16,4 +17,6 @@ public interface MetadataResolverService {
public void reloadFilters(String metadataResolverName);

public MetadataResolver updateMetadataResolverEnabledStatus(MetadataResolver existingResolver) throws ForbiddenException, MetadataFileNotFoundException, InitializationException;
}

public Document generateExternalMetadataFilterConfiguration();
}
6 changes: 6 additions & 0 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ shibui.nameid-filter-ui-schema-location=classpath:nameid-filter.schema.json
# shibui.metadataProviders.target=file:/opt/shibboleth-idp/conf/shibui-metadata-providers.xml
# shibui.metadataProviders.taskRunRate=30000

# Set the following property to periodically write out external metadata providers (ie metadata-filters) configuration.
# There is no default value; the following is just an example
# @see - https://shibboleth.atlassian.net/wiki/spaces/IDP4/pages/1279033515/ByReferenceFilter
# shibui.external.metadataProviders.target=file:/opt/shibboleth-idp/conf/metadata-filters.xml
# shibui.external.metadataProviders.taskRunRate=30000

# Email configuration (local mailhog)
# spring.mail.host=mailhog
# spring.mail.port=1025
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ class MetadataFiltersControllerTests extends AbstractBaseDataJpaTest {
return null
}

@Override
Document generateExternalMetadataFilterConfiguration() {
return null
}

@Override
MetadataResolver findByResourceId(String resourceId) throws EntityNotFoundException {
// This won't get called
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package edu.internet2.tier.shibboleth.admin.ui.service

import com.google.common.collect.Lists
import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest
import edu.internet2.tier.shibboleth.admin.ui.configuration.PlaceholderResolverComponentsConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration
import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute
import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeValue
import edu.internet2.tier.shibboleth.admin.ui.domain.XSString
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ClasspathMetadataResource
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ExternalMetadataResolver
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataQueryProtocolScheme
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.RegexScheme
Expand All @@ -18,6 +23,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSaml
import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository
import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator
import edu.internet2.tier.shibboleth.admin.util.AttributeUtility
import groovy.xml.DOMBuilder
import groovy.xml.MarkupBuilder
import net.shibboleth.ext.spring.resource.ResourceHelper
Expand All @@ -31,6 +37,8 @@ import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.core.io.ClassPathResource
import org.springframework.test.context.ContextConfiguration
import org.w3c.dom.Document
import org.w3c.dom.Node
import org.xmlunit.builder.DiffBuilder
import org.xmlunit.builder.Input
import spock.lang.Ignore
Expand Down Expand Up @@ -67,6 +75,7 @@ class JPAMetadataResolverServiceImplTests extends AbstractBaseDataJpaTest {
@Autowired
TestObjectGenerator testObjectGenerator

AttributeUtility attributeUtility
DOMBuilder domBuilder = DOMBuilder.newInstance()
StringWriter writer = new StringWriter()
MarkupBuilder markupBuilder
Expand All @@ -75,13 +84,51 @@ class JPAMetadataResolverServiceImplTests extends AbstractBaseDataJpaTest {
markupBuilder = new MarkupBuilder(writer)
markupBuilder.omitNullAttributes = true
markupBuilder.omitEmptyAttributes = true
attributeUtility = new AttributeUtility(openSamlObjects)
}

def cleanup() {
metadataResolverRepository.deleteAll()
writer.close()
}

def 'test generating ExternalMetadataResolver XML'() {
given:
def resolver = new ExternalMetadataResolver().with {
it.setEnabled(true)
it.setName("testme")
it.setExternalResolverId("InCommonMD")
it.setDescription("some description that won't appear in the xml")
it.addFilter(new EntityAttributesFilter().with {
it.name = 'EntityAttributes'
EntityAttributesFilterTarget filterTarget = testObjectGenerator.buildEntityAttributesFilterTarget()
filterTarget.setSingleValue("https://sp.example.org/shibboleth")
it.setEntityAttributesFilterTarget(filterTarget)
def attribute = attributeUtility.createAttributeWithStringValues('http://shibboleth.net/ns/attributes/releaseAllValues', null, 'eduPersonPrincipalName')
attribute.nameFormat = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
attribute.namespacePrefix = 'saml2'
attribute.attributeValues.each { val ->
((XSString)val).namespacePrefix = 'saml2' +
''
}
it.attributes = [attribute]
it.intoTransientRepresentation()
it.enabled = true;
it
})
it
}
metadataResolverRepository.save(resolver)
metadataResolverService.reloadFilters("testme")

when:
Document doc = JPAMetadataResolverServiceImpl.cast(metadataResolverService).generateExternalMetadataFilterConfiguration()
Node node = doc.getFirstChild()

then:
generatedXmlIsTheSameAsExpectedXml('/conf/2269.xml', node)
}

def 'test adding a filter'() {
given:
def expectedXML = '''<?xml version="1.0" encoding="UTF-8"?>
Expand Down
Loading

0 comments on commit ba34b5e

Please sign in to comment.