]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts
import 15.2.4
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / core / auth / user-form / user-form.component.ts
CommitLineData
11fdf7f2 1import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
9f95a23c 2import { Validators } from '@angular/forms';
11fdf7f2
TL
3import { ActivatedRoute, Router } from '@angular/router';
4
5import { I18n } from '@ngx-translate/i18n-polyfill';
6import * as _ from 'lodash';
7import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
9f95a23c 8import { forkJoin as observableForkJoin } from 'rxjs';
11fdf7f2
TL
9
10import { AuthService } from '../../../shared/api/auth.service';
11import { RoleService } from '../../../shared/api/role.service';
9f95a23c 12import { SettingsService } from '../../../shared/api/settings.service';
11fdf7f2
TL
13import { UserService } from '../../../shared/api/user.service';
14import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component';
15import { SelectMessages } from '../../../shared/components/select/select-messages.model';
16import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
9f95a23c 17import { Icons } from '../../../shared/enum/icons.enum';
11fdf7f2 18import { NotificationType } from '../../../shared/enum/notification-type.enum';
9f95a23c 19import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
11fdf7f2
TL
20import { CdFormGroup } from '../../../shared/forms/cd-form-group';
21import { CdValidators } from '../../../shared/forms/cd-validators';
9f95a23c 22import { CdPwdExpirationSettings } from '../../../shared/models/cd-pwd-expiration-settings';
11fdf7f2
TL
23import { AuthStorageService } from '../../../shared/services/auth-storage.service';
24import { NotificationService } from '../../../shared/services/notification.service';
9f95a23c 25import { PasswordPolicyService } from '../../../shared/services/password-policy.service';
11fdf7f2
TL
26import { UserFormMode } from './user-form-mode.enum';
27import { UserFormRoleModel } from './user-form-role.model';
28import { UserFormModel } from './user-form.model';
29
30@Component({
31 selector: 'cd-user-form',
32 templateUrl: './user-form.component.html',
33 styleUrls: ['./user-form.component.scss']
34})
35export class UserFormComponent implements OnInit {
9f95a23c 36 @ViewChild('removeSelfUserReadUpdatePermissionTpl', { static: true })
11fdf7f2
TL
37 removeSelfUserReadUpdatePermissionTpl: TemplateRef<any>;
38
39 modalRef: BsModalRef;
40
41 userForm: CdFormGroup;
42 response: UserFormModel;
43
44 userFormMode = UserFormMode;
45 mode: UserFormMode;
46 allRoles: Array<UserFormRoleModel>;
9f95a23c 47 messages = new SelectMessages({ empty: this.i18n('There are no roles.') }, this.i18n);
11fdf7f2
TL
48 action: string;
49 resource: string;
9f95a23c
TL
50 passwordPolicyHelpText = '';
51 passwordStrengthLevelClass: string;
52 passwordValuation: string;
53 icons = Icons;
54 minDate: Date;
55 bsConfig = {
56 dateInputFormat: 'YYYY-MM-DD',
57 containerClass: 'theme-default'
58 };
59 pwdExpirationSettings: CdPwdExpirationSettings;
11fdf7f2
TL
60
61 constructor(
62 private authService: AuthService,
63 private authStorageService: AuthStorageService,
64 private route: ActivatedRoute,
65 private router: Router,
66 private modalService: BsModalService,
67 private roleService: RoleService,
68 private userService: UserService,
69 private notificationService: NotificationService,
70 private i18n: I18n,
9f95a23c
TL
71 public actionLabels: ActionLabelsI18n,
72 private passwordPolicyService: PasswordPolicyService,
73 private formBuilder: CdFormBuilder,
74 private settingsService: SettingsService
11fdf7f2
TL
75 ) {
76 this.resource = this.i18n('user');
77 this.createForm();
9f95a23c 78 this.messages = new SelectMessages({ empty: this.i18n('There are no roles.') }, this.i18n);
11fdf7f2
TL
79 }
80
81 createForm() {
9f95a23c
TL
82 this.passwordPolicyService.getHelpText().subscribe((helpText: string) => {
83 this.passwordPolicyHelpText = helpText;
84 });
85 this.userForm = this.formBuilder.group(
11fdf7f2 86 {
e306af50
TL
87 username: [
88 '',
89 [Validators.required],
90 [CdValidators.unique(this.userService.validateUserName, this.userService)]
91 ],
9f95a23c
TL
92 name: [''],
93 password: [
94 '',
95 [],
96 [
97 CdValidators.passwordPolicy(
98 this.userService,
99 () => this.userForm.getValue('username'),
100 (_valid: boolean, credits: number, valuation: string) => {
101 this.passwordStrengthLevelClass = this.passwordPolicyService.mapCreditsToCssClass(
102 credits
103 );
104 this.passwordValuation = _.defaultTo(valuation, '');
105 }
106 )
107 ]
108 ],
109 confirmpassword: [''],
110 pwdExpirationDate: [''],
111 email: ['', [CdValidators.email]],
112 roles: [[]],
113 enabled: [true, [Validators.required]],
114 pwdUpdateRequired: [true]
11fdf7f2
TL
115 },
116 {
117 validators: [CdValidators.match('password', 'confirmpassword')]
118 }
119 );
120 }
121
122 ngOnInit() {
123 if (this.router.url.startsWith('/user-management/users/edit')) {
124 this.mode = this.userFormMode.editing;
125 this.action = this.actionLabels.EDIT;
126 } else {
127 this.action = this.actionLabels.CREATE;
128 }
9f95a23c 129 this.minDate = new Date();
11fdf7f2 130
9f95a23c
TL
131 const observables = [this.roleService.list(), this.settingsService.getStandardSettings()];
132 observableForkJoin(observables).subscribe(
133 (result: [UserFormRoleModel[], CdPwdExpirationSettings]) => {
134 this.allRoles = _.map(result[0], (role) => {
135 role.enabled = true;
136 return role;
137 });
138 this.pwdExpirationSettings = new CdPwdExpirationSettings(result[1]);
139
140 if (this.mode === this.userFormMode.editing) {
141 this.initEdit();
142 } else {
143 if (this.pwdExpirationSettings.pwdExpirationSpan > 0) {
144 const pwdExpirationDateField = this.userForm.get('pwdExpirationDate');
145 const expirationDate = new Date();
146 expirationDate.setDate(
147 this.minDate.getDate() + this.pwdExpirationSettings.pwdExpirationSpan
148 );
149 pwdExpirationDateField.setValue(expirationDate);
150 pwdExpirationDateField.setValidators([Validators.required]);
151 }
152 }
153 }
154 );
11fdf7f2
TL
155 }
156
157 initEdit() {
158 this.disableForEdit();
159 this.route.params.subscribe((params: { username: string }) => {
160 const username = params.username;
161 this.userService.get(username).subscribe((userFormModel: UserFormModel) => {
162 this.response = _.cloneDeep(userFormModel);
163 this.setResponse(userFormModel);
164 });
165 });
166 }
167
168 disableForEdit() {
169 this.userForm.get('username').disable();
170 }
171
172 setResponse(response: UserFormModel) {
9f95a23c 173 ['username', 'name', 'email', 'roles', 'enabled', 'pwdUpdateRequired'].forEach((key) =>
11fdf7f2
TL
174 this.userForm.get(key).setValue(response[key])
175 );
9f95a23c
TL
176 const expirationDate = response['pwdExpirationDate'];
177 if (expirationDate) {
178 this.userForm.get('pwdExpirationDate').setValue(new Date(expirationDate * 1000));
179 }
11fdf7f2
TL
180 }
181
182 getRequest(): UserFormModel {
183 const userFormModel = new UserFormModel();
9f95a23c 184 ['username', 'password', 'name', 'email', 'roles', 'enabled', 'pwdUpdateRequired'].forEach(
11fdf7f2
TL
185 (key) => (userFormModel[key] = this.userForm.get(key).value)
186 );
9f95a23c
TL
187 const expirationDate = this.userForm.get('pwdExpirationDate').value;
188 if (expirationDate) {
189 if (
190 this.mode !== this.userFormMode.editing ||
191 this.response.pwdExpirationDate !== Number(expirationDate) / 1000
192 ) {
193 expirationDate.setHours(23, 59, 59);
194 }
195 userFormModel['pwdExpirationDate'] = Number(expirationDate) / 1000;
196 }
11fdf7f2
TL
197 return userFormModel;
198 }
199
200 createAction() {
201 const userFormModel = this.getRequest();
202 this.userService.create(userFormModel).subscribe(
203 () => {
204 this.notificationService.show(
205 NotificationType.success,
206 this.i18n('Created user "{{username}}"', { username: userFormModel.username })
207 );
208 this.router.navigate(['/user-management/users']);
209 },
210 () => {
211 this.userForm.setErrors({ cdSubmitButton: true });
212 }
213 );
214 }
215
216 editAction() {
217 if (this.isUserRemovingNeededRolePermissions()) {
218 const initialState = {
219 titleText: this.i18n('Update user'),
220 buttonText: this.i18n('Continue'),
221 bodyTpl: this.removeSelfUserReadUpdatePermissionTpl,
222 onSubmit: () => {
223 this.modalRef.hide();
224 this.doEditAction();
225 },
226 onCancel: () => {
227 this.userForm.setErrors({ cdSubmitButton: true });
228 this.userForm.get('roles').reset(this.userForm.get('roles').value);
229 }
230 };
231 this.modalRef = this.modalService.show(ConfirmationModalComponent, { initialState });
232 } else {
233 this.doEditAction();
234 }
235 }
236
9f95a23c 237 public isCurrentUser(): boolean {
11fdf7f2
TL
238 return this.authStorageService.getUsername() === this.userForm.getValue('username');
239 }
240
241 private isUserChangingRoles(): boolean {
242 const isCurrentUser = this.isCurrentUser();
243 return (
244 isCurrentUser &&
245 this.response &&
246 !_.isEqual(this.response.roles, this.userForm.getValue('roles'))
247 );
248 }
249
250 private isUserRemovingNeededRolePermissions(): boolean {
251 const isCurrentUser = this.isCurrentUser();
252 return isCurrentUser && !this.hasUserReadUpdatePermissions(this.userForm.getValue('roles'));
253 }
254
255 private hasUserReadUpdatePermissions(roles: Array<string> = []) {
256 for (const role of this.allRoles) {
257 if (roles.indexOf(role.name) !== -1 && role.scopes_permissions['user']) {
258 const userPermissions = role.scopes_permissions['user'];
259 return ['read', 'update'].every((permission) => {
260 return userPermissions.indexOf(permission) !== -1;
261 });
262 }
263 }
264 return false;
265 }
266
267 private doEditAction() {
268 const userFormModel = this.getRequest();
269 this.userService.update(userFormModel).subscribe(
270 () => {
271 if (this.isUserChangingRoles()) {
272 this.authService.logout(() => {
273 this.notificationService.show(
274 NotificationType.info,
275 this.i18n('You were automatically logged out because your roles have been changed.')
276 );
277 });
278 } else {
279 this.notificationService.show(
280 NotificationType.success,
281 this.i18n('Updated user "{{username}}"', { username: userFormModel.username })
282 );
283 this.router.navigate(['/user-management/users']);
284 }
285 },
286 () => {
287 this.userForm.setErrors({ cdSubmitButton: true });
288 }
289 );
290 }
291
9f95a23c
TL
292 clearExpirationDate() {
293 this.userForm.get('pwdExpirationDate').setValue(undefined);
294 }
295
11fdf7f2
TL
296 submit() {
297 if (this.mode === this.userFormMode.editing) {
298 this.editAction();
299 } else {
300 this.createAction();
301 }
302 }
303}