@@ -7,8 +7,7 @@ | |||
* "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 | |||
* | |||
* 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 |
@@ -1,3 +1,21 @@ | |||
/* | |||
* 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 { Application } from './application'; | |||
describe('Application', () => { |
@@ -1,2 +1,20 @@ | |||
/* | |||
* 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. | |||
*/ | |||
export class Application { | |||
} |
@@ -1,3 +1,21 @@ | |||
/* | |||
* 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 { RoleTemplate } from './role-template'; | |||
describe('RoleTemplate', () => { |
@@ -1,3 +1,21 @@ | |||
/* | |||
* 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. | |||
*/ | |||
export class RoleTemplate { | |||
id:string | |||
name:string |
@@ -34,5 +34,7 @@ export class Role { | |||
// Web Internal attributes | |||
enabled: boolean = true | |||
level:number = -1 | |||
root_path: Array<string> | |||
assigned_origin: boolean; | |||
} |
@@ -8,7 +8,6 @@ | |||
~ 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 | |||
@@ -89,7 +88,7 @@ | |||
[attr.disabled]="userForm.valid?null:true">{{'users.add.submit'|translate}}</button> | |||
</div> | |||
<div *ngIf="success" class="alert alert-success" role="alert"> | |||
User <a [routerLink]="['/user','users','edit',result?.user_id]">{{result?.user_id}}</a> was added to the list. | |||
User <a [routerLink]="['/security','users','edit',result?.user_id]">{{result?.user_id}}</a> was added to the list. | |||
</div> | |||
<div *ngIf="error" class="alert alert-danger" role="alert" > | |||
<h4 class="alert-heading">{{'users.add.errortitle'|translate}}</h4> |
@@ -8,7 +8,6 @@ | |||
* 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 |
@@ -8,7 +8,6 @@ | |||
* 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 |
@@ -8,7 +8,6 @@ | |||
* 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 |
@@ -16,11 +16,11 @@ | |||
* under the License. | |||
*/ | |||
import {Component, OnInit} from '@angular/core'; | |||
import {Component, EventEmitter, OnInit, Output} from '@angular/core'; | |||
import {ActivatedRoute} from '@angular/router'; | |||
import {UserService} from "../../../../services/user.service"; | |||
import {FormBuilder, FormControl} from "@angular/forms"; | |||
import {catchError, map, switchMap, tap} from 'rxjs/operators'; | |||
import {catchError, filter, map, switchMap, tap} from 'rxjs/operators'; | |||
import {ManageUsersBaseComponent} from "../manage-users-base.component"; | |||
import {ErrorResult} from "../../../../model/error-result"; | |||
@@ -38,6 +38,9 @@ export class ManageUsersEditComponent extends ManageUsersBaseComponent implement | |||
editMode: boolean; | |||
minUserIdSize = 0; | |||
@Output() | |||
userIdEvent: EventEmitter<string> = new EventEmitter<string>(true); | |||
constructor(private route: ActivatedRoute, public userService: UserService, public fb: FormBuilder) { | |||
super(userService, fb); | |||
this.editMode=false; | |||
@@ -46,15 +49,21 @@ export class ManageUsersEditComponent extends ManageUsersBaseComponent implement | |||
this.editMode=true; | |||
} | |||
}) | |||
} | |||
ngOnInit(): void { | |||
this.editUser = this.route.params.pipe( | |||
map(params => params.userid), switchMap(userid => userService.getUser(userid))).subscribe(user => { | |||
map(params => params.userid), | |||
filter(userid=>userid!=null), | |||
tap(userid=>{ | |||
this.userIdEvent.emit(userid) | |||
}), | |||
switchMap(userid => this.userService.getUser(userid))).subscribe(user => { | |||
this.editUser = user; | |||
this.originUser = user; | |||
this.copyToForm(this.editProperties, this.editUser); | |||
}); | |||
} | |||
ngOnInit(): void { | |||
// This resets the validators of the base class | |||
this.userForm.get('user_id').clearValidators(); | |||
this.userForm.get('user_id').clearAsyncValidators(); |
@@ -8,7 +8,6 @@ | |||
~ 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 |
@@ -8,7 +8,6 @@ | |||
* 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 |
@@ -8,7 +8,6 @@ | |||
* 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 |
@@ -8,7 +8,6 @@ | |||
* 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 |
@@ -1,50 +1,102 @@ | |||
<h3>Roles</h3> | |||
<table class="table"> | |||
<thead class="thead-light"> | |||
<tr class="d-flex"> | |||
<th class="col-3">Role</th> | |||
<th class="col-2">Scope</th> | |||
<th class="col-1">Assign</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr *ngFor="let baseRole of baseRoles" class="d-flex"> | |||
<td class="col-3" [innerHTML]="getRoleContent(baseRole)"></td> | |||
<td class="col-2"> | |||
{{baseRole.application_id}} | |||
</td> | |||
<td class="col-1"> | |||
<div class="form-check form-check-inline"><input class="form-check-input" type="checkbox" | |||
[attr.disabled]="baseRole.enabled?null:true" | |||
[attr.id]="baseRole.id" | |||
[(ngModel)]="baseRole.assigned" | |||
(change)="changeBaseAssignment(baseRole, $event)" | |||
> | |||
<!-- | |||
~ 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="row"> | |||
<div class="row col-md-6"> | |||
<h4 class="col-md-2 mt-3">{{'users.roles.base_roles'|translate}} </h4> | |||
<h4 class="col-md-2 offset-md-4 mt-3"><span class="badge badge-primary">{{userid}}</span></h4> | |||
</div> | |||
<ng-container *ngIf="roles$|async as myRoles"> | |||
<table class="table col-md-12"> | |||
<thead class="thead-light"> | |||
<tr class="d-flex"> | |||
<th class="col-3">{{'users.roles.table.role'|translate}}</th> | |||
<th class="col-2">{{'users.roles.table.scope'|translate}}</th> | |||
<th class="col-1">{{'users.roles.table.assign'|translate}}</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr *ngFor="let baseRoleItem of myRoles.baseRoles|keyvalue" class="d-flex"> | |||
<td class="col-3" [innerHTML]="getRoleContent(baseRoleItem.value)"></td> | |||
<td class="col-2"> | |||
{{baseRoleItem.value.application_id}} | |||
</td> | |||
<td class="col-1"> | |||
<div class="form-check form-check-inline"><input class="form-check-input" type="checkbox" | |||
[attr.disabled]="baseRoleItem.value.enabled?null:true" | |||
[attr.id]="baseRoleItem.key" | |||
[(ngModel)]="baseRoleItem.value.assigned" | |||
(change)="changeBaseAssignment(baseRoleItem.value, $event)" | |||
> | |||
</div> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<h4>{{'users.roles.template_roles'|translate}}</h4> | |||
<table class="table"> | |||
<thead class="thead-light"> | |||
<tr class="d-flex"> | |||
<th scope="row" class="col-1">{{'users.roles.table.repository'|translate}}</th> | |||
<th scope="col" class="col-1" | |||
*ngFor="let templateRole of templateRoles$ | async">{{templateRole.name}}</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr class="d-flex" *ngFor="let res of myRoles.templateRoleInstances | keyvalue"> | |||
<td class="table-secondary col-1">{{res.key}}</td> | |||
<td class="col-1 text-center" *ngFor="let templateRole of templateRoles$ | async"> | |||
<div class="form-check form-check-inline" | |||
*ngIf="getInstanceContent(templateRole, res.value) as role"> | |||
<input class="form-check-input" | |||
[attr.disabled]="role.enabled?null:true" | |||
type="checkbox" [attr.id]="role.id" | |||
[(ngModel)]="role.assigned" (ngModelChange)="changeTemplateAssignment(role, $event)"/> | |||
</div> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<div class="form-group col-md-12 mt-3 "> | |||
<button class="btn btn-primary" type="submit" | |||
(click)="saveAssignments()">{{'form.button.save'|translate}}</button> | |||
</div> | |||
<ng-container *ngIf="saved"> | |||
<div *ngIf="success" class="alert alert-success" role="alert"> | |||
Roles have been assigned | |||
</div> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<h3>Repository Roles</h3> | |||
<table class="table"> | |||
<thead class="thead-light"> | |||
<tr class="d-flex"> | |||
<th scope="row" class="col-1">Repository</th> | |||
<th scope="col" class="col-1" *ngFor="let templateRole of templateRoles$ | async">{{templateRole.name}}</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr class="d-flex" *ngFor="let res of templateRoleInstances | keyvalue" > | |||
<td class="table-secondary col-1">{{res.key}}</td> | |||
<td class="col-1 text-center" *ngFor="let templateRole of templateRoles$ | async"> | |||
<div class="form-check form-check-inline" *ngIf="getInstanceContent(templateRole, res.value) as role"> | |||
<input class="form-check-input" type="checkbox" [attr.id]="role.id" | |||
[(ngModel)]="role.assigned" (ngModelChange)="changeInstAssignment(role, $event)"/> | |||
<div *ngIf="!success" class="alert alert-danger" role="alert"> | |||
<h4 class="alert-heading">Errors</h4> | |||
<ng-container *ngFor="let message of errors.error_messages; first as isFirst"> | |||
<hr *ngIf="!isFirst"> | |||
<p>{{message.message}}</p> | |||
</ng-container> | |||
</div> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</ng-container> | |||
</ng-container> | |||
</table> | |||
</div> |
@@ -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. | |||
*/ | |||
@@ -1,3 +1,21 @@ | |||
/* | |||
* 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 { ManageUsersRolesComponent } from './manage-users-roles.component'; |
@@ -1,54 +1,125 @@ | |||
import { Component, OnInit } from '@angular/core'; | |||
/* | |||
* 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 {AfterViewInit, Component, EventEmitter, OnInit, Output} from '@angular/core'; | |||
import { Role } from '@app/model/role'; | |||
import {UserService} from "@app/services/user.service"; | |||
import {ActivatedRoute} from "@angular/router"; | |||
import {filter, map, switchMap} from "rxjs/operators"; | |||
import {catchError, filter, map, multicast, share, switchMap, tap} from "rxjs/operators"; | |||
import {RoleTree} from "@app/model/role-tree"; | |||
import {RoleService} from "@app/services/role.service"; | |||
import {RoleTemplate} from "@app/model/role-template"; | |||
import {Observable} from "rxjs"; | |||
import {Observable, of} from "rxjs"; | |||
import {Util} from "@app/modules/shared/shared.module"; | |||
import { RoleResult } from './role-result'; | |||
import {fromArray} from "rxjs/internal/observable/fromArray"; | |||
import {ErrorResult} from "@app/model/error-result"; | |||
import {HttpResponse} from "@angular/common/http"; | |||
@Component({ | |||
selector: 'app-manage-users-roles', | |||
templateUrl: './manage-users-roles.component.html', | |||
styleUrls: ['./manage-users-roles.component.scss'] | |||
}) | |||
export class ManageUsersRolesComponent implements OnInit { | |||
export class ManageUsersRolesComponent implements OnInit, AfterViewInit { | |||
baseRoles : Array<Role> | |||
roles$ : Observable<RoleResult> | |||
currentRoles: RoleResult | |||
guest: Role | |||
registered: Role | |||
// Map of (resource, [roles]) | |||
templateRoleInstances: Map<string, Array<Role>> | |||
templateRoles$: Observable<RoleTemplate[]>; | |||
userid: string; | |||
success:boolean=true; | |||
errors: ErrorResult[]=[]; | |||
saved:boolean=false; | |||
constructor(private route : ActivatedRoute, private userService : UserService, private roleService : RoleService) { | |||
this.route.params.pipe( | |||
map(params => params.userid), filter(userid => userid!=null), switchMap(userid => userService.userRoleTree(userid))).subscribe(roleTree => { | |||
this.parseRoleTree(roleTree); | |||
}); | |||
@Output() | |||
userIdEvent: EventEmitter<string> = new EventEmitter<string>(true); | |||
constructor(private route : ActivatedRoute, private userService : UserService, private roleService : RoleService) { | |||
} | |||
ngOnInit(): void { | |||
this.roles$ = this.route.params.pipe( | |||
map(params => params.userid), filter(userid => userid != null), | |||
tap(userid => this.userid = userid), | |||
tap(userid=>{ | |||
this.userIdEvent.emit(userid) | |||
}), | |||
switchMap(userid => { | |||
return this.userService.userRoleTree(userid) | |||
}), | |||
map(roleTree=>this.parseRoleTree(roleTree)), | |||
// This is to avoid multiple userService.userRoleTree() calls for template and base roles | |||
share() | |||
); | |||
this.templateRoles$ = this.roleService.getTemplates(); | |||
} | |||
private parseRoleTree(roleTree:RoleTree): void { | |||
let roleTable = []; | |||
for(let rootRole of roleTree.root_roles) { | |||
roleTable = this.recurseRoleTree(rootRole, roleTable, 0); | |||
private parseRoleTree(roleTree:RoleTree): RoleResult { | |||
let roleResult = new RoleResult(); | |||
let rootRoles = roleTree.root_roles; | |||
rootRoles.sort((a, b)=>{ | |||
if (b.id=='guest') { | |||
return 1; | |||
} else if (b.id=='registered-user') { | |||
return 1; | |||
} else { | |||
return -1; | |||
} | |||
}) | |||
for (let rootRole of rootRoles) { | |||
this.recurseTree(rootRole, roleResult, 0, null); | |||
} | |||
return roleResult; | |||
} | |||
private recurseTree(role:Role,roleResult:RoleResult, level:number, parent: Role) : void { | |||
let newLevel=level; | |||
if (parent!=null) { | |||
if (role.root_path==null) { | |||
role.root_path = (parent.root_path == null ? [] : parent.root_path.slice()); | |||
} | |||
role.root_path.push(parent.id) | |||
} | |||
this.baseRoles = roleTable; | |||
let templateMap : Map<string,Array<Role>> = new Map<string, Array<Role>>(); | |||
for (let rootRole of roleTree.root_roles) { | |||
templateMap = this.recurseTemplates(rootRole, templateMap, 0); | |||
role.assigned_origin = role.assigned; | |||
if (role.template_instance) { | |||
newLevel = this.parseTemplateRole(role,roleResult.templateRoleInstances,newLevel) | |||
} else { | |||
newLevel = this.parseBaseRole(role, roleResult.baseRoles, newLevel) | |||
} | |||
for(let childRole of role.children) { | |||
let recurseChild = childRole; | |||
if (childRole.template_instance) { | |||
if (roleResult.templateRoleInstances.has(childRole.resource) && roleResult.templateRoleInstances.get(childRole.resource).has(childRole.model_id)) { | |||
recurseChild = roleResult.templateRoleInstances.get(childRole.resource).get(childRole.model_id) | |||
} | |||
} else { | |||
let existingBaseRole = roleResult.baseRoles.find(role => role.id == childRole.id) | |||
if (existingBaseRole != null) { | |||
recurseChild = existingBaseRole; | |||
} | |||
} | |||
this.recurseTree(recurseChild, roleResult, newLevel, role); | |||
} | |||
this.templateRoleInstances = templateMap; | |||
} | |||
private recurseRoleTree(role:Role, roles : Array<Role>, level:number) : Array<Role> { | |||
private parseBaseRole(role:Role, roles : Array<Role>, level:number) : number { | |||
if (role.id=='guest') { | |||
this.guest = role; | |||
} else if (role.id=='registered-user') { | |||
@@ -56,34 +127,32 @@ export class ManageUsersRolesComponent implements OnInit { | |||
} | |||
role.enabled=true; | |||
let newLevel; | |||
if (!role.template_instance && role.assignable) { | |||
if (role.assignable) { | |||
role.level=level | |||
roles.push(role); | |||
newLevel = level+1; | |||
} else { | |||
newLevel = level; | |||
} | |||
for(let childRole of role.children) { | |||
roles = this.recurseRoleTree(childRole, roles, newLevel); | |||
} | |||
return roles; | |||
return newLevel; | |||
} | |||
private recurseTemplates(role:Role, roles : Map<string, Array<Role>>, level:number) : Map<string, Array<Role>> { | |||
private parseTemplateRole(role:Role, roles : Map<string, Map<string, Role>>, level:number) : number { | |||
let newLevel=level; | |||
role.enabled=true; | |||
if (role.template_instance && role.assignable) { | |||
if (role.assignable) { | |||
role.level=level | |||
let roleList = roles.get(role.resource) | |||
if (roleList==null) { | |||
roleList = [] | |||
let modelRoleMap = roles.get(role.resource) | |||
if (modelRoleMap==null) { | |||
modelRoleMap = new Map<string, Role>(); | |||
} | |||
roleList.push(role); | |||
roles.set(role.resource, roleList); | |||
} | |||
for(let childRole of role.children) { | |||
roles = this.recurseTemplates(childRole, roles, level+1); | |||
modelRoleMap.set(role.model_id, role); | |||
roles.set(role.resource, modelRoleMap); | |||
newLevel = level + 1; | |||
} else { | |||
newLevel = level; | |||
} | |||
return roles; | |||
return newLevel; | |||
} | |||
getRoleContent(role:Role) : string { | |||
@@ -101,55 +170,192 @@ export class ManageUsersRolesComponent implements OnInit { | |||
changeBaseAssignment(role : Role, event) { | |||
let cLevel=-1 | |||
let assignStatus; | |||
let assignStatus | |||
// Guest is special and exclusive | |||
if (role.id==this.guest.id) { | |||
if (role.assigned) { | |||
for (let cRole of this.baseRoles) { | |||
this.currentRoles.baseRoles.forEach((cRole:Role)=> { | |||
if (cRole.id != this.guest.id) { | |||
cRole.assigned = false; | |||
cRole.enabled=true; | |||
} | |||
} | |||
}) | |||
role.enabled = false; | |||
this.currentRoles.templateRoleInstances.forEach((value, key)=>{ | |||
value.forEach((templateInstance, modelId) => { | |||
templateInstance.assigned = false; | |||
templateInstance.enabled = true; | |||
}); | |||
}) | |||
} | |||
} else { | |||
this.guest.enabled = true; | |||
for (let cRole of this.baseRoles) { | |||
this.currentRoles.baseRoles.forEach((cRole)=> { | |||
if (cRole.id == role.id) { | |||
console.log("Value: " + cRole.assigned); | |||
cLevel = cRole.level; | |||
assignStatus = cRole.assigned; | |||
if (assignStatus) { | |||
this.guest.assigned = false; | |||
this.guest.enabled = true; | |||
} else { | |||
if (!this.baseRoles.find(role=>role.assigned)) { | |||
if (!this.isAnyAssigned()) { | |||
this.guest.assigned=true; | |||
this.guest.enabled = false; | |||
} | |||
} | |||
} else { | |||
console.log("Level " + cLevel); | |||
if (cLevel >= 0 && cLevel < cRole.level) { | |||
if (cLevel >= 0 && cLevel < cRole.level && cRole.root_path.find(pRoleId => pRoleId==role.id)) { | |||
if (assignStatus) { | |||
cRole.assigned = true; | |||
cRole.enabled = false; | |||
} else { | |||
cRole.enabled = true; | |||
cRole.assigned=cRole.assigned_origin | |||
} | |||
} else if (cLevel >= 0) { | |||
break; | |||
return; | |||
} | |||
} | |||
}) | |||
this.currentRoles.templateRoleInstances.forEach((value, key)=>{ | |||
value.forEach((templateInstance, modelId) => { | |||
if(templateInstance.root_path.find(roleId => roleId==role.id)) { | |||
if (role.assigned) { | |||
templateInstance.assigned = true; | |||
templateInstance.enabled = false | |||
} else { | |||
templateInstance.enabled = true; | |||
templateInstance.assigned=templateInstance.assigned_origin | |||
} | |||
} | |||
} | |||
) | |||
}) | |||
} | |||
} | |||
isAnyAssigned() : boolean { | |||
if (Array.from(this.currentRoles.baseRoles.values()).find(role=>role.assigned)!=null) { | |||
return true; | |||
} | |||
return Array.from(this.currentRoles.templateRoleInstances.values()).map((roleMap: Map<string, Role>) => Array.from(roleMap.values())) | |||
.find(values=>values.find(role=>role.assigned))!=null | |||
} | |||
changeTemplateAssignment(role : Role, event) { | |||
if (role.assigned) { | |||
if (this.guest.assigned) { | |||
this.guest.assigned = false; | |||
this.guest.enabled = true; | |||
} | |||
if (!this.registered.assigned) { | |||
this.registered.assigned=true; | |||
} | |||
} else { | |||
if (!this.isAnyAssigned()) { | |||
this.guest.assigned=true; | |||
this.guest.enabled = false; | |||
} | |||
} | |||
} | |||
changeInstAssignment(role : Role, event) { | |||
console.log("Change " + role.id); | |||
console.log("Assignment changed "+JSON.stringify(event)); | |||
console.log("Event target "+event.target); | |||
getInstanceContent(template:RoleTemplate, roles:Map<string,Role>) : Role { | |||
return roles.get(template.id) | |||
} | |||
saveAssignments() { | |||
this.saved=false; | |||
this.success=true; | |||
this.errors = []; | |||
let assignmentMap : Map<string, Role> = new Map(this.currentRoles.baseRoles.filter(role => role.assigned != role.assigned_origin).map(role => [role.id, role])); | |||
let assignments : Array<Role> = [] | |||
let unassignments : Array<Role> = [] | |||
assignmentMap.forEach((role, roleId)=>{ | |||
if (role.level>0) { | |||
for(let parentId of role.root_path) { | |||
if (assignmentMap.has(parentId) && assignmentMap.get(parentId).assigned) { | |||
return; | |||
} | |||
} | |||
} | |||
if (role.assigned) { | |||
assignments.push(role); | |||
} else { | |||
unassignments.push(role); | |||
} | |||
}) | |||
this.currentRoles.templateRoleInstances.forEach((templMap, resource)=> { | |||
templMap.forEach((role, modelId)=> { | |||
if (role.assigned!=role.assigned_origin) { | |||
if (role.level>0) { | |||
for(let parentId of role.root_path) { | |||
if (assignmentMap.has(parentId) && assignmentMap.get(parentId).assigned) { | |||
return; | |||
} | |||
} | |||
} | |||
if (role.assigned) { | |||
assignments.push(role); | |||
} else { | |||
unassignments.push(role); | |||
} | |||
} | |||
}) | |||
} | |||
) | |||
fromArray(assignments).pipe(switchMap((role) => this.roleService.assignRole(role.id, this.userid)), | |||
catchError((err: ErrorResult, caught) => { | |||
this.success = false; | |||
this.errors.push(err); | |||
return []; | |||
} | |||
) | |||
).subscribe((result:HttpResponse<Role>)=> { | |||
this.updateRole(result.body, true); | |||
this.saved=true; | |||
} | |||
); | |||
fromArray(unassignments).pipe(switchMap((role) => this.roleService.unAssignRole(role.id, this.userid)), | |||
catchError((err: ErrorResult, caught) => { | |||
this.success = false; | |||
this.errors.push(err); | |||
return []; | |||
} | |||
) | |||
).subscribe(result=>{ | |||
this.updateRole(result.body,false); | |||
this.saved=true; | |||
}); | |||
this.saved=true; | |||
} | |||
private updateRole(role:Role, assignment:boolean) : void { | |||
if (role!=null) { | |||
if (role.template_instance) { | |||
this.currentRoles.templateRoleInstances.forEach((templMap, resource)=>{ | |||
templMap.forEach((tmplRole, modelId)=> { | |||
if (tmplRole.id == role.id) { | |||
Util.deepCopy(role, tmplRole, false); | |||
tmplRole.assigned = assignment; | |||
} | |||
} | |||
) | |||
}) | |||
} else { | |||
let target = this.currentRoles.baseRoles.find(baseRole => baseRole.id == role.id); | |||
Util.deepCopy(role, target, false); | |||
target.assigned = assignment; | |||
} | |||
} | |||
} | |||
getInstanceContent(template:RoleTemplate, roles:Array<Role>) : Role { | |||
return roles.find(role=>role.model_id==template.id) | |||
ngAfterViewInit(): void { | |||
this.roles$.subscribe(roleResult => this.currentRoles = roleResult); | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
/* | |||
* 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 {Role} from "@app/model/role"; | |||
export class RoleResult { | |||
// Map of (roleId, Role) | |||
baseRoles: Array<Role> = []; | |||
// Map of (resource, [(modelId, role), ...]) | |||
templateRoleInstances: Map<string, Map<string, Role>> = new Map<string, Map<string, Role>>(); | |||
} |
@@ -8,7 +8,6 @@ | |||
~ 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 | |||
@@ -25,11 +24,11 @@ | |||
<a class="nav-link" routerLink="/security/users/add" routerLinkActive="active" href="#">{{'users.add.head' |translate }}</a> | |||
</li> | |||
<li class="nav-item"> | |||
<a class="nav-link" routerLink="/security/users/edit" routerLinkActive="active" href="#">{{'users.edit.head' |translate }}</a> | |||
<a class="nav-link" routerLink="/security/users/edit{{userId$|async}}" routerLinkActive="active" href="#">{{'users.edit.head' |translate }}</a> | |||
</li> | |||
<li class="nav-item"> | |||
<a class="nav-link" routerLink="/security/users/roles" routerLinkActive="active" href="#">{{'users.roles.head' |translate }}</a> | |||
<a class="nav-link" routerLink="/security/users/roles{{userId$|async}}" routerLinkActive="active" href="#">{{'users.roles.head' |translate }}</a> | |||
</li> | |||
</ul> | |||
<router-outlet ></router-outlet> | |||
<router-outlet (activate)="onChildActivate($event)" ></router-outlet> |
@@ -8,7 +8,6 @@ | |||
* 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 |
@@ -8,7 +8,6 @@ | |||
* 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 |
@@ -8,7 +8,6 @@ | |||
* 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 | |||
@@ -17,7 +16,12 @@ | |||
* under the License. | |||
*/ | |||
import { Component, OnInit } from '@angular/core'; | |||
import {AfterViewInit, Component, OnInit} from '@angular/core'; | |||
import {ActivatedRoute, Params} from "@angular/router"; | |||
import {iif, Observable, of, pipe, merge, combineLatest} from "rxjs"; | |||
import {combineAll, filter, map, mergeMap, share, switchMap, tap} from 'rxjs/operators'; | |||
import {flatMap} from "rxjs/internal/operators"; | |||
import {fromArray} from "rxjs/internal/observable/fromArray"; | |||
@Component({ | |||
selector: 'app-manage-users', | |||
@@ -26,9 +30,49 @@ import { Component, OnInit } from '@angular/core'; | |||
}) | |||
export class ManageUsersComponent implements OnInit { | |||
constructor() { } | |||
userId$:Observable<string> | |||
constructor(private route : ActivatedRoute) { | |||
} | |||
ngOnInit(): void { | |||
} | |||
onChildActivate(componentReference) { | |||
// console.log("Activating "+componentReference+" - "+JSON.stringify(componentReference,getCircularReplacer())) | |||
if (componentReference.userIdEvent!=null) { | |||
let componentEmit : Observable<string> = componentReference.userIdEvent.pipe( | |||
tap(userid=>console.log("Event "+componentReference.class+" "+userid)), | |||
map((userid: string) => this.getSubPath(userid))); | |||
if (this.userId$!=null) { | |||
this.userId$ = merge(this.userId$, componentEmit) | |||
} else { | |||
this.userId$ = componentEmit; | |||
} | |||
} | |||
} | |||
getSubPath(userid:string) { | |||
if (userid!=null && userid.length>0) { | |||
return '/' + userid; | |||
} else { | |||
return ''; | |||
} | |||
} | |||
} | |||
const getCircularReplacer = () => { | |||
const seen = new WeakSet(); | |||
return (key, value) => { | |||
if (typeof value === "object" && value !== null) { | |||
if (seen.has(value)) { | |||
return; | |||
} | |||
seen.add(value); | |||
} | |||
return value; | |||
}; | |||
}; |
@@ -71,7 +71,7 @@ export function httpTranslateLoader(http: HttpClient) { | |||
} | |||
export const Util = { | |||
deepCopy(src: Object, dst: Object) { | |||
deepCopy(src: Object, dst: Object, overwriteWithEmptyString:boolean=true) { | |||
Object.keys(src).forEach((key, idx) => { | |||
let srcEl = src[key]; | |||
if (typeof (srcEl) == 'object') { | |||
@@ -81,6 +81,14 @@ export const Util = { | |||
} | |||
dstEl = dst[key]; | |||
this.deepCopy(srcEl, dstEl); | |||
} else if (typeof(srcEl)=='string') { | |||
if (overwriteWithEmptyString) { | |||
dst[key]=srcEl | |||
} else { | |||
if ((srcEl as string).length>0) { | |||
dst[key]=srcEl | |||
} | |||
} | |||
} else { | |||
// console.debug("setting " + key + " = " + srcEl); | |||
dst[key] = srcEl; |
@@ -7,8 +7,7 @@ | |||
* "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 | |||
* | |||
* 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 |
@@ -7,8 +7,7 @@ | |||
* "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 | |||
* | |||
* 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 |
@@ -7,8 +7,7 @@ | |||
* "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 | |||
* | |||
* 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 |
@@ -7,8 +7,7 @@ | |||
* "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 | |||
* | |||
* 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 |
@@ -1,3 +1,21 @@ | |||
/* | |||
* 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 { TestBed } from '@angular/core/testing'; | |||
import { RoleService } from './role.service'; |
@@ -1,7 +1,27 @@ | |||
/* | |||
* 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 { Injectable } from '@angular/core'; | |||
import {ArchivaRequestService} from "@app/services/archiva-request.service"; | |||
import {RoleTemplate} from "@app/model/role-template"; | |||
import { Observable } from 'rxjs'; | |||
import { Role } from '@app/model/role'; | |||
import {HttpResponse} from "@angular/common/http"; | |||
@Injectable({ | |||
providedIn: 'root' | |||
@@ -14,4 +34,12 @@ export class RoleService { | |||
return this.rest.executeRestCall("get", "redback", "roles/templates", null); | |||
} | |||
public assignRole(roleId, userId) : Observable<HttpResponse<Role>> { | |||
return this.rest.executeResponseCall<Role>("put", "redback", "roles/" + roleId + "/user/" + userId, null); | |||
} | |||
public unAssignRole(roleId, userId) : Observable<HttpResponse<Role>> { | |||
return this.rest.executeResponseCall<Role>("delete", "redback", "roles/" + roleId + "/user/" + userId, null); | |||
} | |||
} |
@@ -116,7 +116,15 @@ | |||
} | |||
}, | |||
"roles": { | |||
"head": "Edit Roles" | |||
"head": "Edit Roles", | |||
"base_roles": "Roles", | |||
"template_roles": "Repository Roles", | |||
"table": { | |||
"role": "Role", | |||
"scope": "Scope", | |||
"assign": "Assign", | |||
"repository": "Repository" | |||
} | |||
} | |||
}, | |||
"search": { | |||
@@ -133,7 +141,8 @@ | |||
}, | |||
"button": { | |||
"yes": "Yes", | |||
"no": "No" | |||
"no": "No", | |||
"save": "Save Changes" | |||
} | |||
}, | |||
"password": { |