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);
if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) {
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText());
if (enableTls && jsonConfig.has("tlsVersion") && !jsonConfig.get("tlsVersion").isNull()) {
String tlsVersion = jsonConfig.get("tlsVersion").asText();
if (StringUtils.isNoneEmpty(tlsVersion)) {
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", tlsVersion);
}
}
return javaMailProperties;
}

View File

@ -96,6 +96,10 @@ export class AlarmService {
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,
config?: RequestConfig): Observable<PageData<AlarmInfo>> {
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 { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import { isDefined } from '@core/utils';
@Injectable({
providedIn: 'root'
@ -67,4 +68,12 @@ export class UserService {
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 }}
</mat-error>
</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 }}
</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-label translate>common.username</mat-label>
<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>;
smtpProtocols = ['smtp', 'smtps'];
tlsVersions = ['TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'];
constructor(protected store: Store<AppState>,
private router: Router,
private adminService: AdminService,
@ -67,6 +69,7 @@ export class MailServerComponent extends PageComponent implements OnInit, HasCon
Validators.pattern(/^[0-9]{1,6}$/),
Validators.maxLength(6)]],
enableTls: ['false'],
tlsVersion: [],
username: [''],
password: ['']
});

View File

@ -30,6 +30,28 @@
<mat-card-content style="padding-top: 16px;">
<form #securitySettingsForm="ngForm" [formGroup]="securitySettingsFormGroup" (ngSubmit)="save()">
<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-header>
<mat-panel-title>
@ -105,6 +127,16 @@
{{ 'admin.password-expiration-period-days-range' | translate }}
</mat-error>
</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>
</mat-expansion-panel>
<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() {
this.securitySettingsFormGroup = this.fb.group({
maxFailedLoginAttempts: [null, [Validators.min(0)]],
userLockoutNotificationEmail: ['', []],
passwordPolicy: this.fb.group(
{
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)],
minimumDigits: [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>
<mat-card class="profile-card">
<mat-card-title>
<div fxLayout="column">
<div fxLayout="row">
<div fxFlex fxLayout="column">
<span class="mat-headline" translate>profile.profile</span>
<span class="profile-email" style='opacity: 0.7;'>{{ profile ? profile.get('email').value : '' }}</span>
</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-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>

View File

@ -28,5 +28,15 @@
font-size: 16px;
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">
<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"
[disabled]="(isLoading$ | async)"
(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 { map } from 'rxjs/operators';
import { Authority } from '@shared/models/authority.enum';
import { isUndefined } from '@core/utils';
@Component({
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 {
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 {
switch (action.action) {
case 'loginAsUser':
@ -224,6 +241,12 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
case 'resendActivation':
this.resendActivation(action.event, action.entity);
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;
}

View File

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