diff options
author | Martin Stockhammer <martin_s@apache.org> | 2020-12-28 21:05:44 +0100 |
---|---|---|
committer | Martin Stockhammer <martin_s@apache.org> | 2020-12-28 21:05:44 +0100 |
commit | c7344aa1c443678f69ee280774ab2505391a562a (patch) | |
tree | d00958bbadcce948d9067fa7c61fe6b30e369d97 /archiva-modules/archiva-web/archiva-webapp/src | |
parent | cb0e4d19d78956b536c62772ae042c281d07ad9f (diff) | |
download | archiva-c7344aa1c443678f69ee280774ab2505391a562a.tar.gz archiva-c7344aa1c443678f69ee280774ab2505391a562a.zip |
Adding user info and password change
Diffstat (limited to 'archiva-modules/archiva-web/archiva-webapp/src')
11 files changed, 319 insertions, 37 deletions
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts index c07b1fecc..aa5083d6a 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app-routing.module.ts @@ -29,6 +29,7 @@ import {BrowseComponent} from "./modules/repo/browse/browse.component"; import {UploadComponent} from "./modules/repo/upload/upload.component"; import {RoutingGuardService as Guard} from "./services/routing-guard.service"; import {SecurityConfigurationComponent} from "./modules/security/security-configuration/security-configuration.component"; +import {UserInfoComponent} from "@app/modules/shared/user-info/user-info.component"; /** * You can use Guard (RoutingGuardService) for permission checking. The service needs data with one parameter 'perm', @@ -55,6 +56,7 @@ const routes: Routes = [ ] }, {path: 'contact', component: ContactComponent}, + {path: 'me/info', component: UserInfoComponent}, {path: 'about', component: AboutComponent}, {path: 'login', component: LoginComponent}, {path: 'logout', component: HomeComponent}, diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html index c49a00ec8..cd9172279 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html @@ -50,9 +50,11 @@ <div class="collapse navbar-collapse" id="navbarsDefault"> <div class="navbar-nav ml-auto"> <span *ngIf="auth.authenticated" class="navbar-text border-right pr-2 mr-2"> - {{user.userInfo.full_name}} + <a class="nav-link" routerLink="/me/info" > + {{user.userInfo.full_name}} + </a> </span> - <ul class="navbar-nav"> + <ul class="navbar-nav d-lg-flex align-items-center"> <li class="nav-item active"> <a class="nav-link" routerLink="/"> <i class="fas fa-home mr-1"></i>{{ 'menu.home' |translate }} diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts index a6bb5475b..38fc25d71 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts @@ -37,6 +37,8 @@ import {RouterModule} from "@angular/router"; import { WithLoadingPipe } from './with-loading.pipe'; import { StripLoadingPipe } from './strip-loading.pipe'; import { ToastComponent } from './toast/toast.component'; +import { UserInfoComponent } from './user-info/user-info.component'; +import {FormsModule} from "@angular/forms"; export { LoadingValue } from './model/loading-value'; export { PageQuery } from './model/page-query'; @@ -48,7 +50,8 @@ export { PageQuery } from './model/page-query'; SortedTableHeaderRowComponent, WithLoadingPipe, StripLoadingPipe, - ToastComponent + ToastComponent, + UserInfoComponent ], exports: [ CommonModule, @@ -84,6 +87,7 @@ export { PageQuery } from './model/page-query'; deps: [HttpClient] } }), + FormsModule, ] }) export class SharedModule { diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts index ff6a03313..2873b8a6c 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts @@ -41,9 +41,7 @@ import {AppNotification} from "@app/model/app-notification"; <ng-template #text>{{ toast.body }}</ng-template> </ngb-toast> `, - styles: [".ngb-toasts{margin:.5em;padding:0.5em;position:fixed;right:2px;top:20px;z-index:1200}" - ], - host: {'[class.ngb-toasts]': 'true'} + styles: [':host { margin:.5em; padding:1em; position:fixed; right:10px; top:40px; z-index:1200; }'] }) export class ToastComponent implements OnInit { diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.html new file mode 100644 index 000000000..a8b7c4f16 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.html @@ -0,0 +1,87 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> + +<div class="col-md-12"> + <h2>{{'me.title'|translate}}</h2> + <form #passwordForm="ngForm" (ngSubmit)="changePassword()" class="mt-4"> + <div class="form-group row" *ngFor="let att of ['user_id','full_name','email','language']"> + <label for="{{att}}" class="col-md-1 col-form-label">{{'users.attributes.' + att|translate}}</label> + <div class="col-md-4"> + <input id="{{att}}" class="form-control" value="{{userService.userInfo[att]}}" readonly> + </div> + </div> + <div class="form-group row" + *ngFor="let dateAtt of ['timestamp_account_creation','timestamp_last_password_change']"> + <label for="{{dateAtt}}" class="col-md-1 col-form-label">{{'users.attributes.' + dateAtt|translate}}</label> + <ng-container *ngIf="userService.userInfo[dateAtt]"> + <div class="col-md-4"> + <input id="{{dateAtt}}" class="form-control" + value="{{userService.userInfo[dateAtt]|date:'YYYY-MM-dd HH:mm:ss'}}" readonly> + </div> + </ng-container> + </div> + <div class="form-group row"> + <div class="col-md-1"> </div> + <div class="col-md-4"> + <div class="form-check" *ngFor="let checkAtt of ['password_change_required','validated','locked']"> + <input id="{{checkAtt}}" type="checkbox" class="form-check-input disabled" + [checked]="userService.userInfo[checkAtt]?'true':null" disabled="disabled"> + <label for="{{checkAtt}}" + class="form-check-label">{{'users.attributes.' + checkAtt|translate}}</label> + </div> + </div> + </div> + <hr class="mt-3" /> + <h2>{{'users.edit.changePasswordTitle'|translate}}</h2> + <div class="form-group row mt-4" > + <label for="current_password" class="col-md-1 col-form-label">{{'users.attributes.current_password'|translate}}</label> + <div class="col-md-4"> + <input id="current_password" name="current_password" type="password" class="form-control" required + [(ngModel)]="formData.current_password" #v_current_password="ngModel" [ngClass]="valid('current_password')"> + <small class="invalid-feedback" *ngIf="v_current_password.errors?.required">{{'form.error.required'|translate}}</small> + </div> + </div> + <div class="form-group row" > + <label for="password" class="col-md-1 col-form-label">{{'users.attributes.new_password'|translate}}</label> + <div class="col-md-4"> + <input id="password" name="password" type="password" class="form-control" required + [ngClass]="valid('password')" + [(ngModel)]="formData.password" #v_password="ngModel" > + <small class="invalid-feedback" *ngIf="v_password.errors?.required">{{'form.error.required'|translate}}</small> + <small class="invalid-feedback" *ngIf="v_password.errors?.invalidpassword">{{v_password.errors.invalidpassword}}</small> + </div> + </div> + <div class="form-group row" > + <label for="confirm_password" class="col-md-1 col-form-label">{{'users.attributes.confirm_password'|translate}}</label> + <div class="col-md-4"> + <input id="confirm_password" name="confirm_password" type="password" class="form-control" required + [(ngModel)]="formData.confirm_password" [class.is-valid]="confirmIsValid()" [class.is-invalid]="confirmIsNotValid()"> + </div> + </div> + <div class="form-group row" > + <div class="col-md-1"> </div> + <div class="col-md-4"> + <button class="btn btn-primary" + type="submit">{{'users.edit.submitPassword'|translate}}</button> + </div> + </div> + + </form> +</div> + + diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.scss b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.scss new file mode 100644 index 000000000..343c3b1c0 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.scss @@ -0,0 +1,18 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.spec.ts new file mode 100644 index 000000000..4f9cfbc73 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.spec.ts @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserInfoComponent } from './user-info.component'; + +describe('UserInfoComponent', () => { + let component: UserInfoComponent; + let fixture: ComponentFixture<UserInfoComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ UserInfoComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserInfoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.ts new file mode 100644 index 000000000..c317ef63a --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/user-info/user-info.component.ts @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {Component, OnInit, ViewChild} from '@angular/core'; +import {NgForm} from '@angular/forms'; +import {UserService} from "@app/services/user.service"; +import {ToastService} from "@app/services/toast.service"; +import {ErrorResult} from "@app/model/error-result"; + +@Component({ + selector: 'app-user-info', + templateUrl: './user-info.component.html', + styleUrls: ['./user-info.component.scss'] +}) +export class UserInfoComponent implements OnInit { + + @ViewChild('passwordForm') passwordForm: NgForm; + + formData = { + current_password:"", + password: "", + confirm_password: "" + } + + + constructor(public userService: UserService, private toastService: ToastService) { } + + ngOnInit(): void { + } + + changePassword() { + console.log("Submit Password ") + if (!this.formData.current_password || this.formData.current_password.length==0) { + this.passwordForm.controls['current_password'].setErrors({'required':'true'}) + this.passwordForm.controls['current_password'].markAsDirty() + } + if (!this.formData.password || this.formData.password.length==0) { + this.passwordForm.controls['password'].setErrors({'required':'true'}) + this.passwordForm.controls['password'].markAsDirty() + } + if (this.passwordForm.valid) { + this.userService.changeOwnPassword(this.formData.current_password, this.formData.password, this.formData.confirm_password).subscribe( + val => { + this.toastService.showSuccessByKey('user-info','users.edit.passwordChanged') + this.formData.password="" + this.formData.current_password="" + this.formData.confirm_password="" + this.passwordForm.reset() + }, + ( error : ErrorResult)=>{ + console.log("Error " + error.error_messages[0].message); + if (error.error_messages.length>0) { + if (error.error_messages[0].error_key.startsWith('user.password.violation')) { + this.toastService.showError('user-info', error.error_messages[0].message) + this.passwordForm.controls['password'].setErrors({'invalidpassword':error.error_messages[0].message}) + } + } + } + ) + } else { + this.toastService.showErrorByKey('user-info','form.error.invaliddata') + } + + } + + confirmIsValid() : boolean { + return (this.formData.password && this.formData.password.length >0 && + this.formData.confirm_password && this.formData.confirm_password.length>0 && this.formData.password==this.formData.confirm_password ) + } + + confirmIsNotValid() : boolean { + return (this.formData.password && this.formData.password.length>0 && + this.formData.confirm_password && this.formData.confirm_password.length>0 && this.formData.password!=this.formData.confirm_password ) + } + + valid(field:string) { + if (this.passwordForm) { + let ctrl = this.passwordForm.controls[field]; + if (ctrl && ( ctrl.dirty || ctrl.touched ) && ctrl.valid) { + return 'is-valid' + } + if (ctrl && ( ctrl.dirty || ctrl.touched ) && ctrl.invalid) { + return 'is-invalid' + } + } + } + +} diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts index 2c00a41ea..4e8ca0ce0 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/archiva-request.service.ts @@ -128,6 +128,7 @@ export class ArchivaRequestService { * @param errorMsg the errorMsg as returned by a REST call */ public translateError(errorMsg: ErrorMessage): string { + console.log("Translating error "+errorMsg.error_key) if (errorMsg.error_key != null && errorMsg.error_key != '') { let parms = {}; if (errorMsg.args != null && errorMsg.args.length > 0) { diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts index d42d77c42..fa89b40c5 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts @@ -328,7 +328,7 @@ export class UserService implements OnInit, OnDestroy { }), map((httpResponse: HttpResponse<string>) => httpResponse.status == 200)); } - public userRoleTree(userid:String): Observable<RoleTree> { + public userRoleTree(userid:string): Observable<RoleTree> { return this.rest.executeResponseCall<RoleTree>("get", "redback","users/"+userid+"/roletree", null).pipe( catchError((error: HttpErrorResponse)=>{ if (error.status==404) { @@ -341,4 +341,18 @@ export class UserService implements OnInit, OnDestroy { ).pipe(map((httpResponse:HttpResponse<RoleTree>)=>httpResponse.body)) } + public changeOwnPassword(current_password:string, password:string, confirm_password:string) { + let data = { + "user_id":this.userInfo.user_id, + "current_password":current_password, + "new_password":password, + "new_password_confirmation":confirm_password + } + return this.rest.executeRestCall<any>("post", "redback", "users/me/password/update", data).pipe( + catchError((error: HttpErrorResponse)=>{ + return throwError(this.rest.getTranslatedErrorResult(error)); + }) + ); + } + } diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json index e39cf2157..645701eae 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json @@ -6,7 +6,7 @@ "userid": "User Id", "submit": "Login" }, - "modal" : { + "modal": { "close": "Close" }, "menu": { @@ -19,7 +19,7 @@ "error": { "modal": { "title": "Application Error", - "info":"The backend does not answer as expected. Please check, if your archiva service is running. See detail messages below." + "info": "The backend does not answer as expected. Please check, if your archiva service is running. See detail messages below." }, "http": { "unknownError": "We got a bad response from the backend REST service. Maybe the connection is broken, or the service is down. Please check your network to the backend service and the archiva.log for any errors.", @@ -47,7 +47,7 @@ "user": { "section": "User", "users": "Manage Users", - "roles":"Manage Roles", + "roles": "Manage Roles", "config": "Security Configuration" }, "doc": { @@ -56,32 +56,38 @@ "restapi": "REST API" } }, - "api" : { + "api": { "rb.auth.invalid_credentials": "Invalid credentials given", "user.password.violation.length": "You must provide a password between {arg0} and {arg1} characters in length.", "user.password.violation.alpha": "You must provide a password containing at least {arg0} alphabetic {arg0, plural, one {character} other {characters}}.", - "user.password.violation.numeric":"You must provide a password containing at least {arg0} numeric {arg0, plural, one {character} other {characters}}.", - "user.password.violation.reuse":"The password must not match any of the previous {arg0} {arg0, plural, one {password} other {passwords}}.", - "user.password.violation.alphanum.only":"You must provide a password containing all alpha-numeric characters.", - "user.password.violation.whitespace.detected":"You must provide a password without whitespace characters." + "user.password.violation.numeric": "You must provide a password containing at least {arg0} numeric {arg0, plural, one {character} other {characters}}.", + "user.password.violation.reuse": "The password must not match any of the previous {arg0} {arg0, plural, one {password} other {passwords}}.", + "user.password.violation.alphanum.only": "You must provide a password containing all alpha-numeric characters.", + "user.password.violation.whitespace.detected": "You must provide a password without whitespace characters." }, "users": { - "attributes":{ - "id": "ID", - "user_id": "Login Name", - "email": "Email", - "full_name": "Name", - "validated": "User Validated", - "locked": "User Locked", - "password_change_required": "Password Change Required", - "last_login": "Last Login", - "created": "Created", - "permanent": "Permanent", - "last_password_change": "Last Password Change", - "password": "Password", - "confirm_password": "Confirm Password" + "attributes": { + "id": "ID", + "user_id": "Login Name", + "email": "Email", + "full_name": "Name", + "validated": "User Validated", + "locked": "User Locked", + "password_change_required": "Password Change Required", + "last_login": "Last Login", + "created": "Created", + "permanent": "Permanent", + "last_password_change": "Last Password Change", + "timestamp_last_password_change": "Last Password Change", + "timestamp_account_creation": "Account Created", + "timestamp_last_login": "Last Login", + "password": "Password", + "new_password": "New Password", + "current_password": "Current Password", + "confirm_password": "Confirm Password", + "language": "UI Language" }, - "input" : { + "input": { "small": { "user_id": "Must be a unique key with at least {minSize} characters. No spaces allowed.", "full_name": "This is the display name of the user" @@ -92,7 +98,6 @@ "password": "Enter password", "confirm_password": "Confirm password" }, - "list": { "head": "List Users" }, @@ -107,10 +112,13 @@ "head": "View/Edit User", "small": { "password": "If the password field is empty, it will not be updated." - } + }, + "submitPassword": "Change Password", + "passwordChanged": "Password has been changed successfully", + "changePasswordTitle": "Change password" }, "delete": { - "head":"Delete User", + "head": "Delete User", "modal": { "title": "Delete User", "text": "Are you sure? <br/> Do you want to delete the user <strong>{user_id}</strong>?" @@ -164,7 +172,6 @@ "assignable": "Assignable" } }, - "permissions": { "attributes": { "permission": "Permission", @@ -182,7 +189,8 @@ "error": { "required": "Value is empty. This is required.", "containsWhitespace": "Value must not contain whitespace.", - "userexists": "This user exists already." + "userexists": "This user exists already.", + "invaliddata": "The data is not valid" }, "button": { "yes": "Yes", @@ -197,8 +205,10 @@ "action": "Action" }, "password": { - "violations" : { - + "violations": { } + }, + "me": { + "title": "Current User Information" } }
\ No newline at end of file |