Merge remote-tracking branch 'origin/dev-latest' into feature/dds-agent-settlement-reports
# Conflicts: # src/app/core/shell/shell.component.html # src/app/organization/organization-routing.module.ts # src/environments/.env.ts
This commit is contained in:
@@ -11,6 +11,7 @@ import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angula
|
||||
|
||||
/** Custom Services */
|
||||
import { AuthenticationService } from '../../core/authentication/authentication.service';
|
||||
import { TenantMenuAccessService } from './tenant-menu-access.service';
|
||||
|
||||
/** Environment */
|
||||
import { environment } from '../../../environments/environment';
|
||||
@@ -23,6 +24,7 @@ export class HasPermissionDirective {
|
||||
private templateRef = inject<TemplateRef<any>>(TemplateRef);
|
||||
private viewContainer = inject(ViewContainerRef);
|
||||
private authenticationService = inject(AuthenticationService);
|
||||
private tenantMenuAccessService = inject(TenantMenuAccessService);
|
||||
|
||||
/** User Permissions */
|
||||
private userPermissions: any[];
|
||||
@@ -75,6 +77,13 @@ export class HasPermissionDirective {
|
||||
// Menu & Sidebar permissions are ALWAYS enforced (hide/show nav items based on role)
|
||||
const isMenuPermission = permission.startsWith('MENU_') || permission.startsWith('SIDEBAR_');
|
||||
|
||||
// The tenant's own ceiling on which menu sections it may ever show — checked before anything
|
||||
// else below, ALL_FUNCTIONS included, because ALL_FUNCTIONS bypassing this is exactly the gap
|
||||
// this exists to close (see TenantMenuAccessApiResource). Non-menu permissions never touch this.
|
||||
if (isMenuPermission && !this.tenantMenuAccessService.isMenuAllowed(permission)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For non-menu permissions: if RBAC is disabled, show everything (backward compatibility)
|
||||
if (!this.rbacEnabled && !isMenuPermission) {
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { catchError, of, tap } from 'rxjs';
|
||||
|
||||
/**
|
||||
* The current tenant's own cap on which top-nav menu codes may ever show — see
|
||||
* `TenantMenuAccessApiResource` on the backend for why this exists separately from a user's own
|
||||
* permission list: a role carrying `ALL_FUNCTIONS` bypasses every ordinary permission check, so
|
||||
* `HasPermissionDirective` needs a second, tenant-level list that check cannot see past.
|
||||
*
|
||||
* Loaded once per session, into a plain `Set` rather than an `Observable` the directive would have
|
||||
* to subscribe to — `mifosxHasPermission`'s setter runs synchronously and once per binding, so
|
||||
* `ShellComponent` loads this before the nav renders (see `ready`) and every directive instance
|
||||
* afterwards just reads the already-populated set.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TenantMenuAccessService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
/** Menu codes this tenant has explicitly turned off. Absence from this set means "shown" —
|
||||
* a tenant that never configured anything (every tenant, until an operator narrows one) has
|
||||
* nothing here, which is what keeps every existing deployment behaving exactly as it does today. */
|
||||
private disabledCodes = new Set<string>();
|
||||
|
||||
/** True once the initial load has settled, success or failure. `ShellComponent` waits on this
|
||||
* before rendering the nav, so a menu is never shown-then-hidden a moment later. */
|
||||
private loaded = false;
|
||||
|
||||
isReady(): boolean {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
isMenuAllowed(menuCode: string): boolean {
|
||||
return !this.disabledCodes.has(menuCode);
|
||||
}
|
||||
|
||||
/** Fetches this tenant's list once. A failed request is treated as "nothing disabled" — the same
|
||||
* as a tenant that never configured this at all — so a transient network hiccup on login never
|
||||
* makes the whole nav disappear. */
|
||||
load() {
|
||||
return this.http.get<{ menuCode: string; enabled: boolean }[]>('/tenant-menu-access').pipe(
|
||||
tap((rows) => {
|
||||
this.disabledCodes = new Set((rows || []).filter((r) => r.enabled === false).map((r) => r.menuCode));
|
||||
this.loaded = true;
|
||||
}),
|
||||
catchError(() => {
|
||||
this.disabledCodes = new Set();
|
||||
this.loaded = true;
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user