diff --git a/backend/build.gradle b/backend/build.gradle index ca484d6cb..2ffd807f7 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -60,7 +60,7 @@ dependencies { } // shibboleth idp deps - [].each { + ['idp-profile-spring', 'idp-profile-api'].each { compile "net.shibboleth.idp:${it}:${project.'shibboleth.version'}" } @@ -106,7 +106,7 @@ dependencies { testCompile "org.xmlunit:xmlunit-core:2.5.1" testRuntime 'cglib:cglib-nodep:3.2.5' - testCompile "net.shibboleth.ext:spring-extensions:5.4.0-SNAPSHOT" + compile "net.shibboleth.ext:spring-extensions:5.4.0-SNAPSHOT" //JSON schema generator testCompile 'com.kjetland:mbknor-jackson-jsonschema_2.12:1.0.29' diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy index 18796b950..a4a8451fd 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy @@ -16,7 +16,9 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverReposit import groovy.util.logging.Slf4j import groovy.xml.DOMBuilder import groovy.xml.MarkupBuilder +import net.shibboleth.utilities.java.support.logic.ScriptedPredicate import net.shibboleth.utilities.java.support.resolver.ResolverException +import net.shibboleth.utilities.java.support.scripting.EvaluableScript import org.opensaml.saml.common.profile.logic.EntityIdPredicate import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver import org.opensaml.saml.metadata.resolver.MetadataResolver @@ -28,6 +30,8 @@ import org.opensaml.saml.saml2.metadata.EntityDescriptor import org.springframework.beans.factory.annotation.Autowired import org.w3c.dom.Document +import javax.annotation.Nonnull + import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver.ResourceType.CLASSPATH import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver.ResourceType.SVN @@ -64,11 +68,24 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter target = new org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter() Map, Collection> rules = new HashMap<>() - if (entityAttributesFilter.getEntityAttributesFilterTarget().getEntityAttributesFilterTargetType() == EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY) { - rules.put( - new EntityIdPredicate(entityAttributesFilter.getEntityAttributesFilterTarget().getValue()), - (List) (List) entityAttributesFilter.getAttributes() - ) + switch (entityAttributesFilter.getEntityAttributesFilterTarget().getEntityAttributesFilterTargetType()) { + case EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY: + rules.put( + new EntityIdPredicate(entityAttributesFilter.getEntityAttributesFilterTarget().getValue()), + (List) (List) entityAttributesFilter.getAttributes() + ) + break + case EntityAttributesFilterTarget.EntityAttributesFilterTargetType.CONDITION_SCRIPT: + rules.put(new ScriptedPredicate(new EvaluableScript(entityAttributesFilter.entityAttributesFilterTarget.value[0])), + (List) (List) entityAttributesFilter.getAttributes()) + break + case EntityAttributesFilterTarget.EntityAttributesFilterTargetType.REGEX: + rules.put(new ScriptedPredicate(new EvaluableScript(generateJavaScriptRegexScript(entityAttributesFilter.entityAttributesFilterTarget.value[0]))), + (List) (List) entityAttributesFilter.getAttributes()) + break + default: + // do nothing, we'd have exploded elsewhere previously. + break } target.setRules(rules) metadataFilters.add(target) @@ -86,6 +103,12 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { } } + private class ScriptedPredicate extends net.shibboleth.utilities.java.support.logic.ScriptedPredicate { + protected ScriptedPredicate(@Nonnull EvaluableScript theScript) { + super(theScript) + } + } + // TODO: enhance @Override Document generateConfiguration() { @@ -146,15 +169,44 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { filter.attributes.each { attribute -> mkp.yieldUnescaped(openSamlObjects.marshalToXmlString(attribute, false)) } - if (filter.entityAttributesFilterTarget.entityAttributesFilterTargetType == EntityAttributesFilterTarget - .EntityAttributesFilterTargetType.ENTITY) { - filter.entityAttributesFilterTarget.value.each { - Entity(it) - } + switch (filter.entityAttributesFilterTarget.entityAttributesFilterTargetType) { + case EntityAttributesFilterTarget + .EntityAttributesFilterTargetType.ENTITY: + filter.entityAttributesFilterTarget.value.each { + Entity(it) + } + break + case EntityAttributesFilterTarget + .EntityAttributesFilterTargetType.CONDITION_SCRIPT: + case EntityAttributesFilterTarget + .EntityAttributesFilterTargetType.REGEX: + ConditionScript() { + Script() { + def script + if (filter.entityAttributesFilterTarget.entityAttributesFilterTargetType == + EntityAttributesFilterTarget.EntityAttributesFilterTargetType.CONDITION_SCRIPT) { + script = filter.entityAttributesFilterTarget.value[0] + } else if (filter.entityAttributesFilterTarget.entityAttributesFilterTargetType == + EntityAttributesFilterTarget.EntityAttributesFilterTargetType.REGEX) { + script = generateJavaScriptRegexScript(filter.entityAttributesFilterTarget.value[0]) + } + mkp.yieldUnescaped("\n\n") + } + } + break + default: + // do nothing, we'd have exploded elsewhere previously. + break } } } + private String generateJavaScriptRegexScript(String regex) { + return """ + "use strict"; + ${regex}.test(input.getEntityID());\n""" + } + void constructXmlNodeForFilter(EntityRoleWhiteListFilter filter, def markupBuilderDelegate) { markupBuilderDelegate.MetadataFilter( 'xsi:type': 'EntityRoleWhiteList', diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index 18a8a4953..c9b6493fa 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -1,6 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityIdsSearchResultRepresentation; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; @@ -11,6 +10,7 @@ import edu.internet2.tier.shibboleth.admin.ui.service.DirectoryServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchService; +import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.EntityService; import edu.internet2.tier.shibboleth.admin.ui.service.FilterService; import edu.internet2.tier.shibboleth.admin.ui.service.FilterTargetService; @@ -24,13 +24,6 @@ import edu.internet2.tier.shibboleth.admin.util.AttributeUtility; import edu.internet2.tier.shibboleth.admin.util.LuceneUtility; import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.document.Document; -import org.apache.lucene.index.IndexReader; -import org.apache.lucene.queryparser.classic.ParseException; -import org.apache.lucene.queryparser.classic.QueryParser; -import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.ScoreDoc; -import org.apache.lucene.search.TopDocs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -46,9 +39,6 @@ import org.springframework.web.util.UrlPathHelper; import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; @Configuration public class CoreShibUiConfiguration { @@ -92,44 +82,20 @@ public AttributeUtility attributeUtility() { return new AttributeUtility(openSamlObjects()); } - @Autowired - Analyzer fullTokenAnalyzer; - - @Autowired - DirectoryService directoryService; - @Autowired LocaleResolver localeResolver; @Autowired ResourceBundleMessageSource messageSource; - @Autowired - LuceneUtility luceneUtility; - @Bean public EntityDescriptorFilesScheduledTasks entityDescriptorFilesScheduledTasks(EntityDescriptorRepository entityDescriptorRepository) { return new EntityDescriptorFilesScheduledTasks(this.metadataDir, entityDescriptorRepository, openSamlObjects()); } @Bean - public EntityIdsSearchService entityIdsSearchService() { - return (resourceId, term, limit) -> { - List entityIds = new ArrayList<>(); - try { - IndexReader indexReader = luceneUtility.getIndexReader(resourceId); - IndexSearcher searcher = new IndexSearcher(indexReader); - QueryParser parser = new QueryParser("content", fullTokenAnalyzer); - TopDocs topDocs = searcher.search(parser.parse(term.trim()), limit); - for (ScoreDoc scoreDoc : topDocs.scoreDocs) { - Document document = searcher.doc(scoreDoc.doc); - entityIds.add(document.get("id")); - } - } catch (IOException | ParseException e) { - logger.error(e.getMessage(), e); - } - return new EntityIdsSearchResultRepresentation(entityIds); - }; + public EntityIdsSearchService entityIdsSearchService(LuceneUtility luceneUtility, Analyzer fullTokenAnalyzer) { + return new EntityIdsSearchServiceImpl(luceneUtility, fullTokenAnalyzer); } @Bean @@ -199,7 +165,7 @@ public DirectoryService directoryService() { } @Bean - public LuceneUtility luceneUtility() { - return new LuceneUtility(); + public LuceneUtility luceneUtility(DirectoryService directoryService) { + return new LuceneUtility(directoryService); } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/MetadataResolverConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/MetadataResolverConfiguration.java index cdcd54f02..0222ba46e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/MetadataResolverConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/MetadataResolverConfiguration.java @@ -1,29 +1,20 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver; 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.service.IndexWriterService; +import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverConverterService; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.resolver.ResolverException; -import org.apache.http.HttpResponse; -import org.apache.http.impl.client.HttpClients; -import org.apache.lucene.document.Document; -import org.apache.lucene.document.Field; -import org.apache.lucene.document.StringField; -import org.apache.lucene.document.TextField; -import org.apache.lucene.index.IndexWriter; -import org.joda.time.DateTime; import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver; import org.opensaml.saml.metadata.resolver.MetadataResolver; -import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain; -import org.opensaml.saml.metadata.resolver.impl.FileBackedHTTPMetadataResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -44,70 +35,24 @@ public class MetadataResolverConfiguration { @Autowired MetadataResolverRepository metadataResolverRepository; + @Autowired + MetadataResolverConverterService metadataResolverConverterService; + @Bean public MetadataResolver metadataResolver() throws ResolverException, ComponentInitializationException { - ChainingMetadataResolver metadataResolver = new ChainingMetadataResolver(); + ChainingMetadataResolver metadataResolver = new OpenSamlChainingMetadataResolver(); metadataResolver.setId("chain"); List resolvers = new ArrayList<>(); - String incommonMRId = "incommonmd"; - // TODO: remove this later when we allow for creation of arbitrary metadata resolvers - FileBackedHTTPMetadataResolver incommonMR = new FileBackedHTTPMetadataResolver(HttpClients.createMinimal(), "http://md.incommon.org/InCommon/InCommon-metadata.xml", "/tmp/incommonmd.xml"){ - @Override - protected void initMetadataResolver() throws ComponentInitializationException { - super.initMetadataResolver(); - - IndexWriter indexWriter; - try { - indexWriter = indexWriterService.getIndexWriter(incommonMRId); - } catch (IOException e) { - throw new ComponentInitializationException(e); - } - - for (String entityId: this.getBackingStore().getIndexedDescriptors().keySet()) { - - Document document = new Document(); - document.add(new StringField("id", entityId, Field.Store.YES)); - document.add(new TextField("content", entityId, Field.Store.YES)); // TODO: change entityId to be content of entity descriptor block - try { - indexWriter.addDocument(document); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - try { - indexWriter.commit(); - } catch (IOException e) { - throw new ComponentInitializationException(e); - } - } - - // TODO: this is probably not the best way to do this - @Nullable - @Override - public DateTime getLastRefresh() { - return null; - } - - // TODO: this is probably not the best way to do this - @Override - protected void processConditionalRetrievalHeaders(HttpResponse response) { - // let's do nothing 'cause we want to allow a refresh + Iterable persistedResolvers = metadataResolverRepository.findAll(); + for (edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver resolver : persistedResolvers) { + try { + MetadataResolver openSamlResolver = metadataResolverConverterService.convertToOpenSamlRepresentation(resolver); + resolvers.add(openSamlResolver); + } catch (IOException e) { + //TODO: do something interesting here? } - }; - incommonMR.setId(incommonMRId); - incommonMR.setParserPool(openSamlObjects.getParserPool()); - incommonMR.setMetadataFilter(new MetadataFilterChain()); - incommonMR.initialize(); - - - resolvers.add(incommonMR); - - if (!metadataResolverRepository.findAll().iterator().hasNext()) { - edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver mr = new edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver(); - mr.setName("incommonmd"); - metadataResolverRepository.save(mr); } metadataResolver.setResolvers(resolvers); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java index b6e1f0538..c76ccb89e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java @@ -5,14 +5,17 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidationService; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import edu.internet2.tier.shibboleth.admin.ui.service.IndexWriterService; +import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverConverterService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolversPositionOrderContainerService; import lombok.extern.slf4j.Slf4j; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resolver.ResolverException; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.index.IndexWriter; +import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -37,6 +40,7 @@ import java.io.IOException; import java.io.StringWriter; import java.net.URI; +import java.util.ArrayList; import java.util.List; import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidator.ValidationResult; @@ -61,6 +65,12 @@ public class MetadataResolversController { @Autowired IndexWriterService indexWriterService; + @Autowired + org.opensaml.saml.metadata.resolver.MetadataResolver chainingMetadataResolver; + + @Autowired + MetadataResolverConverterService metadataResolverConverterService; + @ExceptionHandler({InvalidTypeIdException.class, IOException.class, HttpMessageNotReadableException.class}) public ResponseEntity unableToParseJson(Exception ex) { return ResponseEntity.badRequest().body(new ErrorResponse(HttpStatus.BAD_REQUEST.toString(), ex.getMessage())); @@ -99,7 +109,7 @@ public ResponseEntity getOne(@PathVariable String resourceId) { @PostMapping("/MetadataResolvers") @Transactional - public ResponseEntity create(@RequestBody MetadataResolver newResolver) { + public ResponseEntity create(@RequestBody MetadataResolver newResolver) throws IOException, ResolverException { if (resolverRepository.findByName(newResolver.getName()) != null) { return ResponseEntity.status(HttpStatus.CONFLICT).build(); } @@ -112,12 +122,26 @@ public ResponseEntity create(@RequestBody MetadataResolver newResolver) { MetadataResolver persistedResolver = resolverRepository.save(newResolver); positionOrderContainerService.appendPositionOrderForNew(persistedResolver); + updateChainingMetadataResolver(persistedResolver); + return ResponseEntity.created(getResourceUriFor(persistedResolver)).body(persistedResolver); } + private void updateChainingMetadataResolver(MetadataResolver persistedResolver) throws IOException, ResolverException { + org.opensaml.saml.metadata.resolver.MetadataResolver openSamlResolver = metadataResolverConverterService.convertToOpenSamlRepresentation(persistedResolver); + List resolverList = new ArrayList<>(((ChainingMetadataResolver) chainingMetadataResolver).getResolvers()); + for (org.opensaml.saml.metadata.resolver.MetadataResolver resolver : resolverList) { + if (resolver.getId().equals(persistedResolver.getResourceId())) { + resolverList.remove(resolver); + } + } + resolverList.add(openSamlResolver); + ((ChainingMetadataResolver) chainingMetadataResolver).setResolvers(resolverList); + } + @PutMapping("/MetadataResolvers/{resourceId}") @Transactional - public ResponseEntity update(@PathVariable String resourceId, @RequestBody MetadataResolver updatedResolver) { + public ResponseEntity update(@PathVariable String resourceId, @RequestBody MetadataResolver updatedResolver) throws IOException, ResolverException { MetadataResolver existingResolver = resolverRepository.findByResourceId(resourceId); if (existingResolver == null) { return ResponseEntity.notFound().build(); @@ -137,6 +161,8 @@ public ResponseEntity update(@PathVariable String resourceId, @RequestBody Me MetadataResolver persistedResolver = resolverRepository.save(updatedResolver); + updateChainingMetadataResolver(persistedResolver); + return ResponseEntity.ok(persistedResolver); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java index e1c9ddc6b..d1b3692d2 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java @@ -20,9 +20,6 @@ @Setter @ToString public class DynamicHttpMetadataResolver extends MetadataResolver { - - - public static final String DEFAULT_TIMEOUT = "PT5S"; @Embedded diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java index f659a412b..b6d303fb3 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java @@ -2,7 +2,6 @@ import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @@ -15,7 +14,6 @@ @Setter @ToString public class FileBackedHttpMetadataResolver extends MetadataResolver { - public FileBackedHttpMetadataResolver() { type = "FileBackedHttpMetadataResolver"; } @@ -34,5 +32,4 @@ public FileBackedHttpMetadataResolver() { @Embedded private HttpMetadataResolverAttributes httpMetadataResolverAttributes; - } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FilesystemMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FilesystemMetadataResolver.java index 0e16c8353..1370f0881 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FilesystemMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FilesystemMetadataResolver.java @@ -17,7 +17,6 @@ @Setter @ToString public class FilesystemMetadataResolver extends MetadataResolver { - public FilesystemMetadataResolver() { type = "FilesystemMetadataResolver"; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java index 2bdb67d1f..20ca36251 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java @@ -14,7 +14,6 @@ @Setter @ToString public class LocalDynamicMetadataResolver extends MetadataResolver { - public LocalDynamicMetadataResolver() { type = "LocalDynamicMetadataResolver"; } @@ -27,5 +26,4 @@ public LocalDynamicMetadataResolver() { @Embedded private DynamicMetadataResolverAttributes dynamicMetadataResolverAttributes; - } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/ResourceBackedMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/ResourceBackedMetadataResolver.java index 5737d0162..55c833f2d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/ResourceBackedMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/ResourceBackedMetadataResolver.java @@ -17,7 +17,6 @@ @Setter @ToString public class ResourceBackedMetadataResolver extends MetadataResolver { - public ResourceBackedMetadataResolver() { type = "ResourceBackedMetadataResolver"; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlChainingMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlChainingMetadataResolver.java new file mode 100644 index 000000000..e5265cc29 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlChainingMetadataResolver.java @@ -0,0 +1,60 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; + +import com.google.common.base.Predicates; +import com.google.common.collect.Collections2; +import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resolver.ResolverException; +import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver; +import org.opensaml.saml.metadata.resolver.MetadataResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class OpenSamlChainingMetadataResolver extends ChainingMetadataResolver { + @Nonnull private final Logger log = LoggerFactory.getLogger(OpenSamlChainingMetadataResolver.class); + + @Nonnull @NonnullElements private List mutableResolvers; + + public OpenSamlChainingMetadataResolver() { + this.mutableResolvers = Collections.emptyList(); + } + + public OpenSamlChainingMetadataResolver(@Nonnull List mutableResolvers) { + this.mutableResolvers = mutableResolvers; + } + + @Override + public void setResolvers(@Nonnull @NonnullElements final List newResolvers) + throws ResolverException { + if (newResolvers == null || newResolvers.isEmpty()) { + mutableResolvers = Collections.emptyList(); + return; + } + + mutableResolvers = new ArrayList<>(Collections2.filter(newResolvers, Predicates.notNull())); + } + + @Nonnull + @NonnullElements + @Override + public List getResolvers() { + return mutableResolvers; + } + + @Override + protected void doInitialize() throws ComponentInitializationException { + super.doInitialize(); + if (mutableResolvers == null) { + log.warn("OpenSamlChainingMetadataResolver was not configured with any member MetadataResolvers"); + mutableResolvers = Collections.emptyList(); + } + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFileBackedHTTPMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFileBackedHTTPMetadataResolver.java new file mode 100644 index 000000000..76d3d9384 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFileBackedHTTPMetadataResolver.java @@ -0,0 +1,68 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resolver.ResolverException; +import org.apache.http.HttpResponse; +import org.apache.http.impl.client.HttpClients; +import org.apache.lucene.index.IndexWriter; +import org.joda.time.DateTime; +import org.opensaml.saml.metadata.resolver.impl.FileBackedHTTPMetadataResolver; + +import javax.annotation.Nullable; + +import static edu.internet2.tier.shibboleth.admin.util.DurationUtility.toMillis; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class OpenSamlFileBackedHTTPMetadataResolver extends FileBackedHTTPMetadataResolver { + private IndexWriter indexWriter; + private FileBackedHttpMetadataResolver sourceResolver; + + private OpenSamlMetadataResolverDelegate delegate; + + public OpenSamlFileBackedHTTPMetadataResolver(IndexWriter indexWriter, + FileBackedHttpMetadataResolver sourceResolver) throws ResolverException { + super(HttpClients.createMinimal(), sourceResolver.getMetadataURL(), sourceResolver.getBackingFile()); + this.indexWriter = indexWriter; + this.sourceResolver = sourceResolver; + this.delegate = new OpenSamlMetadataResolverDelegate(); + + this.setId(sourceResolver.getResourceId()); + + OpenSamlMetadataResolverConstructorHelper.updateOpenSamlMetadataResolverFromHttpMetadataResolverAttributes( + this, sourceResolver.getHttpMetadataResolverAttributes()); + OpenSamlMetadataResolverConstructorHelper.updateOpenSamlMetadataResolverFromReloadableMetadataResolverAttributes( + this, sourceResolver.getReloadableMetadataResolverAttributes()); + + this.setBackupFile(sourceResolver.getBackingFile()); + this.setBackupFileInitNextRefreshDelay(toMillis(sourceResolver.getBackupFileInitNextRefreshDelay())); + this.setInitializeFromBackupFile(sourceResolver.getInitializeFromBackupFile()); + + //TODO: Where does this get set in OpenSAML land? + // sourceResolver.getMetadataURL(); + } + + // TODO: this is still probably not the best way to do this? + @Nullable + @Override + public DateTime getLastRefresh() { + return null; + } + + // TODO: this is still probably not the best way to do this? + @Override + protected void processConditionalRetrievalHeaders(HttpResponse response) { + // let's do nothing 'cause we want to allow a refresh + } + + @Override + protected void initMetadataResolver() throws ComponentInitializationException { + super.initMetadataResolver(); + + delegate.addIndexedDescriptorsFromBackingStore(this.getBackingStore(), + this.sourceResolver.getResourceId(), + indexWriter); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFilesystemMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFilesystemMetadataResolver.java new file mode 100644 index 000000000..94ded9133 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFilesystemMetadataResolver.java @@ -0,0 +1,49 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; + +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resolver.ResolverException; +import org.apache.lucene.index.IndexWriter; +import org.joda.time.DateTime; +import org.opensaml.saml.metadata.resolver.impl.FilesystemMetadataResolver; + +import javax.annotation.Nullable; +import java.io.File; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class OpenSamlFilesystemMetadataResolver extends FilesystemMetadataResolver { + private IndexWriter indexWriter; + private edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver sourceResolver; + private OpenSamlMetadataResolverDelegate delegate; + + public OpenSamlFilesystemMetadataResolver(File metadataFile, + IndexWriter indexWriter, + edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver sourceResolver) throws ResolverException { + super(metadataFile); + this.indexWriter = indexWriter; + this.sourceResolver = sourceResolver; + this.delegate = new OpenSamlMetadataResolverDelegate(); + + this.setId(sourceResolver.getResourceId()); + + OpenSamlMetadataResolverConstructorHelper.updateOpenSamlMetadataResolverFromReloadableMetadataResolverAttributes( + this, sourceResolver.getReloadableMetadataResolverAttributes()); + } + + // TODO: this is still probably not the best way to do this? + @Nullable + @Override + public DateTime getLastRefresh() { + return null; + } + + @Override + protected void initMetadataResolver() throws ComponentInitializationException { + super.initMetadataResolver(); + + delegate.addIndexedDescriptorsFromBackingStore(this.getBackingStore(), + this.sourceResolver.getResourceId(), + indexWriter); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFunctionDrivenDynamicHTTPMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFunctionDrivenDynamicHTTPMetadataResolver.java new file mode 100644 index 000000000..2887fa85a --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFunctionDrivenDynamicHTTPMetadataResolver.java @@ -0,0 +1,48 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import org.apache.http.impl.client.HttpClients; +import org.apache.lucene.index.IndexWriter; +import org.opensaml.saml.metadata.resolver.impl.FunctionDrivenDynamicHTTPMetadataResolver; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class OpenSamlFunctionDrivenDynamicHTTPMetadataResolver extends FunctionDrivenDynamicHTTPMetadataResolver { + private IndexWriter indexWriter; + private DynamicHttpMetadataResolver sourceResolver; + private OpenSamlMetadataResolverDelegate delegate; + + public OpenSamlFunctionDrivenDynamicHTTPMetadataResolver(IndexWriter indexWriter, + DynamicHttpMetadataResolver sourceResolver) { + super(HttpClients.createMinimal()); + this.indexWriter = indexWriter; + this.sourceResolver = sourceResolver; + this.delegate = new OpenSamlMetadataResolverDelegate(); + + this.setId(sourceResolver.getResourceId()); + + OpenSamlMetadataResolverConstructorHelper.updateOpenSamlMetadataResolverFromDynamicMetadataResolverAttributes( + this, sourceResolver.getDynamicMetadataResolverAttributes()); + + OpenSamlMetadataResolverConstructorHelper.updateOpenSamlMetadataResolverFromHttpMetadataResolverAttributes( + this, sourceResolver.getHttpMetadataResolverAttributes()); + + this.setSupportedContentTypes(sourceResolver.getSupportedContentTypes()); + + //TODO: These don't seem to be used anywhere. + // In the parser, if not null, a warning is logged .. but nothing else happens with them. + // sourceResolver.getMaxConnectionsPerRoute(); + // sourceResolver.getMaxConnectionsTotal(); + } + + @Override + protected void initMetadataResolver() throws ComponentInitializationException { + super.initMetadataResolver(); + + delegate.addIndexedDescriptorsFromBackingStore(this.getBackingStore(), + this.sourceResolver.getResourceId(), + indexWriter); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlLocalDynamicMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlLocalDynamicMetadataResolver.java new file mode 100644 index 000000000..abbfbd26b --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlLocalDynamicMetadataResolver.java @@ -0,0 +1,45 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; + +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import org.apache.lucene.index.IndexWriter; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.persist.XMLObjectLoadSaveManager; +import org.opensaml.saml.metadata.resolver.impl.LocalDynamicMetadataResolver; + +import javax.annotation.Nonnull; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class OpenSamlLocalDynamicMetadataResolver extends LocalDynamicMetadataResolver { + private IndexWriter indexWriter; + private edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver sourceResolver; + private OpenSamlMetadataResolverDelegate delegate; + + public OpenSamlLocalDynamicMetadataResolver(@Nonnull XMLObjectLoadSaveManager manager, + IndexWriter indexWriter, + edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver sourceResolver) { + super(manager); + this.indexWriter = indexWriter; + this.sourceResolver = sourceResolver; + this.delegate = new OpenSamlMetadataResolverDelegate(); + + this.setId(sourceResolver.getResourceId()); + + OpenSamlMetadataResolverConstructorHelper.updateOpenSamlMetadataResolverFromDynamicMetadataResolverAttributes( + this, sourceResolver.getDynamicMetadataResolverAttributes()); + + //TODO: Where do these refs get used in OpenSAML land? + // sourceResolver.getSourceKeyGeneratorRef(); + // sourceResolver.getSourceManagerRef(); + } + + @Override + protected void initMetadataResolver() throws ComponentInitializationException { + super.initMetadataResolver(); + + delegate.addIndexedDescriptorsFromBackingStore(this.getBackingStore(), + this.sourceResolver.getResourceId(), + indexWriter); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverConstructorHelper.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverConstructorHelper.java new file mode 100644 index 000000000..7e674ab2b --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverConstructorHelper.java @@ -0,0 +1,75 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicMetadataResolverAttributes; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResolverAttributes; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadataResolverAttributes; +import org.opensaml.saml.metadata.resolver.MetadataResolver; +import org.opensaml.saml.metadata.resolver.impl.AbstractDynamicMetadataResolver; +import org.opensaml.saml.metadata.resolver.impl.AbstractReloadingMetadataResolver; + +import static edu.internet2.tier.shibboleth.admin.util.DurationUtility.toMillis; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class OpenSamlMetadataResolverConstructorHelper { + + public static void updateOpenSamlMetadataResolverFromDynamicMetadataResolverAttributes(MetadataResolver metadataResolver, DynamicMetadataResolverAttributes attributes) { + AbstractDynamicMetadataResolver dynamicMetadataResolver = (AbstractDynamicMetadataResolver) metadataResolver; + + dynamicMetadataResolver.setBackgroundInitializationFromCacheDelay(toMillis(attributes.getBackgroundInitializationFromCacheDelay())); + dynamicMetadataResolver.setCleanupTaskInterval(toMillis(attributes.getCleanupTaskInterval())); + dynamicMetadataResolver.setInitializeFromPersistentCacheInBackground(attributes.getInitializeFromPersistentCacheInBackground()); + dynamicMetadataResolver.setMaxCacheDuration(toMillis(attributes.getMaxCacheDuration())); + dynamicMetadataResolver.setMaxIdleEntityData(toMillis(attributes.getMaxIdleEntityData())); + dynamicMetadataResolver.setMinCacheDuration(toMillis(attributes.getMinCacheDuration())); + dynamicMetadataResolver.setBackgroundInitializationFromCacheDelay(toMillis(attributes.getBackgroundInitializationFromCacheDelay())); + dynamicMetadataResolver.setRefreshDelayFactor(attributes.getRefreshDelayFactor().floatValue()); + + //TODO: What should we do here if this data is null/empty? + dynamicMetadataResolver.setRemoveIdleEntityData(attributes.getRemoveIdleEntityData() == null ? false : attributes.getRemoveIdleEntityData()); + + //TODO: This takes a XMLObjectLoadSaveManager. Do we have what we need to create one? + // dynamicMetadataResolver.setPersistentCacheManager(); attributes.getPersistentCacheManagerDirectory(); + // attributes.getPersistentCacheManagerRef(); + + //TODO: This takes a Function. We've got a ref. How to convert? + // dynamicMetadataResolver.setPersistentCacheKeyGenerator(); attributes.getPersistentCacheKeyGeneratorRef(); + + //TODO: This takes a Predicate. We've got a predicate ref. How to convert? + // dynamicMetadataResolver.setInitializationFromCachePredicate(); attributes.getInitializationFromCachePredicateRef(); + + //TODO: This takes a ParserPool. We've got a ParserPoolRef. How to convert? + // dynamicMetadataResolver.setParserPool(); attributes.getParserPoolRef(); + + //TODO: Where does this get used in OpenSAML land? + // attributes.getTaskTimerRef(); + } + + public static void updateOpenSamlMetadataResolverFromHttpMetadataResolverAttributes(MetadataResolver metadataResolver, HttpMetadataResolverAttributes attributes) { + //TODO: Implement once we figure out what needs to happen here. + } + + public static void updateOpenSamlMetadataResolverFromReloadableMetadataResolverAttributes(MetadataResolver metadataResolver, ReloadableMetadataResolverAttributes attributes) { + AbstractReloadingMetadataResolver reloadingMetadataResolver = (AbstractReloadingMetadataResolver) metadataResolver; + + reloadingMetadataResolver.setExpirationWarningThreshold(toMillis(attributes.getExpirationWarningThreshold())); + reloadingMetadataResolver.setMaxRefreshDelay(toMillis(attributes.getMaxRefreshDelay())); + reloadingMetadataResolver.setMinRefreshDelay(toMillis(attributes.getMinRefreshDelay())); + + //TODO: I think we may need to take another look at setting the defaults properly on our attributes. + reloadingMetadataResolver.setRefreshDelayFactor(attributes.getRefreshDelayFactor() == null ? 0.75f : attributes.getRefreshDelayFactor().floatValue()); + + //TODO: What should we do here if this data is null/empty? + reloadingMetadataResolver.setResolveViaPredicatesOnly(attributes.getResolveViaPredicatesOnly() == null ? false : attributes.getResolveViaPredicatesOnly()); + + //TODO: This takes a set of MetadataIndex's. We've got an IndexesRef. How to convert? + // reloadingMetadataResolver.setIndexes(); attributes.getIndexesRef(); + + //TODO: This takes a ParserPool. We've got a ParserPoolRef. How to convert? + // reloadingMetadataResolver.setParserPool(); attributes.getParserPoolRef(); + + //TODO: Where does this get used in OpenSAML land? + // attributes.getTaskTimerRef(); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverDelegate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverDelegate.java new file mode 100644 index 000000000..ab4cfd8aa --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverDelegate.java @@ -0,0 +1,49 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; + +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.StringField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.IndexWriter; +import org.opensaml.saml.metadata.resolver.impl.AbstractMetadataResolver; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class OpenSamlMetadataResolverDelegate extends AbstractMetadataResolver { + private static final Logger logger = LoggerFactory.getLogger(OpenSamlMetadataResolverDelegate.class); + + @Nonnull + @Override + public Iterable resolve(@Nullable CriteriaSet criteria) { + throw new UnsupportedOperationException("This method should not be called."); + } + + void addIndexedDescriptorsFromBackingStore(AbstractMetadataResolver.EntityBackingStore backingStore, String resourceId, IndexWriter indexWriter) throws ComponentInitializationException { + for (String entityId : backingStore.getIndexedDescriptors().keySet()) { + Document document = new Document(); + document.add(new StringField("id", entityId, Field.Store.YES)); + document.add(new TextField("content", entityId, Field.Store.YES)); // TODO: change entityId to be content of entity descriptor block + document.add(new StringField("tag", resourceId, Field.Store.YES)); + try { + indexWriter.addDocument(document); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + try { + indexWriter.commit(); + } catch (IOException e) { + throw new ComponentInitializationException(e); + } + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlResourceBackedMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlResourceBackedMetadataResolver.java new file mode 100644 index 000000000..addbdf9c7 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlResourceBackedMetadataResolver.java @@ -0,0 +1,49 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; + +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resource.Resource; +import org.apache.lucene.index.IndexWriter; +import org.joda.time.DateTime; +import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver; + +import javax.annotation.Nullable; +import java.io.IOException; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class OpenSamlResourceBackedMetadataResolver extends ResourceBackedMetadataResolver { + private IndexWriter indexWriter; + private edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver sourceResolver; + private OpenSamlMetadataResolverDelegate delegate; + + public OpenSamlResourceBackedMetadataResolver(Resource resource, + IndexWriter indexWriter, + edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver sourceResolver) throws IOException { + super(resource); + this.indexWriter = indexWriter; + this.sourceResolver = sourceResolver; + this.delegate = new OpenSamlMetadataResolverDelegate(); + + this.setId(sourceResolver.getResourceId()); + + OpenSamlMetadataResolverConstructorHelper.updateOpenSamlMetadataResolverFromReloadableMetadataResolverAttributes( + this, sourceResolver.getReloadableMetadataResolverAttributes()); + } + + // TODO: this is still probably not the best way to do this? + @Nullable + @Override + public DateTime getLastRefresh() { + return null; + } + + @Override + protected void initMetadataResolver() throws ComponentInitializationException { + super.initMetadataResolver(); + + delegate.addIndexedDescriptorsFromBackingStore(this.getBackingStore(), + this.sourceResolver.getResourceId(), + indexWriter); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DirectoryServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DirectoryServiceImpl.java index 553af3094..d19591b35 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DirectoryServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DirectoryServiceImpl.java @@ -3,6 +3,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,6 +26,6 @@ public Directory getDirectory(String resourceId) { @Override public List getDirectories() { - return (List) directoryMap.values(); + return new ArrayList<>(directoryMap.values()); } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchService.java index 6639d5b1a..2e1707e4e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchService.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityIdsSearchResultRepresentation; +import edu.internet2.tier.shibboleth.admin.util.LuceneUtility; import net.andreinc.mockneat.MockNeat; import java.util.ArrayList; @@ -10,7 +11,6 @@ /** * API component responsible for entity ids search. */ -@FunctionalInterface public interface EntityIdsSearchService { /** diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceImpl.java new file mode 100644 index 000000000..337b904b4 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceImpl.java @@ -0,0 +1,52 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityIdsSearchResultRepresentation; +import edu.internet2.tier.shibboleth.admin.util.LuceneUtility; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.queryparser.classic.QueryParser; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class EntityIdsSearchServiceImpl implements EntityIdsSearchService { + private static final Logger logger = LoggerFactory.getLogger(EntityIdsSearchServiceImpl.class); + private Analyzer fullTokenAnalyzer; + private LuceneUtility luceneUtility; + + public EntityIdsSearchServiceImpl(LuceneUtility luceneUtility, Analyzer fullTokenAnalyzer) { + this.luceneUtility = luceneUtility; + this.fullTokenAnalyzer = fullTokenAnalyzer; + } + + @Override + public EntityIdsSearchResultRepresentation findBySearchTermAndOptionalLimit(String resourceId, + String searchTerm, + int limit) { + List entityIds = new ArrayList<>(); + try { + IndexReader indexReader = luceneUtility.getIndexReader(resourceId); + IndexSearcher searcher = new IndexSearcher(indexReader); + QueryParser parser = new QueryParser("content", fullTokenAnalyzer); + TopDocs topDocs = searcher.search(parser.parse(searchTerm.trim()), limit); + for (ScoreDoc scoreDoc : topDocs.scoreDocs) { + Document document = searcher.doc(scoreDoc.doc); + entityIds.add(document.get("id")); + } + } catch (IOException | ParseException e) { + logger.error(e.getMessage(), e); + } + return new EntityIdsSearchResultRepresentation(entityIds); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverConverterService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverConverterService.java new file mode 100644 index 000000000..8ac04c154 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverConverterService.java @@ -0,0 +1,13 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import net.shibboleth.utilities.java.support.resolver.ResolverException; +import org.opensaml.saml.metadata.resolver.MetadataResolver; + +import java.io.IOException; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public interface MetadataResolverConverterService { + MetadataResolver convertToOpenSamlRepresentation(edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver resolver) throws IOException, ResolverException; +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverConverterServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverConverterServiceImpl.java new file mode 100644 index 000000000..fcd66cd35 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverConverterServiceImpl.java @@ -0,0 +1,110 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver; +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; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlFileBackedHTTPMetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlFilesystemMetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlFunctionDrivenDynamicHTTPMetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlLocalDynamicMetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlResourceBackedMetadataResolver; +import net.shibboleth.ext.spring.resource.ResourceHelper; +import net.shibboleth.utilities.java.support.resolver.ResolverException; +import net.shibboleth.utilities.java.support.resource.Resource; +import org.apache.lucene.index.IndexWriter; +import org.opensaml.core.xml.persist.FilesystemLoadSaveManager; +import org.opensaml.core.xml.persist.XMLObjectLoadSaveManager; +import org.opensaml.saml.metadata.resolver.MetadataResolver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import javax.validation.ConstraintViolationException; +import java.io.File; +import java.io.IOException; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Service +public class MetadataResolverConverterServiceImpl implements MetadataResolverConverterService { + + @Autowired + IndexWriterService indexWriterService; + + private OpenSamlFunctionDrivenDynamicHTTPMetadataResolver convertToOpenSamlRepresentation(DynamicHttpMetadataResolver resolver) throws IOException { + IndexWriter indexWriter = indexWriterService.getIndexWriter(resolver.getResourceId()); + + return new OpenSamlFunctionDrivenDynamicHTTPMetadataResolver(indexWriter, + resolver); + } + + private OpenSamlFileBackedHTTPMetadataResolver convertToOpenSamlRepresentation(FileBackedHttpMetadataResolver resolver) throws IOException, ResolverException { + IndexWriter indexWriter = indexWriterService.getIndexWriter(resolver.getResourceId()); + + return new OpenSamlFileBackedHTTPMetadataResolver(indexWriter, resolver); + } + + private OpenSamlFilesystemMetadataResolver convertToOpenSamlRepresentation(FilesystemMetadataResolver resolver) throws IOException, ResolverException { + IndexWriter indexWriter = indexWriterService.getIndexWriter(resolver.getResourceId()); + File metadataFile = new File(resolver.getMetadataFile()); + + return new OpenSamlFilesystemMetadataResolver(metadataFile, + indexWriter, + resolver); + } + + private OpenSamlLocalDynamicMetadataResolver convertToOpenSamlRepresentation(LocalDynamicMetadataResolver resolver) throws IOException { + IndexWriter indexWriter = indexWriterService.getIndexWriter(resolver.getResourceId()); + + XMLObjectLoadSaveManager manager = null; + try { + manager = new FilesystemLoadSaveManager(resolver.getSourceDirectory()); + } catch (ConstraintViolationException e) { + // the base directory string instance was null or empty + //TODO: What should we do here? Currently, this causes a test to fail. + } + + return new OpenSamlLocalDynamicMetadataResolver(manager, indexWriter, resolver); + } + + private OpenSamlResourceBackedMetadataResolver convertToOpenSamlRepresentation(ResourceBackedMetadataResolver resolver) throws IOException { + IndexWriter indexWriter = indexWriterService.getIndexWriter(resolver.getResourceId()); + ResourceBackedMetadataResolver.ResourceType resourceType = resolver.validateAndDetermineResourceType(); + Resource resource = null; + switch (resourceType) { + case SVN: + //TODO: What sort of resource type should be created here? URL? + break; + case CLASSPATH: + resource = ResourceHelper.of(new ClassPathResource(resolver.getClasspathMetadataResource().getFile())); + break; + default: + throw new RuntimeException("Unsupported resource type!"); + } + + return new OpenSamlResourceBackedMetadataResolver(resource, + indexWriter, + resolver); + } + + @Override + public MetadataResolver convertToOpenSamlRepresentation(edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver resolver) throws IOException, ResolverException { + switch (resolver.getType()) { + case "LocalDynamicMetadataResolver": + return convertToOpenSamlRepresentation((LocalDynamicMetadataResolver) resolver); + case "FileBackedHttpMetadataResolver": + return convertToOpenSamlRepresentation((FileBackedHttpMetadataResolver) resolver); + case "DynamicHttpMetadataResolver": + return convertToOpenSamlRepresentation((DynamicHttpMetadataResolver) resolver); + case "FilesystemMetadataResolver": + return convertToOpenSamlRepresentation((FilesystemMetadataResolver) resolver); + case "ResourceBackedMetadataResolver": + return convertToOpenSamlRepresentation((ResourceBackedMetadataResolver) resolver); + default: + throw new RuntimeException("Unsupported metadata resolver type!"); + } + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/DurationUtility.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/DurationUtility.java new file mode 100644 index 000000000..b19328e27 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/DurationUtility.java @@ -0,0 +1,39 @@ +package edu.internet2.tier.shibboleth.admin.util; + +import org.apache.commons.lang3.StringUtils; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.Duration; +import java.util.Date; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +public class DurationUtility { + private static final DatatypeFactory datatypeFactory; + + static { + DatatypeFactory factory; + try { + factory = DatatypeFactory.newInstance(); + } catch (DatatypeConfigurationException e) { + factory = null; + } + datatypeFactory = factory; + } + + public static Long toMillis(String xmlDuration) { + if (datatypeFactory == null) { + throw new RuntimeException("DatatypeFactory was never initialized!"); + } + if (StringUtils.isBlank(xmlDuration)) { + return 0L; + } + Duration duration = datatypeFactory.newDuration(xmlDuration); + + Date zero = new Date(0); + duration.addTo(zero); // potentially can return undesired results for large xmlDurations + return zero.getTime(); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/LuceneUtility.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/LuceneUtility.java index 9b53ebdbb..57a96051e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/LuceneUtility.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/LuceneUtility.java @@ -8,7 +8,6 @@ import org.apache.lucene.store.Directory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; import java.util.ArrayList; @@ -19,10 +18,12 @@ */ public class LuceneUtility { private static final Logger logger = LoggerFactory.getLogger(LuceneUtility.class); - - @Autowired private DirectoryService directoryService; + public LuceneUtility(DirectoryService directoryService) { + this.directoryService = directoryService; + } + public IndexReader getIndexReader(String resourceId) throws IOException { IndexReader indexReader; if (StringUtils.isBlank(resourceId)) { @@ -35,7 +36,8 @@ public IndexReader getIndexReader(String resourceId) throws IOException { logger.error(e.getMessage(), e); } }); - IndexReader[] indexReaders = (IndexReader[]) indexReaderList.toArray(); + IndexReader[] indexReaders = new IndexReader[indexReaderList.size()]; + indexReaders = indexReaderList.toArray(indexReaders); indexReader = new MultiReader(indexReaders, true); } else { indexReader = DirectoryReader.open(directoryService.getDirectory(resourceId)); diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy index 2cb3c7d9d..1796e3b70 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver 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.service.IndexWriterService @@ -36,7 +37,7 @@ class TestConfiguration { @Bean MetadataResolver metadataResolver() { - ChainingMetadataResolver metadataResolver = new ChainingMetadataResolver() + ChainingMetadataResolver metadataResolver = new OpenSamlChainingMetadataResolver() metadataResolver.setId("chain") String resolverId = "test" diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy index 3ceb55417..9775160bd 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy @@ -6,13 +6,14 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver 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.json.JsonOutput import groovy.json.JsonSlurper +import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver import org.opensaml.saml.metadata.resolver.MetadataResolver -import org.opensaml.saml.metadata.resolver.impl.FilesystemMetadataResolver import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestConfiguration @@ -20,9 +21,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.context.annotation.Bean import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders - import org.springframework.test.context.ActiveProfiles - import spock.lang.Specification import spock.lang.Unroll @@ -154,6 +153,10 @@ class MetadataResolversControllerIntegrationTests extends Specification { def "POST new concrete MetadataResolver of type #resolverType -> /api/MetadataResolvers"(String resolverType) { given: 'New MetadataResolver JSON representation' def resolver = generator.buildRandomMetadataResolverOfType(resolverType) + String sourceDirectory + if (resolverType.equals('Localdynamic')) { + sourceDirectory = ((LocalDynamicMetadataResolver) resolver).sourceDirectory + } when: 'POST request is made with new DynamicHttpMetadataResolver JSON representation' def result = this.restTemplate.postForEntity(BASE_URI, createRequestHttpEntityFor { mapper.writeValueAsString(resolver) }, String) @@ -162,6 +165,14 @@ class MetadataResolversControllerIntegrationTests extends Specification { result.statusCodeValue == 201 result.headers.Location[0].contains(BASE_URI) + cleanup: + if (sourceDirectory != null) { + def tmpDirectory = new File(sourceDirectory) + if (tmpDirectory.exists()) { + tmpDirectory.deleteDir() + } + } + where: resolverType | _ 'DynamicHttp' | _ @@ -175,6 +186,10 @@ class MetadataResolversControllerIntegrationTests extends Specification { def "PUT concrete MetadataResolver of type #resolverType with updated changes -> /api/MetadataResolvers/{resourceId}"(String resolverType) { given: 'One resolver is available in data store' def resolver = generator.buildRandomMetadataResolverOfType(resolverType) + String sourceDirectory + if (resolverType.equals('Localdynamic')) { + sourceDirectory = ((LocalDynamicMetadataResolver) resolver).sourceDirectory + } def resolverResourceId = resolver.resourceId metadataResolverRepository.save(resolver) @@ -198,6 +213,14 @@ class MetadataResolversControllerIntegrationTests extends Specification { then: updatedResolverMap.name == 'Updated DynamicHttpMetadataResolver' + cleanup: + if (sourceDirectory != null) { + def tmpDirectory = new File(sourceDirectory) + if (tmpDirectory.exists()) { + tmpDirectory.deleteDir() + } + } + where: resolverType | _ 'DynamicHttp' | _ @@ -290,7 +313,7 @@ class MetadataResolversControllerIntegrationTests extends Specification { static class Config { @Bean MetadataResolver metadataResolver() { - new FilesystemMetadataResolver(new File('fake')) + new ChainingMetadataResolver() } } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy index b158c83d0..279f4a6ba 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy @@ -140,7 +140,7 @@ class IncommonJPAMetadataResolverServiceImplTests extends Specification { metadataResolverRepository.save(new TestObjectGenerator(attributeUtility).localDynamicMetadataResolver()) // Generate and test edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver - metadataResolverRepository.save(new TestObjectGenerator(attributeUtility).resourceBackedMetadataResolverForSVN()) + metadataResolverRepository.save(new TestObjectGenerator(attributeUtility).resourceBackedMetadataResolverForClasspath()) } return resolver diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy index c6c64e7e1..381ab3a18 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy @@ -135,6 +135,32 @@ class JPAMetadataResolverServiceImplTests extends Specification { !diff.hasDifferences() } + def 'test generating EntityAttributesFilter xml snippet with condition script'() { + given: + def filter = testObjectGenerator.entityAttributesFilterWithConditionScript() + + when: + genXmlSnippet(markupBuilder) { + JPAMetadataResolverServiceImpl.cast(metadataResolverService).constructXmlNodeForFilter(filter, it) + } + + then: + generatedXmlIsTheSameAsExpectedXml('/conf/661.xml', domBuilder.parseText(writer.toString())) + } + + def 'test generating EntityAttributesFilter xml snippet with regex'() { + given: + def filter = testObjectGenerator.entityAttributesFilterWithRegex() + + when: + genXmlSnippet(markupBuilder) { + JPAMetadataResolverServiceImpl.cast(metadataResolverService).constructXmlNodeForFilter(filter, it) + } + + then: + generatedXmlIsTheSameAsExpectedXml('/conf/661.2.xml', domBuilder.parseText(writer.toString())) + } + def 'test generating EntityRoleWhitelistFilter xml snippet'() { given: def filter = testObjectGenerator.entityRoleWhitelistFilter() diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy index 04066c581..29db1fb1d 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy @@ -29,6 +29,7 @@ class TestHelpers { static void generatedXmlIsTheSameAsExpectedXml(String expectedXmlResource, Document generatedXml) { assert !DiffBuilder.compare(Input.fromStream(TestHelpers.getResourceAsStream(expectedXmlResource))) .withTest(Input.fromDocument(generatedXml)) + .withAttributeFilter({attribute -> !attribute.name.equals("sourceDirectory")}) .ignoreComments() .ignoreWhitespace() .build() diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy index 4469e1b36..e7d227160 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy @@ -10,6 +10,7 @@ import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import edu.internet2.tier.shibboleth.admin.util.MDDCConstants import org.opensaml.saml.saml2.metadata.Organization +import java.nio.file.Files import java.util.function.Supplier /** @@ -175,6 +176,24 @@ class TestObjectGenerator { } } + EntityAttributesFilter entityAttributesFilterWithConditionScript() { + new EntityAttributesFilter().with { + it.name = 'EntityAttributes' + it.setEntityAttributesFilterTarget(buildEntityAttributesFilterTargetWithConditionScript()) + it.intoTransientRepresentation() + it + } + } + + EntityAttributesFilter entityAttributesFilterWithRegex() { + new EntityAttributesFilter().with { + it.name = 'EntityAttributes' + it.setEntityAttributesFilterTarget(buildEntityAttributesFilterTargetWithRegex()) + it.intoTransientRepresentation() + it + } + } + RequiredValidUntilFilter requiredValidUntilFilter() { return new RequiredValidUntilFilter().with { it.maxValidityInterval = 'P14D' @@ -305,12 +324,32 @@ class TestObjectGenerator { EntityAttributesFilterTarget buildEntityAttributesFilterTarget() { EntityAttributesFilterTarget entityAttributesFilterTarget = new EntityAttributesFilterTarget() - entityAttributesFilterTarget.setSingleValue(generator.randomStringList()) + entityAttributesFilterTarget.setSingleValue(generator.randomString()) entityAttributesFilterTarget.setEntityAttributesFilterTargetType(randomFilterTargetType()) return entityAttributesFilterTarget } + EntityAttributesFilterTarget buildEntityAttributesFilterTargetWithConditionScript() { + EntityAttributesFilterTarget entityAttributesFilterTarget = new EntityAttributesFilterTarget() + + entityAttributesFilterTarget.setSingleValue("Pretend this is JavaScript.") + entityAttributesFilterTarget.setEntityAttributesFilterTargetType( + EntityAttributesFilterTarget.EntityAttributesFilterTargetType.CONDITION_SCRIPT) + + return entityAttributesFilterTarget + } + + EntityAttributesFilterTarget buildEntityAttributesFilterTargetWithRegex() { + EntityAttributesFilterTarget entityAttributesFilterTarget = new EntityAttributesFilterTarget() + + entityAttributesFilterTarget.setSingleValue("/foo.*/") // JavaScript-style regex + entityAttributesFilterTarget.setEntityAttributesFilterTargetType( + EntityAttributesFilterTarget.EntityAttributesFilterTargetType.REGEX) + + return entityAttributesFilterTarget + } + FilterTargetRepresentation buildFilterTargetRepresentation() { FilterTargetRepresentation representation = new FilterTargetRepresentation() @@ -389,7 +428,7 @@ class TestObjectGenerator { randomResolver = localDynamicMetadataResolver() break case 'ResourceBacked': - randomResolver = resourceBackedMetadataResolverForSVN() + randomResolver = resourceBackedMetadataResolverForClasspath() break case 'Filesystem': randomResolver = filesystemMetadataResolver() @@ -437,14 +476,22 @@ class TestObjectGenerator { new DynamicHttpMetadataResolver().with { it.name = 'DynamicHTTP' it.xmlId = 'DynamicHTTP' + it.dynamicMetadataResolverAttributes = new DynamicMetadataResolverAttributes().with { + it + } it } } LocalDynamicMetadataResolver localDynamicMetadataResolver() { + def tmpDirectory = Files.createTempDirectory("groovy") new LocalDynamicMetadataResolver().with { it.name = 'LocalDynamic' it.xmlId = 'LocalDynamic' + it.sourceDirectory = tmpDirectory + it.dynamicMetadataResolverAttributes = new DynamicMetadataResolverAttributes().with { + it + } it } } @@ -459,6 +506,25 @@ class TestObjectGenerator { it.workingCopyDirectory = '%{idp.home}/metadata/svn' it } + it.reloadableMetadataResolverAttributes = new ReloadableMetadataResolverAttributes().with { + it + } + it + } + } + + ResourceBackedMetadataResolver resourceBackedMetadataResolverForClasspath() { + new ResourceBackedMetadataResolver().with { + it.name = 'ClasspathResourceMetadata' + it.xmlId = 'ClasspathResourceMetadata' + it.classpathMetadataResource = new ClasspathMetadataResource().with { + it.file = 'metadata/metadata.xml' + it + } + it.reloadableMetadataResolverAttributes = new ReloadableMetadataResolverAttributes().with { + it.refreshDelayFactor = 0.3 + it + } it } } diff --git a/backend/src/test/resources/conf/278.2.xml b/backend/src/test/resources/conf/278.2.xml index 216f9626c..42c21c122 100644 --- a/backend/src/test/resources/conf/278.2.xml +++ b/backend/src/test/resources/conf/278.2.xml @@ -47,17 +47,23 @@ certificateFile="%{idp.home}/credentials/inc-md-cert.pem" /> - + + xsi:type="resource:ClasspathResource" + file="metadata/metadata.xml" /> diff --git a/backend/src/test/resources/conf/278.xml b/backend/src/test/resources/conf/278.xml index 6f845bf59..856244654 100644 --- a/backend/src/test/resources/conf/278.xml +++ b/backend/src/test/resources/conf/278.xml @@ -40,17 +40,23 @@ certificateFile="%{idp.home}/credentials/inc-md-cert.pem" /> - + + xsi:type="resource:ClasspathResource" + file="metadata/metadata.xml" /> \ No newline at end of file diff --git a/backend/src/test/resources/conf/661.2.xml b/backend/src/test/resources/conf/661.2.xml new file mode 100644 index 000000000..fbfdb9a9f --- /dev/null +++ b/backend/src/test/resources/conf/661.2.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/backend/src/test/resources/conf/661.xml b/backend/src/test/resources/conf/661.xml new file mode 100644 index 000000000..30cdd7e0a --- /dev/null +++ b/backend/src/test/resources/conf/661.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ui/src/app/metadata/provider/action/collection.action.ts b/ui/src/app/metadata/provider/action/collection.action.ts index 3eb1f1889..a58164407 100644 --- a/ui/src/app/metadata/provider/action/collection.action.ts +++ b/ui/src/app/metadata/provider/action/collection.action.ts @@ -33,6 +33,8 @@ export enum ProviderCollectionActionTypes { CHANGE_PROVIDER_ORDER_UP = '[Metadata Provider Collection] Change Order Up', CHANGE_PROVIDER_ORDER_DOWN = '[Metadata Provider Collection] Change Order Down', + + CLEAR_SELECTION = '[Metadata Provider Collection] Clear Provider Selection' } export class LoadProviderRequest implements Action { @@ -173,6 +175,10 @@ export class ChangeProviderOrderDown implements Action { constructor(public payload: string) { } } +export class ClearProviderSelection implements Action { + readonly type = ProviderCollectionActionTypes.CLEAR_SELECTION; +} + export type ProviderCollectionActionsUnion = | LoadProviderRequest | LoadProviderSuccess @@ -196,4 +202,5 @@ export type ProviderCollectionActionsUnion = | GetOrderProviderSuccess | GetOrderProviderFail | ChangeProviderOrderUp - | ChangeProviderOrderDown; + | ChangeProviderOrderDown + | ClearProviderSelection; diff --git a/ui/src/app/metadata/provider/component/provider-editor-nav.component.html b/ui/src/app/metadata/provider/component/provider-editor-nav.component.html index 3ce1eef57..b130e77d1 100644 --- a/ui/src/app/metadata/provider/component/provider-editor-nav.component.html +++ b/ui/src/app/metadata/provider/component/provider-editor-nav.component.html @@ -1,4 +1,4 @@ - + - +