Skip to content

feat: permissions guards actions#1355

Open
menoude-aneo wants to merge 2 commits into
mainfrom
feat/add-permissions-guards-actions
Open

feat: permissions guards actions#1355
menoude-aneo wants to merge 2 commits into
mainfrom
feat/add-permissions-guards-actions

Conversation

@menoude-aneo
Copy link
Copy Markdown
Contributor

No description provided.

@menoude-aneo menoude-aneo self-assigned this Nov 17, 2025
@menoude-aneo menoude-aneo changed the title Feat/add permissions guards actions feat: permissions guards actions Nov 17, 2025
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Nov 17, 2025

Lines Statements Branches Functions
Coverage: 96%
96.97% (5489/5660) 87.41% (1042/1192) 95.06% (1427/1501)

JUnit

Tests Skipped Failures Errors Time
2022 0 💤 0 ❌ 0 🔥 1m 27s ⏱️
Files coverage (96%)
File% Stmts% Branch% Funcs% LinesUncovered Line #s
All files96.9787.4195.0696.99 
src/app/applications100100100100 
   index.component.html100100100100 
   index.component.ts100100100100 
src/app/applications/components100100100100 
   table.component.html100100100100 
   table.component.ts100100100100 
src/app/applications/services100100100100 
   applications-data.service.ts100100100100 
   applications-filters.service.ts100100100100 
   applications-grpc.service.ts100100100100 
   applications-index.service.ts100100100100 
src/app/components98.387.0998.8398.16 
   auto-complete.component.html100100100100 
   auto-complete.component.ts100100100100 
   auto-refresh-button.component.html100100100100 
   auto-refresh-button.component.ts100100100100 
   auto-refresh-dialog.component.html100100100100 
   auto-refresh-dialog.component.ts100100100100 
   columns-button.component.html100100100100 
   columns-button.component.ts100100100100 
   count-tasks-by-status.component.html100100100100 
   count-tasks-by-status.component.ts9062.510089.7470–71, 82–83
   icon-picker-dialog.component.html100100100100 
   icon-picker-dialog.component.ts100100100100 
   inspect-list.component.html100100100100 
   inspect-list.component.ts100100100100 
   inspection-header.component.html100100100100 
   inspection-header.component.ts10066.6610010055
   inspection-toolbar.component.html100100100100 
   inspection-toolbar.component.ts100100100100 
   manage-custom-dialog.component.html100100100100 
   manage-custom-dialog.component.ts100100100100 
   page-header.component.html100100100100 
   page-header.component.ts100100100100 
   page-section-header.component.html100100100100 
   page-section-header.component.ts100100100100 
   refresh-button.component.html100100100100 
   refresh-button.component.ts100100100100 
   share-url.component.html100100100100 
   share-url.component.ts100100100100 
   show-action-area.component.html100100100100 
   show-action-area.component.ts100100100100 
   show-actions.component.html100100100100 
   show-actions.component.ts100100100100 
   show-card-content.component.html100100100100 
   show-card-content.component.ts100100100100 
   show-card.component.html100100100100 
   show-card.component.ts100100100100 
   show-page.component.html100100100100 
   show-page.component.ts100100100100 
   spinner.component.html100100100100 
   spinner.component.ts100100100100 
   status-chip.component.html100100100100 
   status-chip.component.ts500046.1520–30
   status-color-picker.component.html100100100100 
   status-color-picker.component.ts100100100100 
   status-color-picker.dialog.component.html100100100100 
   status-color-picker.dialog.component.ts100100100100 
   table-actions-toolbar.component.html100100100100 
   table-actions-toolbar.component.ts100100100100 
   table-container.component.html100100100100 
   table-container.component.ts100100100100 
   table-dashboard-actions-toolbar.component.html100100100100 
   table-dashboard-actions-toolbar.component.ts100100100100 
   table-index-actions-toolbar.component.html100100100100 
   table-index-actions-toolbar.component.ts100100100100 
   view-tasks-by-status.component.html100100100100 
   view-tasks-by-status.component.ts100100100100 
src/app/components/dialogs/columns-modify-dialog100100100100 
   columns-modify-dialog.component.html100100100100 
   columns-modify-dialog.component.ts100100100100 
src/app/components/dialogs/columns-modify-dialog/components100100100100 
   columns-modify-area.component.html100100100100 
   columns-modify-area.component.ts100100100100 
src/app/components/filters100100100100 
   filters-chips.component.html100100100100 
   filters-chips.component.ts100100100100 
   filters-dialog-and.component.html100100100100 
   filters-dialog-and.component.ts100100100100 
   filters-dialog-filter-field.component.html100100100100 
   filters-dialog-filter-field.component.ts100100100100 
   filters-dialog-input.component.html100100100100 
   filters-dialog-input.component.ts100100100100 
   filters-dialog-or.component.html100100100100 
   filters-dialog-or.component.ts100100100100 
   filters-dialog.component.html100100100100 
   filters-dialog.component.ts100100100100 
   filters-toolbar.component.html100100100100 
   filters-toolbar.component.ts100100100100 
src/app/components/graph99.410097.4399.36 
   graph-legend.component.html100100100100 
   graph-legend.component.ts100100100100 
   graph.component.html100100100100 
   graph.component.ts99.3110097.1499.28123
src/app/components/inspection98.6695.2395.9198.6 
   byte-array.component.html100100100100 
   byte-array.component.ts95.1266.6683.339597–98
   field-content.component.html100100100100 
   field-content.component.ts100100100100 
   inspection-card.component.html100100100100 
   inspection-card.component.ts100100100100 
   inspection-json.component.html100100100100 
   inspection-json.component.ts100100100100 
   inspection-list-grid.component.html100100100100 
   inspection-list-grid.component.ts100100100100 
   inspection-object.component.html100100100100 
   inspection-object.component.ts96.1510091.669670
   inspection.component.html100100100100 
   inspection.component.ts100100100100 
   json.component.html100100100100 
   json.component.ts100100100100 
   message.component.html100100100100 
   message.component.ts100100100100 
src/app/components/navigation100100100100 
   change-language-button.component.html100100100100 
   change-language-button.component.ts100100100100 
   navigation.component.html100100100100 
   navigation.component.ts100100100100 
   theme-selector.component.html100100100100 
   theme-selector.component.ts100100100100 
src/app/components/navigation/external-services100100100100 
   external-services.component.html100100100100 
   external-services.component.ts100100100100 
   form-external-service.component.html100100100100 
   form-external-service.component.ts100100100100 
   manage-external-services-dialog.component.html100100100100 
   manage-external-services-dialog.component.ts100100100100 
src/app/components/navigation/scheme-switcher100100100100 
   scheme-switcher.component.html100100100100 
   scheme-switcher.component.ts100100100100 
src/app/components/navigation/version-menu100100100100 
   repository-version.component.html100100100100 
   repository-version.component.ts100100100100 
   versions-menu.component.html100100100100 
   versions-menu.component.ts100100100100 
src/app/components/statuses100100100100 
   add-statuses-group-dialog.component.html100100100100 
   add-statuses-group-dialog.component.ts100100100100 
   edit-status-group-dialog.component.html100100100100 
   edit-status-group-dialog.component.ts100100100100 
   form-statuses-group.component.html100100100100 
   form-statuses-group.component.ts100100100100 
   manage-groups-dialog.component.html100100100100 
   manage-groups-dialog.component.ts100100100100 
src/app/components/table99.6110098.7199.58 
   table-actions.component.html100100100100 
   table-actions.component.ts100100100100 
   table-cell.component.html100100100100 
   table-cell.component.ts98.3610095.2398.33115
   table-column-header.component.html100100100100 
   table-column-header.component.ts100100100100 
   table-inspect-message-dialog.component.html100100100100 
   table-inspect-message-dialog.component.ts100100100100 
   table-inspect-message.component.html100100100100 
   table-inspect-message.component.ts100100100100 
   table-inspect-object-dialog.component.html100100100100 
   table-inspect-object-dialog.component.ts100100100100 
   table-inspect-object.component.html100100100100 
   table-inspect-object.component.ts100100100100 
   table.component.html100100100100 
   table.component.ts100100100100 
src/app/components/table/cells9571.4283.3394.87 
   byte-array-cell.component.html100100100100 
   byte-array-cell.component.ts94.8771.4283.3394.7397–98
src/app/dashboard92.5558.336592.39 
   index.component.html100100100100 
   index.component.ts92.4758.336592.3226–250
src/app/dashboard/components93.3371.4291.4892.94 
   add-line-dialog.component.html100100100100 
   add-line-dialog.component.ts79.3153.3373.3378.9450, 93, 107, 114–133
   edit-name-line-dialog.component.html100100100100 
   edit-name-line-dialog.component.ts100100100100 
   reorganize-lines-dialog.component.html100100100100 
   reorganize-lines-dialog.component.ts100100100100 
   split-lines-dialog.component.html100100100100 
   split-lines-dialog.component.ts100100100100 
   statuses-group-card.component.html100100100100 
   statuses-group-card.component.ts100100100100 
src/app/dashboard/components/lines98.5162.597.9198.43 
   applications-line.component.html100100100100 
   applications-line.component.ts100100100100 
   partitions-line.component.html100100100100 
   partitions-line.component.ts100100100100 
   results-line.component.html100100100100 
   results-line.component.ts100100100100 
   sessions-line.component.html100100100100 
   sessions-line.component.ts94.4408094.1168–69
   task-by-status-line.component.html100100100100 
   task-by-status-line.component.ts97.8481.8110097.77102–103
   tasks-line.component.html100100100100 
   tasks-line.component.ts100100100100 
src/app/dashboard/services100100100100 
   dashboard-index.service.ts100100100100 
   dashboard-storage.service.ts100100100100 
src/app/healthcheck100100100100 
   healthcheck.component.html100100100100 
   healthcheck.component.ts100100100100 
src/app/healthcheck/services100100100100 
   healthcheck-grpc.service.ts100100100100 
src/app/partitions100100100100 
   index.component.html100100100100 
   index.component.ts100100100100 
   show.component.html100100100100 
   show.component.ts100100100100 
src/app/partitions/components100100100100 
   table.component.html100100100100 
   table.component.ts100100100100 
src/app/partitions/services100100100100 
   partitions-data.service.ts100100100100 
   partitions-filters.service.ts100100100100 
   partitions-grpc.service.ts100100100100 
   partitions-index.service.ts100100100100 
   partitions-inspection.service.ts100100100100 
src/app/pipes100100100100 
   custom-column.pipe.ts100100100100 
   duration.pipe.ts100100100100 
   empty-cell.pipe.ts100100100100 
   pretty.pipe.ts100100100100 
src/app/profile100100100100 
   index.component.html100100100100 
   index.component.ts100100100100 
   types.ts100100100100 
src/app/profile/guards100100100100 
   user-connected.guard.ts100100100100 
src/app/results75.532560.7175.75 
   index.component.html100100100100 
   index.component.ts100100100100 
   show.component.html100100100100 
   show.component.ts64.582554.1664.8383–124, 193–209
src/app/results/components55.5522.2230.5557.25 
   table.component.html100100100100 
   table.component.ts55.2222.2230.5556.9164–110, 124–160, 233–274
src/app/results/services10094.73100100 
   results-data.service.ts100100100100 
   results-filters.service.ts100100100100 
   results-grpc.service.ts100100100100 
   results-index.service.ts10088.8810010089
   results-inspection.service.ts100100100100 
   results-statuses.service.ts100100100100 
src/app/services98.2796.3796.5398.07 
   auto-refresh.service.ts100100100100 
   byte-array.service.ts100100100100 
   cache.service.ts100100100100 
   default-config.service.ts97.911009597.82366
   environment.service.ts100100100100 
   filters-cache.service.ts100100100100 
   filters.service.ts100100100100 
   graph-data.service.ts100100100100 
   grpc-build-request.service.ts100100100100 
   grpc-events.service.ts100100100100 
   grpc-sort-field.service.ts100100100100 
   icons.service.ts100100100100 
   navigation.service.ts95.349094.1194.8779–80
   notification.service.ts100100100100 
   query-params.service.ts100100100100 
   share-url.service.ts100100100100 
   storage.service.ts100100100100 
   table-storage.service.ts100100100100 
   table-url.service.ts100100100100 
   table.service.ts100100100100 
   tasks-by-status.service.ts100100100100 
   theme.service.ts100100100100 
   user-grpc.service.ts100100100100 
   user.service.ts36.360022.2214–36
   utils.service.ts100100100100 
   versions-grpc.service.ts100100100100 
   versions.service.ts100100100100 
src/app/sessions95.726591.2295.57 
   graph.component.html100100100100 
   graph.component.ts100100100100 
   index.component.html100100100100 
   index.component.ts96.4233.3387.596.2998–99
   show.component.html100100100100 
   show.component.ts94.8767.5690.994.73109–110, 129–140
src/app/sessions/components96.4761.5392.8597.26 
   table.component.html100100100100 
   table.component.ts96.4261.5392.8597.2242–43
src/app/sessions/services100100100100 
   sessions-data.service.ts100100100100 
   sessions-filters.service.ts100100100100 
   sessions-grpc.service.ts100100100100 
   sessions-index.service.ts100100100100 
   sessions-inspection.service.ts100100100100 
   sessions-statuses.service.ts100100100100 
src/app/settings100100100100 
   index.component.html100100100100 
   index.component.ts100100100100 
src/app/settings/component100100100100 
   clear-all-dialog.component.html100100100100 
   clear-all-dialog.component.ts100100100100 
src/app/tasks96.2947.0592.396.15 
   index.component.html100100100100 
   index.component.ts95.7733.3391.6695.65101–102, 107
   show.component.html100100100100 
   show.component.ts96.7762.592.8596.6189–90
src/app/tasks/components10093.75100100 
   manage-view-in-logs-dialog.component.html100100100100 
   manage-view-in-logs-dialog.component.ts100100100100 
   table.component.html100100100100 
   table.component.ts10092.85100100135
src/app/tasks/services96.129495.7496.5 
   tasks-data.service.ts100100100100 
   tasks-filters.service.ts100100100100 
   tasks-grpc.service.ts100100100100 
   tasks-index.service.ts85.3676.9288.2386.84254–261
   tasks-inspection.service.ts100100100100 
   tasks-statuses.service.ts100100100100 
src/app/types100100100100 
   navigation.ts100100100100 
   status.ts100100100100 
   themes.ts100100100100 
src/app/types/components99.3210098.07100 
   dashboard-line-table.ts99.1310097.36100 
   index.ts99.0510097.29100 
   show.ts100100100100 
   table.ts100100100100 
src/app/types/services100100100100 
   data-filter.service.ts100100100100 
   grpcService.ts100100100100 
   inspectionService.ts100100100100 
   table-data.service.ts100100100100 
test100100100100 
   d3.js100100100100 
   force-graph.js100100100100 

@menoude-aneo menoude-aneo force-pushed the feat/add-permissions-guards-actions branch from fdf414b to 0045a4e Compare November 19, 2025 11:04
@sonarqubecloud
Copy link
Copy Markdown

Comment thread src/app/app.config.ts
throw new Error('No user');
}
userService.user = data.user;
navigationService.refreshSidebar();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you have to do this, since it already initialize the sidebar on its construction (when the service is provided for the first time)

Comment on lines +8 to +22
export class ApplicationsAccessGuard implements CanActivate {
private readonly userService = inject(UserService);
private readonly router = inject(Router);

canActivate(): boolean {
const permissions = this.userService.user?.permissions ?? [];
const hasPermission = permissions.includes('Applications:ListApplications');

if (!hasPermission) {
this.router.navigate(['/dashboard']);
return false;
}

return true;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you create an abstract class with just an abstract member permission, which would represent in this case "Applications:ListApplications".

We would have in the end:

export class ApplicationsAccessGuard extends AbstractAcessGuard {
  permission = 'Applications:ListApplications';
}

{ provide: UserService, useValue: mockUserService },
{ provide: UserService, useValue: mockUserService }
]
}).inject(CountTasksByStatusComponent);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you provide a test for:

  • hasCountPermission
  • The changes you've made in the ngOnInit ?
  • The changes you've made in the initRefresh ?

Comment on lines +96 to +99
get hasDownloadPermission(): boolean {
const permissions = this.userService.user?.permissions ?? [];
return permissions.includes('Results:DownloadResultData');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not generic enough. if we have another download action, we will have to add it there, and it will not be easy to do it in every component.
If you look inside the inspection-object component, you will see that we use an object called field. I think you should update the type Field by adding a permission (which could be a string or an array of string containing "result:downloadResultdata").
Then you could add the input permissions to the component, pass it via inspection-object, and replace the string with this input.
Finally, you will just have to add the permission inside the various ...-inspection.service.ts.

{ provide: NotificationService, useValue: mockNotificationService },
{ provide: UserService, useValue: mockUserService },
]
}).inject(ByteArrayComponent);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide test for hasDownloadPermission

Comment on lines +27 to +31
<button mat-flat-button
[color]="hasCancelTaskPermission ? 'accent' : ''"
(click)="onCancelTasksSelection()"
[disabled]="!selection.length || !hasCancelTaskPermission"
[class.disabled-button]="!hasCancelTaskPermission">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change color or add disabled-button class.

Comment on lines +95 to +103
get hasListTasksPermission(): boolean {
const permissions = this.userService.user?.permissions ?? [];
return permissions.includes('Tasks:ListTasks') || permissions.includes('Tasks:ListTasksDetailed') || permissions.includes('Tasks:GetResultId');
}

get hasCancelTaskPermission(): boolean {
const permissions = this.userService.user?.permissions ?? [];
return permissions.includes('Tasks:CancelTask');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside a service

Comment on lines +245 to +262
private readonly detailedColumns: string[] = [
'acquiredAt', 'endedAt', 'initialTaskId', 'ownerPodId', 'podHostname',
'podTtl', 'receivedAt', 'startedAt', 'statusMessage', 'submittedAt',
'creationToEndDuration', 'processingToEndDuration', 'receivedToEndDuration',
'countDataDependencies', 'countExpectedOutputIds', 'countParentTaskIds',
'countRetryOfIds', 'error', 'fetchedAt', 'processedAt', 'payloadId'
];

get availableTableColumns(): TableColumn<TaskSummary, TaskOptions>[] {
const permissions = this.userService.user?.permissions ?? [];
const hasDetailedPermission = permissions.includes('Tasks:ListTasksDetailed');

if (hasDetailedPermission) {
return this._allTableColumns;
}

return this._allTableColumns.filter(column => !this.detailedColumns.includes(column.key as string));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TaskDetailed is a weirdly specific thing for armonik, but it is overall just a task. No need to make such a specific implementation to check it. ListTask is the same.

Comment on lines +21 to +25
<button mat-flat-button
[color]="canCancel ? 'accent' : ''"
(click)="cancel()"
[disabled]="!canCancel"
[class.disabled-button]="!canCancel">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change code or add disabled-button

Comment on lines +88 to +92
get hasCancelTaskPermission(): boolean {
const permissions = this.userService.user?.permissions ?? [];
return permissions.includes('Tasks:CancelTask');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a service

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants