UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

530 lines (523 loc) 374 kB
import * as i0 from '@angular/core'; import { Component, Input, Injectable, EventEmitter, NgModule, Output, ViewChild } from '@angular/core'; import * as i1$1 from '@c8y/ngx-components'; import { Permissions, gettext, CoreModule, hookRoute, memoize, Status, NavigatorNode, hookTab, hookNavigator, hookPatternMessages } from '@c8y/ngx-components'; import * as i1$2 from '@c8y/client'; import { TenantLoginOptionType, TfaStrategy, UserManagementSource, GrantType } from '@c8y/client'; import * as i1 from '@angular/forms'; import { ControlContainer, NgForm } from '@angular/forms'; import * as i2 from '@angular/common'; import { DatePipe } from '@angular/common'; import * as i4 from 'ngx-bootstrap/popover'; import { PopoverModule } from 'ngx-bootstrap/popover'; import { map, catchError, tap, switchMap, shareReplay, publishReplay, refCount, take, mapTo, mergeAll, toArray } from 'rxjs/operators'; import { forkJoin, from, of, BehaviorSubject, defer, throwError, Subject, EMPTY, merge } from 'rxjs'; import { isString, omit, defaults, omitBy, isEmpty, cloneDeep, isFinite, pickBy, identity, map as map$1, pick, findKey, has, get, reduce, pull, defaultsDeep, at, head, reject, isUndefined } from 'lodash-es'; import * as i2$1 from '@ngx-translate/core'; import * as i4$1 from 'ngx-bootstrap/tooltip'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import * as i4$3 from 'ngx-bootstrap/collapse'; import { CollapseModule } from 'ngx-bootstrap/collapse'; import { HttpStatusCode } from '@angular/common/http'; import { __decorate, __metadata } from 'tslib'; import * as i1$3 from 'ngx-bootstrap/modal'; import * as i4$2 from '@c8y/ngx-components/assets-navigator'; import { AssetSelectorModule } from '@c8y/ngx-components/assets-navigator'; import * as i6 from 'ngx-bootstrap/datepicker'; import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; import * as i1$4 from '@angular/router'; class UserAgent { constructor(value) { this._id = this.uniqueId(); this.value = value; } get id() { return this._id; } uniqueId() { const dateString = Date.now().toString(36); const randomString = Math.random().toString(36).substr(2); return dateString + randomString; } } function isOauthInternal(tenantLoginOption) { return tenantLoginOption.type === TenantLoginOptionType.OAUTH2_INTERNAL; } function isBasic(tenantLoginOption) { return tenantLoginOption.type === TenantLoginOptionType.BASIC; } class BasicAuthSettingsComponent { constructor(controlContainer) { this.controlContainer = controlContainer; this.preferredLoginOptionType = TenantLoginOptionType.BASIC; this.tenantLoginOptionTypeEnum = TenantLoginOptionType; } ngOnChanges(changes) { if (changes.authConfiguration && changes.authConfiguration.currentValue) { this.preferredLoginOptionType = changes.authConfiguration.currentValue.preferredLoginOptionType; } } ngDoCheck() { if (this.preferredLoginOptionType !== this.authConfiguration.preferredLoginOptionType) { this.preferredLoginOptionType = this.authConfiguration.preferredLoginOptionType; if (this.preferredLoginOptionType === TenantLoginOptionType.BASIC) { this.forbiddenWebBrowsers = false; } } } get forbiddenWebBrowsers() { return this.authenticationRestrictions.forbiddenClients.includes('WEB_BROWSERS'); } set forbiddenWebBrowsers(value) { this.authenticationRestrictions.forbiddenClients = value ? ['WEB_BROWSERS'] : []; } forbiddenUserAgentsRemove(id) { this.authenticationRestrictions.forbiddenUserAgents = this.remove(this.authenticationRestrictions.forbiddenUserAgents, id); this.controlContainer.control.markAsDirty(); } trustedUserAgentsRemove(id) { this.authenticationRestrictions.trustedUserAgents = this.remove(this.authenticationRestrictions.trustedUserAgents, id); this.controlContainer.control.markAsDirty(); } get authenticationRestrictions() { return this.authConfiguration.loginOptions.find(isBasic).authenticationRestrictions; } add(collection) { collection.push(new UserAgent('')); } remove(collection, id) { const newArray = collection.filter(obj => obj.id !== id); if (!newArray.length) { newArray.push(new UserAgent('')); } return newArray; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BasicAuthSettingsComponent, deps: [{ token: i1.ControlContainer }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: BasicAuthSettingsComponent, selector: "c8y-basic-auth-settings", inputs: { authConfiguration: "authConfiguration" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"card-block separator-top\"\n *ngIf=\"authConfiguration.preferredLoginOptionType !== 'BASIC'\"\n>\n <div class=\"col-sm-2\">\n <div class=\"h4 text-normal text-right text-left-xs\">\n {{ 'Basic Auth restrictions' | translate }}\n </div>\n </div>\n\n <div class=\"col-sm-9\">\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"c8y-switch\" title=\"{{ 'Forbidden for web browsers' | translate }}\">\n <input type=\"checkbox\" name=\"forbiddenWebBrowsers\" [(ngModel)]=\"forbiddenWebBrowsers\" />\n <span></span>\n <span>{{ 'Forbidden for web browsers' | translate }}</span>\n </label>\n <div\n class=\"alert alert-warning\"\n *ngIf=\"\n preferredLoginOptionType === tenantLoginOptionTypeEnum.BASIC && forbiddenWebBrowsers\n \"\n >\n {{\n 'You are about to forbid browsers from using Basic authentication. This will prevent users from using web applications on your tenant because you are going to set Basic authentication as the preferred login mode.'\n | translate\n }}\n </div>\n </c8y-form-group>\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <label title=\"{{ 'Forbidden user agents' | translate }}\">\n {{ 'Forbidden user agents' | translate }}\n </label>\n <div\n class=\"input-group m-t-8\"\n *ngFor=\"\n let forbiddenUserAgent of authenticationRestrictions.forbiddenUserAgents;\n last as isLast;\n first as isFirst\n \"\n >\n <input\n type=\"text\"\n [name]=\"'forbiddenUserAgent' + forbiddenUserAgent.id\"\n [(ngModel)]=\"forbiddenUserAgent.value\"\n class=\"form-control\"\n data-cy=\"c8y-basic-auth--forbidden-agent\"\n placeholder=\"{{ 'e.g.' | translate }} forbidden-agent\"\n />\n <div class=\"input-group-btn col-sm-2\">\n <button\n *ngIf=\"!(isFirst && isLast && forbiddenUserAgent.value === '')\"\n title=\"{{ 'Remove' | translate }}\"\n [name]=\"'forbiddenUserAgentRemove' + forbiddenUserAgent.id\"\n type=\"button\"\n class=\"btn btn-dot text-primary\"\n (click)=\"forbiddenUserAgentsRemove(forbiddenUserAgent.id)\"\n >\n <i class=\"dlt-c8y-icon-minus-circle text-danger\"></i>\n </button>\n <button\n title=\"{{ 'Add' | translate }}\"\n type=\"button\"\n class=\"btn btn-dot text-primary\"\n (click)=\"add(authenticationRestrictions.forbiddenUserAgents)\"\n *ngIf=\"isLast\"\n >\n <i class=\"dlt-c8y-icon-plus-circle\"></i>\n </button>\n </div>\n </div>\n </div>\n <div class=\"col-sm-6\">\n <label title=\"{{ 'Trusted user agents' | translate }}\">\n {{ 'Trusted user agents' | translate }}\n </label>\n <div\n class=\"input-group m-t-8\"\n *ngFor=\"\n let trustedUserAgent of authenticationRestrictions.trustedUserAgents;\n last as isLast;\n first as isFirst\n \"\n >\n <input\n type=\"text\"\n [name]=\"'trustedUserAgent' + trustedUserAgent.id\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} trusted-agent\"\n data-cy=\"c8y-basic-auth--trusted-agent\"\n [(ngModel)]=\"trustedUserAgent.value\"\n />\n <div class=\"input-group-btn col-sm-2\">\n <button\n *ngIf=\"!(isFirst && isLast && trustedUserAgent.value === '')\"\n title=\"{{ 'Remove' | translate }}\"\n [name]=\"'trustedUserAgentRemove' + trustedUserAgent.id\"\n type=\"button\"\n class=\"btn btn-dot btn-dot--danger text-primary\"\n (click)=\"trustedUserAgentsRemove(trustedUserAgent.id)\"\n >\n <i class=\"dlt-c8y-icon-minus-circle\"></i>\n </button>\n <button\n title=\"{{ 'Add' | translate }}\"\n type=\"button\"\n (click)=\"add(authenticationRestrictions.trustedUserAgents)\"\n class=\"btn btn-dot text-primary\"\n *ngIf=\"isLast\"\n >\n <i class=\"dlt-c8y-icon-plus-circle\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i1$1.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "pipe", type: i1$1.C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: BasicAuthSettingsComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-basic-auth-settings', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], template: "<div\n class=\"card-block separator-top\"\n *ngIf=\"authConfiguration.preferredLoginOptionType !== 'BASIC'\"\n>\n <div class=\"col-sm-2\">\n <div class=\"h4 text-normal text-right text-left-xs\">\n {{ 'Basic Auth restrictions' | translate }}\n </div>\n </div>\n\n <div class=\"col-sm-9\">\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"c8y-switch\" title=\"{{ 'Forbidden for web browsers' | translate }}\">\n <input type=\"checkbox\" name=\"forbiddenWebBrowsers\" [(ngModel)]=\"forbiddenWebBrowsers\" />\n <span></span>\n <span>{{ 'Forbidden for web browsers' | translate }}</span>\n </label>\n <div\n class=\"alert alert-warning\"\n *ngIf=\"\n preferredLoginOptionType === tenantLoginOptionTypeEnum.BASIC && forbiddenWebBrowsers\n \"\n >\n {{\n 'You are about to forbid browsers from using Basic authentication. This will prevent users from using web applications on your tenant because you are going to set Basic authentication as the preferred login mode.'\n | translate\n }}\n </div>\n </c8y-form-group>\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <label title=\"{{ 'Forbidden user agents' | translate }}\">\n {{ 'Forbidden user agents' | translate }}\n </label>\n <div\n class=\"input-group m-t-8\"\n *ngFor=\"\n let forbiddenUserAgent of authenticationRestrictions.forbiddenUserAgents;\n last as isLast;\n first as isFirst\n \"\n >\n <input\n type=\"text\"\n [name]=\"'forbiddenUserAgent' + forbiddenUserAgent.id\"\n [(ngModel)]=\"forbiddenUserAgent.value\"\n class=\"form-control\"\n data-cy=\"c8y-basic-auth--forbidden-agent\"\n placeholder=\"{{ 'e.g.' | translate }} forbidden-agent\"\n />\n <div class=\"input-group-btn col-sm-2\">\n <button\n *ngIf=\"!(isFirst && isLast && forbiddenUserAgent.value === '')\"\n title=\"{{ 'Remove' | translate }}\"\n [name]=\"'forbiddenUserAgentRemove' + forbiddenUserAgent.id\"\n type=\"button\"\n class=\"btn btn-dot text-primary\"\n (click)=\"forbiddenUserAgentsRemove(forbiddenUserAgent.id)\"\n >\n <i class=\"dlt-c8y-icon-minus-circle text-danger\"></i>\n </button>\n <button\n title=\"{{ 'Add' | translate }}\"\n type=\"button\"\n class=\"btn btn-dot text-primary\"\n (click)=\"add(authenticationRestrictions.forbiddenUserAgents)\"\n *ngIf=\"isLast\"\n >\n <i class=\"dlt-c8y-icon-plus-circle\"></i>\n </button>\n </div>\n </div>\n </div>\n <div class=\"col-sm-6\">\n <label title=\"{{ 'Trusted user agents' | translate }}\">\n {{ 'Trusted user agents' | translate }}\n </label>\n <div\n class=\"input-group m-t-8\"\n *ngFor=\"\n let trustedUserAgent of authenticationRestrictions.trustedUserAgents;\n last as isLast;\n first as isFirst\n \"\n >\n <input\n type=\"text\"\n [name]=\"'trustedUserAgent' + trustedUserAgent.id\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} trusted-agent\"\n data-cy=\"c8y-basic-auth--trusted-agent\"\n [(ngModel)]=\"trustedUserAgent.value\"\n />\n <div class=\"input-group-btn col-sm-2\">\n <button\n *ngIf=\"!(isFirst && isLast && trustedUserAgent.value === '')\"\n title=\"{{ 'Remove' | translate }}\"\n [name]=\"'trustedUserAgentRemove' + trustedUserAgent.id\"\n type=\"button\"\n class=\"btn btn-dot btn-dot--danger text-primary\"\n (click)=\"trustedUserAgentsRemove(trustedUserAgent.id)\"\n >\n <i class=\"dlt-c8y-icon-minus-circle\"></i>\n </button>\n <button\n title=\"{{ 'Add' | translate }}\"\n type=\"button\"\n (click)=\"add(authenticationRestrictions.trustedUserAgents)\"\n class=\"btn btn-dot text-primary\"\n *ngIf=\"isLast\"\n >\n <i class=\"dlt-c8y-icon-plus-circle\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }] }], ctorParameters: () => [{ type: i1.ControlContainer }], propDecorators: { authConfiguration: [{ type: Input }] } }); class AuthConfigurationGuard { constructor(permissions) { this.permissions = permissions; } canActivate() { return this.permissions.hasAnyRole([ Permissions.ROLE_TENANT_ADMIN, Permissions.ROLE_TENANT_MANAGEMENT_ADMIN ]); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AuthConfigurationGuard, deps: [{ token: i1$1.Permissions }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AuthConfigurationGuard }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AuthConfigurationGuard, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1$1.Permissions }] }); class TypedOption { constructor(category, key, type, defaultValue, value) { this.category = category; this.key = key; this.type = type; this.defaultValue = defaultValue; this.value = value; } apply(option) { this.category = option.category; this.key = option.key; this.value = option.value; } getValue() { try { return this.getValueByType(); } catch (ex) { return this.defaultValue; } } getValueByType() { switch (this.type) { case 'boolean': return this.value.toLowerCase() === 'true'; case 'number': return this.parseNumberValue(this.value); case 'string': return this.parseStringValue(this.value); default: throw new TypeError(); } } parseNumberValue(stringValue) { const value = Number.parseInt(stringValue, 10); if (typeof value !== 'number' || isNaN(value)) { throw new Error(); } return value; } parseStringValue(value) { if (!isString(value)) { throw new Error(); } return value; } } class TenantLoginOptionMapper { mapTo(tenantLoginOption) { const loginOption = omit(this.prapareTenantLoginOption(tenantLoginOption), 'authenticationRestrictions'); if (isBasic(loginOption)) { loginOption.authenticationRestrictions = this.mapAuthenticationRestrictionsTo(tenantLoginOption.authenticationRestrictions); } return loginOption; } mapFrom(originalLoginOption, newLoginOption) { if (isBasic(originalLoginOption)) { return this.mapBasicLoginOption(originalLoginOption, newLoginOption); } if (isOauthInternal(originalLoginOption)) { return this.mapOauthInternalLoginOption(originalLoginOption, newLoginOption); } throw new Error(`TenantLoginOptionMapper: The tenant login option cannot be mapped. Login option with type: ${originalLoginOption.type} is not supported.`); } mapAuthenticationRestrictionsTo(authenticationRestrictions) { const restrictions = defaults({}, omitBy(authenticationRestrictions, isEmpty), { forbiddenUserAgents: [''], trustedUserAgents: [''], forbiddenClients: [] }); restrictions.forbiddenUserAgents = restrictions.forbiddenUserAgents.map(val => new UserAgent(val)); restrictions.trustedUserAgents = restrictions.trustedUserAgents.map(val => new UserAgent(val)); return restrictions; } mapBasicLoginOption(originalLoginOption, newLoginOption) { const loginOption = omit(originalLoginOption, ['sessionConfiguration']); loginOption.authenticationRestrictions = this.mapAuthenticationRestrictionsFrom(newLoginOption.authenticationRestrictions); return loginOption; } mapOauthInternalLoginOption(originalLoginOption, newLoginOption) { const loginOption = omit(originalLoginOption, ['authenticationRestrictions']); newLoginOption.sessionConfiguration !== null ? (loginOption.sessionConfiguration = newLoginOption.sessionConfiguration) : delete loginOption.sessionConfiguration; return loginOption; } mapAuthenticationRestrictionsFrom(authenticationRestrictions) { return { trustedUserAgents: authenticationRestrictions.trustedUserAgents .filter(({ value }) => value) .map(({ value }) => value), forbiddenUserAgents: authenticationRestrictions.forbiddenUserAgents .filter(({ value }) => value) .map(({ value }) => value), forbiddenClients: authenticationRestrictions.forbiddenClients.filter(value => value) }; } prapareTenantLoginOption(tenantLoginOption) { return omit(tenantLoginOption, [ 'self', 'strengthValidity', 'tfaStrategy', 'greenMinLength', 'enforceStrength', 'strengthValidity', '_type' ]); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TenantLoginOptionMapper, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TenantLoginOptionMapper, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TenantLoginOptionMapper, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class AuthConfigurationService { constructor(tenantLoginOptionsService, tenantOptionsService, systemOptionsService, appState, tenantUiService, tenantLoginOptionMapper, tenantService) { this.tenantLoginOptionsService = tenantLoginOptionsService; this.tenantOptionsService = tenantOptionsService; this.systemOptionsService = systemOptionsService; this.appState = appState; this.tenantUiService = tenantUiService; this.tenantLoginOptionMapper = tenantLoginOptionMapper; this.tenantService = tenantService; this.systemOptionsWithDefaultValue = [ new TypedOption('password', 'limit.validity', 'number', null), new TypedOption('password', 'enforce.strength', 'boolean', false), new TypedOption('two-factor-authentication', 'tenant-scope-settings.enabled', 'boolean', false), new TypedOption('two-factor-authentication', 'enabled', 'boolean', false), // note: this definition is inconsistent with backend and is overridden in getSystemOptions$ new TypedOption('two-factor-authentication', 'enforced', 'boolean', false), new TypedOption('two-factor-authentication', 'enforced.group', 'string', '') ]; this.tenantOptionsWithDefaultValue = [ new TypedOption('password', 'limit.validity', 'number', 0), new TypedOption('password', 'strength.validity', 'boolean', false), new TypedOption('two-factor-authentication', 'enabled', 'boolean', false), new TypedOption('two-factor-authentication', 'token.validity', 'number', 43200), // 30 days new TypedOption('two-factor-authentication', 'pin.validity', 'number', 30), new TypedOption('two-factor-authentication', 'enforced', 'boolean', false), new TypedOption('two-factor-authentication', 'strategy', 'string', 'SMS'), new TypedOption('oauth.internal', 'basic-token.lifespan.seconds', 'number', null), new TypedOption('configuration', 'tenant.login.ignore-case', 'boolean', false) ]; } getAuthConfiguration$() { const loginOptions$ = this.getLoginOptions$(); return forkJoin({ loginOptions: this.map(loginOptions$), tenantOptions: this.getTenantOptions$(), systemOptions: this.getSystemOptions$(), smsGatewayAvailable: this.isSmsApplicationAvailable$(), preferredLoginOptionType: this.getPreferredLoginOptionType$(loginOptions$) }); } save(newAuthConfiguration, previousAuthConfiguration) { const tenantOptions = this.prepareTenantOptions(newAuthConfiguration, previousAuthConfiguration); const updateTenantOptions = tenantOptions.map(tenantOption => { const fixedOption = this.fixTfaStrategy(tenantOption); if (fixedOption) { return fixedOption; } return this.tenantOptionsService.create(tenantOption); }); const basicLoginOption = this.prepareBasicLoginOption(newAuthConfiguration, previousAuthConfiguration); const oauthInternalLoginOption = this.prepareOauthInternalLoginOption(newAuthConfiguration, previousAuthConfiguration); return Promise.all([ this.saveOrUpdateLoginOption(basicLoginOption), this.saveOrUpdateLoginOption(oauthInternalLoginOption), ...updateTenantOptions ]); } map(loginOptions$) { return loginOptions$.pipe(map(loginOptions => loginOptions.map(loginOption => this.tenantLoginOptionMapper.mapTo(loginOption)))); } saveOrUpdateLoginOption(loginOption) { return loginOption.id ? this.tenantLoginOptionsService.update(loginOption) : this.tenantLoginOptionsService.create(loginOption); } prepareBasicLoginOption(newAuthConfiguration, previousAuthConfiguration) { const basicLoginOption = this.originalLoginOptionWithDefaults(previousAuthConfiguration, TenantLoginOptionType.BASIC); basicLoginOption.visibleOnLoginPage = this.visibleOnLoginPage(newAuthConfiguration, TenantLoginOptionType.BASIC); return this.tenantLoginOptionMapper.mapFrom(basicLoginOption, this.getLoginOptionFromAuthConfiguration(newAuthConfiguration, TenantLoginOptionType.BASIC)); } prepareOauthInternalLoginOption(newAuthConfiguration, previousAuthConfiguration) { const oauthInternalLoginOption = this.originalLoginOptionWithDefaults(previousAuthConfiguration, TenantLoginOptionType.OAUTH2_INTERNAL); oauthInternalLoginOption.visibleOnLoginPage = this.visibleOnLoginPage(newAuthConfiguration, TenantLoginOptionType.OAUTH2_INTERNAL); return this.tenantLoginOptionMapper.mapFrom(oauthInternalLoginOption, this.getLoginOptionFromAuthConfiguration(newAuthConfiguration, TenantLoginOptionType.OAUTH2_INTERNAL)); } originalLoginOptionWithDefaults(previousAuthConfiguration, loginOptionType) { return defaults({}, this.getLoginOptionFromAuthConfiguration(previousAuthConfiguration, loginOptionType), this.getDefaultLoginOption(loginOptionType)); } getLoginOptionFromAuthConfiguration(authConfiguration, loginOptionType) { return authConfiguration.loginOptions.find(loginOption => loginOption.type === loginOptionType); } visibleOnLoginPage(authConfiguration, loginOptionType) { return authConfiguration.preferredLoginOptionType === loginOptionType; } prepareTenantOptions(newAuthConfiguration, previousAuthConfiguration) { const getValue = (authCfg, tenantOption) => authCfg.tenantOptions[tenantOption.category][tenantOption.key]; const hasChanged = tenantOption => getValue(newAuthConfiguration, tenantOption) !== getValue(previousAuthConfiguration, tenantOption); return this.tenantOptionsWithDefaultValue .filter(tenantOption => getValue(newAuthConfiguration, tenantOption) !== null) .filter(tenantOption => hasChanged(tenantOption)) .map(tenantOption => ({ category: tenantOption.category, key: tenantOption.key, value: getValue(newAuthConfiguration, tenantOption).toString() })); } getLoginOptions$() { return forkJoin([ this.getLoginOption(TenantLoginOptionType.OAUTH2), this.getLoginOption(TenantLoginOptionType.BASIC), this.getLoginOption(TenantLoginOptionType.OAUTH2_INTERNAL) ]).pipe(map(loginOptions => loginOptions.filter(loginOption => !!loginOption))); } getLoginOption(tenantLoginOptionType) { return from(this.tenantLoginOptionsService.detail(tenantLoginOptionType)).pipe(map(res => res.data), catchError(() => tenantLoginOptionType !== TenantLoginOptionType.OAUTH2 ? of(this.getDefaultLoginOption(tenantLoginOptionType)) : of(null))); } getPreferredLoginOptionType$(loginOptions$) { return loginOptions$.pipe(map(loginOptions => { return this.tenantUiService.getPreferredLoginOption(loginOptions).type; })); } getTenantOptions$() { return forkJoin(this.tenantOptionsWithDefaultValue.map((option) => from(this.tenantOptionsService.detail(option)).pipe(map(res => { option.apply(res.data); return option; }), catchError(() => of(option))))).pipe(map(options => this.getOptionsObject(options))); } getSystemOptions$() { return forkJoin(this.systemOptionsWithDefaultValue.map((option) => { const fixedOption = this.fixTfaEnforcedSystemOption(option); if (fixedOption) { return fixedOption; } return from(this.systemOptionsService.detail(option)).pipe(map(res => { option.apply(res.data); return option; }), catchError(() => of(option))); })).pipe(map(options => this.getOptionsObject(options))); } /** * Returns an observable with fixed `two-factor-authentication.enforced` system option or null. * This method fixes problem with inconsistent value. System option `two-factor-authentication.enforced` is list of tenants when UI using boolean value. * This part will be removed after implementing new endpoint in MTM-50490. */ fixTfaEnforcedSystemOption(option) { if (option.category === 'two-factor-authentication' && option.key === 'enforced') { return from(this.tenantService.getTfaSettings(this.tenantUiService.currentTenant)).pipe(map(tfaSettings => { option.value = tfaSettings.enforcedOnSystemLevel.toString(); return option; })); } return null; } /** * Returns a promise or null. * This method is needed now, because simply changing TFA strategy tenant option does not trigger all the necessary backend logic to apply the change, therefore, we need to call tenant's `/tfa` endpoint, which applies the change for all users in the tenant. * Within MTM-50490, we're going to simplify the process further by replacing multiple requests with one new endpoint that will handle saving of all authentication settings. */ fixTfaStrategy(option) { if (option.category === 'two-factor-authentication' && option.key === 'strategy') { const strategy = option.value === 'SMS' ? TfaStrategy.SMS : TfaStrategy.TOTP; return this.tenantService.updateTfaStrategy(this.tenantUiService.currentTenant, strategy); } return null; } isSmsApplicationAvailable$() { return from(this.appState.isApplicationAvailable('sms-gateway')); } getOptionsObject(options) { return options.reduce((optionsObject, option) => { optionsObject[option.category] = optionsObject[option.category] || {}; optionsObject[option.category][option.key] = option.getValue(); return optionsObject; }, {}); } getDefaultLoginOption(tenantLoginOptionType) { return { userManagementSource: UserManagementSource.INTERNAL, grantType: GrantType.PASSWORD, providerName: 'Cumulocity', visibleOnLoginPage: false, type: tenantLoginOptionType }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AuthConfigurationService, deps: [{ token: i1$2.TenantLoginOptionsService }, { token: i1$2.TenantOptionsService }, { token: i1$2.SystemOptionsService }, { token: i1$1.AppStateService }, { token: i1$1.TenantUiService }, { token: TenantLoginOptionMapper }, { token: i1$2.TenantService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AuthConfigurationService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AuthConfigurationService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1$2.TenantLoginOptionsService }, { type: i1$2.TenantOptionsService }, { type: i1$2.SystemOptionsService }, { type: i1$1.AppStateService }, { type: i1$1.TenantUiService }, { type: TenantLoginOptionMapper }, { type: i1$2.TenantService }] }); class SessionConfigurationComponent { constructor(tenantUiService, translateService) { this.tenantUiService = tenantUiService; this.translateService = translateService; this.tenantLoginOptionTypeEnum = TenantLoginOptionType; this.ABSOLUTE_TIMEOUT_VALIDATION_MESSAGE = gettext('The value must be greater than "Token lifespan" and not less than {{ minAbsoluteTimeout }}.'); this.RENEWAL_TIMEOUT_VALIDATION_MESSAGE = gettext('The value must be less than "Token lifespan".'); this.MAX_TOKEN_LIFESPAN_VALIDATION_MESSAGE = gettext('The value must be less than "Session absolute timeout".'); this.MIN_TOKEN_LIFESPAN_VALIDATION_MESSAGE = gettext('The value must be greater than "Session renewal timeout".'); this.USER_AGENT_VALIDATION_REQUIRED_POPOVER = gettext('If selected, then every request needs to use the same "User-Agent" header as the first request which initiated the session.'); this.MIN_ABSOLUTE_TIMEOUT = 15 * 60; this.ABSOLUTE_TIMEOUT_VALIDATION_MESSAGE = this.translateService.instant(this.ABSOLUTE_TIMEOUT_VALIDATION_MESSAGE, { minAbsoluteTimeout: this.MIN_ABSOLUTE_TIMEOUT }); } ngOnChanges(changes) { if (changes.authConfiguration && changes.authConfiguration.currentValue) { const oauthInternal = changes.authConfiguration.currentValue.loginOptions.find(isOauthInternal) || {}; this.originalSessionConfiguration = cloneDeep(oauthInternal.sessionConfiguration); this.sessionConfiguration = oauthInternal.sessionConfiguration; this.previousTokenLifespan = this.authConfiguration.tenantOptions['oauth.internal']['basic-token.lifespan.seconds']; } } get renewalTimeoutSeconds() { const sessionConfiguration = this.sessionConfiguration; return this.convertToSeconds(sessionConfiguration.renewalTimeoutMillis); } set renewalTimeoutSeconds(value) { this.sessionConfiguration.renewalTimeoutMillis = this.convertToMillis(value); } get absoluteTimeoutSeconds() { const sessionConfiguration = this.sessionConfiguration; return this.convertToSeconds(sessionConfiguration.absoluteTimeoutMillis); } set absoluteTimeoutSeconds(value) { this.sessionConfiguration.absoluteTimeoutMillis = this.convertToMillis(value); } get maximumNumberOfParallelSessions() { return this.sessionConfiguration.maximumNumberOfParallelSessions; } set maximumNumberOfParallelSessions(value) { this.sessionConfiguration.maximumNumberOfParallelSessions = value; } get userAgentValidationRequired() { return this.sessionConfiguration.userAgentValidationRequired; } set userAgentValidationRequired(value) { this.sessionConfiguration.userAgentValidationRequired = value; } get basicTokenLifespan() { return this.authConfiguration.tenantOptions['oauth.internal']['basic-token.lifespan.seconds']; } set basicTokenLifespan(value) { this.authConfiguration.tenantOptions['oauth.internal']['basic-token.lifespan.seconds'] = value; } get useSessionConfiguration() { return !!this.sessionConfiguration; } set useSessionConfiguration(value) { this.sessionConfiguration = value ? defaults({}, this.originalSessionConfiguration, { absoluteTimeoutMillis: 1209600000, // 14 days renewalTimeoutMillis: 86400000, // 1 day maximumNumberOfParallelSessions: 5, userAgentValidationRequired: false }) : null; this.basicTokenLifespan = this.previousTokenLifespan || 172800; // 2 days } get absoluteTimeoutConstraints() { return { min: Math.max(this.MIN_ABSOLUTE_TIMEOUT, this.basicTokenLifespan + 1) }; } get renewalTimeoutConstraints() { return { min: this.MIN_ABSOLUTE_TIMEOUT / 2, max: this.basicTokenLifespan ? this.basicTokenLifespan - 1 : null }; } get basicTokenLifespanConstraints() { return { min: this.renewalTimeoutSeconds ? this.renewalTimeoutSeconds + 1 : null, max: this.absoluteTimeoutSeconds ? this.absoluteTimeoutSeconds - 1 : null }; } get sessionConfiguration() { return this.authConfiguration.loginOptions.find(isOauthInternal).sessionConfiguration; } set sessionConfiguration(value) { this.authConfiguration.loginOptions.find(isOauthInternal).sessionConfiguration = value; } convertToMillis(seconds) { return isFinite(seconds) ? seconds * 1000 : null; } convertToSeconds(milliseconds) { return isFinite(milliseconds) ? Math.ceil(milliseconds / 1000) : null; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SessionConfigurationComponent, deps: [{ token: i1$1.TenantUiService }, { token: i2$1.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: SessionConfigurationComponent, selector: "c8y-session-configuration", inputs: { authConfiguration: "authConfiguration" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"card-block separator-top overflow-auto\"\n *ngIf=\"authConfiguration.preferredLoginOptionType === tenantLoginOptionTypeEnum.OAUTH2_INTERNAL\"\n>\n <div class=\"col-sm-2\">\n <div class=\"h4 text-normal text-right text-left-xs\">\n {{ 'OAI-Secure session configuration' | translate }}\n </div>\n </div>\n\n <div class=\"col-sm-9\">\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"c8y-switch\" title=\"{{ 'Use session configuration' | translate }}\">\n <input\n type=\"checkbox\"\n name=\"useSessionConfiguration\"\n [(ngModel)]=\"useSessionConfiguration\"\n />\n <span></span>\n <span>{{ 'Use session configuration' | translate }}</span>\n </label>\n </c8y-form-group>\n </div>\n </div>\n\n <fieldset *ngIf=\"sessionConfiguration\">\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"c8y-switch\" title=\"{{ 'User agent validation required' | translate }}\">\n <input\n type=\"checkbox\"\n name=\"userAgentValidationRequired\"\n [(ngModel)]=\"userAgentValidationRequired\"\n />\n <span></span>\n <span>{{ 'User agent validation required' | translate }}</span>\n <button\n class=\"btn-help btn-help--sm\"\n type=\"button\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ USER_AGENT_VALIDATION_REQUIRED_POPOVER | translate }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n ></button>\n </label>\n </c8y-form-group>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label title=\"{{ 'Session absolute timeout' | translate }}\">\n {{ 'Session absolute timeout' | translate }}\n </label>\n <div class=\"input-group\">\n <input\n type=\"number\"\n name=\"absoluteTimeoutSeconds\"\n class=\"form-control text-right\"\n [(ngModel)]=\"absoluteTimeoutSeconds\"\n [required]=\"useSessionConfiguration\"\n [min]=\"absoluteTimeoutConstraints.min\"\n step=\"1\"\n />\n <span class=\"input-group-addon\" translate>seconds</span>\n </div>\n <c8y-messages>\n <c8y-message\n name=\"min\"\n text=\"{{ ABSOLUTE_TIMEOUT_VALIDATION_MESSAGE | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label title=\"{{ 'Session renewal timeout' | translate }}\">\n {{ 'Session renewal timeout' | translate }}\n </label>\n <div class=\"input-group\">\n <input\n type=\"number\"\n name=\"renewalTimeoutSeconds\"\n class=\"form-control text-right\"\n [(ngModel)]=\"renewalTimeoutSeconds\"\n [required]=\"useSessionConfiguration\"\n [max]=\"renewalTimeoutConstraints.max\"\n [min]=\"renewalTimeoutConstraints.min\"\n step=\"1\"\n />\n <span class=\"input-group-addon\" translate>seconds</span>\n </div>\n <c8y-messages>\n <c8y-message\n name=\"max\"\n text=\"{{ RENEWAL_TIMEOUT_VALIDATION_MESSAGE | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label title=\"{{ 'Maximum parallel sessions per user' | translate }}\">\n {{ 'Maximum parallel sessions per user' | translate }}\n </label>\n <div class=\"input-group\">\n <input\n type=\"number\"\n name=\"maximumNumberOfParallelSessions\"\n class=\"form-control text-right\"\n [(ngModel)]=\"maximumNumberOfParallelSessions\"\n [required]=\"useSessionConfiguration\"\n [min]=\"1\"\n step=\"1\"\n />\n <span class=\"input-group-addon\" translate>sessions</span>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label title=\"{{ 'Token lifespan' | translate }}\">\n {{ 'Token lifespan' | translate }}\n </label>\n <div class=\"input-group\">\n <input\n type=\"number\"\n name=\"basicTokenLifespan\"\n class=\"form-control text-right\"\n [(ngModel)]=\"basicTokenLifespan\"\n [required]=\"useSessionConfiguration\"\n [max]=\"basicTokenLifespanConstraints.max\"\n [min]=\"basicTokenLifespanConstraints.min\"\n step=\"1\"\n />\n <span class=\"input-group-addon\" translate>seconds</span>\n </div>\n <c8y-messages>\n <c8y-message\n name=\"max\"\n text=\"{{ MAX_TOKEN_LIFESPAN_VALIDATION_MESSAGE | translate }}\"\n ></c8y-message>\n <c8y-message\n name=\"min\"\n text=\"{{ MIN_TOKEN_LIFESPAN_VALIDATION_MESSAGE | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i1$1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.MinValidationDirective, selector: "[min]", inputs: ["min"] }, { kind: "directive", type: i1$1.MaxValidationDirective, selector: "[max]", inputs: ["max"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i1$1.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: i1$1.MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "component", type: i1$1.MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: i1$1.RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: i4.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "pipe", type: i1$1.C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SessionConfigurationComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-session-configuration', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], template: "<div\n class=\"card-block separator-top overflow-auto\"\n *ngIf=\"authConfiguration.preferredLoginOptionType === tenantLoginOptionTypeEnum.OAUTH2_INTERNAL\"\n>\n <div class=\"col-sm-2\">\n <div class=\"h4 text-normal text-right text-left-xs\">\n {{ 'OAI-Secure session configuration' | translate }}\n </div>\n </div>\n\n <div class=\"col-sm-9\">\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"c8y-switch\" title=\"{{ 'Use session configuration' | translate }}\">\n <input\n type=\"checkbox\"\n name=\"useSessionConfiguration\"\n [(ngModel)]=\"useSessionConfiguration\"\n />\n <span></span>\n <span>{{ 'Use session configuration' | translate }}</span>\n </label>\n </c8y-form-group>\n </div>\n </div>\n\n <fieldset *ngIf=\"sessionConfiguration\">\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"c8y-switch\" title=\"{{ 'User agent validation required' | translate }}\">\n <input\n type=\"checkbox\"\n name=\"userAgentValidationRequired\"\n [(ngModel)]=\"userAgentValidationRequired\"\n />\n <span></span>\n <span>{{ 'User agent validation required' | translate }}</span>\n <button\n class=\"btn-help btn-help--sm\"\n type=\"button\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ USER_AGENT_VALIDATION_REQUIRED_POPOVER | translate }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n ></button>\n </label>\n </c8y-form-group>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label title=\"{{ 'Session absolute timeout' | translate }}\">\n {{