From 3b6de6ba269a5005f40f310e2dfae683cafe1780 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 18 Sep 2018 14:53:37 -0700 Subject: [PATCH 1/7] [SHIBUI-812] Added application.yml which contains default filter attributes as well as a placeholder for new custom attributes. Added CustomAttributesConfiguration and supporting code to allow for reading from the application.yml. Updated ModelRepresentationConversions to support multiple release attributes. Updated unit tests to attempt to fix the negative refresh delay issue. Again. Added spring-boot-configuration-processor to backend build to support @ConfigurationProperties. --- backend/build.gradle | 6 +++- .../admin/ui/ShibbolethUiApplication.java | 1 + .../CoreShibUiConfiguration.java | 7 +++++ .../CustomAttributesConfiguration.java | 26 +++++++++++++++++ .../controller/ConfigurationController.java | 24 ++++++++++++++++ .../util/ModelRepresentationConversions.java | 11 +++----- backend/src/main/resources/application.yml | 28 +++++++++++++++++++ .../admin/ui/util/TestObjectGenerator.groovy | 2 ++ backend/src/test/resources/conf/278.2.xml | 4 ++- backend/src/test/resources/conf/278.xml | 4 ++- backend/src/test/resources/conf/532.xml | 4 ++- 11 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java create mode 100644 backend/src/main/resources/application.yml diff --git a/backend/build.gradle b/backend/build.gradle index 167bd3994..e8ec167ce 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -112,6 +112,10 @@ dependencies { //JSON schema generator testCompile 'com.kjetland:mbknor-jackson-jsonschema_2.12:1.0.29' testCompile 'javax.validation:validation-api:2.0.1.Final' + + //Configuration Annotation Processor + //This could go in the spring boot section above, but I wasn't sure about the compileOnly vs compile + compileOnly "org.springframework.boot:spring-boot-configuration-processor" } def generatedSrcDir = new File(buildDir, 'generated/src/main/java') @@ -217,4 +221,4 @@ docker { noCache true files tasks.bootWar.outputs buildArgs(['JAR_FILE': 'shibui.war']) -} \ No newline at end of file +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java index 1fc74cf9d..1a55dea6f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui; +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomAttributesConfiguration; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index c9b6493fa..8e87d9434 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; @@ -41,6 +42,7 @@ import javax.servlet.http.HttpServletRequest; @Configuration +@EnableConfigurationProperties(CustomAttributesConfiguration.class) public class CoreShibUiConfiguration { private static final Logger logger = LoggerFactory.getLogger(CoreShibUiConfiguration.class); @@ -168,4 +170,9 @@ public DirectoryService directoryService() { public LuceneUtility luceneUtility(DirectoryService directoryService) { return new LuceneUtility(directoryService); } + + @Bean + public CustomAttributesConfiguration customAttributesConfiguration() { + return new CustomAttributesConfiguration(); + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java new file mode 100644 index 000000000..de15bd815 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java @@ -0,0 +1,26 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Configuration +@ConfigurationProperties(prefix="custom") +public class CustomAttributesConfiguration { + + private List> attributes = new ArrayList<>(); + + public List> getAttributes() { + return attributes; + } + + public void setAttributes(List> attributes) { + this.attributes = attributes; + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java new file mode 100644 index 000000000..4a0388428 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java @@ -0,0 +1,24 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomAttributesConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Controller +@RequestMapping(value = "/api") +public class ConfigurationController { + + @Autowired + CustomAttributesConfiguration customAttributesConfiguration; + + @GetMapping(value = "/customAttributes") + public ResponseEntity getCustomAttributes() { + return ResponseEntity.ok(customAttributesConfiguration.getAttributes()); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java index 27fd92445..e68225b5a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java @@ -47,14 +47,11 @@ public static List getAttributeReleaseListFromAttributeList(List attribute.getName().equals(MDDCConstants.RELEASE_ATTRIBUTES)) .collect(Collectors.toList()); - if (releaseAttributes.size() != 1) { - // TODO: What do we do if there is more than one? - } - if (releaseAttributes.size() == 0) { - return new ArrayList<>(); - } else { - return getStringListOfAttributeValues(releaseAttributes.get(0).getAttributeValues()); + List attributeValues = new ArrayList<>(); + for (Attribute attribute : releaseAttributes) { + attributeValues.addAll(getStringListOfAttributeValues(attribute.getAttributeValues())); } + return attributeValues; } public static boolean getBooleanValueOfAttribute(Attribute attribute) { diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 000000000..b7ee1eabd --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,28 @@ +custom: + attributes: + # Default attributes + - name: eduPersonPrincipalName + displayName: eduPersonPrincipalName (EPPN) + - name: uid + displayName: uid + - name: mail + displayName: mail + - name: surname + displayName: surname + - name: givenName + displayName: givenName + - name: eduPersonAffiliation + displayName: eduPersonAffiliation + - name: eduPersonScopedAffiliation + displayName: eduPersonScopedAffiliation + - name: eduPersonPrimaryAffiliation + displayName: eduPersonPrimaryAffiliation + - name: eduPersonEntitlement + displayName: eduPersonEntitlement + - name: eduPersonAssurance + displayName: eduPersonAssurance + - name: eduPersonUniqueId + displayName: eduPersonUniqueId + - name: employeeNumber + displayName: employeeNumber + # Custom attributes diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy index 13dc8a554..8cace3792 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy @@ -475,6 +475,8 @@ class TestObjectGenerator { it.metadataURL = 'https://idp.unicon.net/idp/shibboleth' it.reloadableMetadataResolverAttributes = new ReloadableMetadataResolverAttributes().with { + it.minRefreshDelay = 'PT0M' + it.maxRefreshDelay = 'P1D' it } it diff --git a/backend/src/test/resources/conf/278.2.xml b/backend/src/test/resources/conf/278.2.xml index 5a34f756b..200e8ab9f 100644 --- a/backend/src/test/resources/conf/278.2.xml +++ b/backend/src/test/resources/conf/278.2.xml @@ -39,7 +39,9 @@ + metadataURL="https://idp.unicon.net/idp/shibboleth" + minRefreshDelay='PT0M' + maxRefreshDelay='P1D'> diff --git a/backend/src/test/resources/conf/278.xml b/backend/src/test/resources/conf/278.xml index a337f72d7..deec8e9bf 100644 --- a/backend/src/test/resources/conf/278.xml +++ b/backend/src/test/resources/conf/278.xml @@ -32,7 +32,9 @@ + metadataURL="https://idp.unicon.net/idp/shibboleth" + minRefreshDelay='PT0M' + maxRefreshDelay='P1D'> diff --git a/backend/src/test/resources/conf/532.xml b/backend/src/test/resources/conf/532.xml index 85fead908..360a4c832 100644 --- a/backend/src/test/resources/conf/532.xml +++ b/backend/src/test/resources/conf/532.xml @@ -8,5 +8,7 @@ + metadataURL="https://idp.unicon.net/idp/shibboleth" + minRefreshDelay='PT0M' + maxRefreshDelay='P1D' /> From 739924a29fdfff35a56708548fbdb7527c2b2754 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 19 Sep 2018 11:16:45 -0700 Subject: [PATCH 2/7] [SHIBUI-812] Replaced usage of HashMap in declaration with generic. --- .../ui/configuration/CustomAttributesConfiguration.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java index de15bd815..aa12be6b2 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java @@ -4,8 +4,8 @@ import org.springframework.context.annotation.Configuration; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Bill Smith (wsmith@unicon.net) @@ -14,13 +14,13 @@ @ConfigurationProperties(prefix="custom") public class CustomAttributesConfiguration { - private List> attributes = new ArrayList<>(); + private List> attributes = new ArrayList<>(); - public List> getAttributes() { + public List> getAttributes() { return attributes; } - public void setAttributes(List> attributes) { + public void setAttributes(List> attributes) { this.attributes = attributes; } } From b2d9580e981b2f04c033f25debd5d773eb3cdbd7 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 19 Sep 2018 11:22:44 -0700 Subject: [PATCH 3/7] [SHIBUI-812] Just a little gradle cleanup. --- backend/build.gradle | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/build.gradle b/backend/build.gradle index e8ec167ce..0eb96b365 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -77,6 +77,9 @@ dependencies { // TODO: figure out what this should really be runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' + //Spring Configuration Annotation Processor - makes IntelliJ happy about @ConfigurationProperties + compileOnly "org.springframework.boot:spring-boot-configuration-processor" + // lucene deps ['core', 'analyzers-common', 'queryparser'].each { compile "org.apache.lucene:lucene-${it}:${project.'lucene.version'}" @@ -112,10 +115,6 @@ dependencies { //JSON schema generator testCompile 'com.kjetland:mbknor-jackson-jsonschema_2.12:1.0.29' testCompile 'javax.validation:validation-api:2.0.1.Final' - - //Configuration Annotation Processor - //This could go in the spring boot section above, but I wasn't sure about the compileOnly vs compile - compileOnly "org.springframework.boot:spring-boot-configuration-processor" } def generatedSrcDir = new File(buildDir, 'generated/src/main/java') From 9651c732bfca7c3898792b47b8b37b48a8a7ec17 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 20 Sep 2018 10:02:34 -0700 Subject: [PATCH 4/7] [SHIBUI-812] Removed unused import. --- .../tier/shibboleth/admin/ui/ShibbolethUiApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java index 1a55dea6f..1fc74cf9d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java @@ -1,6 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui; -import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomAttributesConfiguration; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; From 56bf45c629bd207d931c2cffd6df0ec396b62918 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 24 Sep 2018 08:57:39 -0700 Subject: [PATCH 5/7] SHIBUI-812 Implemented dynamic attribute release list --- ui/src/app/metadata/domain/domain.module.ts | 4 +++- .../domain/service/list-values.service.ts | 24 +++++++------------ .../widget/check/checklist.component.html | 10 ++++---- .../widget/check/checklist.component.ts | 12 ++++++++++ .../filter/entity-attributes.schema.json | 16 +------------ 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/ui/src/app/metadata/domain/domain.module.ts b/ui/src/app/metadata/domain/domain.module.ts index c51bbb260..43ceee433 100644 --- a/ui/src/app/metadata/domain/domain.module.ts +++ b/ui/src/app/metadata/domain/domain.module.ts @@ -13,6 +13,7 @@ import { MetadataProviderService } from './service/provider.service'; import { EntityEffects } from './effect/entity.effect'; import { PreviewDialogComponent } from './component/preview-dialog.component'; import { MetadataFilterService } from './service/filter.service'; +import { AttributesService } from './service/attributes.service'; export const COMPONENTS = [ PreviewDialogComponent @@ -44,7 +45,8 @@ export class DomainModule { ProviderStatusEmitter, ProviderValueEmitter, MetadataProviderService, - MetadataFilterService + MetadataFilterService, + AttributesService ] }; } diff --git a/ui/src/app/metadata/domain/service/list-values.service.ts b/ui/src/app/metadata/domain/service/list-values.service.ts index 644a2be7b..dff88ea7e 100644 --- a/ui/src/app/metadata/domain/service/list-values.service.ts +++ b/ui/src/app/metadata/domain/service/list-values.service.ts @@ -2,10 +2,14 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { debounceTime, distinctUntilChanged, combineLatest } from 'rxjs/operators'; +import { AttributesService } from './attributes.service'; +import { ReleaseAttribute } from '../model/properties/release-attribute'; @Injectable() export class ListValuesService { - constructor() {} + constructor( + private attributes: AttributesService + ) {} readonly nameIdFormats: Observable = of([ 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', @@ -20,21 +24,9 @@ export class ListValuesService { 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' ]); - readonly attributesToRelease: Observable<{ key: string, label: string }[]> = of([ - { key: 'eduPersonPrincipalName', label: 'eduPersonPrincipalName (EPPN)' }, - { key: 'uid', label: 'uid' }, - { key: 'mail', label: 'mail' }, - { key: 'surname', label: 'surname' }, - { key: 'givenName', label: 'givenName' }, - { key: 'displayName', label: 'displayName' }, - { key: 'eduPersonAffiliation', label: 'eduPersonAffiliation' }, - { key: 'eduPersonScopedAffiliation', label: 'eduPersonScopedAffiliation' }, - { key: 'eduPersonPrimaryAffiliation', label: 'eduPersonPrimaryAffiliation' }, - { key: 'eduPersonEntitlement', label: 'eduPersonEntitlement' }, - { key: 'eduPersonAssurance', label: 'eduPersonAssurance' }, - { key: 'eduPersonUniqueId', label: 'eduPersonUniqueId' }, - { key: 'employeeNumber', label: 'employeeNumber' } - ]); + get attributesToRelease(): Observable { + return this.attributes.query(); + } searchStringList = (list: Observable): Function => (text$: Observable) => diff --git a/ui/src/app/schema-form/widget/check/checklist.component.html b/ui/src/app/schema-form/widget/check/checklist.component.html index ec85c8459..9a6a7a71e 100644 --- a/ui/src/app/schema-form/widget/check/checklist.component.html +++ b/ui/src/app/schema-form/widget/check/checklist.component.html @@ -8,8 +8,8 @@ - - {{ attr.label }} + + {{ attr.label | translate }}
@@ -18,13 +18,13 @@ (change)="onCheck(attr.key)" [checked]="checked[attr.key]" id="input-{{ i }}" - [attr.name]="attr.label" - [attr.aria-label]="attr.label" + [attr.name]="attr.label | translate" + [attr.aria-label]="attr.label | translate" role="checkbox" aria-checked="false" />
diff --git a/ui/src/app/schema-form/widget/check/checklist.component.ts b/ui/src/app/schema-form/widget/check/checklist.component.ts index aca5b12b5..16dd3224a 100644 --- a/ui/src/app/schema-form/widget/check/checklist.component.ts +++ b/ui/src/app/schema-form/widget/check/checklist.component.ts @@ -1,6 +1,8 @@ import { Component, AfterViewInit } from '@angular/core'; import { ArrayWidget } from 'ngx-schema-form'; +import { AttributesService } from '../../../metadata/domain/service/attributes.service'; +import { Observable, of } from 'rxjs'; /* istanbul ignore next */ @Component({ @@ -10,6 +12,12 @@ import { ArrayWidget } from 'ngx-schema-form'; export class ChecklistComponent extends ArrayWidget implements AfterViewInit { checked: any = {}; + constructor( + private attributes: AttributesService + ) { + super(); + } + ngAfterViewInit(): void { super.ngAfterViewInit(); this.formProperty.value.forEach(val => this.checked[val] = true); @@ -19,6 +27,10 @@ export class ChecklistComponent extends ArrayWidget implements AfterViewInit { this.formProperty.setValue(Object.keys(this.checked), false); } + get data(): Observable<{ key: string, label: string }[]> { + return this.schema.widget.data ? of(this.schema.widget.data) : this.attributes.query(this.schema.widget.dataUrl); + } + onCheck(value) { if (!this.checked[value]) { this.checked[value] = true; diff --git a/ui/src/assets/schema/filter/entity-attributes.schema.json b/ui/src/assets/schema/filter/entity-attributes.schema.json index e3fdc5bde..307756c63 100644 --- a/ui/src/assets/schema/filter/entity-attributes.schema.json +++ b/ui/src/assets/schema/filter/entity-attributes.schema.json @@ -182,21 +182,7 @@ "description": "Attribute release table - select the attributes you want to release (default unchecked)", "widget": { "id": "checklist", - "data": [ - { "key": "eduPersonPrincipalName", "label": "eduPersonPrincipalName (EPPN)" }, - { "key": "uid", "label": "uid" }, - { "key": "mail", "label": "mail" }, - { "key": "surname", "label": "surname" }, - { "key": "givenName", "label": "givenName" }, - { "key": "displayName", "label": "displayName" }, - { "key": "eduPersonAffiliation", "label": "eduPersonAffiliation" }, - { "key": "eduPersonScopedAffiliation", "label": "eduPersonScopedAffiliation" }, - { "key": "eduPersonPrimaryAffiliation", "label": "eduPersonPrimaryAffiliation" }, - { "key": "eduPersonEntitlement", "label": "eduPersonEntitlement" }, - { "key": "eduPersonAssurance", "label": "eduPersonAssurance" }, - { "key": "eduPersonUniqueId", "label": "eduPersonUniqueId" }, - { "key": "employeeNumber", "label": "employeeNumber" } - ] + "dataUrl": "/customAttributes" }, "items": { "type": "string" From e7c339f55394ad3c183ad0ce79db65c077d0983d Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 24 Sep 2018 09:22:44 -0700 Subject: [PATCH 6/7] SHIBUI-812 Integrated attribute release into backend --- backend/src/main/resources/application.yml | 24 ++++++------ .../resources/i18n/messages_en.properties | 15 +++++++- .../resources/i18n/messages_es.properties | 13 +++++++ .../attribute-release-form.component.html | 6 +-- .../model/properties/release-attribute.ts | 4 ++ .../domain/service/attributes.service.ts | 38 +++++++++++++++++++ 6 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 ui/src/app/metadata/domain/model/properties/release-attribute.ts create mode 100644 ui/src/app/metadata/domain/service/attributes.service.ts diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index b7ee1eabd..563d01073 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -2,27 +2,27 @@ custom: attributes: # Default attributes - name: eduPersonPrincipalName - displayName: eduPersonPrincipalName (EPPN) + displayName: label.attribute-eduPersonPrincipalName - name: uid - displayName: uid + displayName: label.attribute-uid - name: mail - displayName: mail + displayName: label.attribute-mail - name: surname - displayName: surname + displayName: label.attribute-surname - name: givenName - displayName: givenName + displayName: label.attribute-givenName - name: eduPersonAffiliation - displayName: eduPersonAffiliation + displayName: label.attribute-eduPersonAffiliation - name: eduPersonScopedAffiliation - displayName: eduPersonScopedAffiliation + displayName: label.attribute-eduPersonScopedAffiliation - name: eduPersonPrimaryAffiliation - displayName: eduPersonPrimaryAffiliation + displayName: label.attribute-eduPersonPrimaryAffiliation - name: eduPersonEntitlement - displayName: eduPersonEntitlement + displayName: label.attribute-eduPersonEntitlement - name: eduPersonAssurance - displayName: eduPersonAssurance + displayName: label.attribute-eduPersonAssurance - name: eduPersonUniqueId - displayName: eduPersonUniqueId + displayName: label.attribute-eduPersonUniqueId - name: employeeNumber - displayName: employeeNumber + displayName: label.attribute-employeeNumber # Custom attributes diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 2c6a755ab..607e2a38b 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -8,7 +8,7 @@ action.clear=Clear action.delete=Delete action.remove=Remove action.save=Save -action.toggle=Toggle { label } +action.toggle=Toggle action.add-contact=Add Contact action.add-contacts=Add Contacts action.use-mine=Use My Changes @@ -284,6 +284,19 @@ label.retained-roles=Retained Roles label.remove-roleless-entity-descriptors=Remove Roleless Entity Descriptors? label.remove-empty-entities-descriptors=Remove Empty Entities Descriptors? +label.attribute-eduPersonPrincipalName=eduPersonPrincipalName (EPPN) +label.attribute-uid=uid +label.attribute-mail=mail +label.attribute-surname=surname +label.attribute-givenName=givenName +label.attribute-eduPersonAffiliation=eduPersonAffiliation +label.attribute-eduPersonScopedAffiliation=eduPersonScopedAffiliation +label.attribute-eduPersonPrimaryAffiliation=eduPersonPrimaryAffiliation +label.attribute-eduPersonEntitlement=eduPersonEntitlement +label.attribute-eduPersonAssurance=eduPersonAssurance +label.attribute-eduPersonUniqueId=eduPersonUniqueId +label.attribute-employeeNumber=employeeNumber + message.must-be-unique=Must be unique. message.conflict=Conflict diff --git a/backend/src/main/resources/i18n/messages_es.properties b/backend/src/main/resources/i18n/messages_es.properties index f90cad6a4..9fa3c547e 100644 --- a/backend/src/main/resources/i18n/messages_es.properties +++ b/backend/src/main/resources/i18n/messages_es.properties @@ -284,6 +284,19 @@ label.retained-roles=(es) Retained Roles label.remove-roleless-entity-descriptors=(es) Remove Roleless Entity Descriptors? label.remove-empty-entities-descriptors=(es) Remove Empty Entities Descriptors? +label.attribute-eduPersonPrincipalName=(es) eduPersonPrincipalName (EPPN) +label.attribute-uid=(es) uid +label.attribute-mail=(es) mail +label.attribute-surname=(es) surname +label.attribute-givenName=(es) givenName +label.attribute-eduPersonAffiliation=(es) eduPersonAffiliation +label.attribute-eduPersonScopedAffiliation=(es) eduPersonScopedAffiliation +label.attribute-eduPersonPrimaryAffiliation=(es) eduPersonPrimaryAffiliation +label.attribute-eduPersonEntitlement=(es) eduPersonEntitlement +label.attribute-eduPersonAssurance=(es) eduPersonAssurance +label.attribute-eduPersonUniqueId=(es) eduPersonUniqueId +label.attribute-employeeNumber=(es) employeeNumber + message.must-be-unique=(es) Must be unique. message.conflict=(es) Conflict diff --git a/ui/src/app/metadata/domain/component/forms/attribute-release-form.component.html b/ui/src/app/metadata/domain/component/forms/attribute-release-form.component.html index f6e70f5c2..9ee740517 100644 --- a/ui/src/app/metadata/domain/component/forms/attribute-release-form.component.html +++ b/ui/src/app/metadata/domain/component/forms/attribute-release-form.component.html @@ -10,7 +10,7 @@ - {{ attr.label }} + {{ attr.label }}
@@ -19,8 +19,8 @@ (change)="onCheck($event, attr.key)" [checked]="isChecked(attr.key)" id="input-{{ i }}" - [attr.name]="attr.label" - [attr.aria-label]="attr.label" + [attr.name]="attr.label | translate" + [attr.aria-label]="attr.label | translate" role="checkbox" aria-checked="false"/> diff --git a/ui/src/app/metadata/domain/model/properties/release-attribute.ts b/ui/src/app/metadata/domain/model/properties/release-attribute.ts new file mode 100644 index 000000000..7edc37b6a --- /dev/null +++ b/ui/src/app/metadata/domain/model/properties/release-attribute.ts @@ -0,0 +1,4 @@ +export interface ReleaseAttribute { + key: string; + label: string; +} diff --git a/ui/src/app/metadata/domain/service/attributes.service.ts b/ui/src/app/metadata/domain/service/attributes.service.ts new file mode 100644 index 000000000..c6b671e01 --- /dev/null +++ b/ui/src/app/metadata/domain/service/attributes.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, shareReplay, map } from 'rxjs/operators'; +import { ReleaseAttribute } from '../model/properties/release-attribute'; + +const CACHE_SIZE = 1; + +@Injectable() +export class AttributesService { + + readonly endpoint = '/customAttributes'; + readonly base = '/api'; + + private cache$: Observable; + + constructor( + private http: HttpClient + ) { } + + query(path: string = this.endpoint): Observable { + if (!this.cache$) { + this.cache$ = this.requestAttributes(path).pipe( + shareReplay(CACHE_SIZE) + ); + } + + return this.cache$; + } + + requestAttributes(path: string): Observable { + return this.http.get(`${this.base}${path}`, {}) + .pipe( + map(attrs => attrs.map((attr: any) => ({ key: attr.name, label: attr.displayName }))), + catchError(err => throwError([])) + ); + } +} From 6b425dece8e2ab9d031e147c53e6b8b7c0c0d46b Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 24 Sep 2018 09:54:46 -0700 Subject: [PATCH 7/7] SHIBUI-812 Fixed tests --- .../attribute-release-form.component.spec.ts | 3 +- .../descriptor-info-form.component.spec.ts | 3 +- .../forms/finish-form.component.spec.ts | 3 +- .../forms/key-info-form.component.spec.ts | 3 +- .../forms/logout-form.component.spec.ts | 3 +- .../forms/metadata-ui-form.component.spec.ts | 3 +- .../organization-info-form.component.spec.ts | 3 +- .../relying-party-form.component.spec.ts | 3 +- .../domain/service/attributes.service.spec.ts | 0 .../service/list-values.service.spec.ts | 3 ++ ui/src/testing/attributes.stub.ts | 16 +++++++ ui/src/testing/list-values.stub.ts | 47 +++++++++++++++++++ 12 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 ui/src/app/metadata/domain/service/attributes.service.spec.ts create mode 100644 ui/src/testing/attributes.stub.ts create mode 100644 ui/src/testing/list-values.stub.ts diff --git a/ui/src/app/metadata/domain/component/forms/attribute-release-form.component.spec.ts b/ui/src/app/metadata/domain/component/forms/attribute-release-form.component.spec.ts index 2c67eae1c..c05e6bad3 100644 --- a/ui/src/app/metadata/domain/component/forms/attribute-release-form.component.spec.ts +++ b/ui/src/app/metadata/domain/component/forms/attribute-release-form.component.spec.ts @@ -8,6 +8,7 @@ import { AttributeReleaseFormComponent } from './attribute-release-form.componen import { ListValuesService } from '../../../domain/service/list-values.service'; import * as stubs from '../../../../../testing/resolver.stub'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; +import { MockListValueService } from '../../../../../testing/list-values.stub'; describe('Attribute Release Form Component', () => { let fixture: ComponentFixture; @@ -19,7 +20,7 @@ describe('Attribute Release Form Component', () => { ProviderValueEmitter, ProviderStatusEmitter, NgbPopoverConfig, - ListValuesService + { provide: ListValuesService, useClass: MockListValueService } ], imports: [ NoopAnimationsModule, diff --git a/ui/src/app/metadata/domain/component/forms/descriptor-info-form.component.spec.ts b/ui/src/app/metadata/domain/component/forms/descriptor-info-form.component.spec.ts index 7527f5e3c..d902432fd 100644 --- a/ui/src/app/metadata/domain/component/forms/descriptor-info-form.component.spec.ts +++ b/ui/src/app/metadata/domain/component/forms/descriptor-info-form.component.spec.ts @@ -11,6 +11,7 @@ import { DescriptorInfoFormComponent } from './descriptor-info-form.component'; import * as stubs from '../../../../../testing/resolver.stub'; import { SharedModule } from '../../../../shared/shared.module'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; +import { MockListValueService } from '../../../../../testing/list-values.stub'; @Component({ template: `` @@ -48,7 +49,7 @@ describe('Descriptor Info Form Component', () => { ProviderValueEmitter, ProviderStatusEmitter, NgbPopoverConfig, - ListValuesService + { provide: ListValuesService, useClass: MockListValueService } ], imports: [ NoopAnimationsModule, diff --git a/ui/src/app/metadata/domain/component/forms/finish-form.component.spec.ts b/ui/src/app/metadata/domain/component/forms/finish-form.component.spec.ts index 0d3ea08a4..882590b2b 100644 --- a/ui/src/app/metadata/domain/component/forms/finish-form.component.spec.ts +++ b/ui/src/app/metadata/domain/component/forms/finish-form.component.spec.ts @@ -14,6 +14,7 @@ import * as stubs from '../../../../../testing/resolver.stub'; import { FileBackedHttpMetadataResolver } from '../../entity'; import { InputDefaultsDirective } from '../../../../shared/directive/input-defaults.directive'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; +import { MockListValueService } from '../../../../../testing/list-values.stub'; @Component({ template: `` @@ -42,7 +43,7 @@ describe('Finished Form Component', () => { ProviderValueEmitter, ProviderStatusEmitter, NgbPopoverConfig, - ListValuesService, + { provide: ListValuesService, useClass: MockListValueService }, { provide: Router, useClass: RouterStub }, { provide: ActivatedRoute, useClass: ActivatedRouteStub } ], diff --git a/ui/src/app/metadata/domain/component/forms/key-info-form.component.spec.ts b/ui/src/app/metadata/domain/component/forms/key-info-form.component.spec.ts index fd6ccd5db..0b6751cc3 100644 --- a/ui/src/app/metadata/domain/component/forms/key-info-form.component.spec.ts +++ b/ui/src/app/metadata/domain/component/forms/key-info-form.component.spec.ts @@ -11,6 +11,7 @@ import * as stubs from '../../../../../testing/resolver.stub'; import { FileBackedHttpMetadataResolver } from '../../entity'; import { InputDefaultsDirective } from '../../../../shared/directive/input-defaults.directive'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; +import { MockListValueService } from '../../../../../testing/list-values.stub'; @Component({ template: `` @@ -43,7 +44,7 @@ describe('Security (Key) Info Form Component', () => { ProviderValueEmitter, ProviderStatusEmitter, NgbPopoverConfig, - ListValuesService + { provide: ListValuesService, useClass: MockListValueService } ], imports: [ NoopAnimationsModule, diff --git a/ui/src/app/metadata/domain/component/forms/logout-form.component.spec.ts b/ui/src/app/metadata/domain/component/forms/logout-form.component.spec.ts index 83f1fafe4..89d8b9b09 100644 --- a/ui/src/app/metadata/domain/component/forms/logout-form.component.spec.ts +++ b/ui/src/app/metadata/domain/component/forms/logout-form.component.spec.ts @@ -12,6 +12,7 @@ import * as stubs from '../../../../../testing/resolver.stub'; import { FileBackedHttpMetadataResolver } from '../../entity'; import { InputDefaultsDirective } from '../../../../shared/directive/input-defaults.directive'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; +import { MockListValueService } from '../../../../../testing/list-values.stub'; @Component({ template: `` @@ -41,7 +42,7 @@ describe('Logout Endpoints Form Component', () => { ProviderValueEmitter, ProviderStatusEmitter, NgbPopoverConfig, - ListValuesService + { provide: ListValuesService, useClass: MockListValueService } ], imports: [ NoopAnimationsModule, diff --git a/ui/src/app/metadata/domain/component/forms/metadata-ui-form.component.spec.ts b/ui/src/app/metadata/domain/component/forms/metadata-ui-form.component.spec.ts index b74dc94db..3c86d3b13 100644 --- a/ui/src/app/metadata/domain/component/forms/metadata-ui-form.component.spec.ts +++ b/ui/src/app/metadata/domain/component/forms/metadata-ui-form.component.spec.ts @@ -11,6 +11,7 @@ import * as stubs from '../../../../../testing/resolver.stub'; import { FileBackedHttpMetadataResolver } from '../../entity'; import { InputDefaultsDirective } from '../../../../shared/directive/input-defaults.directive'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; +import { MockListValueService } from '../../../../../testing/list-values.stub'; @Component({ template: `` @@ -39,7 +40,7 @@ describe('Metadata UI Form Component', () => { ProviderValueEmitter, ProviderStatusEmitter, NgbPopoverConfig, - ListValuesService + { provide: ListValuesService, useClass: MockListValueService } ], imports: [ NoopAnimationsModule, diff --git a/ui/src/app/metadata/domain/component/forms/organization-info-form.component.spec.ts b/ui/src/app/metadata/domain/component/forms/organization-info-form.component.spec.ts index 57708fbad..e5a4d30e4 100644 --- a/ui/src/app/metadata/domain/component/forms/organization-info-form.component.spec.ts +++ b/ui/src/app/metadata/domain/component/forms/organization-info-form.component.spec.ts @@ -7,6 +7,7 @@ import { ListValuesService } from '../../../domain/service/list-values.service'; import { OrganizationInfoFormComponent } from './organization-info-form.component'; import * as stubs from '../../../../../testing/resolver.stub'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; +import { MockListValueService } from '../../../../../testing/list-values.stub'; describe('Organization Info Form Component', () => { let fixture: ComponentFixture; @@ -18,7 +19,7 @@ describe('Organization Info Form Component', () => { ProviderValueEmitter, ProviderStatusEmitter, NgbPopoverConfig, - ListValuesService + { provide: ListValuesService, useClass: MockListValueService } ], imports: [ NoopAnimationsModule, diff --git a/ui/src/app/metadata/domain/component/forms/relying-party-form.component.spec.ts b/ui/src/app/metadata/domain/component/forms/relying-party-form.component.spec.ts index 0849591c6..274bbc3e0 100644 --- a/ui/src/app/metadata/domain/component/forms/relying-party-form.component.spec.ts +++ b/ui/src/app/metadata/domain/component/forms/relying-party-form.component.spec.ts @@ -10,6 +10,7 @@ import * as stubs from '../../../../../testing/resolver.stub'; import { SharedModule } from '../../../../shared/shared.module'; import { FileBackedHttpMetadataResolver } from '../../entity'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; +import { MockListValueService } from '../../../../../testing/list-values.stub'; @Component({ @@ -48,7 +49,7 @@ describe('Relying Party Form Component', () => { ProviderValueEmitter, ProviderStatusEmitter, NgbPopoverConfig, - ListValuesService + { provide: ListValuesService, useClass: MockListValueService } ], imports: [ NoopAnimationsModule, diff --git a/ui/src/app/metadata/domain/service/attributes.service.spec.ts b/ui/src/app/metadata/domain/service/attributes.service.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/app/metadata/domain/service/list-values.service.spec.ts b/ui/src/app/metadata/domain/service/list-values.service.spec.ts index e90c51a76..2c26ba912 100644 --- a/ui/src/app/metadata/domain/service/list-values.service.spec.ts +++ b/ui/src/app/metadata/domain/service/list-values.service.spec.ts @@ -2,6 +2,8 @@ import { TestBed, async, inject } from '@angular/core/testing'; import { EntityValidators } from './entity-validators.service'; import { Observable, of } from 'rxjs'; import { ListValuesService } from './list-values.service'; +import { AttributesService } from './attributes.service'; +import { MockAttributeService } from '../../../../testing/attributes.stub'; describe(`ListValuesService`, () => { let service: ListValuesService; @@ -9,6 +11,7 @@ describe(`ListValuesService`, () => { TestBed.configureTestingModule({ imports: [], providers: [ + { provide: AttributesService, useClass: MockAttributeService }, ListValuesService ] }); diff --git a/ui/src/testing/attributes.stub.ts b/ui/src/testing/attributes.stub.ts new file mode 100644 index 000000000..200bf8ade --- /dev/null +++ b/ui/src/testing/attributes.stub.ts @@ -0,0 +1,16 @@ +import { Observable, of } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { ReleaseAttribute } from '../app/metadata/domain/model/properties/release-attribute'; + +@Injectable() +export class MockAttributeService { + + readonly path = '/customAttributes'; + readonly base = '/api'; + + constructor() { } + + query(path: string = this.path): Observable { + return of([]); + } +} diff --git a/ui/src/testing/list-values.stub.ts b/ui/src/testing/list-values.stub.ts new file mode 100644 index 000000000..3e9ecf152 --- /dev/null +++ b/ui/src/testing/list-values.stub.ts @@ -0,0 +1,47 @@ +import { Observable, of } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { ReleaseAttribute } from '../app/metadata/domain/model/properties/release-attribute'; +import { debounceTime, distinctUntilChanged, combineLatest } from 'rxjs/operators'; + +@Injectable() +export class MockListValueService { + + constructor() { } + + readonly nameIdFormats: Observable = of([ + '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' + ]); + + readonly authenticationMethods: Observable = of([ + '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' + ]); + + get attributesToRelease(): Observable { + return of([]); + } + + searchStringList = (list: Observable): Function => + (text$: Observable) => + text$.pipe( + debounceTime(100), + distinctUntilChanged(), + combineLatest( + list, + (term, formats) => formats.filter( + v => v.toLowerCase().match(term.toLowerCase()) + ) + .slice(0, 4)) + ) + + get searchFormats(): Function { + return this.searchStringList(this.nameIdFormats); + } + get searchAuthenticationMethods(): Function { + return this.searchStringList(this.authenticationMethods); + } +}