Skip to content

Commit

Permalink
Merge branch 'master' into feature/SHIBUI-1044
Browse files Browse the repository at this point in the history
# Conflicts:
#	backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java
  • Loading branch information
jj committed Dec 12, 2018
2 parents ec99101 + 5abca88 commit d99732c
Show file tree
Hide file tree
Showing 65 changed files with 5,257 additions and 190 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ For more information, see `docs`

## Requirements

* Java 8 (note that ONLY Java 8 is supported at this time)
* Java 8 (note that ONLY Java 8 is supported at this time; other later versions might work)

## Running

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

import com.fasterxml.jackson.databind.ObjectMapper
import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation
import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocationRegistry
import edu.internet2.tier.shibboleth.admin.ui.service.JsonSchemaBuilderService
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

import javax.annotation.PostConstruct

import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.nameIdFormatFilterSchema
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR

/**
* Controller implementing REST resource responsible for exposing structure definition for nameid format filter user
* interface in terms of JSON schema.
*
* @author Dmitriy Kopylenko
*/
@RestController
@RequestMapping('/api/ui/NameIdFormatFilter')
@Slf4j
class NameIdFormatFilterUiDefinitionController {

@Autowired
JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry

JsonSchemaResourceLocation jsonSchemaLocation

@Autowired
ObjectMapper jacksonObjectMapper

@Autowired
JsonSchemaBuilderService jsonSchemaBuilderService

@GetMapping
ResponseEntity<?> getUiDefinitionJsonSchema() {
try {
def parsedJson = jacksonObjectMapper.readValue(this.jsonSchemaLocation.url, Map)
jsonSchemaBuilderService.addRelyingPartyOverridesCollectionDefinitionsToJson(parsedJson["definitions"])
return ResponseEntity.ok(parsedJson)
}
catch (Exception e) {
log.error(e.getMessage(), e)
return ResponseEntity.status(INTERNAL_SERVER_ERROR)
.body([jsonParseError : e.getMessage(),
sourceUiSchemaDefinitionFile: this.jsonSchemaLocation.url])
}
}

@PostConstruct
void init() {
this.jsonSchemaLocation = nameIdFormatFilterSchema(this.jsonSchemaResourceLocationRegistry)
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package edu.internet2.tier.shibboleth.admin.ui.service

import com.google.common.base.Predicate
import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration
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.EntityRoleWhiteListFilter
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilter
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter
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.FileBackedHttpMetadataResolver
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver
Expand All @@ -27,13 +30,17 @@ import org.opensaml.saml.common.profile.logic.EntityIdPredicate
import org.opensaml.saml.metadata.resolver.MetadataResolver
import org.opensaml.saml.metadata.resolver.filter.MetadataFilter
import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain
import org.opensaml.saml.metadata.resolver.filter.impl.NameIDFormatFilter
import org.opensaml.saml.saml2.core.Attribute
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.filters.NameIdFormatFilterTarget.NameIdFormatFilterTargetType.ENTITY
import static edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilterTarget.NameIdFormatFilterTargetType.CONDITION_SCRIPT
import static edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilterTarget.NameIdFormatFilterTargetType.REGEX
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

Expand All @@ -52,18 +59,30 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
@Autowired
private MetadataResolversPositionOrderContainerService resolversPositionOrderContainerService

@Autowired
private ShibUIConfiguration shibUIConfiguration

// TODO: enhance
@Override
void reloadFilters(String metadataResolverResourceId) {
OpenSamlChainingMetadataResolver chainingMetadataResolver = (OpenSamlChainingMetadataResolver) metadataResolver
MetadataResolver targetMetadataResolver = chainingMetadataResolver.getResolvers().find { it.id == metadataResolverResourceId }
MetadataResolver targetMetadataResolver = chainingMetadataResolver.getResolvers().find {
it.id == metadataResolverResourceId
}
edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver jpaMetadataResolver = metadataResolverRepository.findByResourceId(metadataResolverResourceId)

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

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

// set up namespace protection
if (shibUIConfiguration.protectedAttributeNamespaces && shibUIConfiguration.protectedAttributeNamespaces.size() > 0) {
def target = new org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter()
target.attributeFilter = new ScriptedPredicate(new EvaluableScript(protectedNamespaceScript()))
metadataFilters.add(target)
}

for (edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter metadataFilter : jpaMetadataResolver.getMetadataFilters()) {
if (metadataFilter instanceof EntityAttributesFilter) {
EntityAttributesFilter entityAttributesFilter = (EntityAttributesFilter) metadataFilter
Expand Down Expand Up @@ -92,6 +111,30 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
target.setRules(rules)
metadataFilters.add(target)
}
if (metadataFilter instanceof NameIdFormatFilter) {
NameIdFormatFilter nameIdFormatFilter = NameIdFormatFilter.cast(metadataFilter)
NameIDFormatFilter openSamlTargetFilter = new OpenSamlNameIdFormatFilter()
openSamlTargetFilter.removeExistingFormats = nameIdFormatFilter.removeExistingFormats
Map<Predicate<EntityDescriptor>, Collection<String>> predicateRules = [:]
def type = nameIdFormatFilter.nameIdFormatFilterTarget.nameIdFormatFilterTargetType
def values = nameIdFormatFilter.nameIdFormatFilterTarget.value
switch (type) {
case ENTITY:
predicateRules[new EntityIdPredicate(values)] = nameIdFormatFilter.formats
break
case CONDITION_SCRIPT:
predicateRules[new ScriptedPredicate(new EvaluableScript(values[0]))] = nameIdFormatFilter.formats
break
case REGEX:
predicateRules[new ScriptedPredicate(new EvaluableScript(generateJavaScriptRegexScript(values[0])))] = nameIdFormatFilter.formats
break
default:
// do nothing, we'd have exploded elsewhere previously.
break
}
openSamlTargetFilter.rules = predicateRules
metadataFilters << openSamlTargetFilter
}
}
metadataFilterChain.setFilters(metadataFilters)
}
Expand All @@ -104,6 +147,21 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
}
}

private String protectedNamespaceScript() {
return """(function (attribute) {
"use strict";
var namespaces = [${shibUIConfiguration.protectedAttributeNamespaces.collect({"\"${it}\""}).join(', ')}];
// check the parameter
if (attribute === null) { return true; }
for (var i in namespaces) {
if (attribute.getName().startsWith(namespaces[i])) {
return false;
}
}
return true;
}(input));"""
}

private class ScriptedPredicate extends net.shibboleth.utilities.java.support.logic.ScriptedPredicate<EntityDescriptor> {
protected ScriptedPredicate(@Nonnull EvaluableScript theScript) {
super(theScript)
Expand Down Expand Up @@ -133,9 +191,18 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
if ((mr.type != 'BaseMetadataResolver') && (mr.enabled)) {
constructXmlNodeForResolver(mr, delegate) {
//TODO: enhance
def didNamespaceProtectionFilter = !(shibUIConfiguration.protectedAttributeNamespaces && shibUIConfiguration.protectedAttributeNamespaces.size() > 0)
mr.metadataFilters.each { edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter filter ->
if (filter instanceof EntityAttributesFilter && !didNamespaceProtectionFilter) {
constructXmlNodeForEntityAttributeNamespaceProtection(delegate)
didNamespaceProtectionFilter = true
}
constructXmlNodeForFilter(filter, delegate)
}
if (!didNamespaceProtectionFilter) {
constructXmlNodeForEntityAttributeNamespaceProtection(delegate)
didNamespaceProtectionFilter = true
}
}
}
}
Expand All @@ -144,8 +211,18 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
}
}

void constructXmlNodeForEntityAttributeNamespaceProtection(def markupBuilderDelegate) {
markupBuilderDelegate.MetadataFilter('xsi:type': 'EntityAttributes') {
AttributeFilterScript() {
Script() {
mkp.yieldUnescaped("\n<![CDATA[\n${protectedNamespaceScript()}\n]]>\n")
}
}
}
}

void constructXmlNodeForFilter(SignatureValidationFilter filter, def markupBuilderDelegate) {
if(filter.xmlShouldBeGenerated()) {
if (filter.xmlShouldBeGenerated()) {
markupBuilderDelegate.MetadataFilter(id: filter.name,
'xsi:type': 'SignatureValidation',
'xmlns:md': 'urn:oasis:names:tc:SAML:2.0:metadata',
Expand Down Expand Up @@ -216,14 +293,52 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
}

void constructXmlNodeForFilter(RequiredValidUntilFilter filter, def markupBuilderDelegate) {
if(filter.xmlShouldBeGenerated()) {
if (filter.xmlShouldBeGenerated()) {
markupBuilderDelegate.MetadataFilter(
'xsi:type': 'RequiredValidUntil',
maxValidityInterval: filter.maxValidityInterval
)
}
}

void constructXmlNodeForFilter(NameIdFormatFilter filter, def markupBuilderDelegate) {
def type = filter.nameIdFormatFilterTarget.nameIdFormatFilterTargetType
markupBuilderDelegate.MetadataFilter(
'xsi:type': 'NameIDFormat',
'xmlns:md': 'urn:oasis:names:tc:SAML:2.0:metadata',
'removeExistingFormats': filter.removeExistingFormats ?: null
) {
filter.formats.each {
Format(it)
}
switch (type) {
case ENTITY:
filter.nameIdFormatFilterTarget.value.each {
Entity(it)
}
break
case CONDITION_SCRIPT:
case REGEX:
ConditionScript() {
Script() {
def script
def scriptValue = filter.nameIdFormatFilterTarget.value[0]
if (type == CONDITION_SCRIPT) {
script = scriptValue
} else if (type == REGEX) {
script = generateJavaScriptRegexScript(scriptValue)
}
mkp.yieldUnescaped("\n<![CDATA[\n${script}\n]]>\n")
}
}
break
default:
// do nothing, we'd have exploded elsewhere previously.
break
}
}
}

void constructXmlNodeForResolver(FilesystemMetadataResolver resolver, def markupBuilderDelegate, Closure childNodes) {
markupBuilderDelegate.MetadataProvider(id: resolver.xmlId,
'xsi:type': 'FilesystemMetadataProvider',
Expand Down Expand Up @@ -441,4 +556,5 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import javax.servlet.http.HttpServletRequest;

@Configuration
@EnableConfigurationProperties(CustomPropertiesConfiguration.class)
@EnableConfigurationProperties({CustomPropertiesConfiguration.class, ShibUIConfiguration.class})
public class CoreShibUiConfiguration {
private static final Logger logger = LoggerFactory.getLogger(CoreShibUiConfiguration.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.FILESYSTEM_METADATA_RESOLVER;
import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.LOCAL_DYNAMIC_METADATA_RESOLVER;
import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER;
import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.NAME_ID_FORMAT_FILTER;

/**
* @author Dmitriy Kopylenko
Expand Down Expand Up @@ -48,6 +49,11 @@ public class JsonSchemaComponentsConfiguration {
@Setter
private String dynamicHttpMetadataResolverUiSchemaLocation = "classpath:dynamic-http-metadata-provider.schema.json";

//Configured via @ConfigurationProperties (using setter method) with 'shibui.nameid-filter-ui-schema-location' property and
// default value set here if that property is not explicitly set in application.properties
@Setter
private String nameIdFormatFilterUiSchemaLocation = "classpath:nameid-filter.schema.json";

@Bean
public JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(ResourceLoader resourceLoader, ObjectMapper jacksonMapper) {
return JsonSchemaResourceLocationRegistry.inMemory()
Expand Down Expand Up @@ -80,6 +86,12 @@ public JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(Res
.resourceLoader(resourceLoader)
.jacksonMapper(jacksonMapper)
.detectMalformedJson(true)
.build())
.register(NAME_ID_FORMAT_FILTER, JsonSchemaLocationBuilder.with()
.jsonSchemaLocation(nameIdFormatFilterUiSchemaLocation)
.resourceLoader(resourceLoader)
.jacksonMapper(jacksonMapper)
.detectMalformedJson(true)
.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.SignatureValidationFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
Expand Down Expand Up @@ -224,6 +225,13 @@ else if(filterWithUpdatedData instanceof RequiredValidUntilFilter) {
RequiredValidUntilFilter fromFilter = RequiredValidUntilFilter.class.cast(filterWithUpdatedData);
toFilter.setMaxValidityInterval(fromFilter.getMaxValidityInterval());
}
else if (filterWithUpdatedData instanceof NameIdFormatFilter) {
NameIdFormatFilter toFilter = NameIdFormatFilter.class.cast(filterToBeUpdated);
NameIdFormatFilter fromFilter = NameIdFormatFilter.class.cast(filterWithUpdatedData);
toFilter.setRemoveExistingFormats(fromFilter.getRemoveExistingFormats());
toFilter.setFormats(fromFilter.getFormats());
toFilter.setNameIdFormatFilterTarget(fromFilter.getNameIdFormatFilterTarget());
}
//TODO: add other types of concrete filters update here
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ public List<XMLObject> getOrderedChildren() {
return null;
}

//TODO: refactor mappings so we're also able persist and use non-null parent which happens to be used in some code paths
//where we use open saml API (re-filtering, for example)
@Transient
private transient XMLObject parent;
@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
@JsonSubTypes({@JsonSubTypes.Type(value=EntityRoleWhiteListFilter.class, name="EntityRoleWhiteList"),
@JsonSubTypes.Type(value=EntityAttributesFilter.class, name="EntityAttributes"),
@JsonSubTypes.Type(value=SignatureValidationFilter.class, name="SignatureValidation"),
@JsonSubTypes.Type(value=RequiredValidUntilFilter.class, name="RequiredValidUntil")})
@JsonSubTypes.Type(value=RequiredValidUntilFilter.class, name="RequiredValidUntil"),
@JsonSubTypes.Type(value=NameIdFormatFilter.class, name="NameIDFormat")})
public class MetadataFilter extends AbstractAuditable {

@JsonProperty("@type")
Expand Down
Loading

0 comments on commit d99732c

Please sign in to comment.