Commit 5531f92c6a920cec0e0481c21a9b28ce3795787e

Authored by Igor Kulikov
1 parent 05f0619a

Merge security functionality

... ... @@ -112,8 +112,11 @@ public class DefaultMailService implements MailService {
112 112 }
113 113 }
114 114 javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls);
115   - if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) {
116   - javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText());
  115 + if (enableTls && jsonConfig.has("tlsVersion") && !jsonConfig.get("tlsVersion").isNull()) {
  116 + String tlsVersion = jsonConfig.get("tlsVersion").asText();
  117 + if (StringUtils.isNoneEmpty(tlsVersion)) {
  118 + javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", tlsVersion);
  119 + }
117 120 }
118 121 return javaMailProperties;
119 122 }
... ...
... ... @@ -96,6 +96,10 @@ export class AlarmService {
96 96 return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config));
97 97 }
98 98
  99 + public deleteAlarm(alarmId: string, config?: RequestConfig): Observable<void> {
  100 + return this.http.delete<void>(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config));
  101 + }
  102 +
99 103 public getAlarms(query: AlarmQuery,
100 104 config?: RequestConfig): Observable<PageData<AlarmInfo>> {
101 105 return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`,
... ...
... ... @@ -21,6 +21,7 @@ import { Observable } from 'rxjs';
21 21 import { HttpClient } from '@angular/common/http';
22 22 import { PageLink } from '@shared/models/page/page-link';
23 23 import { PageData } from '@shared/models/page/page-data';
  24 +import { isDefined } from '@core/utils';
24 25
25 26 @Injectable({
26 27 providedIn: 'root'
... ... @@ -67,4 +68,12 @@ export class UserService {
67 68 return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptionsFromConfig(config));
68 69 }
69 70
  71 + public setUserCredentialsEnabled(userId: string, userCredentialsEnabled?: boolean, config?: RequestConfig): Observable<any> {
  72 + let url = `/api/user/${userId}/userCredentialsEnabled`;
  73 + if (isDefined(userCredentialsEnabled)) {
  74 + url += `?userCredentialsEnabled=${userCredentialsEnabled}`;
  75 + }
  76 + return this.http.post<User>(url, null, defaultHttpOptionsFromConfig(config));
  77 + }
  78 +
70 79 }
... ...
... ... @@ -76,9 +76,17 @@
76 76 {{ 'admin.timeout-invalid' | translate }}
77 77 </mat-error>
78 78 </mat-form-field>
79   - <tb-checkbox formControlName="enableTls" trueValue="true" falseValue="false">
  79 + <tb-checkbox formControlName="enableTls" trueValue="true" falseValue="false" style="display: block; padding-bottom: 16px;">
80 80 {{ 'admin.enable-tls' | translate }}
81 81 </tb-checkbox>
  82 + <mat-form-field class="mat-block" *ngIf="mailSettings.get('enableTls').value === 'true'">
  83 + <mat-label translate>admin.tls-version</mat-label>
  84 + <mat-select formControlName="tlsVersion">
  85 + <mat-option *ngFor="let tlsVersion of tlsVersions" [value]="tlsVersion">
  86 + {{ tlsVersion }}
  87 + </mat-option>
  88 + </mat-select>
  89 + </mat-form-field>
82 90 <mat-form-field class="mat-block">
83 91 <mat-label translate>common.username</mat-label>
84 92 <input matInput formControlName="username" placeholder="{{ 'common.enter-username' | translate }}"/>
... ...
... ... @@ -37,6 +37,8 @@ export class MailServerComponent extends PageComponent implements OnInit, HasCon
37 37 adminSettings: AdminSettings<MailServerSettings>;
38 38 smtpProtocols = ['smtp', 'smtps'];
39 39
  40 + tlsVersions = ['TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'];
  41 +
40 42 constructor(protected store: Store<AppState>,
41 43 private router: Router,
42 44 private adminService: AdminService,
... ... @@ -67,6 +69,7 @@ export class MailServerComponent extends PageComponent implements OnInit, HasCon
67 69 Validators.pattern(/^[0-9]{1,6}$/),
68 70 Validators.maxLength(6)]],
69 71 enableTls: ['false'],
  72 + tlsVersion: [],
70 73 username: [''],
71 74 password: ['']
72 75 });
... ...
... ... @@ -33,6 +33,28 @@
33 33 <mat-expansion-panel [expanded]="true">
34 34 <mat-expansion-panel-header>
35 35 <mat-panel-title>
  36 + <div class="tb-panel-title" translate>admin.general-policy</div>
  37 + </mat-panel-title>
  38 + </mat-expansion-panel-header>
  39 + <mat-form-field class="mat-block">
  40 + <mat-label translate>admin.max-failed-login-attempts</mat-label>
  41 + <input matInput type="number"
  42 + formControlName="maxFailedLoginAttempts"
  43 + step="1"
  44 + min="0"/>
  45 + <mat-error *ngIf="securitySettingsFormGroup.get('maxFailedLoginAttempts').hasError('min')">
  46 + {{ 'admin.minimum-max-failed-login-attempts-range' | translate }}
  47 + </mat-error>
  48 + </mat-form-field>
  49 + <mat-form-field class="mat-block">
  50 + <mat-label translate>admin.user-lockout-notification-email</mat-label>
  51 + <input matInput type="email"
  52 + formControlName="userLockoutNotificationEmail"/>
  53 + </mat-form-field>
  54 + </mat-expansion-panel>
  55 + <mat-expansion-panel [expanded]="true">
  56 + <mat-expansion-panel-header>
  57 + <mat-panel-title>
36 58 <div class="tb-panel-title" translate>admin.password-policy</div>
37 59 </mat-panel-title>
38 60 </mat-expansion-panel-header>
... ... @@ -105,6 +127,16 @@
105 127 {{ 'admin.password-expiration-period-days-range' | translate }}
106 128 </mat-error>
107 129 </mat-form-field>
  130 + <mat-form-field class="mat-block">
  131 + <mat-label translate>admin.password-reuse-frequency-days</mat-label>
  132 + <input matInput type="number"
  133 + formControlName="passwordReuseFrequencyDays"
  134 + step="1"
  135 + min="0"/>
  136 + <mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy').get('passwordReuseFrequencyDays').hasError('min')">
  137 + {{ 'admin.password-reuse-frequency-days-range' | translate }}
  138 + </mat-error>
  139 + </mat-form-field>
108 140 </section>
109 141 </mat-expansion-panel>
110 142 <div fxLayout="row" fxLayoutAlign="end center" style="width: 100%;" class="layout-wrap">
... ...
... ... @@ -56,6 +56,8 @@ export class SecuritySettingsComponent extends PageComponent implements OnInit,
56 56
57 57 buildSecuritySettingsForm() {
58 58 this.securitySettingsFormGroup = this.fb.group({
  59 + maxFailedLoginAttempts: [null, [Validators.min(0)]],
  60 + userLockoutNotificationEmail: ['', []],
59 61 passwordPolicy: this.fb.group(
60 62 {
61 63 minimumLength: [null, [Validators.required, Validators.min(5), Validators.max(50)]],
... ... @@ -63,7 +65,8 @@ export class SecuritySettingsComponent extends PageComponent implements OnInit,
63 65 minimumLowercaseLetters: [null, Validators.min(0)],
64 66 minimumDigits: [null, Validators.min(0)],
65 67 minimumSpecialCharacters: [null, Validators.min(0)],
66   - passwordExpirationPeriodDays: [null, Validators.min(0)]
  68 + passwordExpirationPeriodDays: [null, Validators.min(0)],
  69 + passwordReuseFrequencyDays: [null, Validators.min(0)]
67 70 }
68 71 )
69 72 });
... ...
... ... @@ -18,11 +18,19 @@
18 18 <div>
19 19 <mat-card class="profile-card">
20 20 <mat-card-title>
21   - <div fxLayout="column">
22   - <span class="mat-headline" translate>profile.profile</span>
23   - <span class="profile-email" style='opacity: 0.7;'>{{ profile ? profile.get('email').value : '' }}</span>
  21 + <div fxLayout="row">
  22 + <div fxFlex fxLayout="column">
  23 + <span class="mat-headline" translate>profile.profile</span>
  24 + <span class="profile-email" style='opacity: 0.7;'>{{ profile ? profile.get('email').value : '' }}</span>
  25 + </div>
  26 + <div fxFlex fxLayout="column">
  27 + <span class="mat-subheader" translate>profile.last-login-time</span>
  28 + <span class="profile-last-login-ts" style='opacity: 0.7;'>{{ user?.additionalInfo?.lastLoginTs | date:'yyyy-MM-dd HH:mm:ss' }}</span>
  29 + </div>
24 30 </div>
25 31 </mat-card-title>
  32 + <mat-card-title>
  33 + </mat-card-title>
26 34 <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
27 35 </mat-progress-bar>
28 36 <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
... ...
... ... @@ -28,5 +28,15 @@
28 28 font-size: 16px;
29 29 font-weight: 400;
30 30 }
  31 + .mat-subheader {
  32 + line-height: 24px;
  33 + color: rgba(0,0,0,0.54);
  34 + font-size: 14px;
  35 + font-weight: 400;
  36 + }
  37 + .profile-last-login-ts {
  38 + font-size: 16px;
  39 + font-weight: 400;
  40 + }
31 41 }
32 42 }
... ...
... ... @@ -18,6 +18,18 @@
18 18 <div class="tb-details-buttons">
19 19 <button mat-raised-button color="primary"
20 20 [disabled]="(isLoading$ | async)"
  21 + (click)="onEntityAction($event, 'disableAccount')"
  22 + [fxShow]="!isEdit && isUserCredentialsEnabled()">
  23 + {{'user.disable-account' | translate }}
  24 + </button>
  25 + <button mat-raised-button color="primary"
  26 + [disabled]="(isLoading$ | async)"
  27 + (click)="onEntityAction($event, 'enableAccount')"
  28 + [fxShow]="!isEdit && !isUserCredentialsEnabled()">
  29 + {{'user.enable-account' | translate }}
  30 + </button>
  31 + <button mat-raised-button color="primary"
  32 + [disabled]="(isLoading$ | async)"
21 33 (click)="onEntityAction($event, 'displayActivationLink')"
22 34 [fxShow]="!isEdit">
23 35 {{'user.display-activation-link' | translate }}
... ...
... ... @@ -23,6 +23,7 @@ import { User } from '@shared/models/user.model';
23 23 import { selectAuth, selectUserDetails } from '@core/auth/auth.selectors';
24 24 import { map } from 'rxjs/operators';
25 25 import { Authority } from '@shared/models/authority.enum';
  26 +import { isUndefined } from '@core/utils';
26 27
27 28 @Component({
28 29 selector: 'tb-user',
... ... @@ -51,6 +52,14 @@ export class UserComponent extends EntityComponent<User> {
51 52 }
52 53 }
53 54
  55 + isUserCredentialsEnabled(): boolean {
  56 + if (!this.entity || !this.entity.additionalInfo || isUndefined(this.entity.additionalInfo.userCredentialsEnabled)) {
  57 + return true;
  58 + } else {
  59 + return this.entity.additionalInfo.userCredentialsEnabled === true;
  60 + }
  61 + }
  62 +
54 63 buildForm(entity: User): FormGroup {
55 64 return this.fb.group(
56 65 {
... ...
... ... @@ -213,6 +213,23 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
213 213 });
214 214 }
215 215
  216 + setUserCredentialsEnabled($event: Event, user: User, userCredentialsEnabled: boolean) {
  217 + if ($event) {
  218 + $event.stopPropagation();
  219 + }
  220 + this.userService.setUserCredentialsEnabled(user.id.id, userCredentialsEnabled).subscribe(() => {
  221 + if (!user.additionalInfo) {
  222 + user.additionalInfo = {};
  223 + }
  224 + user.additionalInfo.userCredentialsEnabled = userCredentialsEnabled;
  225 + this.store.dispatch(new ActionNotificationShow(
  226 + {
  227 + message: this.translate.instant(userCredentialsEnabled ? 'user.enable-account-message' : 'user.disable-account-message'),
  228 + type: 'success'
  229 + }));
  230 + });
  231 + }
  232 +
216 233 onUserAction(action: EntityAction<User>): boolean {
217 234 switch (action.action) {
218 235 case 'loginAsUser':
... ... @@ -224,6 +241,12 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
224 241 case 'resendActivation':
225 242 this.resendActivation(action.event, action.entity);
226 243 return true;
  244 + case 'disableAccount':
  245 + this.setUserCredentialsEnabled(action.event, action.entity, false);
  246 + return true;
  247 + case 'enableAccount':
  248 + this.setUserCredentialsEnabled(action.event, action.entity, true);
  249 + return true;
227 250 }
228 251 return false;
229 252 }
... ...
... ... @@ -48,7 +48,8 @@ export enum ActionType {
48 48 ALARM_ACK = 'ALARM_ACK',
49 49 ALARM_CLEAR = 'ALARM_CLEAR',
50 50 LOGIN = 'LOGIN',
51   - LOGOUT = 'LOGOUT'
  51 + LOGOUT = 'LOGOUT',
  52 + LOCKOUT = 'LOCKOUT'
52 53 }
53 54
54 55 export enum ActionStatus {
... ... @@ -77,7 +78,8 @@ export const actionTypeTranslations = new Map<ActionType, string>(
77 78 [ActionType.ALARM_ACK, 'audit-log.type-alarm-ack'],
78 79 [ActionType.ALARM_CLEAR, 'audit-log.type-alarm-clear'],
79 80 [ActionType.LOGIN, 'audit-log.type-login'],
80   - [ActionType.LOGOUT, 'audit-log.type-logout']
  81 + [ActionType.LOGOUT, 'audit-log.type-logout'],
  82 + [ActionType.LOCKOUT, 'audit-log.type-lockout']
81 83 ]
82 84 );
83 85
... ...