diff --git a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json index 887508b7f..1969c9ab4 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -25,35 +25,6 @@ "@type", "content" ], - "anyOf": [ - { - "properties": { - "@type": { - "enum": [ - "Regex" - ] - } - }, - "required": [ - "@type", - "content", - "match" - ] - }, - { - "properties": { - "@type": { - "enum": [ - "MetadataQueryProtocol" - ] - } - }, - "required": [ - "@type", - "content" - ] - } - ], "properties": { "@type": { "title": "label.md-request-type", diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index adeae5ce9..6aca80c1d 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/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=If checkbox is clicked, the metadata provider is enabled for integration with the IdP +label.enable-provider-upon-saving=Enable Metadata Provider? label.certificate-type=Type label.metadata-file=Metadata File @@ -388,6 +388,10 @@ message.org-displayName-required=Organization Name is required. message.org-url-required=Organization Name is required. message.org-incomplete=These three fields must all be entered if any single field has a value. +message.content-required=Missing required property: Type +message.match-required=Missing required property: Match +message.value-required=Missing required property: Value + message.conflict=Conflict message.data-version-contention=Data Version Contention message.contention-new-version=A newer version of this metadata source has been saved. Below are a list of changes. You can use your changes or their changes. @@ -434,7 +438,7 @@ tooltip.assertion-consumer-service-location-binding=Assertion Consumer Service L tooltip.mark-as-default=Mark as Default tooltip.protocol-support-enumeration=Protocol Support Enumeration tooltip.nameid-format=Content is name identifier format which is added to all the applicable roles of the entities which match any of the following or {{}}elements. -tooltip.enable-this-service-upon-saving=Enable this service upon saving +tooltip.enable-this-service-upon-saving=If checkbox is clicked, the metadata provider is enabled for integration with the IdP tooltip.authentication-requests-signed=Authentication Requests Signed tooltip.want-assertions-signed=Want Assertions Signed tooltip.certificate-name=Certificate Name @@ -520,7 +524,7 @@ tooltip.source-directory=Convenience mechanism for wiring a FilesystemLoadSaveMa tooltip.remove-idle-entity-data=Flag indicating whether idle metadata should be removed. tooltip.do-resolver-initialization=Initialize this resolver? In the case of Filesystem resolvers, this will cause the system to read the file and index the resolver. -tooltip.md-request-type=Options are 1) Metadata Query Protocol, 2) Template, 3) Regex. +tooltip.md-request-type=Options are 1) Metadata Query Protocol, 2) Regex. tooltip.md-request-value=Content of the element. tooltip.transform-ref=A reference to a transform function for the entityID. If used, the child element must be empty. tooltip.encoding-style=Determines whether and how the entityID value will be URL encoded prior to replacement. Allowed values are: 1) "none" - no encoding is performed, 2) "form" - encoded using URL form parameter encoding (for query parameters), 3) "path" - encoded using URL path encoding, or 4) "fragment" - encoded using URL fragment encoding. The precise definition of these terms is defined in the documentation for the methods of the Guava library\u0027s UrlEscapers class. diff --git a/ui/src/app/metadata/domain/service/resolver.service.ts b/ui/src/app/metadata/domain/service/resolver.service.ts index 4012f9f23..895e086f0 100644 --- a/ui/src/app/metadata/domain/service/resolver.service.ts +++ b/ui/src/app/metadata/domain/service/resolver.service.ts @@ -46,7 +46,7 @@ export class ResolverService { headers: new HttpHeaders().set('Content-Type', 'application/xml'), params: new HttpParams().set('spName', name) }).pipe(catchError(error => { - return throwError({ errorCode: error.status, errorMessage: `Unable to upload file ... ${error.error}` }); + return throwError({ errorCode: error.status, errorMessage: `Unable to upload file ... ${error.error.errorMessage}` }); })); } @@ -56,7 +56,7 @@ export class ResolverService { headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'), params: new HttpParams().set('spName', name) }).pipe(catchError(error => { - return throwError({ errorCode: error.status, errorMessage: `Unable to upload file ... ${error.error}` }); + return throwError({ errorCode: error.status, errorMessage: `Unable to upload file ... ${error.error.errorMessage}` }); })); } diff --git a/ui/src/app/metadata/filter/action/filter.action.ts b/ui/src/app/metadata/filter/action/filter.action.ts index 1034d9e09..a745d78c4 100644 --- a/ui/src/app/metadata/filter/action/filter.action.ts +++ b/ui/src/app/metadata/filter/action/filter.action.ts @@ -7,6 +7,7 @@ export enum FilterActionTypes { SELECT_FILTER_TYPE = '[Filter] Select Filter Type', UPDATE_FILTER = '[Filter] Update Filter', CANCEL_CREATE_FILTER = '[Filter] Cancel Create Filter', + CLEAR_FILTER = '[Filter] Clear Filter', LOAD_ENTITY_PREVIEW = '[Filter] Load Preview data', LOAD_ENTITY_PREVIEW_SUCCESS = '[Filter] Load Preview data success', LOAD_ENTITY_PREVIEW_ERROR = '[Filter] Load Preview data error' @@ -38,6 +39,10 @@ export class CancelCreateFilter implements Action { readonly type = FilterActionTypes.CANCEL_CREATE_FILTER; } +export class ClearFilter implements Action { + readonly type = FilterActionTypes.CLEAR_FILTER; +} + export class UpdateFilterChanges implements Action { readonly type = FilterActionTypes.UPDATE_FILTER; @@ -57,4 +62,5 @@ export type FilterActionsUnion = | CancelCreateFilter | LoadEntityPreview | LoadEntityPreviewSuccess - | LoadEntityPreviewError; + | LoadEntityPreviewError + | ClearFilter; diff --git a/ui/src/app/metadata/filter/container/new-filter.component.ts b/ui/src/app/metadata/filter/container/new-filter.component.ts index 7fa2a5c7f..c9b686777 100644 --- a/ui/src/app/metadata/filter/container/new-filter.component.ts +++ b/ui/src/app/metadata/filter/container/new-filter.component.ts @@ -58,18 +58,21 @@ export class NewFilterComponent implements OnDestroy, OnInit { this.model = {}; this.definition$ = this.store.select(fromFilter.getFilterType).pipe( + takeUntil(this.ngUnsubscribe), filter(t => !!t), map(t => MetadataFilterTypes[t]) ); this.schema$ = this.definition$.pipe( + takeUntil(this.ngUnsubscribe), filter(d => !!d), switchMap(d => { - return this.schemaService.get(d.schema); + return this.schemaService.get(d.schema).pipe(takeUntil(this.ngUnsubscribe)); }), shareReplay() ); this.validators$ = this.definition$.pipe( + takeUntil(this.ngUnsubscribe), withLatestFrom(this.store.select(fromFilter.getFilterNames)), map(([definition, names]) => definition.getValidators(names)) ); @@ -107,7 +110,6 @@ export class NewFilterComponent implements OnDestroy, OnInit { } save(): void { - console.log(this.filter); this.store.dispatch(new AddFilterRequest(this.filter)); } diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts index 65d5f8d2c..392cc0115 100644 --- a/ui/src/app/metadata/filter/effect/collection.effect.ts +++ b/ui/src/app/metadata/filter/effect/collection.effect.ts @@ -39,7 +39,7 @@ import { removeNulls, array_move } from '../../../shared/util'; import { EntityAttributesFilterEntity } from '../../domain/entity/filter/entity-attributes-filter'; import { MetadataFilterService } from '../../domain/service/filter.service'; import { SelectProviderRequest } from '../../provider/action/collection.action'; -import { UpdateFilterChanges } from '../action/filter.action'; +import { UpdateFilterChanges, ClearFilter } from '../action/filter.action'; /* istanbul ignore next */ @Injectable() @@ -112,6 +112,12 @@ export class FilterCollectionEffects { map(([filter, provider]) => new SelectProviderRequest(provider)) ); + @Effect() + addFilterSuccessResetState$ = this.actions$.pipe( + ofType(FilterCollectionActionTypes.ADD_FILTER_SUCCESS), + map(() => new ClearFilter()) + ); + @Effect() updateFilter$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.UPDATE_FILTER_REQUEST), diff --git a/ui/src/app/metadata/filter/model/nameid.filter.ts b/ui/src/app/metadata/filter/model/nameid.filter.ts index 3787af80b..f3cf67960 100644 --- a/ui/src/app/metadata/filter/model/nameid.filter.ts +++ b/ui/src/app/metadata/filter/model/nameid.filter.ts @@ -3,7 +3,7 @@ import { MetadataFilter } from '../../domain/model'; import { NameIDFormatFilterEntity } from '../../domain/entity/filter/nameid-format-filter'; export const NameIDFilter: FormDefinition = { - label: 'NameIDFilter', + label: 'NameIDFormat', type: 'NameIDFormat', schema: '/api/ui/NameIdFormatFilter', getEntity(filter: MetadataFilter): NameIDFormatFilterEntity { diff --git a/ui/src/app/metadata/filter/reducer/filter.reducer.ts b/ui/src/app/metadata/filter/reducer/filter.reducer.ts index 9b47e8df0..68aaf40ed 100644 --- a/ui/src/app/metadata/filter/reducer/filter.reducer.ts +++ b/ui/src/app/metadata/filter/reducer/filter.reducer.ts @@ -47,6 +47,7 @@ export function reducer(state = initialState, action: FilterActionsUnion): Filte } }; } + case FilterActionTypes.CLEAR_FILTER: case FilterActionTypes.CANCEL_CREATE_FILTER: { return { ...initialState diff --git a/ui/src/app/metadata/provider/model/base.provider.form.spec.ts b/ui/src/app/metadata/provider/model/base.provider.form.spec.ts index 7e1aeff61..99adaa6fa 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.spec.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.spec.ts @@ -129,13 +129,14 @@ describe('BaseMetadataProviderForm', () => { describe('parent `/` validator', () => { const validators = BaseMetadataProviderEditor.getValidators(['foo', 'bar']); + const prop = { path: '/name', properties: { name: { type: 'string' } } }; it('should return a list of child errors', () => { - expect(validators['/']({name: 'foo'}, { path: '/name' }, {}).length).toBe(1); + expect(validators['/']({name: 'foo'}, prop, {}).length).toBe(1); }); it('should ignore properties that don\'t exist a list of child errors', () => { - expect(validators['/']({ foo: 'bar' }, { path: '/foo' }, {})).toBeUndefined(); + expect(validators['/']({ foo: 'bar' }, prop, {})).toBeUndefined(); }); }); }); diff --git a/ui/src/app/metadata/provider/model/base.provider.form.ts b/ui/src/app/metadata/provider/model/base.provider.form.ts index ecbcab666..e341a2fa5 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.ts @@ -8,12 +8,11 @@ export const BaseMetadataProviderEditor: Wizard = { const validators = { '/': (value, property, form_current) => { let errors; - // iterate all customer Object.keys(value).forEach((key) => { const item = value[key]; const validatorKey = `/${key}`; const validator = validators.hasOwnProperty(validatorKey) ? validators[validatorKey] : null; - const error = validator ? validator(item, { path: `/${key}` }, form_current) : null; + const error = validator ? validator(item, property.properties[key], form_current) : null; if (error) { errors = errors || []; errors.push(error); diff --git a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts index 7f38d02e3..6ebf388d2 100644 --- a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts @@ -23,13 +23,54 @@ export const DynamicHttpMetadataProviderWizard: Wizard { - return !UriValidator.isUri(value) ? { - code: 'INVALID_URI', + + validators['/metadataRequestURLConstructionScheme'] = (value, property, form) => { + let errors; + let keys = Object.keys(property.schema.properties); + + keys.forEach((item) => { + const path = `/metadataRequestURLConstructionScheme/${item}`; + const error = validators[path](value[item], property.properties[item], form); + if (error) { + errors = errors || []; + errors.push(error); + } + }); + return errors; + }; + + validators['/metadataRequestURLConstructionScheme/content'] = (value, property, form) => { + const err = !value ? { + code: 'REQUIRED', path: `#${property.path}`, - message: 'message.uri-valid-format', + message: 'message.value-required', params: [value] } : null; + return err; + }; + + validators['/metadataRequestURLConstructionScheme/@type'] = (value, property, form) => { + const err = !value ? { + code: 'REQUIRED', + path: `#${property.path}`, + message: 'message.type-required', + params: [value] + } : null; + return err; + }; + + validators['/metadataRequestURLConstructionScheme/match'] = (value, property, form) => { + if (!property.parent || !property.parent.value) { + return null; + } + const isRegex = property.parent.value['@type'] === 'Regex'; + const err = isRegex && !value ? { + code: 'REQUIRED', + path: `#${property.path}`, + message: 'message.match-required', + params: [value] + } : null; + return err; }; return validators; @@ -43,7 +84,6 @@ export const DynamicHttpMetadataProviderWizard: Wizard + + + , + error + + diff --git a/ui/src/assets/schema/provider/dynamic-http.schema.json b/ui/src/assets/schema/provider/dynamic-http.schema.json index f9dec02b8..1969c9ab4 100644 --- a/ui/src/assets/schema/provider/dynamic-http.schema.json +++ b/ui/src/assets/schema/provider/dynamic-http.schema.json @@ -25,35 +25,6 @@ "@type", "content" ], - "anyOf": [ - { - "properties": { - "@type": { - "enum": [ - "Regex" - ] - } - }, - "required": [ - "@type", - "content", - "match" - ] - }, - { - "properties": { - "@type": { - "enum": [ - "MetadataQueryProtocol" - ] - } - }, - "required": [ - "@type", - "content" - ] - } - ], "properties": { "@type": { "title": "label.md-request-type", @@ -152,8 +123,8 @@ "step": 0.01 }, "placeholder": "label.real-number", - "minimum": 0, - "maximum": 1, + "minimum": 0.01, + "maximum": 0.99, "default": null }, "minCacheDuration": { @@ -222,6 +193,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",