Merge pull request #4461 from tsiegleauq/permission-list-view

Add permissions to ListViews
This commit is contained in:
Sean 2019-04-06 21:33:44 +02:00 committed by GitHub
commit 2f330933a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 327 additions and 202 deletions

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild } from '@angular/router';
import { CanActivate, ActivatedRouteSnapshot, CanActivateChild, Router } from '@angular/router';
import { OperatorService } from './operator.service';
@ -11,9 +11,12 @@ import { OperatorService } from './operator.service';
})
export class AuthGuard implements CanActivate, CanActivateChild {
/**
* @param operator
* Constructor
*
* @param router To navigate to a target URL
* @param operator Asking for the required permission
*/
public constructor(private operator: OperatorService) {}
public constructor(private router: Router, private operator: OperatorService) {}
/**
* Checks of the operator has the required permission to see the state.
@ -22,10 +25,9 @@ export class AuthGuard implements CanActivate, CanActivateChild {
* `data: {basePerm: ['<perm1>', '<perm2>']}` to lock the access to users
* only with the given permission(s).
*
* @param route required by `canActivate()`
* @param state the state (URL) that the user want to access
* @param route the route the user wants to navigate to
*/
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
public canActivate(route: ActivatedRouteSnapshot): boolean {
const basePerm: string | string[] = route.data.basePerm;
if (!basePerm) {
@ -39,10 +41,19 @@ export class AuthGuard implements CanActivate, CanActivateChild {
/**
* Calls {@method canActivate}. Should have the same logic.
* @param route
* @param state
*
* @param route the route the user wants to navigate to
*/
public canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return this.canActivate(route, state);
public canActivateChild(route: ActivatedRouteSnapshot): boolean {
if (this.canActivate(route)) {
return true;
} else {
this.router.navigate(['/error'], {
queryParams: {
error: 'Authentication Error',
msg: route.data.basePerm
}
});
}
}
}

View File

@ -10,12 +10,17 @@ import { WatchSortingTreeGuard } from 'app/shared/utils/watch-sorting-tree.guard
const routes: Routes = [
{ path: '', component: AgendaListComponent, pathMatch: 'full' },
{ path: 'import', component: AgendaImportListComponent },
{ path: 'topics/new', component: TopicDetailComponent },
{ path: 'sort-agenda', component: AgendaSortComponent, canDeactivate: [WatchSortingTreeGuard] },
{ path: 'speakers', component: ListOfSpeakersComponent },
{ path: 'topics/:id', component: TopicDetailComponent },
{ path: ':id/speakers', component: ListOfSpeakersComponent }
{ path: 'import', component: AgendaImportListComponent, data: { basePerm: 'agenda.can_manage' } },
{ path: 'topics/new', component: TopicDetailComponent, data: { basePerm: 'agenda.can_manage' } },
{
path: 'sort-agenda',
component: AgendaSortComponent,
canDeactivate: [WatchSortingTreeGuard],
data: { basePerm: 'agenda.can_manage' }
},
{ path: 'speakers', component: ListOfSpeakersComponent, data: { basePerm: 'agenda.can_see' } },
{ path: 'topics/:id', component: TopicDetailComponent, data: { basePerm: 'agenda.can_see' } },
{ path: ':id/speakers', component: ListOfSpeakersComponent, data: { basePerm: 'agenda.can_see' } }
];
@NgModule({

View File

@ -12,6 +12,7 @@
<span>{{ selectedRows.length }}&nbsp;</span><span translate>selected</span>
</div>
</os-head-bar>
<mat-drawer-container class="on-transition-fade">
<os-sort-filter-bar
[filterCount]="filteredCount"

View File

@ -5,12 +5,14 @@ import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-poli
import { StartComponent } from './components/start/start.component';
import { LegalNoticeComponent } from './components/legal-notice/legal-notice.component';
import { SearchComponent } from './components/search/search.component';
import { ErrorComponent } from './components/error/error.component';
const routes: Routes = [
{
path: '',
component: StartComponent,
pathMatch: 'full'
pathMatch: 'full',
data: { basePerm: 'core.can_see_frontpage' }
},
{
path: 'legalnotice',
@ -23,6 +25,10 @@ const routes: Routes = [
{
path: 'search',
component: SearchComponent
},
{
path: 'error',
component: ErrorComponent
}
];

View File

@ -0,0 +1,9 @@
<os-head-bar>
<div class="title-slot">
<h2 translate>Error</h2>
</div>
</os-head-bar>
<mat-card class="os-card on-transition-fade">
<h1 translate>You do not have the required permission to see that page!</h1>
</mat-card>

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ErrorComponent } from './error.component';
import { E2EImportsModule } from 'e2e-imports.module';
describe('ErrorComponent', () => {
let component: ErrorComponent;
let fixture: ComponentFixture<ErrorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ErrorComponent],
imports: [E2EImportsModule]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ErrorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,32 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
/**
* A component to show error states
*/
@Component({
selector: 'os-error',
templateUrl: './error.component.html',
styleUrls: ['./error.component.scss']
})
export class ErrorComponent implements OnInit {
/**
* Constructor
*
* @param route get paramters
*/
public constructor(private route: ActivatedRoute) {}
/**
* Show the required debug output in the log
*/
public ngOnInit(): void {
this.route.queryParams.subscribe(params => {
if (params && params.error) {
// print the error and the error message in terminal for debug purposes.
// Will make it easier tell where user errors are
console.error(`${params.error}! Required: "${params.msg}"`);
}
});
}
}

View File

@ -7,6 +7,6 @@
<mat-card class="os-card">
<div class="app-content" translate>
<h1>{{ welcomeTitle | translate }}</h1>
<div [innerHTML]="(welcomeText) | translate"></div>
<div [innerHTML]="welcomeText | translate"></div>
</div>
</mat-card>

View File

@ -8,9 +8,17 @@ import { StartComponent } from './components/start/start.component';
import { LegalNoticeComponent } from './components/legal-notice/legal-notice.component';
import { SearchComponent } from './components/search/search.component';
import { CountUsersComponent } from './components/count-users/count-users.component';
import { ErrorComponent } from './components/error/error.component';
@NgModule({
imports: [CommonModule, CommonRoutingModule, SharedModule],
declarations: [PrivacyPolicyComponent, StartComponent, LegalNoticeComponent, SearchComponent, CountUsersComponent]
declarations: [
PrivacyPolicyComponent,
StartComponent,
LegalNoticeComponent,
SearchComponent,
CountUsersComponent,
ErrorComponent
]
})
export class OsCommonModule {}

View File

@ -17,6 +17,7 @@
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</div>
<mat-table class="os-headed-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
<!-- Timestamp -->
<ng-container matColumnDef="time">

View File

@ -1,5 +1,5 @@
<os-head-bar
[mainButton]="canEdit"
[mainButton]="canUploadFiles"
[editMode]="editFile"
[multiSelectMode]="isMultiSelect"
(mainEvent)="onMainEvent()"
@ -38,7 +38,7 @@
</div>
<!-- Menu -->
<div class="menu-slot" *ngIf="canEdit">
<div class="menu-slot" *osPerms="'mediafiles.can_manage'">
<button type="button" mat-icon-button [matMenuTriggerFor]="mediafilesMenu">
<mat-icon>more_vert</mat-icon>
</button>
@ -142,24 +142,30 @@
</mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="pageSize"></mat-paginator>
</mat-drawer-container>
<!-- Template for the managing buttons -->
<ng-template #manageButton let-file="file" let-action="action">
<button mat-menu-item (click)="onManageButton($event, file, action)">
<mat-icon color="accent"> {{ isUsedAs(file, action) ? 'check_box' : 'check_box_outline_blank' }} </mat-icon>
<span>{{ getNameOfAction(action) }}</span>
</button>
</ng-template>
<!-- Menu for single files in the list -->
<mat-menu #singleFileMenu="matMenu">
<ng-template matMenuContent let-file="file">
<!-- Exclusive for images -->
<div *ngIf="file.isImage()">
<div *ngFor="let action of logoActions">
<ng-container
*ngTemplateOutlet="manageButton; context: { file: file, action: action }"
></ng-container>
<ng-container *ngTemplateOutlet="manageButton; context: { file: file, action: action }"></ng-container>
</div>
</div>
<!-- Exclusive for fonts -->
<div *ngIf="file.isFont()">
<div *ngFor="let action of fontActions">
<ng-container
*ngTemplateOutlet="manageButton; context: { file: file, action: action }"
></ng-container>
<ng-container *ngTemplateOutlet="manageButton; context: { file: file, action: action }"></ng-container>
</div>
</div>
@ -176,14 +182,6 @@
</ng-template>
</mat-menu>
<!-- Template for the managing buttons -->
<ng-template #manageButton let-file="file" let-action="action">
<button mat-menu-item (click)="onManageButton($event, file, action)">
<mat-icon color="accent"> {{ isUsedAs(file, action) ? 'check_box' : 'check_box_outline_blank' }} </mat-icon>
<span>{{ getNameOfAction(action) }}</span>
</button>
</ng-template>
<!-- Menu for Mediafiles -->
<mat-menu #mediafilesMenu="matMenu">
<div *ngIf="!isMultiSelect">
@ -209,4 +207,3 @@
</button>
</div>
</mat-menu>
</mat-drawer-container>

View File

@ -60,6 +60,13 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile,
/**
* @returns true if the user can manage media files
*/
public get canUploadFiles(): boolean {
return this.operator.hasPerms('mediafiles.can_see') && this.operator.hasPerms('mediafiles.can_upload');
}
/**
* @return true if the user can manage media files
*/
public get canEdit(): boolean {
return this.operator.hasPerms('mediafiles.can_manage');
}

View File

@ -5,7 +5,7 @@ import { MediaUploadComponent } from './components/media-upload/media-upload.com
const routes: Routes = [
{ path: '', component: MediafileListComponent, pathMatch: 'full' },
{ path: 'upload', component: MediaUploadComponent }
{ path: 'upload', component: MediaUploadComponent, data: { basePerm: 'mediafiles.can_upload' } }
];
@NgModule({

View File

@ -704,7 +704,12 @@
listname="{{ 'Attachments' | translate }}"
[InputListValues]="mediafilesObserver"
></os-search-value-selector>
<button type="button" mat-icon-button (click)="onUploadAttachmentsButton(uploadDialog)">
<button
type="button"
mat-icon-button
(click)="onUploadAttachmentsButton(uploadDialog)"
*osPerms="'mediafiles.can_upload'"
>
<mat-icon>cloud_upload</mat-icon>
</button>
</div>

View File

@ -9,40 +9,49 @@ const routes: Routes = [
},
{
path: 'import',
loadChildren: './modules/motion-import/motion-import.module#MotionImportModule'
loadChildren: './modules/motion-import/motion-import.module#MotionImportModule',
data: { basePerm: 'motions.can_manage' }
},
{
path: 'statute-paragraphs',
loadChildren: './modules/statute-paragraph/statute-paragraph.module#StatuteParagraphModule'
loadChildren: './modules/statute-paragraph/statute-paragraph.module#StatuteParagraphModule',
data: { basePerm: 'motions.can_manage' }
},
{
path: 'comment-section',
loadChildren: './modules/motion-comment-section/motion-comment-section.module#MotionCommentSectionModule'
loadChildren: './modules/motion-comment-section/motion-comment-section.module#MotionCommentSectionModule',
data: { basePerm: 'motions.can_manage' }
},
{
path: 'call-list',
loadChildren: './modules/call-list/call-list.module#CallListModule'
loadChildren: './modules/call-list/call-list.module#CallListModule',
data: { basePerm: 'motions.can_manage' }
},
{
path: 'category',
loadChildren: './modules/category/category.module#CategoryModule'
loadChildren: './modules/category/category.module#CategoryModule',
data: { basePerm: 'motions.can_see' }
},
{
path: 'blocks',
loadChildren: './modules/motion-block/motion-block.module#MotionBlockModule'
loadChildren: './modules/motion-block/motion-block.module#MotionBlockModule',
data: { basePerm: 'motions.can_manage' }
},
{
path: 'workflow',
loadChildren: './modules/motion-workflow/motion-workflow.module#MotionWorkflowModule'
loadChildren: './modules/motion-workflow/motion-workflow.module#MotionWorkflowModule',
data: { basePerm: 'motions.can_manage' }
},
{
path: 'new',
loadChildren: './modules/motion-detail/motion-detail.module#MotionDetailModule'
loadChildren: './modules/motion-detail/motion-detail.module#MotionDetailModule',
data: { basePerm: 'motions.can_create' }
},
{
path: ':id',
loadChildren: './modules/motion-detail/motion-detail.module#MotionDetailModule',
runGuardsAndResolvers: 'paramsChange'
runGuardsAndResolvers: 'paramsChange',
data: { basePerm: 'motions.can_see' }
}
];

View File

@ -6,9 +6,7 @@
</os-head-bar>
<mat-card *ngIf="!projectorToCreate && projectors && projectors.length > 1">
<span translate>
Reference projector for current list of speakers: </span
>&nbsp;
<span translate> Reference projector for current list of speakers: </span>&nbsp;
<mat-form-field>
<mat-select
[disabled]="!!editId"

View File

@ -11,7 +11,8 @@ const routes: Routes = [
},
{
path: 'detail/:id',
component: ProjectorDetailComponent
component: ProjectorDetailComponent,
data: { basePerm: 'core.can_see_projector' }
}
];

View File

@ -20,39 +20,48 @@ const routes: Routes = [
},
{
path: 'agenda',
loadChildren: './agenda/agenda.module#AgendaModule'
loadChildren: './agenda/agenda.module#AgendaModule',
data: { basePerm: 'agenda.can_see' }
},
{
path: 'assignments',
loadChildren: './assignments/assignments.module#AssignmentsModule'
loadChildren: './assignments/assignments.module#AssignmentsModule',
data: { basePerm: 'assignment.can_see' }
},
{
path: 'mediafiles',
loadChildren: './mediafiles/mediafiles.module#MediafilesModule'
loadChildren: './mediafiles/mediafiles.module#MediafilesModule',
data: { basePerm: 'mediafiles.can_see' }
},
{
path: 'motions',
loadChildren: './motions/motions.module#MotionsModule'
loadChildren: './motions/motions.module#MotionsModule',
data: { basePerm: 'motions.can_see' }
},
{
path: 'settings',
loadChildren: './config/config.module#ConfigModule'
loadChildren: './config/config.module#ConfigModule',
data: { basePerm: 'core.can_manage_config' }
},
{
path: 'users',
loadChildren: './users/users.module#UsersModule'
loadChildren: './users/users.module#UsersModule',
data: { basePerm: 'users.can_see_name' }
},
{
path: 'tags',
loadChildren: './tags/tag.module#TagModule'
loadChildren: './tags/tag.module#TagModule',
data: { basePerm: 'core.can_manage_tags' }
},
{
path: 'history',
loadChildren: './history/history.module#HistoryModule'
loadChildren: './history/history.module#HistoryModule',
data: { basePerm: 'core.can_see_history' }
},
{
path: 'projectors',
loadChildren: './projector/projector.module#ProjectorModule'
loadChildren: './projector/projector.module#ProjectorModule',
data: { basePerm: 'core.can_see_projector' }
}
],
canActivateChild: [AuthGuard]

View File

@ -105,6 +105,7 @@
</mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="pageSize"></mat-paginator>
</mat-drawer-container>
<mat-menu #userMenu="matMenu">
<div *ngIf="!isMultiSelect">
@ -213,7 +214,6 @@
</div>
</div>
</mat-menu>
</mat-drawer-container>
<ng-template #userInfoDialog>
<h1 mat-dialog-title>

View File

@ -16,39 +16,39 @@ const routes: Routes = [
},
{
path: 'password',
component: PasswordComponent
component: PasswordComponent,
data: { basePerm: 'users.can_change_password' }
},
{
path: 'password/:id',
component: PasswordComponent
component: PasswordComponent,
data: { basePerm: 'can_manage' }
},
{
path: 'new',
component: UserDetailComponent
component: UserDetailComponent,
data: { basePerm: 'users.can_manage' }
},
{
path: 'import',
component: UserImportListComponent
component: UserImportListComponent,
data: { basePerm: 'users.can_manage' }
},
{
path: 'presence',
component: PresenceDetailComponent
// FIXME: CRITICAL: restricted to basePerm: 'users.can_manage' and config 'users_enable_presence_view'
component: PresenceDetailComponent,
// TODO: 'users_enable_presence_view' missing in permissions
data: { basePerm: 'users.can_manage' }
},
{
path: 'groups',
component: GroupListComponent
/**
* FIXME: CRITICAL:
* Refreshing the page, even while having the required permission, will navigate you back to "/"
* Makes developing protected areas impossible.
* Has the be (temporarily) removed if this page should be edited.
*/
// data: { basePerm: 'users.can_manage' }
component: GroupListComponent,
data: { basePerm: 'users.can_manage' }
},
{
path: ':id',
component: UserDetailComponent
component: UserDetailComponent,
data: { basePerm: 'users.can_see_name' }
}
];

View File

@ -83,7 +83,7 @@ class MotionChangeRecommendationAccessPermissions(BaseAccessPermissions):
"""
# Parse data.
if await async_has_perm(user_id, "motions.can_see"):
has_manage_perms = await async_has_perm(user_id, "motion.can_manage")
has_manage_perms = await async_has_perm(user_id, "motions.can_manage")
data = []
for full in full_data:
if not full["internal"] or has_manage_perms: