Skip to content

Commit

Permalink
Showing 31 changed files with 160 additions and 94 deletions.
@@ -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
@@ -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
@@ -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
@@ -160,7 +160,7 @@ private ResponseEntity<?> existingEntityDescriptorCheck(String entityId) {
return ResponseEntity
.status(HttpStatus.CONFLICT)
.headers(headers)
.body(String.format("The entity descriptor with entity id [%s] already exists.", entityId));
.body(new ErrorResponse(String.valueOf(HttpStatus.CONFLICT.value()), String.format("The entity descriptor with entity id [%s] already exists.", entityId)));
}
//No existing entity descriptor, which is an OK condition indicated by returning a null conflict response
return null;
@@ -1,6 +1,7 @@
package edu.internet2.tier.shibboleth.admin.ui.controller;

import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import edu.internet2.tier.shibboleth.admin.ui.domain.exceptions.MetadataFileNotFoundException;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidationService;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver;
@@ -105,32 +106,26 @@ public ResponseEntity<?> getOne(@PathVariable String resourceId) {
}

@PostMapping("/MetadataResolvers")
@Transactional
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<?> create(@RequestBody MetadataResolver newResolver) throws IOException, ResolverException, ComponentInitializationException {
if (resolverRepository.findByName(newResolver.getName()) != null) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}

ResponseEntity<?> validationErrorResponse = validate(newResolver);
if(validationErrorResponse != null) {
if (validationErrorResponse != null) {
return validationErrorResponse;
}

MetadataResolver persistedResolver = resolverRepository.save(newResolver);
positionOrderContainerService.appendPositionOrderForNew(persistedResolver);

//TODO: currently, the update call might explode, but the save works.. in which case, the UI never gets
// n valid response. This operation is not atomic. Should we return an error here?
if (persistedResolver.getDoInitialization()) {
org.opensaml.saml.metadata.resolver.MetadataResolver openSamlRepresentation = metadataResolverConverterService.convertToOpenSamlRepresentation(persistedResolver);
OpenSamlChainingMetadataResolverUtil.updateChainingMetadataResolver((OpenSamlChainingMetadataResolver) chainingMetadataResolver, openSamlRepresentation);
}
doResolverInitialization(persistedResolver);

return ResponseEntity.created(getResourceUriFor(persistedResolver)).body(persistedResolver);
}

@PutMapping("/MetadataResolvers/{resourceId}")
@Transactional
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<?> update(@PathVariable String resourceId, @RequestBody MetadataResolver updatedResolver) throws IOException, ResolverException, ComponentInitializationException {
MetadataResolver existingResolver = resolverRepository.findByResourceId(resourceId);
if (existingResolver == null) {
@@ -143,32 +138,21 @@ public ResponseEntity<?> update(@PathVariable String resourceId, @RequestBody Me
}

ResponseEntity<?> validationErrorResponse = validate(updatedResolver);
if(validationErrorResponse != null) {
if (validationErrorResponse != null) {
return validationErrorResponse;
}

updatedResolver.setAudId(existingResolver.getAudId());

MetadataResolver persistedResolver = resolverRepository.save(updatedResolver);

if (persistedResolver.getDoInitialization()) {
org.opensaml.saml.metadata.resolver.MetadataResolver openSamlRepresentation = null;
try {
openSamlRepresentation = metadataResolverConverterService.convertToOpenSamlRepresentation(persistedResolver);
} catch (FileNotFoundException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.toString(), "label.file-doesnt-exist"));
}
OpenSamlChainingMetadataResolverUtil.updateChainingMetadataResolver((OpenSamlChainingMetadataResolver) chainingMetadataResolver, openSamlRepresentation);
}
doResolverInitialization(persistedResolver);

return ResponseEntity.ok(persistedResolver);
}

@SuppressWarnings("Unchecked")
private ResponseEntity<?> validate(MetadataResolver metadataResolver) {
ValidationResult validationResult = metadataResolverValidationService.validateIfNecessary(metadataResolver);
if(!validationResult.isValid()) {
if (!validationResult.isValid()) {
return ResponseEntity.badRequest().body(validationResult.getErrorMessage());
}
return null;
@@ -181,4 +165,17 @@ private static URI getResourceUriFor(MetadataResolver resolver) {
.build()
.toUri();
}

private void doResolverInitialization(MetadataResolver persistedResolver) throws
ComponentInitializationException, ResolverException, IOException {
if (persistedResolver.getDoInitialization()) {
org.opensaml.saml.metadata.resolver.MetadataResolver openSamlRepresentation = null;
try {
openSamlRepresentation = metadataResolverConverterService.convertToOpenSamlRepresentation(persistedResolver);
} catch (FileNotFoundException e) {
throw new MetadataFileNotFoundException("message.file-doesnt-exist");
}
OpenSamlChainingMetadataResolverUtil.updateChainingMetadataResolver((OpenSamlChainingMetadataResolver) chainingMetadataResolver, openSamlRepresentation);
}
}
}
@@ -1,7 +1,7 @@
package edu.internet2.tier.shibboleth.admin.ui.controller.support;

import com.google.common.collect.ImmutableMap;
import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse;
import edu.internet2.tier.shibboleth.admin.ui.domain.exceptions.MetadataFileNotFoundException;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository;
import org.hibernate.exception.ConstraintViolationException;
@@ -12,6 +12,7 @@
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;

@@ -53,4 +54,10 @@ public final ResponseEntity<ErrorResponse> handleAllOtherExceptions(Exception ex
ErrorResponse errorResponse = new ErrorResponse("400", ex.getLocalizedMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(MetadataFileNotFoundException.class)
public final ResponseEntity<ErrorResponse> metadataFileNotFoundHandler(MetadataFileNotFoundException ex) {
ErrorResponse errorResponse = new ErrorResponse(INTERNAL_SERVER_ERROR.toString(), ex.getLocalizedMessage());
return new ResponseEntity<>(errorResponse, INTERNAL_SERVER_ERROR);
}
}
@@ -0,0 +1,14 @@
package edu.internet2.tier.shibboleth.admin.ui.domain.exceptions;

/**
* Indicates that provided metadata file is not found. Thrown during opensaml API initialization code path, if there is
* such. Throwing such exception is useful in @Transactional context for atomic transaction rollbacks, etc.
*
* @author Dmitriy Kopylenko
*/
public class MetadataFileNotFoundException extends RuntimeException {

public MetadataFileNotFoundException(String message) {
super(message);
}
}
@@ -64,7 +64,7 @@ private OpenSamlFilesystemMetadataResolver convertToOpenSamlRepresentation(Files
IndexWriter indexWriter = indexWriterService.getIndexWriter(resolver.getResourceId());
File metadataFile = new File(resolver.getMetadataFile());
if (resolver.getDoInitialization() && !metadataFile.exists()) {
throw new FileNotFoundException("No file was found on the fileysystem for provided filename: " + resolver.getMetadataFile());
throw new FileNotFoundException("No file was found on the file system for provided filename: " + resolver.getMetadataFile());
}

OpenSamlFilesystemMetadataResolver openSamlResolver = new OpenSamlFilesystemMetadataResolver(openSamlObjects.getParserPool(),
@@ -152,8 +152,8 @@
"step": 0.01
},
"placeholder": "label.real-number",
"minimum": 0,
"maximum": 1,
"minimum": 0.01,
"maximum": 0.99,
"default": null
},
"minCacheDuration": {
@@ -222,6 +222,29 @@
"default": null,
"pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$"
},
"removeIdleEntityData": {
"title": "label.remove-idle-entity-data",
"description": "tooltip.remove-idle-entity-data",
"type": "boolean",
"widget": {
"id": "boolean-radio"
},
"oneOf": [
{
"enum": [
true
],
"description": "value.true"
},
{
"enum": [
false
],
"description": "value.false"
}
],
"default": true
},
"cleanupTaskInterval": {
"title": "label.cleanup-task-interval",
"description": "tooltip.cleanup-task-interval",
8 changes: 5 additions & 3 deletions backend/src/main/resources/i18n/messages.properties
@@ -299,7 +299,7 @@ label.metadata-provider-type=Metadata Provider Type
label.metadata-provider-name=Metadata Provider Name
label.select-metadata-type=Select a metadata provider type
label.metadata-provider-status=Metadata Provider Status
label.enable-provider-upon-saving=Enable Metadata Provider upon saving?
label.enable-provider-upon-saving=Enable Metadata Provider?
label.certificate-type=Type

label.metadata-file=Metadata File
@@ -319,6 +319,7 @@ label.select-metadata-provider-type=Select Metadata Provider Type
label.filter-list=Filter List
label.common-attributes=Common Attributes
label.reloading-attributes=Reloading Attributes
label.dynamic-attributes=Dynamic Attributes
label.metadata-filter-plugins=Metadata Filter Plugins
label.advanced-settings=Advanced Settings
label.edit-metadata-provider=Edit Metadata Provider
@@ -397,6 +398,7 @@ message.wizard-status=Step { index } of { length }
message.entity-id-min-unique=You must add at least one entity id target and they must each be unique.
message.required-for-scripts=Required for Scripts
message.required-for-regex=Required for Regex
message.file-doesnt-exist=The requested file to be processed does not exist on the server.
message.database-constraint=There was a database constraint problem processing the request. Check the request to ensure that fields that must be unique are truly unique.

tooltip.entity-id=Entity ID
@@ -458,7 +460,7 @@ tooltip.http-caching-directory=If httpCaching='file', this attribute specifies w
tooltip.http-max-cache-entries=The maximum number of responses written to cache. This attribute is incompatible with httpClientRef.
tooltip.max-cache-entry-size=The maximum response body size that may be cached, in bytes. This attribute is incompatible with httpClientRef.

tooltip.metadata-provider-name=Metadata Provider Name
tooltip.metadata-provider-name=Metadata Provider Name (for display on the Dashboard only)
tooltip.metadata-provider-type=Metadata Provider Type
tooltip.xml-id=Identifier for logging, identification for command line reload, etc.
tooltip.metadata-url=Identifier for logging, identification for command line reload, etc.
@@ -470,7 +472,7 @@ tooltip.require-valid-metadata=Whether candidate metadata found by the resolver
tooltip.fail-fast-init=Whether to fail initialization of the underlying MetadataResolverService (and possibly the IdP as a whole) if the initialization of a metadata provider fails. When false, the IdP may start, and will continue to attempt to reload valid metadata if configured to do so, but operations that require valid metadata will fail until it does.
tooltip.use-default-predicate-reg=Flag which determines whether the default CriterionPredicateRegistry will be used if a custom one is not supplied explicitly.
tooltip.satisfy-any-predicates=Flag which determines whether predicates used in filtering are connected by a logical 'OR' (true) or by logical 'AND' (false).
tooltip.enable-provider-upon-saving=Enable Metadata Provider upon saving?
tooltip.enable-provider-upon-saving=If checkbox is clicked, the metadata provider is enabled for integration with the IdP

tooltip.max-validity-interval=Defines the window within which the metadata is valid.
tooltip.require-signed-root=If true, this fails to load metadata with no signature on the root XML element.
14 changes: 8 additions & 6 deletions backend/src/main/resources/i18n/messages_en.properties
@@ -304,7 +304,7 @@ label.metadata-provider-type=Metadata Provider Type
label.metadata-provider-name=Metadata Provider Name
label.select-metadata-type=Select a metadata provider type
label.metadata-provider-status=Metadata Provider Status
label.enable-provider-upon-saving=Enable Metadata Provider upon saving?
label.enable-provider-upon-saving=Enable Metadata Provider?
label.certificate-type=Type

label.metadata-file=Metadata File
@@ -324,6 +324,7 @@ label.select-metadata-provider-type=Select Metadata Provider Type
label.filter-list=Filter List
label.common-attributes=Common Attributes
label.reloading-attributes=Reloading Attributes
label.dynamic-attributes=Dynamic Attributes
label.metadata-filter-plugins=Metadata Filter Plugins
label.advanced-settings=Advanced Settings
label.edit-metadata-provider=Edit Metadata Provider
@@ -414,6 +415,7 @@ message.wizard-status=Step { index } of { length }
message.entity-id-min-unique=You must add at least one entity id target and they must each be unique.
message.required-for-scripts=Required for Scripts
message.required-for-regex=Required for Regex
message.file-doesnt-exist=The requested file to be processed does not exist on the server.
message.database-constraint=There was a database constraint problem processing the request. Check the request to ensure that fields that must be unique are truly unique.

tooltip.entity-id=Entity ID
@@ -475,7 +477,7 @@ tooltip.http-caching-directory=If httpCaching='file', this attribute specifies w
tooltip.http-max-cache-entries=The maximum number of responses written to cache. This attribute is incompatible with httpClientRef.
tooltip.max-cache-entry-size=The maximum response body size that may be cached, in bytes. This attribute is incompatible with httpClientRef.

tooltip.metadata-provider-name=Metadata Provider Name
tooltip.metadata-provider-name=Metadata Provider Name (for display on the Dashboard only)
tooltip.metadata-provider-type=Metadata Provider Type
tooltip.xml-id=Identifier for logging, identification for command line reload, etc.
tooltip.metadata-url=Identifier for logging, identification for command line reload, etc.
@@ -487,14 +489,14 @@ tooltip.require-valid-metadata=Whether candidate metadata found by the resolver
tooltip.fail-fast-init=Whether to fail initialization of the underlying MetadataResolverService (and possibly the IdP as a whole) if the initialization of a metadata provider fails. When false, the IdP may start, and will continue to attempt to reload valid metadata if configured to do so, but operations that require valid metadata will fail until it does.
tooltip.use-default-predicate-reg=Flag which determines whether the default CriterionPredicateRegistry will be used if a custom one is not supplied explicitly.
tooltip.satisfy-any-predicates=Flag which determines whether predicates used in filtering are connected by a logical 'OR' (true) or by logical 'AND' (false).
tooltip.enable-provider-upon-saving=Enable Metadata Provider upon saving?
tooltip.enable-provider-upon-saving=If checkbox is clicked, the metadata provider is enabled for integration with the IdP

tooltip.max-validity-interval=Defines the window within which the metadata is valid.
tooltip.require-signed-root=If true, this fails to load metadata with no signature on the root XML element.
tooltip.certificate-file=A key used to verify the signature. Conflicts with trustEngineRef and both of the child elements.
tooltip.retained-roles=Controls whether to keep entity descriptors that contain no roles
tooltip.remove-roleless-entity-descriptors=Controls whether to keep entity descriptors that contain no roles.
tooltip.remove-empty-entities-descriptors=Controls whether to keep entities descriptors that contain no entity descriptors.
tooltip.retained-roles=Note that property replacement cannot be used on this element.
tooltip.remove-roleless-entity-descriptors=Controls whether to keep entity descriptors that contain no roles. Note: If this attribute is set to false, the resulting output may not be schema-valid since an <md:EntityDescriptor> element must include at least one role descriptor.
tooltip.remove-empty-entities-descriptors=Controls whether to keep entities descriptors that contain no entity descriptors. Note: If this attribute is set to false, the resulting output may not be schema-valid since an <md:EntitiesDescriptor> element must include at least one child element, either an <md:EntityDescriptor> element or an <md:EntitiesDescriptor> element.

tooltip.min-refresh-delay=Lower bound on the next refresh from the time calculated based on the metadata\u0027s expiration.
tooltip.max-refresh-delay=Upper bound on the next refresh from the time calculated based on the metadata\u0027s expiration.

0 comments on commit 8e21a09

Please sign in to comment.