Skip to content

Commit

Permalink
[SHIBUI-459]
Browse files Browse the repository at this point in the history
implement service
  • Loading branch information
Jj! committed May 4, 2018
1 parent 2f27866 commit a731e00
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 86 deletions.
2 changes: 2 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ dependencies {
//For easy data mocking capabilities
compile 'net.andreinc.mockneat:mockneat:0.1.4'

compile 'org.codehaus.groovy:groovy-all:2.4.15'

//So it works on Java 9 without explicitly requiring to load that module (needed by Hibernate)
runtimeOnly 'javax.xml.bind:jaxb-api:2.3.0'

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

import com.google.common.base.Predicate;
import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilterTarget
import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository
import groovy.xml.DOMBuilder
import groovy.xml.MarkupBuilder;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
import org.opensaml.saml.common.profile.logic.EntityIdPredicate;
import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver;
import org.opensaml.saml.metadata.resolver.filter.MetadataFilter;
import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain;
import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.w3c.dom.Document;

public class JPAMetadataResolverServiceImpl implements MetadataResolverService {
private static final Logger logger = LoggerFactory.getLogger(JPAMetadataResolverServiceImpl.class);

@Autowired
private MetadataResolver metadataResolver

@Autowired
private MetadataResolverRepository metadataResolverRepository

@Autowired
private OpenSamlObjects openSamlObjects

// TODO: enhance
@Override
public void reloadFilters(String metadataResolverName) {
ChainingMetadataResolver chainingMetadataResolver = (ChainingMetadataResolver)metadataResolver;

// MetadataResolver targetMetadataResolver = chainingMetadataResolver.getResolvers().stream().filter(r -> r.getId().equals(metadataResolverName)).findFirst().get();
MetadataResolver targetMetadataResolver = chainingMetadataResolver.getResolvers().find { it.id == metadataResolverName }
edu.internet2.tier.shibboleth.admin.ui.domain.MetadataResolver jpaMetadataResolver = metadataResolverRepository.findByName(metadataResolverName);

if (targetMetadataResolver && targetMetadataResolver.getMetadataFilter() instanceof MetadataFilterChain) {
MetadataFilterChain metadataFilterChain = (MetadataFilterChain)targetMetadataResolver.getMetadataFilter();

List<MetadataFilter> metadataFilters = new ArrayList<>();

for (edu.internet2.tier.shibboleth.admin.ui.domain.MetadataFilter metadataFilter : jpaMetadataResolver.getMetadataFilters()) {
if (metadataFilter instanceof EntityAttributesFilter) {
EntityAttributesFilter entityAttributesFilter = (EntityAttributesFilter) metadataFilter;

org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter target = new org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter();
Map<Predicate<EntityDescriptor>, Collection<Attribute>> rules = new HashMap<>();
if (entityAttributesFilter.getEntityAttributesFilterTarget().getEntityAttributesFilterTargetType() == EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY) {
rules.put(
new EntityIdPredicate(entityAttributesFilter.getEntityAttributesFilterTarget().getValue()),
(List<Attribute>)(List<? extends Attribute>)entityAttributesFilter.getAttributes()
);
}
target.setRules(rules);
metadataFilters.add(target);
}
}
metadataFilterChain.setFilters(metadataFilters);
}

if (metadataResolver instanceof RefreshableMetadataResolver) {
try {
((RefreshableMetadataResolver)metadataResolver).refresh();
} catch (ResolverException e) {
logger.warn("error refreshing metadataResolver " + metadataResolverName, e);
}
}
}

// TODO: enhance
@Override
public Document generateConfiguration() {
// TODO: this can probably be a better writer
new StringWriter().withCloseable { writer ->
def xml = new MarkupBuilder(writer)

xml.MetadataProvider(id: 'ShibbolethMetadata',
xmlns: 'urn:mace:shibboleth:2.0:metadata',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:type': 'ChainingMetadataProvider',
'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'
) {
metadataResolverRepository.findAll().each { edu.internet2.tier.shibboleth.admin.ui.domain.MetadataResolver mr ->
MetadataProvider(id: 'HTTPMetadata',
'xsi:type': 'FileBackedHTTPMetadataProvider',
backingFile: '%{idp.home}/metadata/incommonmd.xml',
metadataURL: 'http://md.incommon.org/InCommon/InCommon-metadata.xml',
minRefreshDelay: 'PT5M',
maxRefreshDelay: 'PT1H',
refreshDelayFactor: '0.75'
) {
MetadataFilter(
'xsi:type': 'SignatureValidation',
'requireSignedRoot': 'true',
'certificateFile': '%{idp.home}/credentials/inc-md-cert.pem'
)
MetadataFilter(
'xsi:type': 'RequiredValidUntil',
'maxValidityInterval': 'P14D'
)
MetadataFilter(
'xsi:type': 'EntityRoleWhiteList'
) {
RetainedRole('md:SPSSODescriptor')
}
//TODO: enhance
mr.metadataFilters.each { edu.internet2.tier.shibboleth.admin.ui.domain.MetadataFilter filter ->
if (filter instanceof EntityAttributesFilter) {
EntityAttributesFilter entityAttributesFilter = (EntityAttributesFilter)filter
MetadataFilter('xsi:type': 'EntityAttributes') {
// TODO: enhance. currently this does weird things with namespaces
entityAttributesFilter.attributes.each { attribute ->
mkp.yieldUnescaped(openSamlObjects.marshalToXmlString(attribute, false))
}
if (entityAttributesFilter.entityAttributesFilterTarget.entityAttributesFilterTargetType == EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY) {
entityAttributesFilter.entityAttributesFilterTarget.value.each {
Entity(it)
}
}
}
}
}
}
}
}

return DOMBuilder.newInstance().parseText(writer.toString())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,17 @@ public void init() throws ComponentInitializationException {
this.unmarshallerFactory = registry.getUnmarshallerFactory();
}

public String marshalToXmlString(XMLObject ed) throws MarshallingException {
public String marshalToXmlString(XMLObject ed, boolean includeXMLDeclaration) throws MarshallingException {
Marshaller marshaller = this.marshallerFactory.getMarshaller(ed);
String entityDescriptorXmlString = null;
if (marshaller != null) {
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");
if (!includeXMLDeclaration) {
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
}
transformer.transform(new DOMSource(marshaller.marshall(ed)), new StreamResult(writer));
entityDescriptorXmlString = writer.toString();
} catch (TransformerException | IOException e) {
Expand All @@ -108,6 +111,10 @@ public String marshalToXmlString(XMLObject ed) throws MarshallingException {
return entityDescriptorXmlString;
}

public String marshalToXmlString(XMLObject ed) throws MarshallingException {
return this.marshalToXmlString(ed, true);
}

public EntityDescriptor unmarshalFromXml(byte[] entityDescriptorXml) throws Exception {
try (InputStream edIs = ByteSource.wrap(entityDescriptorXml).openBufferedStream()) {
Document doc = this.parserPool.parse(edIs);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package edu.internet2.tier.shibboleth.admin.ui.service

import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration
import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilter
import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilterTarget
import edu.internet2.tier.shibboleth.admin.ui.domain.MetadataFilter
import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository
import edu.internet2.tier.shibboleth.admin.util.AttributeUtility
import org.apache.http.impl.client.HttpClients
import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver
import org.opensaml.saml.metadata.resolver.MetadataResolver
Expand All @@ -26,19 +30,52 @@ import spock.lang.Specification
@ContextConfiguration(classes = [CoreShibUiConfiguration, SearchConfiguration])
@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"])
@EntityScan("edu.internet2.tier.shibboleth.admin.ui")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class IncommonJPAMetadataResolverServiceImplTests extends Specification {
@Autowired
MetadataResolverService metadataResolverService

def 'test generation of metadata-providers.xml'() {
@Autowired
MetadataResolverRepository metadataResolverRepository

@Autowired
AttributeUtility attributeUtility

def 'simple test generation of metadata-providers.xml'() {
when:
def output = metadataResolverService.generateConfiguration()

then:
assert !DiffBuilder.compare(Input.fromStream(this.class.getResourceAsStream('/conf/278.xml'))).withTest(Input.fromDocument(output)).ignoreComments().ignoreWhitespace().build().hasDifferences()
}

def 'test generation of metadata-providers.xml with filters'() {
when:
//TODO: this might break later
def mr = metadataResolverRepository.findAll().iterator().next()
mr.metadataFilters.add(new EntityAttributesFilter().with {
it.entityAttributesFilterTarget = new EntityAttributesFilterTarget().with {
it.entityAttributesFilterTargetType = EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY
it.value = ['https://sp1.example.org']
it
}
def attribute = attributeUtility.createAttributeWithArbitraryValues('here', null, 'there')
attribute.nameFormat = null
attribute.namespacePrefix = 'saml'
attribute.attributeValues.each { val ->
val.namespacePrefix = 'saml'
}
it.attributes = [attribute]
it
})
metadataResolverRepository.save(mr)

def output = metadataResolverService.generateConfiguration()

then:
assert !DiffBuilder.compare(Input.fromStream(this.class.getResourceAsStream('/conf/278.2.xml'))).withTest(Input.fromDocument(output)).ignoreComments().ignoreWhitespace().build().hasDifferences()
}

//TODO: check that this configuration is sufficient
@TestConfiguration
static class TestConfig {
Expand Down
32 changes: 32 additions & 0 deletions backend/src/test/resources/conf/278.2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- This file is an EXAMPLE metadata configuration file. -->
<MetadataProvider id="ShibbolethMetadata" xsi:type="ChainingMetadataProvider"
xmlns="urn:mace:shibboleth:2.0:metadata"
xmlns:resource="urn:mace:shibboleth:2.0:resource"
xmlns:security="urn:mace:shibboleth:2.0:security"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
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">
<MetadataProvider id="HTTPMetadata"
xsi:type="FileBackedHTTPMetadataProvider"
backingFile="%{idp.home}/metadata/incommonmd.xml"
metadataURL="http://md.incommon.org/InCommon/InCommon-metadata.xml"
minRefreshDelay="PT5M"
maxRefreshDelay="PT1H"
refreshDelayFactor="0.75">
<MetadataFilter xsi:type="SignatureValidation" requireSignedRoot="true"
certificateFile="%{idp.home}/credentials/inc-md-cert.pem" />
<MetadataFilter xsi:type="RequiredValidUntil" maxValidityInterval="P14D" />
<MetadataFilter xsi:type="EntityRoleWhiteList">
<RetainedRole>md:SPSSODescriptor</RetainedRole>
</MetadataFilter>
<MetadataFilter xsi:type="EntityAttributes">
<saml:Attribute Name="here">
<saml:AttributeValue>there</saml:AttributeValue>
</saml:Attribute>
<Entity>https://sp1.example.org</Entity>
</MetadataFilter>
</MetadataProvider>

</MetadataProvider>
Loading

0 comments on commit a731e00

Please sign in to comment.