Merge security functionality

This commit is contained in:
Igor Kulikov 2020-02-20 18:50:00 +02:00
parent 05f0619a2d
commit 5531f92c6a
13 changed files with 135 additions and 9 deletions

View File

@ -112,8 +112,11 @@ public class DefaultMailService implements MailService {
} }
} }
javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls); javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls);
if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) { if (enableTls && jsonConfig.has("tlsVersion") && !jsonConfig.get("tlsVersion").isNull()) {
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText()); String tlsVersion = jsonConfig.get("tlsVersion").asText();
if (StringUtils.isNoneEmpty(tlsVersion)) {
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", tlsVersion);
}
} }
return javaMailProperties; return javaMailProperties;
} }

View File

@ -96,6 +96,10 @@ export class AlarmService {
return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config)); return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config));
} }
public deleteAlarm(alarmId: string, config?: RequestConfig): Observable<void> {
return this.http.delete<void>(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config));
}
public getAlarms(query: AlarmQuery, public getAlarms(query: AlarmQuery,
config?: RequestConfig): Observable<PageData<AlarmInfo>> { config?: RequestConfig): Observable<PageData<AlarmInfo>> {
return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`, return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`,

View File

@ -21,6 +21,7 @@ import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { isDefined } from '@core/utils';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -67,4 +68,12 @@ export class UserService {
return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptionsFromConfig(config)); return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptionsFromConfig(config));
} }
public setUserCredentialsEnabled(userId: string, userCredentialsEnabled?: boolean, config?: RequestConfig): Observable<any> {
let url = `/api/user/${userId}/userCredentialsEnabled`;
if (isDefined(userCredentialsEnabled)) {
url += `?userCredentialsEnabled=${userCredentialsEnabled}`;
}
return this.http.post<User>(url, null, defaultHttpOptionsFromConfig(config));
}
} }

View File

@ -76,9 +76,17 @@
{{ 'admin.timeout-invalid' | translate }} {{ 'admin.timeout-invalid' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<tb-checkbox formControlName="enableTls" trueValue="true" falseValue="false"> <tb-checkbox formControlName="enableTls" trueValue="true" falseValue="false" style="display: block; padding-bottom: 16px;">
{{ 'admin.enable-tls' | translate }} {{ 'admin.enable-tls' | translate }}
</tb-checkbox> </tb-checkbox>
<mat-form-field class="mat-block" *ngIf="mailSettings.get('enableTls').value === 'true'">
<mat-label translate>admin.tls-version</mat-label>
<mat-select formControlName="tlsVersion">
<mat-option *ngFor="let tlsVersion of tlsVersions" [value]="tlsVersion">
{{ tlsVersion }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>common.username</mat-label> <mat-label translate>common.username</mat-label>
<input matInput formControlName="username" placeholder="{{ 'common.enter-username' | translate }}"/> <input matInput formControlName="username" placeholder="{{ 'common.enter-username' | translate }}"/>

View File

@ -37,6 +37,8 @@ export class MailServerComponent extends PageComponent implements OnInit, HasCon
adminSettings: AdminSettings<MailServerSettings>; adminSettings: AdminSettings<MailServerSettings>;
smtpProtocols = ['smtp', 'smtps']; smtpProtocols = ['smtp', 'smtps'];
tlsVersions = ['TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'];
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private router: Router, private router: Router,
private adminService: AdminService, private adminService: AdminService,
@ -67,6 +69,7 @@ export class MailServerComponent extends PageComponent implements OnInit, HasCon
Validators.pattern(/^[0-9]{1,6}$/), Validators.pattern(/^[0-9]{1,6}$/),
Validators.maxLength(6)]], Validators.maxLength(6)]],
enableTls: ['false'], enableTls: ['false'],
tlsVersion: [],
username: [''], username: [''],
password: [''] password: ['']
}); });

View File

@ -30,6 +30,28 @@
<mat-card-content style="padding-top: 16px;"> <mat-card-content style="padding-top: 16px;">
<form #securitySettingsForm="ngForm" [formGroup]="securitySettingsFormGroup" (ngSubmit)="save()"> <form #securitySettingsForm="ngForm" [formGroup]="securitySettingsFormGroup" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async">
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="tb-panel-title" translate>admin.general-policy</div>
</mat-panel-title>
</mat-expansion-panel-header>
<mat-form-field class="mat-block">
<mat-label translate>admin.max-failed-login-attempts</mat-label>
<input matInput type="number"
formControlName="maxFailedLoginAttempts"
step="1"
min="0"/>
<mat-error *ngIf="securitySettingsFormGroup.get('maxFailedLoginAttempts').hasError('min')">
{{ 'admin.minimum-max-failed-login-attempts-range' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>admin.user-lockout-notification-email</mat-label>
<input matInput type="email"
formControlName="userLockoutNotificationEmail"/>
</mat-form-field>
</mat-expansion-panel>
<mat-expansion-panel [expanded]="true"> <mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
@ -105,6 +127,16 @@
{{ 'admin.password-expiration-period-days-range' | translate }} {{ 'admin.password-expiration-period-days-range' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>admin.password-reuse-frequency-days</mat-label>
<input matInput type="number"
formControlName="passwordReuseFrequencyDays"
step="1"
min="0"/>
<mat-error *ngIf="securitySettingsFormGroup.get('passwordPolicy').get('passwordReuseFrequencyDays').hasError('min')">
{{ 'admin.password-reuse-frequency-days-range' | translate }}
</mat-error>
</mat-form-field>
</section> </section>
</mat-expansion-panel> </mat-expansion-panel>
<div fxLayout="row" fxLayoutAlign="end center" style="width: 100%;" class="layout-wrap"> <div fxLayout="row" fxLayoutAlign="end center" style="width: 100%;" class="layout-wrap">

View File

@ -56,6 +56,8 @@ export class SecuritySettingsComponent extends PageComponent implements OnInit,
buildSecuritySettingsForm() { buildSecuritySettingsForm() {
this.securitySettingsFormGroup = this.fb.group({ this.securitySettingsFormGroup = this.fb.group({
maxFailedLoginAttempts: [null, [Validators.min(0)]],
userLockoutNotificationEmail: ['', []],
passwordPolicy: this.fb.group( passwordPolicy: this.fb.group(
{ {
minimumLength: [null, [Validators.required, Validators.min(5), Validators.max(50)]], minimumLength: [null, [Validators.required, Validators.min(5), Validators.max(50)]],
@ -63,7 +65,8 @@ export class SecuritySettingsComponent extends PageComponent implements OnInit,
minimumLowercaseLetters: [null, Validators.min(0)], minimumLowercaseLetters: [null, Validators.min(0)],
minimumDigits: [null, Validators.min(0)], minimumDigits: [null, Validators.min(0)],
minimumSpecialCharacters: [null, Validators.min(0)], minimumSpecialCharacters: [null, Validators.min(0)],
passwordExpirationPeriodDays: [null, Validators.min(0)] passwordExpirationPeriodDays: [null, Validators.min(0)],
passwordReuseFrequencyDays: [null, Validators.min(0)]
} }
) )
}); });

View File

@ -18,10 +18,18 @@
<div> <div>
<mat-card class="profile-card"> <mat-card class="profile-card">
<mat-card-title> <mat-card-title>
<div fxLayout="column"> <div fxLayout="row">
<div fxFlex fxLayout="column">
<span class="mat-headline" translate>profile.profile</span> <span class="mat-headline" translate>profile.profile</span>
<span class="profile-email" style='opacity: 0.7;'>{{ profile ? profile.get('email').value : '' }}</span> <span class="profile-email" style='opacity: 0.7;'>{{ profile ? profile.get('email').value : '' }}</span>
</div> </div>
<div fxFlex fxLayout="column">
<span class="mat-subheader" translate>profile.last-login-time</span>
<span class="profile-last-login-ts" style='opacity: 0.7;'>{{ user?.additionalInfo?.lastLoginTs | date:'yyyy-MM-dd HH:mm:ss' }}</span>
</div>
</div>
</mat-card-title>
<mat-card-title>
</mat-card-title> </mat-card-title>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar> </mat-progress-bar>

View File

@ -28,5 +28,15 @@
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
} }
.mat-subheader {
line-height: 24px;
color: rgba(0,0,0,0.54);
font-size: 14px;
font-weight: 400;
}
.profile-last-login-ts {
font-size: 16px;
font-weight: 400;
}
} }
} }

View File

@ -16,6 +16,18 @@
--> -->
<div class="tb-details-buttons"> <div class="tb-details-buttons">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'disableAccount')"
[fxShow]="!isEdit && isUserCredentialsEnabled()">
{{'user.disable-account' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'enableAccount')"
[fxShow]="!isEdit && !isUserCredentialsEnabled()">
{{'user.enable-account' | translate }}
</button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'displayActivationLink')" (click)="onEntityAction($event, 'displayActivationLink')"

View File

@ -23,6 +23,7 @@ import { User } from '@shared/models/user.model';
import { selectAuth, selectUserDetails } from '@core/auth/auth.selectors'; import { selectAuth, selectUserDetails } from '@core/auth/auth.selectors';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { isUndefined } from '@core/utils';
@Component({ @Component({
selector: 'tb-user', selector: 'tb-user',
@ -51,6 +52,14 @@ export class UserComponent extends EntityComponent<User> {
} }
} }
isUserCredentialsEnabled(): boolean {
if (!this.entity || !this.entity.additionalInfo || isUndefined(this.entity.additionalInfo.userCredentialsEnabled)) {
return true;
} else {
return this.entity.additionalInfo.userCredentialsEnabled === true;
}
}
buildForm(entity: User): FormGroup { buildForm(entity: User): FormGroup {
return this.fb.group( return this.fb.group(
{ {

View File

@ -213,6 +213,23 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
}); });
} }
setUserCredentialsEnabled($event: Event, user: User, userCredentialsEnabled: boolean) {
if ($event) {
$event.stopPropagation();
}
this.userService.setUserCredentialsEnabled(user.id.id, userCredentialsEnabled).subscribe(() => {
if (!user.additionalInfo) {
user.additionalInfo = {};
}
user.additionalInfo.userCredentialsEnabled = userCredentialsEnabled;
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant(userCredentialsEnabled ? 'user.enable-account-message' : 'user.disable-account-message'),
type: 'success'
}));
});
}
onUserAction(action: EntityAction<User>): boolean { onUserAction(action: EntityAction<User>): boolean {
switch (action.action) { switch (action.action) {
case 'loginAsUser': case 'loginAsUser':
@ -224,6 +241,12 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
case 'resendActivation': case 'resendActivation':
this.resendActivation(action.event, action.entity); this.resendActivation(action.event, action.entity);
return true; return true;
case 'disableAccount':
this.setUserCredentialsEnabled(action.event, action.entity, false);
return true;
case 'enableAccount':
this.setUserCredentialsEnabled(action.event, action.entity, true);
return true;
} }
return false; return false;
} }

View File

@ -48,7 +48,8 @@ export enum ActionType {
ALARM_ACK = 'ALARM_ACK', ALARM_ACK = 'ALARM_ACK',
ALARM_CLEAR = 'ALARM_CLEAR', ALARM_CLEAR = 'ALARM_CLEAR',
LOGIN = 'LOGIN', LOGIN = 'LOGIN',
LOGOUT = 'LOGOUT' LOGOUT = 'LOGOUT',
LOCKOUT = 'LOCKOUT'
} }
export enum ActionStatus { export enum ActionStatus {
@ -77,7 +78,8 @@ export const actionTypeTranslations = new Map<ActionType, string>(
[ActionType.ALARM_ACK, 'audit-log.type-alarm-ack'], [ActionType.ALARM_ACK, 'audit-log.type-alarm-ack'],
[ActionType.ALARM_CLEAR, 'audit-log.type-alarm-clear'], [ActionType.ALARM_CLEAR, 'audit-log.type-alarm-clear'],
[ActionType.LOGIN, 'audit-log.type-login'], [ActionType.LOGIN, 'audit-log.type-login'],
[ActionType.LOGOUT, 'audit-log.type-logout'] [ActionType.LOGOUT, 'audit-log.type-logout'],
[ActionType.LOCKOUT, 'audit-log.type-lockout']
] ]
); );