From 7b85342fb24f2c38942506db00970753678d9e07 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 3 Oct 2018 11:12:26 -0700 Subject: [PATCH 01/11] SHIBUI-354 Attempt to fix a11y of popovers --- .../shared/component/info-icon.component.html | 10 ++++++++-- .../app/shared/component/info-icon.component.ts | 17 ++++++++++++++++- .../shared/directive/info-label.directive.ts | 11 +---------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/ui/src/app/shared/component/info-icon.component.html b/ui/src/app/shared/component/info-icon.component.html index 58c70b2db..2babfedd5 100644 --- a/ui/src/app/shared/component/info-icon.component.html +++ b/ui/src/app/shared/component/info-icon.component.html @@ -1,12 +1,18 @@ - {{ description }} + {{ description }} \ No newline at end of file diff --git a/ui/src/app/shared/component/info-icon.component.ts b/ui/src/app/shared/component/info-icon.component.ts index d5a7fb207..fe9787c79 100644 --- a/ui/src/app/shared/component/info-icon.component.ts +++ b/ui/src/app/shared/component/info-icon.component.ts @@ -1,4 +1,5 @@ -import { Component, Input, ChangeDetectionStrategy, ElementRef, ViewChild } from '@angular/core'; +import { Component, Input, ChangeDetectionStrategy, Renderer } from '@angular/core'; +import { NgbPopover, NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'info-icon', @@ -8,4 +9,18 @@ import { Component, Input, ChangeDetectionStrategy, ElementRef, ViewChild } from }) export class InfoIconComponent { @Input() description: string; + + id: string = String.fromCharCode(65 + Math.floor(Math.random() * 26)) + Date.now().toString(); + + triggers = 'mouseenter:mouseleave'; + container = 'body'; + placement = ['top']; + + constructor( + private renderer: Renderer + ) { } + focus(element): void { + console.log(element.elementRef.nativeElement); + this.renderer.invokeElementMethod(element.elementRef.nativeElement, 'focus'); + } } diff --git a/ui/src/app/shared/directive/info-label.directive.ts b/ui/src/app/shared/directive/info-label.directive.ts index 893bd139d..c0a4e85cd 100644 --- a/ui/src/app/shared/directive/info-label.directive.ts +++ b/ui/src/app/shared/directive/info-label.directive.ts @@ -7,19 +7,10 @@ import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; providers: [NgbPopover] }) export class InfoLabelDirective { - @HostListener('keydown.space') onSpaceDown() { - this.config.open(); - } - @HostListener('keyup.space') onSpaceUp() { - this.config.close(); - } constructor( private config: NgbPopover, private element: ElementRef ) { - config.triggers = 'mouseenter:mouseleave'; - config.placement = ['top', 'left']; - config.container = 'body'; - element.nativeElement.setAttribute('tabindex', 0); + } } From 541919b0ddaa2caaddc959259131f82e6651f920 Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Thu, 11 Oct 2018 13:29:42 -0700 Subject: [PATCH 02/11] a11y/SHIBUI-354: Added screen reader accessibility to info icon tooltips; --- backend/src/main/resources/i18n/messages_en.properties | 2 +- ui/src/app/shared/component/info-icon.component.html | 7 ++----- ui/src/app/shared/component/info-icon.component.ts | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 6d64dd72a..964ef0e77 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -400,7 +400,7 @@ tooltip.authentication-methods-to-use=Authentication Methods to Use tooltip.ignore-auth-method=Ignore any SP-Requested Authentication Method tooltip.omit-not-before-condition=Omit Not Before Condition tooltip.responder-id=ResponderId -tooltip.instruction=Information icon - press spacebar to read additional information for this form field +tooltip.instruction=Information icon tooltip.attribute-release-table=Attribute release table - select the attributes you want to release (default unchecked) tooltip.metadata-filter-name=Metadata Filter Name tooltip.metadata-filter-type=Metadata Filter Type diff --git a/ui/src/app/shared/component/info-icon.component.html b/ui/src/app/shared/component/info-icon.component.html index 2babfedd5..70c09fadf 100644 --- a/ui/src/app/shared/component/info-icon.component.html +++ b/ui/src/app/shared/component/info-icon.component.html @@ -4,15 +4,12 @@ \ No newline at end of file + diff --git a/ui/src/app/shared/component/info-icon.component.ts b/ui/src/app/shared/component/info-icon.component.ts index fe9787c79..5c340b410 100644 --- a/ui/src/app/shared/component/info-icon.component.ts +++ b/ui/src/app/shared/component/info-icon.component.ts @@ -12,7 +12,7 @@ export class InfoIconComponent { id: string = String.fromCharCode(65 + Math.floor(Math.random() * 26)) + Date.now().toString(); - triggers = 'mouseenter:mouseleave'; + triggers = 'mouseenter:mouseleave focus:blur'; container = 'body'; placement = ['top']; From c54b1717e2434b2fc20f04b378ccc126589cf74e Mon Sep 17 00:00:00 2001 From: Jodie Muramoto Date: Thu, 11 Oct 2018 14:02:05 -0700 Subject: [PATCH 03/11] a11y/SHIBUI-354: Removed spacebar keydown trigger (redundant); --- ui/src/app/shared/component/info-icon.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/app/shared/component/info-icon.component.html b/ui/src/app/shared/component/info-icon.component.html index 70c09fadf..38dee05bd 100644 --- a/ui/src/app/shared/component/info-icon.component.html +++ b/ui/src/app/shared/component/info-icon.component.html @@ -5,7 +5,6 @@ #toggle="ngbPopover" [attr.aria-label]="'tooltip.instruction' | translate" [ngbPopover]="tooltipContent" - (keydown.space)="toggle.open()" [triggers]="triggers" [placement]="placement" [attr.aria-flowto]="id" From 4763697e38002ae8f50031e6a372a0c0b8493e2c Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 12 Oct 2018 09:53:37 -0700 Subject: [PATCH 04/11] SHIBUI-354 Removed old files --- .../directive/info-label.directive.spec.ts | 60 ------------------- .../shared/directive/info-label.directive.ts | 25 -------- ui/src/app/shared/shared.module.ts | 3 - 3 files changed, 88 deletions(-) delete mode 100644 ui/src/app/shared/directive/info-label.directive.spec.ts delete mode 100644 ui/src/app/shared/directive/info-label.directive.ts diff --git a/ui/src/app/shared/directive/info-label.directive.spec.ts b/ui/src/app/shared/directive/info-label.directive.spec.ts deleted file mode 100644 index e1e6d5b59..000000000 --- a/ui/src/app/shared/directive/info-label.directive.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Component, DebugElement, ElementRef } from '@angular/core'; -import { By } from '@angular/platform-browser'; -import { TestBed, ComponentFixture } from '@angular/core/testing'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { NgbPopover, NgbPopoverModule, NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap'; - -import { InfoLabelDirective } from './info-label.directive'; -import * as utility from '../../../testing/utility'; - - -@Component({ - template: `` -}) -class TestComponent { - tooltipName = 'Foobar!'; -} - -describe('Info Label Directive', () => { - let component: TestComponent; - let fixture: ComponentFixture; - let element: DebugElement; - let config: NgbPopover; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [NgbPopover, NgbPopoverConfig], - imports: [ - NgbPopoverModule - ], - declarations: [ - InfoLabelDirective, - TestComponent - ], - }); - - fixture = TestBed.createComponent(TestComponent); - element = fixture.debugElement.query(By.css('i.info-icon')); - config = element.injector.get(NgbPopover); - fixture.detectChanges(); - }); - - describe('functions', () => { - it('should compile', () => { - expect(element).toBeDefined(); - }); - it('should call the config open method when spacebar is pressed', () => { - spyOn(config, 'open'); - utility.dispatchKeyboardEvent(element.nativeElement, 'keydown', 'space'); - fixture.detectChanges(); - expect(config.open).toHaveBeenCalled(); - }); - it('should call the config close method when spacebar is released', () => { - spyOn(config, 'close'); - utility.dispatchKeyboardEvent(element.nativeElement, 'keyup', 'space'); - fixture.detectChanges(); - expect(config.close).toHaveBeenCalled(); - }); - }); -}); diff --git a/ui/src/app/shared/directive/info-label.directive.ts b/ui/src/app/shared/directive/info-label.directive.ts deleted file mode 100644 index 893bd139d..000000000 --- a/ui/src/app/shared/directive/info-label.directive.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Directive, ElementRef, Input, HostListener } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; - -/* tslint:disable:directive-selector */ -@Directive({ - selector: '.info-icon', - providers: [NgbPopover] -}) -export class InfoLabelDirective { - @HostListener('keydown.space') onSpaceDown() { - this.config.open(); - } - @HostListener('keyup.space') onSpaceUp() { - this.config.close(); - } - constructor( - private config: NgbPopover, - private element: ElementRef - ) { - config.triggers = 'mouseenter:mouseleave'; - config.placement = ['top', 'left']; - config.container = 'body'; - element.nativeElement.setAttribute('tabindex', 0); - } -} diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index 20792a8aa..e033083a0 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -6,7 +6,6 @@ import { AutoCompleteComponent } from './autocomplete/autocomplete.component'; import { ValidationClassDirective } from './validation/validation-class.directive'; import { InputDefaultsDirective } from './directive/input-defaults.directive'; import { ValidFormIconComponent } from './component/valid-form-icon.component'; -import { InfoLabelDirective } from './directive/info-label.directive'; import { PrettyXml } from './pipe/pretty-xml.pipe'; import { ToggleSwitchComponent } from './switch/switch.component'; import { ContenteditableDirective } from './contenteditable/contenteditable.directive'; @@ -31,7 +30,6 @@ import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; ValidationClassDirective, InputDefaultsDirective, ValidFormIconComponent, - InfoLabelDirective, PrettyXml, ReplacePipe, CustomDatePipe, @@ -48,7 +46,6 @@ import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; InputDefaultsDirective, ValidFormIconComponent, ValidationClassDirective, - InfoLabelDirective, ContenteditableDirective, ReplacePipe, CustomDatePipe, From e1278e91400c5c61030e9f3015152552d07b11c2 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Thu, 18 Oct 2018 10:46:40 -0400 Subject: [PATCH 05/11] SHIBUI-943: Implement AuditorAware SPI --- .../configuration/auto/WebSecurityConfig.java | 8 ++++++ .../ui/security/DefaultAuditorAware.java | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/DefaultAuditorAware.java diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java index 2e334f75f..f824ca8a5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration.auto; +import edu.internet2.tier.shibboleth.admin.ui.security.DefaultAuditorAware; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -7,6 +8,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.data.domain.AuditorAware; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; @@ -77,6 +79,12 @@ public void configure(WebSecurity web) throws Exception { }; } + @Bean + @Profile("!no-auth") + public AuditorAware defaultAuditorAware() { + return new DefaultAuditorAware(); + } + @Bean @Profile("no-auth") public WebSecurityConfigurerAdapter noAuthUsedForEaseDevelopment() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/DefaultAuditorAware.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/DefaultAuditorAware.java new file mode 100644 index 000000000..080b4312d --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/DefaultAuditorAware.java @@ -0,0 +1,27 @@ +package edu.internet2.tier.shibboleth.admin.ui.security; + +import org.springframework.data.domain.AuditorAware; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; + +import java.util.Optional; + +/** + * Default implementation of Spring Data's AuditorAware SPI to let Spring Data + * plug in authenticated principal's id to @CreatedBy and @LastModifiedBy + * fields of Auditable entities. + * + * @author Dmitriy Kopylenko + */ +public class DefaultAuditorAware implements AuditorAware { + + @Override + public Optional getCurrentAuditor() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !authentication.isAuthenticated()) { + return Optional.empty(); + } + return Optional.of(User.class.cast(authentication.getPrincipal()).getUsername()); + } +} From 41c10c5cee60ec628eb496a533c5d9ddfb37a9f3 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 22 Oct 2018 09:43:18 -0700 Subject: [PATCH 06/11] SHIBUI-921 Added es7 polyfills for array/object methods --- ui/src/polyfills.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/polyfills.ts b/ui/src/polyfills.ts index 98fd84126..083808299 100644 --- a/ui/src/polyfills.ts +++ b/ui/src/polyfills.ts @@ -40,7 +40,8 @@ import 'core-js/es6/set'; /** Evergreen browsers require these. **/ import 'core-js/es6/reflect'; import 'core-js/es7/reflect'; - +import 'core-js/es7/array'; +import 'core-js/es7/object'; /** * Required to support Web Animations `@angular/animation`. From 6b177d859564adbb3ee98c8c81018d684b572d96 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 22 Oct 2018 10:51:24 -0700 Subject: [PATCH 07/11] SHIBUI-918 Implemented fix for disabled select boxes in safari --- ui/src/app/schema-form/widget/select/select.component.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/app/schema-form/widget/select/select.component.html b/ui/src/app/schema-form/widget/select/select.component.html index 819a89553..c5e3557fa 100644 --- a/ui/src/app/schema-form/widget/select/select.component.html +++ b/ui/src/app/schema-form/widget/select/select.component.html @@ -15,8 +15,9 @@ [formControl]="control" [attr.name]="name" class="form-control" - [attr.aria-label]="schema.title"> - From b8104892dd3728ef5e9130a55b14646a2ac74886 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Mon, 22 Oct 2018 11:27:11 -0400 Subject: [PATCH 08/11] Refactor tests based on code review --- ...tadataSourcesUiDefinitionController.groovy | 11 +- ...hemaValidationComponentsConfiguration.java | 13 +- ...dataSourcesJsonSchemaResourceLocation.java | 35 +- .../main/resources/i18n/messages.properties | 4 - .../resources/i18n/messages_en.properties | 4 - ...efinitionControllerIntegrationTests.groovy | 49 ++ ...efinitionControllerIntegrationTests.groovy | 29 ++ ...efinitionControllerIntegrationTests.groovy | 74 --- .../metadata-sources-ui-schema_MALFORMED.json | 443 +----------------- ui/src/app/app.component.html | 8 +- 10 files changed, 116 insertions(+), 554 deletions(-) create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy delete mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionControllerIntegrationTests.groovy diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionController.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionController.groovy index 9d54e5ac0..aeedd07e7 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionController.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionController.groovy @@ -38,15 +38,10 @@ class MetadataSourcesUiDefinitionController { ResponseEntity getUiDefinitionJsonSchema() { try { def parsedJson = jacksonObjectMapper.readValue(this.jsonSchemaLocation.url, Map) - def widget = parsedJson["properties"]["attributeRelease"]["widget"] - def data = [] - customAttributesConfiguration.getAttributes().each { - def attribute = [:] - attribute["key"] = it["name"] - attribute["label"] = it["displayName"] - data << attribute + parsedJson['properties']['attributeRelease']['widget']['data'] = + customAttributesConfiguration.getAttributes().collect { + [key: it['name'], label: it['displayName']] } - widget["data"] = data return ResponseEntity.ok(parsedJson) } catch (Exception e) { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaValidationComponentsConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaValidationComponentsConfiguration.java index 7e983edeb..48fb33ede 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaValidationComponentsConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaValidationComponentsConfiguration.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import edu.internet2.tier.shibboleth.admin.ui.jsonschema.MetadataSourcesJsonSchemaResourceLocation; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; @@ -10,10 +11,20 @@ * @author Dmitriy Kopylenko */ @Configuration +@ConfigurationProperties("shibui") public class JsonSchemaValidationComponentsConfiguration { + //Configured via @ConfigurationProperties (using setter method) with 'shibui.metadata-sources-ui-schema-location' property and default + //value set here if that property is not explicitly set in application.properties + private String metadataSourcesUiSchemaLocation ="classpath:metadata-sources-ui-schema.json"; + + //This setter is used by Boot's @ConfiguratonProperties binding machinery + public void setMetadataSourcesUiSchemaLocation(String metadataSourcesUiSchemaLocation) { + this.metadataSourcesUiSchemaLocation = metadataSourcesUiSchemaLocation; + } + @Bean public MetadataSourcesJsonSchemaResourceLocation metadataSourcesJsonSchemaResourceLocation(ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { - return new MetadataSourcesJsonSchemaResourceLocation(resourceLoader, jacksonMapper); + return new MetadataSourcesJsonSchemaResourceLocation(metadataSourcesUiSchemaLocation, resourceLoader, jacksonMapper); } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataSourcesJsonSchemaResourceLocation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataSourcesJsonSchemaResourceLocation.java index d02b09ff0..98d53cd39 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataSourcesJsonSchemaResourceLocation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataSourcesJsonSchemaResourceLocation.java @@ -1,16 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.jsonschema; import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanInitializationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.core.io.ResourceLoader; -import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -21,28 +15,33 @@ * * @author Dmitriy Kopylenko */ -@ConfigurationProperties("shibui") public class MetadataSourcesJsonSchemaResourceLocation { - //Configured via @ConfigurationProperties with 'shibui.metadata-sources-ui-schema-location' property and default - //value set here if that property is not explicitly set in application.properties - private String metadataSourcesUiSchemaLocation = "classpath:metadata-sources-ui-schema.json"; + private final String metadataSourcesUiSchemaLocation; private URL jsonSchemaUrl; - private ResourceLoader resourceLoader; + private final ResourceLoader resourceLoader; - private ObjectMapper jacksonMapper; + private final ObjectMapper jacksonMapper; + private boolean detectMalformedJsonDuringInit = true; - - public MetadataSourcesJsonSchemaResourceLocation(ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { + public MetadataSourcesJsonSchemaResourceLocation(String metadataSourcesUiSchemaLocation, ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { + this.metadataSourcesUiSchemaLocation = metadataSourcesUiSchemaLocation; this.resourceLoader = resourceLoader; this.jacksonMapper = jacksonMapper; } - public void setMetadataSourcesUiSchemaLocation(String metadataSourcesUiSchemaLocation) { + //This constructor is used in tests + public MetadataSourcesJsonSchemaResourceLocation(String metadataSourcesUiSchemaLocation, + ResourceLoader resourceLoader, + ObjectMapper jacksonMapper, + boolean detectMalformedJsonDuringInit) { this.metadataSourcesUiSchemaLocation = metadataSourcesUiSchemaLocation; + this.resourceLoader = resourceLoader; + this.jacksonMapper = jacksonMapper; + this.detectMalformedJsonDuringInit = detectMalformedJsonDuringInit; } public URL getUrl() { @@ -62,8 +61,10 @@ public URI getUri() { public void init() { try { this.jsonSchemaUrl = this.resourceLoader.getResource(this.metadataSourcesUiSchemaLocation).getURL(); - //Detect malformed JSON schema early, during application start up and fail fast with useful exception message - this.jacksonMapper.readValue(this.jsonSchemaUrl, Map.class); + if(this.detectMalformedJsonDuringInit) { + //Detect malformed JSON schema early, during application start up and fail fast with useful exception message + this.jacksonMapper.readValue(this.jsonSchemaUrl, Map.class); + } } catch (Exception ex) { StringBuilder msg = diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 2661b1fae..83b644910 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -89,10 +89,6 @@ brand.footer.links-label-4=Mailing List brand.footer.links-desc-4=Shibboleth.net open-source community mailing list brand.footer.copyright=Copyright \u00A9 Internet2 -brand.unicon=Unicon -brand.unicon-logo=Unicon Logo -brand.i2=Internet 2 -brand.i2-logo=Internet 2 Logo brand.in-partnership-with=In partnership with brand.and=and diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 2661b1fae..83b644910 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -89,10 +89,6 @@ brand.footer.links-label-4=Mailing List brand.footer.links-desc-4=Shibboleth.net open-source community mailing list brand.footer.copyright=Copyright \u00A9 Internet2 -brand.unicon=Unicon -brand.unicon-logo=Unicon Logo -brand.i2=Internet 2 -brand.i2-logo=Internet 2 Logo brand.in-partnership-with=In partnership with brand.and=and diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy new file mode 100644 index 000000000..f8619f012 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -0,0 +1,49 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.MetadataSourcesJsonSchemaResourceLocation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.context.annotation.Bean +import org.springframework.core.io.ResourceLoader +import org.springframework.test.context.ActiveProfiles +import spock.lang.Specification + +/** + * @author Dmitriy Kopylenko + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("no-auth") +class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Specification { + + @Autowired + private TestRestTemplate restTemplate + + @Autowired + MetadataSourcesJsonSchemaResourceLocation schemaLocation + + static RESOURCE_URI = '/api/ui/MetadataSources' + + def "GET Malformed Metadata Sources UI definition schema"() { + when: 'GET request is made for malformed metadata source UI definition schema' + def result = this.restTemplate.getForEntity(RESOURCE_URI, Object) + + then: "Request results in HTTP 500" + result.statusCodeValue == 500 + result.body.jsonParseError + result.body.sourceUiSchemaDefinitionFile + } + + @TestConfiguration + static class Config { + @Bean + MetadataSourcesJsonSchemaResourceLocation metadataSourcesJsonSchemaResourceLocation(ResourceLoader resourceLoader, + ObjectMapper jacksonMapper) { + + new MetadataSourcesJsonSchemaResourceLocation('classpath:metadata-sources-ui-schema_MALFORMED.json', + resourceLoader, jacksonMapper, false) + } + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy new file mode 100644 index 000000000..c673956fe --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -0,0 +1,29 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.test.context.ActiveProfiles +import spock.lang.Specification + +/** + * @author Dmitriy Kopylenko + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("no-auth") +class GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Specification { + + @Autowired + private TestRestTemplate restTemplate + + static RESOURCE_URI = '/api/ui/MetadataSources' + + def "GET Metadata Sources UI definition schema"() { + when: 'GET request is made for metadata source UI definition schema' + def result = this.restTemplate.getForEntity(RESOURCE_URI, Object) + + then: "Request completed successfully" + result.statusCodeValue == 200 + result.body.properties.entityId.title == 'label.entity-id' + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionControllerIntegrationTests.groovy deleted file mode 100644 index 240a08b77..000000000 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionControllerIntegrationTests.groovy +++ /dev/null @@ -1,74 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.controller - -import edu.internet2.tier.shibboleth.admin.ui.jsonschema.MetadataSourcesJsonSchemaResourceLocation -import org.springframework.beans.factory.BeanInitializationException -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.web.client.TestRestTemplate -import org.springframework.test.context.ActiveProfiles -import spock.lang.Specification - -/** - * @author Dmitriy Kopylenko - */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("no-auth") -class MetadataSourcesUiDefinitionControllerIntegrationTests extends Specification { - - @Autowired - private TestRestTemplate restTemplate - - @Autowired - MetadataSourcesJsonSchemaResourceLocation schemaLocation - - static RESOURCE_URI = '/api/ui/MetadataSources' - - def "GET Metadata Sources UI definition schema"() { - when: 'GET request is made for metadata source UI definition schema' - def result = this.restTemplate.getForEntity(RESOURCE_URI, Object) - - then: "Request completed successfully" - result.statusCodeValue == 200 - result.body.properties.entityId.title == 'label.entity-id' - } - - def "GET Malformed Metadata Sources UI definition schema"() { - when: 'GET request is made for malformed metadata source UI definition schema' - configureMalformedJsonInput(simulateApplicationStartup { false }) - def result = this.restTemplate.getForEntity(RESOURCE_URI, Object) - - then: "Request results in HTTP 500" - result.statusCodeValue == 500 - result.body.jsonParseError - result.body.sourceUiSchemaDefinitionFile - } - - def "Malformed Metadata Sources UI definition schema is detected during application start up"() { - when: 'Application is starting up and malformed JSON schema is detected' - configureMalformedJsonInput(simulateApplicationStartup { true }) - - then: - def ex = thrown(BeanInitializationException) - ex.message.contains('An error is detected during JSON parsing =>') - ex.message.contains('Offending resource =>') - - } - - private configureMalformedJsonInput(boolean simulateApplicationStartup) { - schemaLocation.metadataSourcesUiSchemaLocation = 'classpath:metadata-sources-ui-schema_MALFORMED.json' - try { - schemaLocation.init() - } - catch (Exception e) { - if (simulateApplicationStartup) { - throw e - } - } - - } - - //Just for the nicer, readable, DSL-like - private static boolean simulateApplicationStartup(Closure booleanFlagSupplier) { - booleanFlagSupplier() - } -} \ No newline at end of file diff --git a/backend/src/test/resources/metadata-sources-ui-schema_MALFORMED.json b/backend/src/test/resources/metadata-sources-ui-schema_MALFORMED.json index 7ca5220f0..cef1eb4d9 100644 --- a/backend/src/test/resources/metadata-sources-ui-schema_MALFORMED.json +++ b/backend/src/test/resources/metadata-sources-ui-schema_MALFORMED.json @@ -1,442 +1 @@ -{ - "type": "object" - "properties": { - "entityId": { - "title": "label.entity-id", - "description": "tooltip.entity-id", - "type": "string" - }, - "serviceProviderName": { - "title": "label.service-provider-name", - "description": "tooltip.service-provider-name", - "type": "string" - }, - "serviceEnabled": { - "title": "label.enable-this-service-opon-saving", - "description": "tooltip.enable-this-service-upon-saving", - "type": "boolean" - }, - "organization": { - "type": "object", - "properties": { - "name": { - "title": "label.organization-name", - "description": "tooltip.organization-name", - "type": "string" - }, - "displayName": { - "title": "label.organization-display-name", - "description": "tooltip.organization-display-name", - "type": "string" - }, - "url": { - "title": "label.organization-display-name", - "description": "tooltip.organization-display-name", - "type": "string" - } - }, - "dependencies": { - "name": [ - "displayName", - "url" - ], - "displayName": [ - "name", - "url" - ], - "url": [ - "name", - "displayName" - ] - } - }, - "contacts": { - "title": "label.contact-information", - "description": "tooltip.contact-information", - "type": "array", - "items": { - "$ref": "#/definitions/Contact" - } - }, - "mdui": { - "type": "object", - "properties": { - "displayName": { - "title": "label.display-name", - "description": "tooltip.mdui-display-name", - "type": "string" - }, - "informationUrl": { - "title": "label.information-url", - "description": "tooltip.mdui-information-url", - "type": "string" - }, - "privacyStatementUrl": { - "title": "label.privacy-statement-url", - "description": "tooltip.mdui-privacy-statement-url", - "type": "string" - }, - "description": { - "title": "label.description", - "description": "tooltip.mdui-description", - "type": "string" - }, - "logoUrl": { - "title": "label.logo-url", - "description": "tooltip.mdui-logo-url", - "type": "string" - }, - "logoHeight": { - "title": "label.logo-height", - "description": "tooltip.mdui-logo-height", - "min": 0, - "type": "integer" - }, - "logoWidth": { - "title": "label.logo-width", - "description": "tooltip.mdui-logo-width", - "min": 0, - "type": "integer" - } - } - }, - "securityInfo": { - "type": "object", - "properties": { - "x509CertificateAvailable": { - "title": "label.is-there-a-x509-certificate", - "description": "tooltip.is-there-a-x509-certificate", - "type": "boolean", - "default": false - }, - "authenticationRequestsSigned": { - "title": "label.authentication-requests-signed", - "description": "tooltip.authentication-requests-signed", - "type": "boolean", - "default": false - }, - "wantAssertionsSigned": { - "title": "label.want-assertions-signed", - "description": "tooltip.want-assertions-signed", - "type": "boolean", - "default": false - }, - "x509Certificates": { - "title": "label.x509-certificates", - "type": "array", - "items": { - "$ref": "#/definitions/Certificate" - } - } - } - }, - "assertionConsumerServices": { - "title": "label.assertion-consumer-service-endpoints", - "description": "", - "type": "array", - "items": { - "$ref": "#/definitions/AssertionConsumerService" - } - }, - "serviceProviderSsoDescriptor": { - "type": "object", - "properties": { - "protocolSupportEnum": { - "title": "label.protocol-support-enumeration", - "description": "tooltip.protocol-support-enumeration", - "type": "string", - "placeholder": "label.select-protocol", - "oneOf": [ - { - "enum": [ - "SAML 2" - ], - "description": "SAML 2" - }, - { - "enum": [ - "SAML 1.1" - ], - "description": "SAML 1.1" - } - ] - } - }, - "nameIdFormats": { - "$ref": "#/definitions/NameIdFormatList" - } - }, - "logoutEndpoints": { - "title": "label.logout-endpoints", - "description": "tooltip.logout-endpoints", - "type": "array", - "items": { - "$ref": "#/definitions/LogoutEndpoint" - } - }, - "relyingPartyOverrides": { - "type": "object", - "properties": { - "signAssertion": { - "title": "label.sign-the-assertion", - "description": "tooltip.sign-assertion", - "type": "boolean", - "default": false - }, - "dontSignResponse": { - "title": "label.dont-sign-the-response", - "description": "tooltip.dont-sign-response", - "type": "boolean", - "default": false - }, - "turnOffEncryption": { - "title": "label.turn-off-encryption-of-response", - "description": "tooltip.turn-off-encryption", - "type": "boolean", - "default": false - }, - "useSha": { - "title": "label.use-sha1-signing-algorithm", - "description": "tooltip.usa-sha-algorithm", - "type": "boolean", - "default": false - }, - "ignoreAuthenticationMethod": { - "title": "label.ignore-any-sp-requested-authentication-method", - "description": "tooltip.ignore-auth-method", - "type": "boolean", - "default": false - }, - "forceAuthn": { - "title": "label.force-authn", - "description": "tooltip.force-authn", - "type": "boolean", - "default": false - }, - "omitNotBefore": { - "title": "label.omit-not-before-condition", - "type": "boolean", - "description": "tooltip.omit-not-before-condition", - "default": false - }, - "responderId": { - "title": "label.responder-id", - "description": "tooltip.responder-id", - "type": "string" - }, - "nameIdFormats": { - "$ref": "#/definitions/NameIdFormatList" - }, - "authenticationMethods": { - "$ref": "#/definitions/AuthenticationMethodList" - } - } - }, - "attributeRelease": { - "type": "array", - "description": "Attribute release table - select the attributes you want to release (default unchecked)", - "widget": { - "id": "checklist", - "dataUrl": "/customAttributes" - }, - "items": { - "type": "string" - } - } - }, - "definitions": { - "Contact": { - "type": "object", - "properties": { - "name": { - "title": "label.contact-name", - "description": "tooltip.contact-name", - "type": "string" - }, - "type": { - "title": "label.contact-type", - "description": "tooltip.contact-type", - "type": "string", - "oneOf": [ - { - "enum": [ - "support" - ], - "description": "value.support" - }, - { - "enum": [ - "technical" - ], - "description": "value.technical" - }, - { - "enum": [ - "administrative" - ], - "description": "value.administrative" - }, - { - "enum": [ - "other" - ], - "description": "value.other" - } - ] - }, - "emailAddress": { - "title": "label.contact-email-address", - "description": "tooltip.contact-email", - "type": "string", - "pattern": "^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$" - } - } - }, - "Certificate": { - "name": { - "title": "label.certificate-name-display-only", - "description": "tooltip.certificate-name", - "type": "string" - }, - "type": { - "title": "label.type", - "description": "tooltip.certificate-type", - "type": "string", - "oneOf": [ - { - "enum": [ - "signing" - ], - "description": "value.signing" - }, - { - "enum": [ - "encryption" - ], - "description": "value.encryption" - }, - { - "enum": [ - "both" - ], - "description": "value.both" - } - ], - "default": "both" - }, - "value": { - "title": "label.certificate", - "description": "tooltip.certificate", - "type": "string" - } - }, - "AssertionConsumerService": { - "type": "object", - "properties": { - "locationUrl": { - "title": "label.assertion-consumer-services-location", - "description": "tooltip.assertion-consumer-service-location", - "type": "string", - "widget": { - "id": "string", - "help": "message.valid-url" - } - }, - "binding": { - "title": "label.assertion-consumer-service-location-binding", - "description": "tooltip.assertion-consumer-service-location-binding", - "type": "string", - "oneOf": [ - { - "enum": [ - "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - ], - "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - }, - { - "enum": [ - "urn:oasis:names:tc:SAML:1.0:profiles:browser-post" - ], - "description": "urn:oasis:names:tc:SAML:1.0:profiles:browser-post" - } - ] - }, - "makeDefault": { - "title": "label.mark-as-default", - "description": "tooltip.mark-as-default", - "type": "boolean" - } - } - }, - "NameIdFormatList": { - "title": "label.nameid-format-to-send", - "placeholder": "label.nameid-format", - "description": "tooltip.nameid-format", - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "widget": "datalist", - "data": [ - "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", - "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" - ] - }, - "default": null - }, - "AuthenticationMethodList": { - "title": "label.authentication-methods-to-use", - "description": "tooltip.authentication-methods-to-use", - "type": "array", - "placeholder": "label.authentication-method", - "uniqueItems": true, - "items": { - "type": "string", - "title": "label.authentication-method", - "widget": { - "id": "datalist", - "data": [ - "https://refeds.org/profile/mfa", - "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken", - "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" - ] - } - }, - "default": null - }, - "LogoutEndpoint": { - "title": "label.new-endpoint", - "description": "tooltip.new-endpoint", - "type": "object", - "properties": { - "url": { - "title": "label.url", - "description": "tooltip.url", - "type": "string" - }, - "bindingType": { - "title": "label.binding-type", - "description": "tooltip.binding-type", - "type": "string", - "oneOf": [ - { - "enum": [ - "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - ], - "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - }, - { - "enum": [ - "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - ], - "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - } - ] - - } - } - } - } -} +This is invalid JSON \ No newline at end of file diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index cd56856e3..f09ae298a 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -85,12 +85,12 @@ From 4f259deb533834684675c1b8b42437d3a29f70ab Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Mon, 22 Oct 2018 15:16:40 -0400 Subject: [PATCH 09/11] Polishing --- ...etadataSourcesUiDefinitionControllerIntegrationTests.groovy | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy index f8619f012..9b6a5df54 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -21,9 +21,6 @@ class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Speci @Autowired private TestRestTemplate restTemplate - @Autowired - MetadataSourcesJsonSchemaResourceLocation schemaLocation - static RESOURCE_URI = '/api/ui/MetadataSources' def "GET Malformed Metadata Sources UI definition schema"() { From 9edc46cf6d2c54831e33cacbff6b6f06bb3f29a6 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 22 Oct 2018 13:36:40 -0700 Subject: [PATCH 10/11] SHIBUI-888 Fixed label issue --- .../app/schema-form/widget/select/select.component.html | 8 ++++++-- ui/src/app/shared/pipe/date.pipe.ts | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ui/src/app/schema-form/widget/select/select.component.html b/ui/src/app/schema-form/widget/select/select.component.html index 819a89553..bcd96bcd6 100644 --- a/ui/src/app/schema-form/widget/select/select.component.html +++ b/ui/src/app/schema-form/widget/select/select.component.html @@ -1,9 +1,12 @@
-