Showing
13 changed files
with
135 additions
and
9 deletions
... | ... | @@ -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 | ... | ... |