Browse Source

UI: Refactoring lwm2m security config and add X509 certificate in lwm2m security model

pull/4624/head
Vladyslav_Prykhodko 5 years ago
parent
commit
53c51953c4
  1. 29
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.html
  2. 90
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts
  3. 5
      ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts
  4. 232
      ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html
  5. 262
      ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts
  6. 99
      ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts
  7. 12
      ui-ngx/src/assets/locale/locale.constant-en_US.json

29
ui-ngx/src/app/modules/home/components/device/device-credentials.component.html

@ -75,32 +75,7 @@
('device.client-id-or-user-name-necessary' | translate) : ''"></tb-error> ('device.client-id-or-user-name-necessary' | translate) : ''"></tb-error>
</section> </section>
<div *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.LWM2M_CREDENTIALS"> <div *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.LWM2M_CREDENTIALS">
<mat-form-field class="mat-block"> <tb-security-config-lwm2m formControlName="credentialsValue">
<mat-label translate>device.lwm2m-key</mat-label> </tb-security-config-lwm2m>
<input matInput type="text" formControlName="credentialsId" required>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('required')">
{{ 'device.lwm2m-key-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>device.lwm2m-value</mat-label>
<textarea matInput formControlName="credentialsValue" rows="10" required
[matTooltip]="lwm2mCredentialsValueTooltip(deviceCredentialsFormGroup.get('credentialsValue').hasError('jsonError'))"
matTooltipPosition="above"
></textarea>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('required')">
{{ 'device.lwm2m-value-required' | translate }}
</mat-error>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('jsonError')">
{{ 'device.lwm2m-value-format-error' | translate }}
</mat-error>
<div mat-dialog-actions fxLayoutAlign="center center">
<button mat-raised-button color="primary"
(click)="openSecurityInfoLwM2mDialog($event)"
>
{{'device.lwm2m-value-edit' | translate }}
</button>
</div>
</mat-form-field>
</div> </div>
</section> </section>

90
ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts

@ -34,19 +34,7 @@ import {
DeviceCredentialsType DeviceCredentialsType
} from '@shared/models/device.models'; } from '@shared/models/device.models';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component';
import {
ClientSecurityConfig,
DEFAULT_END_POINT,
DeviceCredentialsDialogLwm2mData,
END_POINT,
getDefaultSecurityConfig,
JSON_ALL_CONFIG,
validateSecurityConfig
} from '@shared/models/lwm2m-security-config.models';
import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
@Component({ @Component({
@ -84,9 +72,7 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
private propagateChange = (v: any) => {}; private propagateChange = (v: any) => {};
constructor(public fb: FormBuilder, constructor(public fb: FormBuilder) {
private translate: TranslateService,
private dialog: MatDialog) {
this.deviceCredentialsFormGroup = this.fb.group({ this.deviceCredentialsFormGroup = this.fb.group({
credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], credentialsType: [DeviceCredentialsType.ACCESS_TOKEN],
credentialsId: [null], credentialsId: [null],
@ -99,15 +85,14 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
}); });
this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); this.deviceCredentialsFormGroup.get('credentialsBasic').disable();
this.deviceCredentialsFormGroup.valueChanges.pipe( this.deviceCredentialsFormGroup.valueChanges.pipe(
distinctUntilChanged(),
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe(() => { ).subscribe(() => {
this.updateView(); this.updateView();
}); });
this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.pipe( this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe((type) => { ).subscribe(() => {
this.credentialsTypeChanged(type); this.credentialsTypeChanged();
}); });
} }
@ -128,8 +113,6 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
let credentialsValue = null; let credentialsValue = null;
if (value.credentialsType === DeviceCredentialsType.MQTT_BASIC) { if (value.credentialsType === DeviceCredentialsType.MQTT_BASIC) {
credentialsBasic = JSON.parse(value.credentialsValue) as DeviceCredentialMQTTBasic; credentialsBasic = JSON.parse(value.credentialsValue) as DeviceCredentialMQTTBasic;
} else if (value.credentialsType === DeviceCredentialsType.LWM2M_CREDENTIALS) {
credentialsValue = JSON.parse(JSON.stringify(value.credentialsValue)) as ClientSecurityConfig;
} else { } else {
credentialsValue = value.credentialsValue; credentialsValue = value.credentialsValue;
} }
@ -176,11 +159,10 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
}; };
} }
credentialsTypeChanged(credentialsType: DeviceCredentialsType): void { credentialsTypeChanged(): void {
const credentialsValue = credentialsType === DeviceCredentialsType.LWM2M_CREDENTIALS ? this.lwm2mDefaultConfig : null;
this.deviceCredentialsFormGroup.patchValue({ this.deviceCredentialsFormGroup.patchValue({
credentialsId: null, credentialsId: null,
credentialsValue, credentialsValue: null,
credentialsBasic: {clientId: '', userName: '', password: ''} credentialsBasic: {clientId: '', userName: '', password: ''}
}); });
this.updateValidators(); this.updateValidators();
@ -198,14 +180,8 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false});
break; break;
case DeviceCredentialsType.X509_CERTIFICATE: case DeviceCredentialsType.X509_CERTIFICATE:
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsBasic').disable({emitEvent: false});
break;
case DeviceCredentialsType.LWM2M_CREDENTIALS: case DeviceCredentialsType.LWM2M_CREDENTIALS:
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required, this.lwm2mConfigJsonValidator]); this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity({emitEvent: false});
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false});
@ -245,56 +221,4 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
onlySelf: true onlySelf: true
}); });
} }
openSecurityInfoLwM2mDialog($event: Event): void {
if ($event) {
$event.stopPropagation();
$event.preventDefault();
}
let credentialsValue = this.deviceCredentialsFormGroup.get('credentialsValue').value;
if (credentialsValue === null || credentialsValue.length === 0) {
credentialsValue = getDefaultSecurityConfig();
} else {
try {
credentialsValue = JSON.parse(credentialsValue);
} catch (e) {
credentialsValue = getDefaultSecurityConfig();
}
}
const credentialsId = this.deviceCredentialsFormGroup.get('credentialsId').value || DEFAULT_END_POINT;
this.dialog.open<SecurityConfigLwm2mComponent, DeviceCredentialsDialogLwm2mData, object>(SecurityConfigLwm2mComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
jsonAllConfig: credentialsValue,
endPoint: credentialsId
}
}).afterClosed().subscribe(
(res) => {
if (res) {
this.deviceCredentialsFormGroup.patchValue({
credentialsValue: this.isDefaultLw2mResponse(res[JSON_ALL_CONFIG]) ? null : JSON.stringify(res[JSON_ALL_CONFIG]),
credentialsId: this.isDefaultLw2mResponse(res[END_POINT]) ? null : JSON.stringify(res[END_POINT]).split('\"').join('')
});
this.deviceCredentialsFormGroup.get('credentialsValue').markAsDirty();
}
}
);
}
private isDefaultLw2mResponse(response: object): boolean {
return Object.keys(response).length === 0 || JSON.stringify(response) === '[{}]';
}
private lwm2mConfigJsonValidator(control: FormControl) {
return validateSecurityConfig(control.value) ? null : {jsonError: {parsedJson: 'error'}};
}
private get lwm2mDefaultConfig(): string {
return JSON.stringify(getDefaultSecurityConfig(), null, 2);
}
lwm2mCredentialsValueTooltip(flag: boolean): string {
return !flag ? '' : 'Example (mode=\"NoSec\"):\n\r ' + this.lwm2mDefaultConfig;
}
} }

5
ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts

@ -130,6 +130,8 @@ export class SecurityConfigLwm2mServerComponent implements OnDestroy, ControlVal
case Lwm2mSecurityType.NO_SEC: case Lwm2mSecurityType.NO_SEC:
this.serverFormGroup.get('clientPublicKeyOrId').clearValidators(); this.serverFormGroup.get('clientPublicKeyOrId').clearValidators();
this.serverFormGroup.get('clientSecretKey').clearValidators(); this.serverFormGroup.get('clientSecretKey').clearValidators();
this.serverFormGroup.get('clientPublicKeyOrId').disable({emitEvent: false});
this.serverFormGroup.get('clientSecretKey').disable();
break; break;
case Lwm2mSecurityType.PSK: case Lwm2mSecurityType.PSK:
this.lenMinClientPublicKeyOrId = 0; this.lenMinClientPublicKeyOrId = 0;
@ -172,5 +174,8 @@ export class SecurityConfigLwm2mServerComponent implements OnDestroy, ControlVal
Validators.minLength(this.lengthClientSecretKey), Validators.minLength(this.lengthClientSecretKey),
Validators.maxLength(this.lengthClientSecretKey) Validators.maxLength(this.lengthClientSecretKey)
]); ]);
this.serverFormGroup.get('clientPublicKeyOrId').enable({emitEvent: false});
this.serverFormGroup.get('clientSecretKey').enable();
} }
} }

232
ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html

@ -15,129 +15,109 @@
limitations under the License. limitations under the License.
--> -->
<form [formGroup]="lwm2mConfigFormGroup" (ngSubmit)="save()" style="min-width: 400px;"> <mat-tab-group>
<mat-toolbar fxLayout="row" color="primary"> <mat-tab label="{{ 'device.lwm2m-security-config.client-tab' | translate }}" [formGroup]="lwm2mConfigFormGroup">
<h2>{{ title }}</h2> <ng-container formGroupName="client">
<span fxFlex></span> <mat-form-field class="mat-block">
<button mat-button mat-icon-button <mat-label translate>device.lwm2m-security-config.endpoint</mat-label>
(click)="cancel()" <input matInput type="text" formControlName="endpoint" required>
type="button"> <mat-error *ngIf="lwm2mConfigFormGroup.get('client.endpoint').hasError('required')">
<mat-icon class="material-icons">close</mat-icon> {{ 'device.lwm2m-security-config.endpoint-required' | translate }}
</button> </mat-error>
</mat-toolbar> </mat-form-field>
<div mat-dialog-content> <mat-form-field class="mat-block">
<mat-form-field class="mat-block"> <mat-label translate>device.lwm2m-security-config.mode</mat-label>
<mat-label translate>device.lwm2m-security-config.endpoint</mat-label> <mat-select formControlName="securityConfigClientMode">
<input matInput type="text" formControlName="endPoint" required> <mat-option *ngFor="let securityConfigClientMode of securityConfigLwM2MTypes"
<mat-error *ngIf="lwm2mConfigFormGroup.get('endPoint').hasError('required')"> [value]="securityConfigClientMode">
{{ 'device.lwm2m-security-config.endpoint-required' | translate }} {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }}
</mat-error> </mat-option>
</mat-form-field> </mat-select>
<mat-tab-group (selectedTabChange)="tabChanged($event)"> </mat-form-field>
<mat-tab label="{{ 'device.lwm2m-security-config.client-tab' | translate }}"> <mat-form-field class="mat-block" *ngIf="lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.PSK">
<mat-form-field class="mat-block"> <mat-label>{{ 'device.lwm2m-security-config.identity' | translate }}</mat-label>
<mat-label translate>device.lwm2m-security-config.mode</mat-label> <input matInput type="text" formControlName="identity" required>
<mat-select formControlName="securityConfigClientMode"> <mat-error *ngIf="lwm2mConfigFormGroup.get('client.identity').hasError('required')">
<mat-option *ngFor="let securityConfigClientMode of securityConfigLwM2MTypes" {{ 'device.lwm2m-security-config.identity-required' | translate }}
[value]="securityConfigClientMode"> </mat-error>
{{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} </mat-form-field>
</mat-option> <mat-form-field class="mat-block" *ngIf="lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.RPK ||
</mat-select> lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.PSK">
</mat-form-field> <mat-label>{{ 'device.lwm2m-security-config.client-key' | translate }}</mat-label>
<div *ngIf="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK"> <textarea matInput
<mat-form-field class="mat-block"> #key
<mat-label>{{ 'device.lwm2m-security-config.identity' | translate }}</mat-label> maxlength="{{lenMaxKeyClient}}"
<input matInput type="text" formControlName="identity" required> cdkTextareaAutosize
<mat-error *ngIf="lwm2mConfigFormGroup.get('identity').hasError('required')"> cdkAutosizeMinRows="1"
{{ 'device.lwm2m-security-config.identity-required' | translate }} cols="1"
</mat-error> formControlName="key"
</mat-form-field> required>
</div> </textarea>
<mat-form-field class="mat-block" *ngIf="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.RPK || <mat-hint align="end">{{key.value?.length || 0}}/{{lenMaxKeyClient}}</mat-hint>
lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.PSK"> <mat-error *ngIf="lwm2mConfigFormGroup.get('client.key').hasError('required')">
<mat-label>{{ 'device.lwm2m-security-config.client-key' | translate }}</mat-label> {{ 'device.lwm2m-security-config.client-key-required' | translate }}
<textarea matInput </mat-error>
#key <mat-error *ngIf="lwm2mConfigFormGroup.get('client.key').hasError('pattern')">
maxlength="{{lenMaxKeyClient}}" {{ 'device.lwm2m-security-config.client-key-pattern' | translate }}
cdkTextareaAutosize </mat-error>
cdkAutosizeMinRows="1" <mat-error *ngIf="(lwm2mConfigFormGroup.get('client.key').hasError('maxlength') ||
cols="1" lwm2mConfigFormGroup.get('client.key').hasError('minlength'))">
formControlName="key" {{ 'device.lwm2m-security-config.client-key-length' | translate: {
required> count: lenMaxKeyClient
</textarea> } }}
<mat-hint align="end">{{key.value?.length || 0}}/{{lenMaxKeyClient}}</mat-hint> </mat-error>
<mat-error *ngIf="lwm2mConfigFormGroup.get('key').hasError('required')"> </mat-form-field>
{{ 'device.lwm2m-security-config.client-key-required' | translate }} <mat-form-field class="mat-block" *ngIf="lwm2mConfigFormGroup.get('client.securityConfigClientMode').value === securityConfigLwM2MType.X509">
</mat-error> <mat-label translate>device.lwm2m-security-config.client-public-key</mat-label>
<mat-error *ngIf="lwm2mConfigFormGroup.get('key').hasError('pattern')"> <textarea matInput
{{ 'device.lwm2m-security-config.client-key-pattern' | translate }} cdkTextareaAutosize
</mat-error> cdkAutosizeMinRows="1"
<mat-error *ngIf="(lwm2mConfigFormGroup.get('key').hasError('maxlength') || cols="1"
lwm2mConfigFormGroup.get('key').hasError('minlength'))"> formControlName="cert">
{{ 'device.lwm2m-security-config.client-key-length' | translate: { </textarea>
count: lenMaxKeyClient <mat-hint translate>device.lwm2m-security-config.client-public-key-hint</mat-hint>
} }} </mat-form-field>
</mat-error> </ng-container>
</mat-form-field> </mat-tab>
<mat-checkbox formControlName="x509" color="primary" <mat-tab label="{{ 'device.lwm2m-security-config.bootstrap-tab' | translate }}" [formGroup]="lwm2mConfigFormGroup">
*ngIf="lwm2mConfigFormGroup.get('securityConfigClientMode').value === securityConfigLwM2MType.X509"> <div style="padding: 2px;" formGroupName="bootstrap">
{{ 'device.lwm2m-security-config.client-certificate' | translate }} <mat-accordion multi="true" class="mat-body-1">
</mat-checkbox> <mat-expansion-panel>
</mat-tab> <mat-expansion-panel-header>
<mat-tab label="{{ 'device.lwm2m-security-config.bootstrap-tab' | translate }}"> <mat-panel-title>
<div style="padding: 2px;"> {{ 'device.lwm2m-security-config.bootstrap-server' | translate }}
<mat-accordion multi="true" class="mat-body-1"> </mat-panel-title>
<mat-expansion-panel> </mat-expansion-panel-header>
<mat-expansion-panel-header> <ng-template matExpansionPanelContent>
<mat-panel-title> <tb-security-config-lwm2m-server
{{ 'device.lwm2m-security-config.bootstrap-server' | translate }} formControlName="bootstrapServer">
</mat-panel-title> </tb-security-config-lwm2m-server>
</mat-expansion-panel-header> </ng-template>
<ng-template matExpansionPanelContent> </mat-expansion-panel>
<tb-security-config-lwm2m-server <mat-expansion-panel>
formControlName="bootstrapServer"> <mat-expansion-panel-header>
</tb-security-config-lwm2m-server> <mat-panel-title>
</ng-template> {{ 'device.lwm2m-security-config.lwm2m-server' | translate }}
</mat-expansion-panel> </mat-panel-title>
<mat-expansion-panel> </mat-expansion-panel-header>
<mat-expansion-panel-header> <ng-template matExpansionPanelContent>
<mat-panel-title> <tb-security-config-lwm2m-server
{{ 'device.lwm2m-security-config.lwm2m-server' | translate }} formControlName="lwm2mServer">
</mat-panel-title> </tb-security-config-lwm2m-server>
</mat-expansion-panel-header> </ng-template>
<ng-template matExpansionPanelContent> </mat-expansion-panel>
<tb-security-config-lwm2m-server </mat-accordion>
formControlName="lwm2mServer"> </div>
</tb-security-config-lwm2m-server> </mat-tab>
</ng-template> <mat-tab label="{{ 'device.lwm2m-security-config.config-json-tab' | translate }}">
</mat-expansion-panel> <ng-template matTabContent>
</mat-accordion> <tb-json-object-edit
</div> [ngModel]="lwm2mConfigFormGroup.value"
</mat-tab> label="{{ 'device.lwm2m-value' | translate }}"
<mat-tab label="{{ 'device.lwm2m-security-config.config-json-tab' | translate }}"> readonly
<ng-template matTabContent> [required]="true"
<tb-json-object-edit [fillHeight]="false">
[formControlName]="formControlNameJsonAllConfig" </tb-json-object-edit>
label="{{ 'device.lwm2m-value' | translate }}" </ng-template>
readonly </mat-tab>
[required]="true" </mat-tab-group>
[fillHeight]="false">
</tb-json-object-edit>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || lwm2mConfigFormGroup.invalid || !lwm2mConfigFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
</div>
</form>

262
ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts

@ -14,19 +14,20 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, forwardRef, OnDestroy } from '@angular/core';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import {
import { DialogComponent } from '@shared/components/dialog.component'; ControlValueAccessor,
import { Store } from '@ngrx/store'; FormBuilder,
import { AppState } from '@core/core.state'; FormGroup,
import { Router } from '@angular/router'; NG_VALIDATORS,
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; NG_VALUE_ACCESSOR,
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; ValidationErrors,
import { TranslateService } from '@ngx-translate/core'; Validator,
Validators
} from '@angular/forms';
import { import {
DeviceCredentialsDialogLwm2mData, getDefaultClientSecurityConfig,
getClientSecurityConfig, getDefaultServerSecurityConfig,
JSON_ALL_CONFIG,
KEY_REGEXP_HEX_DEC, KEY_REGEXP_HEX_DEC,
LEN_MAX_PSK, LEN_MAX_PSK,
LEN_MAX_PUBLIC_KEY_RPK, LEN_MAX_PUBLIC_KEY_RPK,
@ -34,48 +35,67 @@ import {
Lwm2mSecurityType, Lwm2mSecurityType,
Lwm2mSecurityTypeTranslationMap Lwm2mSecurityTypeTranslationMap
} from '@shared/models/lwm2m-security-config.models'; } from '@shared/models/lwm2m-security-config.models';
import { MatTabChangeEvent } from '@angular/material/tabs';
import { MatTab } from '@angular/material/tabs/tab';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { isDefinedAndNotNull } from '@core/utils';
@Component({ @Component({
selector: 'tb-security-config-lwm2m', selector: 'tb-security-config-lwm2m',
templateUrl: './security-config-lwm2m.component.html', templateUrl: './security-config-lwm2m.component.html',
styleUrls: ['./security-config-lwm2m.component.scss'] styleUrls: ['./security-config-lwm2m.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SecurityConfigLwm2mComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => SecurityConfigLwm2mComponent),
multi: true
}
]
}) })
export class SecurityConfigLwm2mComponent extends DialogComponent<SecurityConfigLwm2mComponent, object> implements OnInit, OnDestroy { export class SecurityConfigLwm2mComponent implements ControlValueAccessor, Validator, OnDestroy {
private destroy$ = new Subject();
lwm2mConfigFormGroup: FormGroup; lwm2mConfigFormGroup: FormGroup;
title: string;
securityConfigLwM2MType = Lwm2mSecurityType; securityConfigLwM2MType = Lwm2mSecurityType;
securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType); securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType);
credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap; credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap;
formControlNameJsonAllConfig = JSON_ALL_CONFIG;
jsonAllConfig: Lwm2mSecurityConfigModels;
lenMaxKeyClient = LEN_MAX_PSK; lenMaxKeyClient = LEN_MAX_PSK;
tabPrevious: MatTab;
tabIndexPrevious = 0;
constructor(protected store: Store<AppState>, private destroy$ = new Subject();
protected router: Router, private propagateChange = (v: any) => {};
@Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData,
public dialogRef: MatDialogRef<SecurityConfigLwm2mComponent, object>, constructor(private fb: FormBuilder) {
public fb: FormBuilder, this.lwm2mConfigFormGroup = this.initLwm2mConfigForm();
public translate: TranslateService) {
super(store, router, dialogRef);
} }
ngOnInit() { writeValue(obj: string) {
this.jsonAllConfig = JSON.parse(JSON.stringify(this.data.jsonAllConfig)); if (isDefinedAndNotNull(obj)) {
this.lwm2mConfigFormGroup = this.initLwm2mConfigFormGroup(); this.initClientSecurityConfig(JSON.parse(obj));
this.title = this.translate.instant('device.lwm2m-security-info') + ': ' + this.data.endPoint; }
this.lwm2mConfigFormGroup.get('x509').disable(); }
this.initClientSecurityConfig(this.lwm2mConfigFormGroup.get('jsonAllConfig').value);
this.registerDisableOnLoadFormControl(this.lwm2mConfigFormGroup.get('securityConfigClientMode')); registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched(fn: any) {}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.lwm2mConfigFormGroup.disable({emitEvent: false});
} else {
this.lwm2mConfigFormGroup.enable({emitEvent: false});
}
}
validate(): ValidationErrors | null {
return this.lwm2mConfigFormGroup.valid ? null : {
securityConfigLWm2m: false
};
} }
ngOnDestroy() { ngOnDestroy() {
@ -83,175 +103,97 @@ export class SecurityConfigLwm2mComponent extends DialogComponent<SecurityConfig
this.destroy$.complete(); this.destroy$.complete();
} }
private initClientSecurityConfig = (jsonAllConfig: Lwm2mSecurityConfigModels): void => { private initClientSecurityConfig(config: Lwm2mSecurityConfigModels): void {
if (jsonAllConfig.client.securityConfigClientMode !== Lwm2mSecurityType.NO_SEC) { this.lwm2mConfigFormGroup.patchValue(config, {emitEvent: false});
this.lwm2mConfigFormGroup.patchValue(jsonAllConfig.client, {emitEvent: false}); this.securityConfigClientUpdateValidators(config.client.securityConfigClientMode);
}
this.securityConfigClientUpdateValidators(jsonAllConfig.client.securityConfigClientMode);
} }
private securityConfigClientModeChanged(type: Lwm2mSecurityType): void { private securityConfigClientModeChanged(type: Lwm2mSecurityType): void {
const config = getClientSecurityConfig(type, this.lwm2mConfigFormGroup.get('endPoint').value); const config = getDefaultClientSecurityConfig(type, this.lwm2mConfigFormGroup.get('client.endpoint').value);
switch (type) { switch (type) {
case Lwm2mSecurityType.PSK: case Lwm2mSecurityType.PSK:
config.identity = this.data.endPoint; config.key = this.lwm2mConfigFormGroup.get('client.key').value;
config.key = this.lwm2mConfigFormGroup.get('key').value;
break; break;
case Lwm2mSecurityType.RPK: case Lwm2mSecurityType.RPK:
config.key = this.lwm2mConfigFormGroup.get('key').value; config.key = this.lwm2mConfigFormGroup.get('client.key').value;
break; break;
} }
this.jsonAllConfig.client = config; this.lwm2mConfigFormGroup.get('client').patchValue(config, {emitEvent: false});
this.lwm2mConfigFormGroup.patchValue({
...config,
jsonAllConfig: this.jsonAllConfig
}, {emitEvent: false});
this.securityConfigClientUpdateValidators(type); this.securityConfigClientUpdateValidators(type);
} }
private securityConfigClientUpdateValidators = (mode: Lwm2mSecurityType): void => { private securityConfigClientUpdateValidators = (mode: Lwm2mSecurityType): void => {
switch (mode) { switch (mode) {
case Lwm2mSecurityType.NO_SEC: case Lwm2mSecurityType.NO_SEC:
this.setValidatorsNoSecX509();
this.lwm2mConfigFormGroup.get('client.cert').disable();
break;
case Lwm2mSecurityType.X509: case Lwm2mSecurityType.X509:
this.setValidatorsNoSecX509(); this.setValidatorsNoSecX509();
this.lwm2mConfigFormGroup.get('client.cert').enable();
break; break;
case Lwm2mSecurityType.PSK: case Lwm2mSecurityType.PSK:
this.lenMaxKeyClient = LEN_MAX_PSK; this.lenMaxKeyClient = LEN_MAX_PSK;
this.setValidatorsPskRpk(mode); this.setValidatorsPskRpk(mode);
this.lwm2mConfigFormGroup.get('client.identity').enable();
break; break;
case Lwm2mSecurityType.RPK: case Lwm2mSecurityType.RPK:
this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK;
this.setValidatorsPskRpk(mode); this.setValidatorsPskRpk(mode);
this.lwm2mConfigFormGroup.get('client.identity').disable();
break; break;
} }
this.lwm2mConfigFormGroup.get('identity').updateValueAndValidity({emitEvent: false}); this.lwm2mConfigFormGroup.get('client.identity').updateValueAndValidity({emitEvent: false});
this.lwm2mConfigFormGroup.get('key').updateValueAndValidity({emitEvent: false}); this.lwm2mConfigFormGroup.get('client.key').updateValueAndValidity({emitEvent: false});
} }
private setValidatorsNoSecX509 = (): void => { private setValidatorsNoSecX509 = (): void => {
this.lwm2mConfigFormGroup.get('identity').setValidators([]); this.lwm2mConfigFormGroup.get('client.identity').clearValidators();
this.lwm2mConfigFormGroup.get('key').setValidators([]); this.lwm2mConfigFormGroup.get('client.key').clearValidators();
this.lwm2mConfigFormGroup.get('client.identity').disable({emitEvent: false});
this.lwm2mConfigFormGroup.get('client.key').disable({emitEvent: false});
} }
private setValidatorsPskRpk = (mode: Lwm2mSecurityType): void => { private setValidatorsPskRpk = (mode: Lwm2mSecurityType): void => {
if (mode === Lwm2mSecurityType.PSK) { if (mode === Lwm2mSecurityType.PSK) {
this.lwm2mConfigFormGroup.get('identity').setValidators([Validators.required]); this.lwm2mConfigFormGroup.get('client.identity').setValidators([Validators.required]);
} else { } else {
this.lwm2mConfigFormGroup.get('identity').setValidators([]); this.lwm2mConfigFormGroup.get('client.identity').clearValidators();
}
this.lwm2mConfigFormGroup.get('key').setValidators([Validators.required,
Validators.pattern(KEY_REGEXP_HEX_DEC),
Validators.maxLength(this.lenMaxKeyClient), Validators.minLength(this.lenMaxKeyClient)]);
}
tabChanged = (tabChangeEvent: MatTabChangeEvent): void => {
if (this.tabIndexPrevious !== tabChangeEvent.index) {
this.upDateValueToJson();
} }
this.tabIndexPrevious = tabChangeEvent.index; this.lwm2mConfigFormGroup.get('client.key').setValidators([
Validators.required,
Validators.pattern(KEY_REGEXP_HEX_DEC),
Validators.maxLength(this.lenMaxKeyClient),
Validators.minLength(this.lenMaxKeyClient)
]);
this.lwm2mConfigFormGroup.get('client.key').enable({emitEvent: false});
this.lwm2mConfigFormGroup.get('client.cert').disable({emitEvent: false});
} }
private upDateValueToJson(): void { private initLwm2mConfigForm = (): FormGroup => {
switch (this.tabIndexPrevious) {
case 0:
this.upDateValueToJsonTab0();
break;
case 1:
this.upDateValueToJsonTab1();
break;
}
}
private upDateValueToJsonTab0 = (): void => {
if (this.lwm2mConfigFormGroup.get('identity').dirty && this.lwm2mConfigFormGroup.get('identity').valid ||
this.lwm2mConfigFormGroup.get('key').dirty && this.lwm2mConfigFormGroup.get('key').valid) {
this.updateBootstrapSettings();
this.upDateJsonAllConfig();
}
}
private upDateValueToJsonTab1 = (): void => {
const bootstrap = this.lwm2mConfigFormGroup.get('bootstrapServer').value;
if (bootstrap !== null
&& this.lwm2mConfigFormGroup.get('bootstrapServer').dirty
&& this.lwm2mConfigFormGroup.get('bootstrapServer').valid) {
this.jsonAllConfig.bootstrap.bootstrapServer = bootstrap;
this.upDateJsonAllConfig();
}
const serverConfig = this.lwm2mConfigFormGroup.get('lwm2mServer').value;
if (serverConfig !== null
&& this.lwm2mConfigFormGroup.get('lwm2mServer').dirty
&& this.lwm2mConfigFormGroup.get('lwm2mServer').valid) {
this.jsonAllConfig.bootstrap.lwm2mServer = serverConfig;
this.upDateJsonAllConfig();
}
}
private updateBootstrapSettings() {
const securityMode = 'securityMode';
this.jsonAllConfig.client.identity = this.lwm2mConfigFormGroup.get('identity').value;
this.jsonAllConfig.client.key = this.lwm2mConfigFormGroup.get('key').value;
if (this.lwm2mConfigFormGroup.get('bootstrapServer').value[securityMode] === Lwm2mSecurityType.PSK) {
this.jsonAllConfig.bootstrap.bootstrapServer.clientPublicKeyOrId = this.jsonAllConfig.client.identity;
this.jsonAllConfig.bootstrap.bootstrapServer.clientSecretKey = this.jsonAllConfig.client.key;
this.lwm2mConfigFormGroup.get('bootstrapServer').patchValue(this.jsonAllConfig.bootstrap.bootstrapServer, {emitEvent: false});
}
if (this.lwm2mConfigFormGroup.get('lwm2mServer').value[securityMode] === Lwm2mSecurityType.PSK) {
this.jsonAllConfig.bootstrap.lwm2mServer.clientPublicKeyOrId = this.jsonAllConfig.client.identity;
this.jsonAllConfig.bootstrap.lwm2mServer.clientSecretKey = this.jsonAllConfig.client.key;
this.lwm2mConfigFormGroup.get('lwm2mServer').patchValue(this.jsonAllConfig.bootstrap.lwm2mServer, {emitEvent: false});
}
}
private upDateJsonAllConfig = (): void => {
this.lwm2mConfigFormGroup.patchValue({
jsonAllConfig: this.jsonAllConfig
}, {emitEvent: false});
}
private initLwm2mConfigFormGroup = (): FormGroup => {
if (this.jsonAllConfig.client.securityConfigClientMode === Lwm2mSecurityType.PSK) {
this.data.endPoint = this.jsonAllConfig.client.endpoint;
}
const formGroup = this.fb.group({ const formGroup = this.fb.group({
securityConfigClientMode: [this.jsonAllConfig.client.securityConfigClientMode], client: this.fb.group({
identity: [''], endpoint: ['', Validators.required],
key: [''], securityConfigClientMode: [Lwm2mSecurityType.NO_SEC],
x509: [false], identity: [{value: '', disabled: true}],
bootstrapServer: [this.jsonAllConfig.bootstrap.bootstrapServer], key: [{value: '', disabled: true}],
lwm2mServer: [this.jsonAllConfig.bootstrap.lwm2mServer], cert: [{value: '', disabled: true}]
endPoint: [this.data.endPoint], }),
jsonAllConfig: [this.jsonAllConfig] bootstrap: this.fb.group({
bootstrapServer: [getDefaultServerSecurityConfig()],
lwm2mServer: [getDefaultServerSecurityConfig()]
})
}); });
formGroup.get('securityConfigClientMode').valueChanges.pipe( formGroup.get('client.securityConfigClientMode').valueChanges.pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe((type) => { ).subscribe((type) => {
this.securityConfigClientModeChanged(type); this.securityConfigClientModeChanged(type);
}); });
formGroup.get('endPoint').valueChanges.pipe( formGroup.valueChanges.pipe(
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe((endpoint) => { ).subscribe((value) => {
if (formGroup.get('securityConfigClientMode').value === Lwm2mSecurityType.PSK) { this.propagateChange(JSON.stringify(value));
this.jsonAllConfig.client.endpoint = endpoint;
this.upDateJsonAllConfig();
}
}); });
return formGroup; return formGroup;
} }
save(): void {
this.upDateValueToJson();
this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value.split('\'').join('');
this.data.jsonAllConfig = this.jsonAllConfig;
if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === Lwm2mSecurityType.PSK) {
this.data.endPoint = this.data.jsonAllConfig.client.identity;
}
this.dialogRef.close(this.data);
}
cancel(): void {
this.dialogRef.close(undefined);
}
} }

99
ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts

@ -14,21 +14,12 @@
/// limitations under the License. /// limitations under the License.
/// ///
export const JSON_ALL_CONFIG = 'jsonAllConfig';
export const END_POINT = 'endPoint';
export const DEFAULT_END_POINT = 'default_client_lwm2m_end_point_no_sec';
export const LEN_MAX_PSK = 64; export const LEN_MAX_PSK = 64;
export const LEN_MAX_PRIVATE_KEY = 134; export const LEN_MAX_PRIVATE_KEY = 134;
export const LEN_MAX_PUBLIC_KEY_RPK = 182; export const LEN_MAX_PUBLIC_KEY_RPK = 182;
export const LEN_MAX_PUBLIC_KEY_X509 = 3000; export const LEN_MAX_PUBLIC_KEY_X509 = 3000;
export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/;
export interface DeviceCredentialsDialogLwm2mData {
jsonAllConfig?: Lwm2mSecurityConfigModels;
endPoint?: string;
}
export enum Lwm2mSecurityType { export enum Lwm2mSecurityType {
PSK = 'PSK', PSK = 'PSK',
RPK = 'RPK', RPK = 'RPK',
@ -48,9 +39,9 @@ export const Lwm2mSecurityTypeTranslationMap = new Map<Lwm2mSecurityType, string
export interface ClientSecurityConfig { export interface ClientSecurityConfig {
securityConfigClientMode: Lwm2mSecurityType; securityConfigClientMode: Lwm2mSecurityType;
endpoint: string; endpoint: string;
identity: string; identity?: string;
key: string; key?: string;
x509: boolean; cert?: string;
} }
export interface ServerSecurityConfig { export interface ServerSecurityConfig {
@ -69,85 +60,29 @@ export interface Lwm2mSecurityConfigModels {
bootstrap: BootstrapSecurityConfig; bootstrap: BootstrapSecurityConfig;
} }
export function getClientSecurityConfig(securityConfigMode: Lwm2mSecurityType, endPoint = ''): ClientSecurityConfig { export function getDefaultClientSecurityConfig(securityConfigMode: Lwm2mSecurityType, endPoint = ''): ClientSecurityConfig {
const security = getDefaultClientSecurityConfig(securityConfigMode); let security = {
securityConfigClientMode: securityConfigMode,
endpoint: endPoint,
identity: '',
key: '',
};
switch (securityConfigMode) { switch (securityConfigMode) {
case Lwm2mSecurityType.X509:
security = { ...security, ...{cert: ''}};
break;
case Lwm2mSecurityType.PSK: case Lwm2mSecurityType.PSK:
security.endpoint = endPoint; security = { ...security, ...{identity: endPoint, key: ''}};
security.identity = endPoint;
break; break;
case Lwm2mSecurityType.X509: case Lwm2mSecurityType.RPK:
security.x509 = true; security = { ...security, ...{key: ''}};
break; break;
} }
return security; return security;
} }
export function getDefaultClientSecurityConfig(securityConfigMode: Lwm2mSecurityType): ClientSecurityConfig {
return {
securityConfigClientMode: securityConfigMode,
endpoint: '',
identity: '',
key: '',
x509: false
};
}
export function getDefaultServerSecurityConfig(): ServerSecurityConfig { export function getDefaultServerSecurityConfig(): ServerSecurityConfig {
return { return {
securityMode: Lwm2mSecurityType.NO_SEC, securityMode: Lwm2mSecurityType.NO_SEC
clientPublicKeyOrId: '',
clientSecretKey: ''
};
}
function getDefaultBootstrapSecurityConfig(): BootstrapSecurityConfig {
return {
bootstrapServer: getDefaultServerSecurityConfig(),
lwm2mServer: getDefaultServerSecurityConfig()
}; };
} }
export function getDefaultSecurityConfig(): Lwm2mSecurityConfigModels {
const securityConfigModels = {
client: getClientSecurityConfig(Lwm2mSecurityType.NO_SEC),
bootstrap: getDefaultBootstrapSecurityConfig()
};
return securityConfigModels;
}
const isSecurityConfigModels = (p: any): boolean =>
p.hasOwnProperty('client') &&
isClientSecurityConfigType(p.client) &&
p.hasOwnProperty('bootstrap') &&
isBootstrapSecurityConfig(p.bootstrap);
const isClientSecurityConfigType = (p: any): boolean =>
p.hasOwnProperty('securityConfigClientMode') &&
p.hasOwnProperty('endpoint') &&
p.hasOwnProperty('identity') &&
p.hasOwnProperty('key') &&
p.hasOwnProperty('x509');
const isBootstrapSecurityConfig = (p: any): boolean =>
p.hasOwnProperty('bootstrapServer') &&
isServerSecurityConfig(p.bootstrapServer) &&
p.hasOwnProperty('lwm2mServer') &&
isServerSecurityConfig(p.lwm2mServer);
const isServerSecurityConfig = (p: any): boolean =>
p.hasOwnProperty('securityMode') &&
p.hasOwnProperty('clientPublicKeyOrId') &&
p.hasOwnProperty('clientSecretKey');
export function validateSecurityConfig(config: string): boolean {
try {
const securityConfig = JSON.parse(config);
return isSecurityConfigModels(securityConfig);
} catch (e) {
return false;
}
}

12
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -917,15 +917,7 @@
"access-token-invalid": "Access token length must be from 1 to 20 characters.", "access-token-invalid": "Access token length must be from 1 to 20 characters.",
"rsa-key": "RSA public key", "rsa-key": "RSA public key",
"rsa-key-required": "RSA public key is required.", "rsa-key-required": "RSA public key is required.",
"lwm2m-key": "LwM2M Security config key",
"lwm2m-key-required": "LwM2M Security config key is required.",
"lwm2m-value": "LwM2M Security config", "lwm2m-value": "LwM2M Security config",
"lwm2m-value-required": "LwM2M Security config value is required.",
"lwm2m-value-format-error": "Security config value must be in LwM2M Security config format.",
"lwm2m-endpoint": "Client endpoint/identity",
"lwm2m-security-info": "Security Config Info",
"lwm2m-value-edit": "Edit Security config",
"lwm2m-credentials-value-tip": "Edit security config json editor",
"lwm2m-security-config": { "lwm2m-security-config": {
"identity": "Client Identity", "identity": "Client Identity",
"identity-required": "Client Identity is required.", "identity-required": "Client Identity is required.",
@ -949,7 +941,9 @@
"client-secret-key-required": "Client Secret Key is required.", "client-secret-key-required": "Client Secret Key is required.",
"client-secret-key-pattern": "Client Secret Key must be hexadecimal format.", "client-secret-key-pattern": "Client Secret Key must be hexadecimal format.",
"client-secret-key-length": "Client Secret Key must be {{ count }} characters.", "client-secret-key-length": "Client Secret Key must be {{ count }} characters.",
"config-json-tab": "Json Client Security Config" "config-json-tab": "Json Client Security Config",
"client-public-key": "Client public key",
"client-public-key-hint": "If client public key is empty, the trusted certificate will be used"
}, },
"client-id": "Client ID", "client-id": "Client ID",
"client-id-pattern": "Contains invalid character.", "client-id-pattern": "Contains invalid character.",

Loading…
Cancel
Save