Skip to content

Commit

Permalink
Merge branch 'master' into SHIBUI-922
Browse files Browse the repository at this point in the history
  • Loading branch information
Bill Smith committed Jan 10, 2019
2 parents 38d7283 + b88cdc1 commit 5fc1aa6
Show file tree
Hide file tree
Showing 99 changed files with 5,671 additions and 339 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
3 changes: 3 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ dependencies {

integrationTestCompile 'com.saucelabs:sebuilder-interpreter:1.0.6'
integrationTestCompile 'jp.vmi:selenese-runner-java:3.19.2'

// CSV file support
compile 'com.opencsv:opencsv:4.4'
}

def generatedSrcDir = new File(buildDir, 'generated/src/main/java')
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,5 +1,6 @@
package edu.internet2.tier.shibboleth.admin.ui.jsonschema

import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse
import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation
import mjson.Json
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -10,7 +11,6 @@ import org.springframework.http.ResponseEntity
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter

import javax.annotation.PostConstruct
Expand Down Expand Up @@ -56,8 +56,8 @@ class RelyingPartyOverridesJsonSchemaValidatingControllerAdvice extends RequestB
}

@ExceptionHandler(JsonSchemaValidationFailedException)
final ResponseEntity<?> handleUserNotFoundException(JsonSchemaValidationFailedException ex, WebRequest request) {
new ResponseEntity<>([errors: ex.errors], HttpStatus.BAD_REQUEST)
final ResponseEntity<?> handleJsonSchemaValidationFailedException(JsonSchemaValidationFailedException ex) {
ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse("400", String.join('\n', ex.errors)))
}

@PostConstruct
Expand Down
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 && targetMetadataResolver && jpaMetadataResolver.type in ['FileBackedHttpMetadataResolver', 'DynamicHttpMetadataResolver']) {
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)
def doNamespaceProtectionFilter = { def filter ->
if (mr.type in ['FileBackedMetadataResolver', 'DynamicHttpMetadataResolver'] && (filter == null || filter instanceof EntityAttributesFilter) && !didNamespaceProtectionFilter) {
constructXmlNodeForEntityAttributeNamespaceProtection(delegate)
didNamespaceProtectionFilter = true
}
}
mr.metadataFilters.each { edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter filter ->
doNamespaceProtectionFilter()
constructXmlNodeForFilter(filter, delegate)
}
doNamespaceProtectionFilter()
}
}
}
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
@@ -0,0 +1,48 @@
package edu.internet2.tier.shibboleth.admin.ui.service

import com.opencsv.CSVReader
import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration
import edu.internet2.tier.shibboleth.admin.ui.security.model.Role
import edu.internet2.tier.shibboleth.admin.ui.security.model.User
import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository
import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository
import groovy.util.logging.Slf4j
import org.springframework.boot.context.event.ApplicationStartedEvent
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component

import javax.transaction.Transactional

@Component
@Slf4j
class UserBootstrap {
private final ShibUIConfiguration shibUIConfiguration
private final UserRepository userRepository
private final RoleRepository roleRepository

UserBootstrap(ShibUIConfiguration shibUIConfiguration, UserRepository userRepository, RoleRepository roleRepository) {
this.shibUIConfiguration = shibUIConfiguration
this.userRepository = userRepository
this.roleRepository = roleRepository
}

@Transactional
@EventListener
void bootstrapUsersAndRoles(ApplicationStartedEvent e) {
if (shibUIConfiguration.userBootstrapResource) {
log.info("configuring users from ${shibUIConfiguration.userBootstrapResource.URI}")
new CSVReader(new InputStreamReader(shibUIConfiguration.userBootstrapResource.inputStream)).each { it ->
def (username, password, firstName, lastName, roleName) = it
def role = roleRepository.findByName(roleName).orElse(roleRepository.save(new Role(name: roleName)))
def user = userRepository.findByUsername(username).orElse(new User(username: username)).with {
it.password = password
it.firstName = firstName
it.lastName = lastName
it.roles.add(role)
it
}
userRepository.save(user)
}
}
}
}
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
Loading

0 comments on commit 5fc1aa6

Please sign in to comment.