WEB-575 Fix the WebApp to show the Menus and buttons (create, import, delete, edit) as per the grants of each profile (#3102)

This commit is contained in:
Soni Jay
2026-02-07 09:44:52 -06:00
committed by GitHub
parent 56dcc6064c
commit adecbc27b8
10 changed files with 151 additions and 23 deletions
+61 -7
View File
@@ -281,13 +281,14 @@ Available languages:
#### UI Display Settings #### UI Display Settings
| Variable | Description | Default Value | | Variable | Description | Default Value |
| ---------------------------------- | ------------------------------------------ | ------------- | | ---------------------------------- | -------------------------------------------------- | ------------- |
| MIFOS_DISPLAY_TENANT_SELECTOR | Display tenant selector in Login view | true | | MIFOS_DISPLAY_TENANT_SELECTOR | Display tenant selector in Login view | true |
| MIFOS_DISPLAY_BACKEND_INFO | Display backend info in footer | true | | MIFOS_DISPLAY_BACKEND_INFO | Display backend info in footer | true |
| MIFOS_PRODUCTION_MODE | Show minimal production hero on login page | false | | MIFOS_PRODUCTION_MODE | Show minimal production hero on login page | false |
| MIFOS_ALLOW_SERVER_SWITCH_SELECTOR | Display DNS server list | true | | MIFOS_ALLOW_SERVER_SWITCH_SELECTOR | Display DNS server list | true |
| MIFOS_COMPLIANCE_HIDE_CLIENT_DATA | Hide client names in UI (mask with \*) | false | | 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 #### 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. 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 ## Releases
### 1.0.0 (Tag: 1.0.0-fineract1.11) ### 1.0.0 (Tag: 1.0.0-fineract1.11)
+5
View File
@@ -44,4 +44,9 @@ MIFOS_COMPLIANCE_HIDE_CLIENT_DATA=false
# Production mode - when true, shows minimal hero with only branding at bottom # Production mode - when true, shows minimal hero with only branding at bottom
# Set to true for financial institutions that want a clean, professional look # Set to true for financial institutions that want a clean, professional look
MIFOS_PRODUCTION_MODE=false 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
@@ -86,6 +86,7 @@
matTooltip="{{ 'tooltips.Navigation' | translate }}" matTooltip="{{ 'tooltips.Navigation' | translate }}"
routerLinkActive="active-menu" routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
*mifosxHasPermission="'READ_CLIENT'"
> >
<mat-icon matListIcon> <mat-icon matListIcon>
<fa-icon icon="location-arrow" size="sm"></fa-icon> <fa-icon icon="location-arrow" size="sm"></fa-icon>
@@ -98,6 +99,7 @@
matTooltip="{{ 'tooltips.Checker Inbox and Tasks' | translate }}" matTooltip="{{ 'tooltips.Checker Inbox and Tasks' | translate }}"
routerLinkActive="active-menu" routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
*mifosxHasPermission="'READ_MAKERCHECKER'"
> >
<mat-icon matListIcon> <mat-icon matListIcon>
<i class="fa fa-check"></i> <i class="fa fa-check"></i>
@@ -110,6 +112,7 @@
matTooltip="{{ 'labels.menus.Collection Sheet' | translate }}" matTooltip="{{ 'labels.menus.Collection Sheet' | translate }}"
routerLinkActive="active-menu" routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
*mifosxHasPermission="'READ_COLLECTIONSHEET'"
> >
<mat-icon matListIcon> <mat-icon matListIcon>
<i class="fa fa-tasks"></i> <i class="fa fa-tasks"></i>
@@ -122,6 +125,7 @@
matTooltip="{{ 'tooltips.Individual Collection Sheet' | translate }}" matTooltip="{{ 'tooltips.Individual Collection Sheet' | translate }}"
routerLinkActive="active-menu" routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
*mifosxHasPermission="'READ_COLLECTIONSHEET'"
> >
<mat-icon matListIcon> <mat-icon matListIcon>
<i class="fa fa-tasks"></i> <i class="fa fa-tasks"></i>
@@ -134,6 +138,7 @@
matTooltip="{{ 'tooltips.Notifications' | translate }}" matTooltip="{{ 'tooltips.Notifications' | translate }}"
routerLinkActive="active-menu" routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
*mifosxHasPermission="'READ_NOTIFICATION'"
> >
<mat-icon matListIcon> <mat-icon matListIcon>
<fa-icon icon="bell" size="sm"></fa-icon> <fa-icon icon="bell" size="sm"></fa-icon>
@@ -146,6 +151,7 @@
matTooltip="{{ 'tooltips.Frequent Postings' | translate }}" matTooltip="{{ 'tooltips.Frequent Postings' | translate }}"
routerLinkActive="active-menu" routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
*mifosxHasPermission="'CREATE_JOURNALENTRY'"
> >
<mat-icon matListIcon> <mat-icon matListIcon>
<fa-icon icon="sync" size="sm"></fa-icon> <fa-icon icon="sync" size="sm"></fa-icon>
@@ -158,6 +164,7 @@
matTooltip="{{ 'tooltips.Create Journal Entry' | translate }}" matTooltip="{{ 'tooltips.Create Journal Entry' | translate }}"
routerLinkActive="active-menu" routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
*mifosxHasPermission="'CREATE_JOURNALENTRY'"
> >
<mat-icon matListIcon> <mat-icon matListIcon>
<fa-icon icon="plus" size="sm"></fa-icon> <fa-icon icon="plus" size="sm"></fa-icon>
@@ -170,6 +177,7 @@
matTooltip="{{ 'tooltips.Chart Of Accounts' | translate }}" matTooltip="{{ 'tooltips.Chart Of Accounts' | translate }}"
routerLinkActive="active-menu" routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
*mifosxHasPermission="'READ_GLACCOUNT'"
> >
<mat-icon matListIcon> <mat-icon matListIcon>
<fa-icon icon="sitemap" size="sm"></fa-icon> <fa-icon icon="sitemap" size="sm"></fa-icon>
@@ -219,6 +219,9 @@ export class SidenavComponent implements OnInit, AfterViewInit {
position: string, position: string,
backdrop: boolean backdrop: boolean
): void { ): void {
if (!target) {
return;
}
setTimeout(() => this.popoverService.open(template, target, position, backdrop, {}), 200); setTimeout(() => this.popoverService.open(template, target, position, backdrop, {}), 200);
} }
@@ -226,12 +229,12 @@ export class SidenavComponent implements OnInit, AfterViewInit {
* To show popovers * To show popovers
*/ */
ngAfterViewInit() { ngAfterViewInit() {
if (this.configurationWizardService.showSideNav) { if (this.configurationWizardService.showSideNav && this.logo) {
setTimeout(() => { setTimeout(() => {
this.showPopover(this.templateLogo, this.logo.nativeElement, 'bottom', true); this.showPopover(this.templateLogo, this.logo.nativeElement, 'bottom', true);
}); });
} }
if (this.configurationWizardService.showSideNavChartofAccounts) { if (this.configurationWizardService.showSideNavChartofAccounts && this.chartOfAccounts) {
setTimeout(() => { setTimeout(() => {
this.showPopover(this.templateChartOfAccounts, this.chartOfAccounts.nativeElement, 'top', true); this.showPopover(this.templateChartOfAccounts, this.chartOfAccounts.nativeElement, 'top', true);
}); });
@@ -130,9 +130,15 @@
</mat-menu> </mat-menu>
<mat-menu #institutionMenu="matMenu" [overlapTrigger]="false"> <mat-menu #institutionMenu="matMenu" [overlapTrigger]="false">
<button mat-menu-item routerLink="/clients" tabindex="0">{{ 'labels.menus.Clients' | translate }}</button> <button mat-menu-item routerLink="/clients" tabindex="0" *mifosxHasPermission="'READ_CLIENT'">
<button mat-menu-item routerLink="/groups" tabindex="0">{{ 'labels.menus.Groups' | translate }}</button> {{ 'labels.menus.Clients' | translate }}
<button mat-menu-item routerLink="/centers" tabindex="0">{{ 'labels.menus.Centers' | translate }}</button> </button>
<button mat-menu-item routerLink="/groups" tabindex="0" *mifosxHasPermission="'READ_GROUP'">
{{ 'labels.menus.Groups' | translate }}
</button>
<button mat-menu-item routerLink="/centers" tabindex="0" *mifosxHasPermission="'READ_CENTER'">
{{ 'labels.menus.Centers' | translate }}
</button>
<span class="hide-lt-md"> <span class="hide-lt-md">
<button mat-menu-item [routerLink]="['/accounting']" tabindex="0"> <button mat-menu-item [routerLink]="['/accounting']" tabindex="0">
{{ 'labels.menus.Accounting' | translate }} {{ 'labels.menus.Accounting' | translate }}
@@ -146,28 +152,42 @@
<!-- Toolbar Menus --> <!-- Toolbar Menus -->
<mat-menu #reportsMenu="matMenu" [overlapTrigger]="false"> <mat-menu #reportsMenu="matMenu" [overlapTrigger]="false">
<button mat-menu-item [routerLink]="['/reports']" tabindex="0">{{ 'labels.menus.All' | translate }}</button> <button mat-menu-item [routerLink]="['/reports']" tabindex="0" *mifosxHasPermission="'READ_REPORT'">
<button mat-menu-item [routerLink]="['/reports', 'Client']" tabindex="0"> {{ 'labels.menus.All' | translate }}
</button>
<button mat-menu-item [routerLink]="['/reports', 'Client']" tabindex="0" *mifosxHasPermission="'READ_REPORT'">
{{ 'labels.menus.Clients' | translate }} {{ 'labels.menus.Clients' | translate }}
</button> </button>
<button mat-menu-item [routerLink]="['/reports', 'Loan']" tabindex="0">{{ 'labels.menus.Loans' | translate }}</button> <button mat-menu-item [routerLink]="['/reports', 'Loan']" tabindex="0" *mifosxHasPermission="'READ_REPORT'">
<button mat-menu-item [routerLink]="['/reports', 'Savings']" tabindex="0"> {{ 'labels.menus.Loans' | translate }}
</button>
<button mat-menu-item [routerLink]="['/reports', 'Savings']" tabindex="0" *mifosxHasPermission="'READ_REPORT'">
{{ 'labels.menus.Savings' | translate }} {{ 'labels.menus.Savings' | translate }}
</button> </button>
<button mat-menu-item [routerLink]="['/reports', 'Fund']" tabindex="0">{{ 'labels.menus.Funds' | translate }}</button> <button mat-menu-item [routerLink]="['/reports', 'Fund']" tabindex="0" *mifosxHasPermission="'READ_REPORT'">
<button mat-menu-item [routerLink]="['/reports', 'Accounting']" tabindex="0"> {{ 'labels.menus.Funds' | translate }}
</button>
<button mat-menu-item [routerLink]="['/reports', 'Accounting']" tabindex="0" *mifosxHasPermission="'READ_REPORT'">
{{ 'labels.menus.Accounting' | translate }} {{ 'labels.menus.Accounting' | translate }}
</button> </button>
</mat-menu> </mat-menu>
<mat-menu #adminMenu="matMenu" [overlapTrigger]="false"> <mat-menu #adminMenu="matMenu" [overlapTrigger]="false">
<button mat-menu-item (click)="navigateMenu('/appusers')" tabindex="0">{{ 'labels.menus.Users' | translate }}</button> <button mat-menu-item [routerLink]="['/appusers']" tabindex="0" *mifosxHasPermission="'READ_USER'">
<button mat-menu-item [routerLink]="['/organization']" tabindex="0"> {{ 'labels.menus.Users' | translate }}
</button>
<button mat-menu-item [routerLink]="['/organization']" tabindex="0" *mifosxHasPermission="'READ_OFFICE'">
{{ 'labels.menus.Organization' | translate }} {{ 'labels.menus.Organization' | translate }}
</button> </button>
<button mat-menu-item [routerLink]="['/system']" tabindex="0">{{ 'labels.menus.System' | translate }}</button> <button mat-menu-item [routerLink]="['/system']" tabindex="0" *mifosxHasPermission="'READ_CONFIGURATION'">
<button mat-menu-item [routerLink]="['/products']" tabindex="0">{{ 'labels.menus.Products' | translate }}</button> {{ 'labels.menus.System' | translate }}
<button mat-menu-item routerLink="/templates" tabindex="0">{{ 'labels.menus.Templates' | translate }}</button> </button>
<button mat-menu-item [routerLink]="['/products']" tabindex="0" *mifosxHasPermission="'READ_PRODUCT'">
{{ 'labels.menus.Products' | translate }}
</button>
<button mat-menu-item routerLink="/templates" tabindex="0" *mifosxHasPermission="'READ_TEMPLATE'">
{{ 'labels.menus.Templates' | translate }}
</button>
</mat-menu> </mat-menu>
<!-- Application User Help Menu --> <!-- Application User Help Menu -->
@@ -12,6 +12,9 @@ import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angula
/** Custom Services */ /** Custom Services */
import { AuthenticationService } from '../../core/authentication/authentication.service'; import { AuthenticationService } from '../../core/authentication/authentication.service';
/** Environment */
import { environment } from '../../../environments/environment';
/** /**
* Has Permission Directive * Has Permission Directive
*/ */
@@ -24,6 +27,9 @@ export class HasPermissionDirective {
/** User Permissions */ /** User Permissions */
private userPermissions: any[]; private userPermissions: any[];
/** RBAC Feature Flag */
private rbacEnabled: boolean = environment.productionModeEnableRBAC;
/** /**
* Extracts User Permissions from User Credentials * Extracts User Permissions from User Credentials
* @param {TemplateRef} templateRef Template Reference * @param {TemplateRef} templateRef Template Reference
@@ -55,6 +61,7 @@ export class HasPermissionDirective {
* Checks if user is permitted. * Checks if user is permitted.
* @param {string} permission Permission * @param {string} permission Permission
* @returns {true} * @returns {true}
* - RBAC is disabled (backward compatibility mode)
* -`ALL_FUNCTIONS`: user is a Super user. * -`ALL_FUNCTIONS`: user is a Super user.
* -`ALL_FUNCTIONS_READ`: user has all read permissions and passed permission is 'read' type. * -`ALL_FUNCTIONS_READ`: user has all read permissions and passed permission is 'read' type.
* - User has special permission to access that feature. * - User has special permission to access that feature.
@@ -63,6 +70,11 @@ export class HasPermissionDirective {
* - No value was passed to the has permission directive. * - No value was passed to the has permission directive.
*/ */
private hasPermission(permission: string) { private hasPermission(permission: string) {
// If RBAC is disabled, show all menus/buttons (backward compatibility)
if (!this.rbacEnabled) {
return true;
}
permission = permission.trim(); permission = permission.trim();
if (this.userPermissions.includes('ALL_FUNCTIONS')) { if (this.userPermissions.includes('ALL_FUNCTIONS')) {
return true; return true;
+4
View File
@@ -87,6 +87,10 @@
window['env']['mifosInterbankTransfersApiVersion'] = ''; window['env']['mifosInterbankTransfersApiVersion'] = '';
window['env']['mifosInterbankTransfersEnabled'] = 'true'; 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 // OIDC Plugin Environment variables
window['env']['oidcServerEnabled'] = false; window['env']['oidcServerEnabled'] = false;
window['env']['oidcBaseUrl'] = ''; window['env']['oidcBaseUrl'] = '';
+4
View File
@@ -99,6 +99,10 @@
window['env']['mifosInterbankTransfersApiVersion'] = '$MIFOS_INTERBANK_TRANSFERS_API_VERSION'; window['env']['mifosInterbankTransfersApiVersion'] = '$MIFOS_INTERBANK_TRANSFERS_API_VERSION';
window['env']['mifosInterbankTransfersEnabled'] = '$MIFOS_INTERBANK_TRANSFERS_ENABLED'; 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 // OIDC Plugin Environment variables
window['env']['oidcServerEnabled'] = '$FINERACT_PLUGIN_OIDC_ENABLED'; window['env']['oidcServerEnabled'] = '$FINERACT_PLUGIN_OIDC_ENABLED';
window['env']['oidcBaseUrl'] = '$FINERACT_PLUGIN_OIDC_BASE_URL'; window['env']['oidcBaseUrl'] = '$FINERACT_PLUGIN_OIDC_BASE_URL';
+9
View File
@@ -92,6 +92,15 @@ export const environment = {
complianceHideClientData: complianceHideClientData:
loadedEnv['complianceHideClientData'] === 'true' || loadedEnv['complianceHideClientData'] === true || false, 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: { OIDC: {
// Support legacy FINERACT_PLUGIN_OIDC_* variable names for backward compatibility // Support legacy FINERACT_PLUGIN_OIDC_* variable names for backward compatibility
oidcServerEnabled: oidcServerEnabled:
+9
View File
@@ -94,6 +94,15 @@ export const environment = {
complianceHideClientData: complianceHideClientData:
loadedEnv.complianceHideClientData === 'true' || loadedEnv.complianceHideClientData === true || false, 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: { OIDC: {
// Support legacy FINERACT_PLUGIN_OIDC_* variable names for backward compatibility // Support legacy FINERACT_PLUGIN_OIDC_* variable names for backward compatibility
oidcServerEnabled: oidcServerEnabled: