From adecbc27b86bfb7fac3a854051911c0ac0338e37 Mon Sep 17 00:00:00 2001 From: Soni Jay Date: Sat, 7 Feb 2026 21:14:52 +0530 Subject: [PATCH] WEB-575 Fix the WebApp to show the Menus and buttons (create, import, delete, edit) as per the grants of each profile (#3102) --- README.md | 68 +++++++++++++++++-- env.sample | 5 ++ .../core/shell/sidenav/sidenav.component.html | 8 +++ .../core/shell/sidenav/sidenav.component.ts | 7 +- .../core/shell/toolbar/toolbar.component.html | 48 +++++++++---- .../has-permission.directive.ts | 12 ++++ src/assets/env.js | 4 ++ src/assets/env.template.js | 4 ++ src/environments/environment.prod.ts | 9 +++ src/environments/environment.ts | 9 +++ 10 files changed, 151 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4708564a1..ed33d1816 100644 --- a/README.md +++ b/README.md @@ -281,13 +281,14 @@ Available languages: #### UI Display Settings -| Variable | Description | Default Value | -| ---------------------------------- | ------------------------------------------ | ------------- | -| MIFOS_DISPLAY_TENANT_SELECTOR | Display tenant selector in Login view | true | -| MIFOS_DISPLAY_BACKEND_INFO | Display backend info in footer | true | -| MIFOS_PRODUCTION_MODE | Show minimal production hero on login page | false | -| MIFOS_ALLOW_SERVER_SWITCH_SELECTOR | Display DNS server list | true | -| MIFOS_COMPLIANCE_HIDE_CLIENT_DATA | Hide client names in UI (mask with \*) | false | +| Variable | Description | Default Value | +| ---------------------------------- | -------------------------------------------------- | ------------- | +| MIFOS_DISPLAY_TENANT_SELECTOR | Display tenant selector in Login view | true | +| MIFOS_DISPLAY_BACKEND_INFO | Display backend info in footer | true | +| MIFOS_PRODUCTION_MODE | Show minimal production hero on login page | false | +| MIFOS_ALLOW_SERVER_SWITCH_SELECTOR | Display DNS server list | true | +| MIFOS_COMPLIANCE_HIDE_CLIENT_DATA | Hide client names in UI (mask with \*) | false | +| MIFOS_PRODUCTION_MODE_ENABLE_RBAC | Enable Role-Based Access Control for menus/buttons | false | #### OAUTH Settings @@ -332,6 +333,59 @@ This applies to client name display, e.g. in Institution/Clients list. By default, the “Interbank Transfer” menu will be displayed in the hamburger menu of the Savings account. +### Role-Based Access Control (RBAC) + +**What is RBAC?** +Role-Based Access Control restricts system access and functionality based on user roles and permissions. When enabled, menus and buttons in the WebApp are shown/hidden based on the user's assigned permissions. + +**When to enable RBAC:** + +- **Production environments** where strict access control is required +- **Multi-tenant systems** with different user roles (Teller, Field Officer, Loan Reviewer, etc.) +- **Compliance requirements** that mandate role-based permissions + +**Default Behavior (RBAC Disabled):** +By default, `MIFOS_PRODUCTION_MODE_ENABLE_RBAC=false` maintains **backward compatibility**: + +- All menus and buttons are visible to all logged-in users +- Existing deployments continue to work without configuration changes +- Suitable for development, testing, or single-user scenarios + +**When RBAC is Enabled:** +Set `MIFOS_PRODUCTION_MODE_ENABLE_RBAC=true` to activate permission-based UI control: + +- Menus (Institution, Admin, Reports, Accounting) require specific READ permissions +- Buttons (Create, Edit, Delete, Approve, etc.) require corresponding permissions +- Users see only features they have permissions for +- **Important:** You must configure roles in Apache Fineract® before enabling RBAC + +**How to enable RBAC:** + +1. **Configure environment variable:** + + ```bash + MIFOS_PRODUCTION_MODE_ENABLE_RBAC=true + ``` + +2. **Create roles in Fineract Admin Panel:** + - Navigate to: Admin → System → Manage Roles + - Create roles (e.g., Teller, Field Officer, Loan Reviewer, etc.) + - Assign appropriate permissions to each role + +3. **Assign roles to users:** + - Navigate to: Admin → Users → Edit User + - Select appropriate role for each user + +**Common role examples include:** + +- **Teller**: Cash operations (deposits, withdrawals, repayments) +- **Field Officer**: Client onboarding, loan origination +- **Loan Reviewer**: Credit analysis, loan approval/rejection +- **Treasurer**: Disbursement, accounting, fund movements +- **KYC Officer**: Client identity verification +- **PLD Officer (AML)**: Anti-money laundering monitoring +- **Product Owner**: Product and system configuration + ## Releases ### 1.0.0 (Tag: 1.0.0-fineract1.11) diff --git a/env.sample b/env.sample index d99940839..a130aff08 100644 --- a/env.sample +++ b/env.sample @@ -44,4 +44,9 @@ MIFOS_COMPLIANCE_HIDE_CLIENT_DATA=false # Production mode - when true, shows minimal hero with only branding at bottom # Set to true for financial institutions that want a clean, professional look + MIFOS_PRODUCTION_MODE=false +# Enable Role-Based Access Control (RBAC) for menus and buttons +# When true: Menus/buttons are shown based on user permissions (production mode) +# When false (default): All menus/buttons are shown (backward compatibility) +MIFOS_PRODUCTION_MODE_ENABLE_RBAC=false diff --git a/src/app/core/shell/sidenav/sidenav.component.html b/src/app/core/shell/sidenav/sidenav.component.html index 4ac5c9972..c7e4366c7 100644 --- a/src/app/core/shell/sidenav/sidenav.component.html +++ b/src/app/core/shell/sidenav/sidenav.component.html @@ -86,6 +86,7 @@ matTooltip="{{ 'tooltips.Navigation' | translate }}" routerLinkActive="active-menu" [routerLinkActiveOptions]="{ exact: false }" + *mifosxHasPermission="'READ_CLIENT'" > @@ -98,6 +99,7 @@ matTooltip="{{ 'tooltips.Checker Inbox and Tasks' | translate }}" routerLinkActive="active-menu" [routerLinkActiveOptions]="{ exact: false }" + *mifosxHasPermission="'READ_MAKERCHECKER'" > @@ -110,6 +112,7 @@ matTooltip="{{ 'labels.menus.Collection Sheet' | translate }}" routerLinkActive="active-menu" [routerLinkActiveOptions]="{ exact: false }" + *mifosxHasPermission="'READ_COLLECTIONSHEET'" > @@ -122,6 +125,7 @@ matTooltip="{{ 'tooltips.Individual Collection Sheet' | translate }}" routerLinkActive="active-menu" [routerLinkActiveOptions]="{ exact: false }" + *mifosxHasPermission="'READ_COLLECTIONSHEET'" > @@ -134,6 +138,7 @@ matTooltip="{{ 'tooltips.Notifications' | translate }}" routerLinkActive="active-menu" [routerLinkActiveOptions]="{ exact: false }" + *mifosxHasPermission="'READ_NOTIFICATION'" > @@ -146,6 +151,7 @@ matTooltip="{{ 'tooltips.Frequent Postings' | translate }}" routerLinkActive="active-menu" [routerLinkActiveOptions]="{ exact: false }" + *mifosxHasPermission="'CREATE_JOURNALENTRY'" > @@ -158,6 +164,7 @@ matTooltip="{{ 'tooltips.Create Journal Entry' | translate }}" routerLinkActive="active-menu" [routerLinkActiveOptions]="{ exact: false }" + *mifosxHasPermission="'CREATE_JOURNALENTRY'" > @@ -170,6 +177,7 @@ matTooltip="{{ 'tooltips.Chart Of Accounts' | translate }}" routerLinkActive="active-menu" [routerLinkActiveOptions]="{ exact: false }" + *mifosxHasPermission="'READ_GLACCOUNT'" > diff --git a/src/app/core/shell/sidenav/sidenav.component.ts b/src/app/core/shell/sidenav/sidenav.component.ts index df1dee42e..1a73c7f07 100644 --- a/src/app/core/shell/sidenav/sidenav.component.ts +++ b/src/app/core/shell/sidenav/sidenav.component.ts @@ -219,6 +219,9 @@ export class SidenavComponent implements OnInit, AfterViewInit { position: string, backdrop: boolean ): void { + if (!target) { + return; + } setTimeout(() => this.popoverService.open(template, target, position, backdrop, {}), 200); } @@ -226,12 +229,12 @@ export class SidenavComponent implements OnInit, AfterViewInit { * To show popovers */ ngAfterViewInit() { - if (this.configurationWizardService.showSideNav) { + if (this.configurationWizardService.showSideNav && this.logo) { setTimeout(() => { this.showPopover(this.templateLogo, this.logo.nativeElement, 'bottom', true); }); } - if (this.configurationWizardService.showSideNavChartofAccounts) { + if (this.configurationWizardService.showSideNavChartofAccounts && this.chartOfAccounts) { setTimeout(() => { this.showPopover(this.templateChartOfAccounts, this.chartOfAccounts.nativeElement, 'top', true); }); diff --git a/src/app/core/shell/toolbar/toolbar.component.html b/src/app/core/shell/toolbar/toolbar.component.html index f0e78b68d..d0aa0cee7 100644 --- a/src/app/core/shell/toolbar/toolbar.component.html +++ b/src/app/core/shell/toolbar/toolbar.component.html @@ -130,9 +130,15 @@ - - - + + + - + - - + - - + - - + - - - + + + diff --git a/src/app/directives/has-permission/has-permission.directive.ts b/src/app/directives/has-permission/has-permission.directive.ts index 270f0693a..d5cb1ddc7 100644 --- a/src/app/directives/has-permission/has-permission.directive.ts +++ b/src/app/directives/has-permission/has-permission.directive.ts @@ -12,6 +12,9 @@ import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angula /** Custom Services */ import { AuthenticationService } from '../../core/authentication/authentication.service'; +/** Environment */ +import { environment } from '../../../environments/environment'; + /** * Has Permission Directive */ @@ -24,6 +27,9 @@ export class HasPermissionDirective { /** User Permissions */ private userPermissions: any[]; + /** RBAC Feature Flag */ + private rbacEnabled: boolean = environment.productionModeEnableRBAC; + /** * Extracts User Permissions from User Credentials * @param {TemplateRef} templateRef Template Reference @@ -55,6 +61,7 @@ export class HasPermissionDirective { * Checks if user is permitted. * @param {string} permission Permission * @returns {true} + * - RBAC is disabled (backward compatibility mode) * -`ALL_FUNCTIONS`: user is a Super user. * -`ALL_FUNCTIONS_READ`: user has all read permissions and passed permission is 'read' type. * - User has special permission to access that feature. @@ -63,6 +70,11 @@ export class HasPermissionDirective { * - No value was passed to the has permission directive. */ private hasPermission(permission: string) { + // If RBAC is disabled, show all menus/buttons (backward compatibility) + if (!this.rbacEnabled) { + return true; + } + permission = permission.trim(); if (this.userPermissions.includes('ALL_FUNCTIONS')) { return true; diff --git a/src/assets/env.js b/src/assets/env.js index 51f69fb7f..93c20a168 100644 --- a/src/assets/env.js +++ b/src/assets/env.js @@ -87,6 +87,10 @@ window['env']['mifosInterbankTransfersApiVersion'] = ''; window['env']['mifosInterbankTransfersEnabled'] = 'true'; + // Enable Role-Based Access Control (RBAC) for menu/button permissions + // Set to true to enable RBAC, false (default) for backward compatibility + window['env']['productionModeEnableRBAC'] = false; + // OIDC Plugin Environment variables window['env']['oidcServerEnabled'] = false; window['env']['oidcBaseUrl'] = ''; diff --git a/src/assets/env.template.js b/src/assets/env.template.js index c7555b522..8f748fb95 100644 --- a/src/assets/env.template.js +++ b/src/assets/env.template.js @@ -99,6 +99,10 @@ window['env']['mifosInterbankTransfersApiVersion'] = '$MIFOS_INTERBANK_TRANSFERS_API_VERSION'; window['env']['mifosInterbankTransfersEnabled'] = '$MIFOS_INTERBANK_TRANSFERS_ENABLED'; + // Enable Role-Based Access Control (RBAC) for menu/button permissions + // Set to 'true' to enable RBAC, 'false' (default) for backward compatibility + window['env']['productionModeEnableRBAC'] = '$MIFOS_PRODUCTION_MODE_ENABLE_RBAC'; + // OIDC Plugin Environment variables window['env']['oidcServerEnabled'] = '$FINERACT_PLUGIN_OIDC_ENABLED'; window['env']['oidcBaseUrl'] = '$FINERACT_PLUGIN_OIDC_BASE_URL'; diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 2b3490610..b10101ca9 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -92,6 +92,15 @@ export const environment = { complianceHideClientData: loadedEnv['complianceHideClientData'] === 'true' || loadedEnv['complianceHideClientData'] === true || false, + /** + * Enable Role-Based Access Control (RBAC) for menus and buttons + * When enabled, menus/buttons visibility is controlled by user permissions + * When disabled (default), shows all menus/buttons for backward compatibility + * Set via MIFOS_PRODUCTION_MODE_ENABLE_RBAC env var + */ + productionModeEnableRBAC: + loadedEnv['productionModeEnableRBAC'] === 'true' || loadedEnv['productionModeEnableRBAC'] === true || false, + OIDC: { // Support legacy FINERACT_PLUGIN_OIDC_* variable names for backward compatibility oidcServerEnabled: diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 48ae15463..e364c711d 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -94,6 +94,15 @@ export const environment = { complianceHideClientData: loadedEnv.complianceHideClientData === 'true' || loadedEnv.complianceHideClientData === true || false, + /** + * Enable Role-Based Access Control (RBAC) for menus and buttons + * When enabled, menus/buttons visibility is controlled by user permissions + * When disabled (default), shows all menus/buttons for backward compatibility + * Set via MIFOS_PRODUCTION_MODE_ENABLE_RBAC env var + */ + productionModeEnableRBAC: + loadedEnv.productionModeEnableRBAC === 'true' || loadedEnv.productionModeEnableRBAC === true || false, + OIDC: { // Support legacy FINERACT_PLUGIN_OIDC_* variable names for backward compatibility oidcServerEnabled: