Merge branch 'dev' of https://git.lincpay.tech/CBSNexora/cbsnexora-backend into dev
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
# Conflicts: # fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml
This commit is contained in:
+9
@@ -172,6 +172,15 @@ public class SecurityConfig {
|
||||
// IP allowlist) — a request with no key and no Fineract login still gets nothing,
|
||||
// it just fails inside the handler instead of at this filter.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/clientaccounts/**")).permitAll()
|
||||
// Tenant menu access — same dual-mode X-Api-Key posture as clientaccounts above
|
||||
// for the two GETs (TenantMenuAccessApiResource#read still requires an ordinary
|
||||
// session when no key is sent, same as ClientAllAccountsApiResource does; permitAll
|
||||
// here only opens Spring Security's outer gate for the key path, it does not skip
|
||||
// the handler's own check). The PUT has no session fallback inside the handler at
|
||||
// all, even for a logged-in super user — see TenantMenuAccessApiResource#save.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/tenant-menu-access")).permitAll()
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/tenant-menu-access/tenants")).permitAll()
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/tenant-menu-access")).permitAll()
|
||||
// Login OTP — same posture as PAN/GSTIN public verification: provider-credentialed,
|
||||
// no Fineract auth required.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/login/otp/generate")).permitAll()
|
||||
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.security.apiclient;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.HeaderParam;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Which top-nav menu sections a tenant is allowed to ever show, regardless of what any role inside
|
||||
* that tenant grants itself.
|
||||
*
|
||||
* {@code HasPermissionDirective} on the frontend already hides a menu item a user's own role lacks --
|
||||
* but a role carrying {@code ALL_FUNCTIONS} (a super user, or a role simply misconfigured to hold it,
|
||||
* as the "Clerk" role was found to in two tenants this session) bypasses that check unconditionally.
|
||||
* A bank's own admin screen for roles and permissions therefore cannot be trusted as the only gate on
|
||||
* "does this bank ever get to see the HRM/NPA/Admin section" -- whoever ends up with ALL_FUNCTIONS in
|
||||
* that tenant, today or after some future role edit, would still see it. This table is the ceiling
|
||||
* that check now also has to clear.
|
||||
*
|
||||
* Reading it accepts either credential (see {@link #read} below) -- a bank's own user needs their own
|
||||
* tenant's list to render their nav correctly, the same way they already read their own permissions,
|
||||
* and the platform operator needs to read ANY tenant's current list before toggling it. Writing it
|
||||
* accepts only the platform's own {@code X-Api-Key} + {@link ApiClientCredentialService} credential --
|
||||
* the same mechanism {@code ClientAllAccountsApiResource} uses for third-party callers, but with no
|
||||
* session fallback at all here. That is deliberate -- a bank's own super user must never be able to
|
||||
* lift its own ceiling by granting themselves a permission from inside their own tenant, which is
|
||||
* exactly what a normal {@code ALL_FUNCTIONS} gate would allow. The key is minted once via
|
||||
* {@code POST /v1/apiclients} by the platform operator and never handed to a bank.
|
||||
*/
|
||||
@Path("/v1/tenant-menu-access")
|
||||
@Component
|
||||
@Tag(name = "Tenant Menu Access",
|
||||
description = "Per-tenant cap on which top-nav menu sections can ever be shown, independent of any role's own permissions. Read by any logged-in user of the tenant; written only via a platform-operator API key.")
|
||||
@RequiredArgsConstructor
|
||||
public class TenantMenuAccessApiResource {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PlatformSecurityContext context;
|
||||
private final ApiClientCredentialService apiClientCredentialService;
|
||||
private final TenantDetailsService tenantDetailsService;
|
||||
private final Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
|
||||
private static final String[] IP_HEADER_CANDIDATES = { "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP" };
|
||||
|
||||
// The whole, fixed set of top-nav menu codes this screen knows about -- see 0541 for how this
|
||||
// list was seeded. A write is only ever a PATCH over this table's existing rows (see save()); a
|
||||
// code outside this set cannot be created by the API, so a typo in a request body is silently a
|
||||
// no-op rather than junk data the frontend would then have to guard against.
|
||||
private static final Set<String> KNOWN_MENU_CODES = Set.of("MENU_INSTITUTION", "SIDEBAR_CFCFRMS", "MENU_TASKS",
|
||||
"MENU_CASHIER", "MENU_CLERK", "MENU_BRANCH_OPS", "MENU_CLEARING", "MENU_DEPOSITS", "MENU_ADVANCES",
|
||||
"MENU_LOAN_ORIGINATION", "MENU_SHARE", "MENU_NPA", "MENU_GENERAL_LEDGER", "MENU_SUB_LEDGER", "MENU_ACCOUNTING",
|
||||
"MENU_CKYC_MGMT", "MENU_INSURANCE", "MENU_DIGITAL", "MENU_HRM", "MENU_ADMIN", "MENU_RBI", "MENU_INVESTMENT",
|
||||
"MENU_REPORTS", "MENU_OTHERS", "MENU_CONFIG_WIZARD", "MENU_CHEQUEBOOK", "MENU_LETTER_MGMT");
|
||||
|
||||
/**
|
||||
* This tenant's own menu-access list, for the tenant named in {@code Fineract-Platform-TenantId}.
|
||||
* Dual-mode read, same posture as {@code ClientAllAccountsApiResource}: an ordinary logged-in
|
||||
* user reads their own tenant's list exactly like they already read their own permission list
|
||||
* (no elevated permission required), or the platform operator reads ANY tenant's list with the
|
||||
* API key, to know the current state before toggling it on their own control screen. Only the
|
||||
* write side ({@link #save}) has no session fallback -- reading configuration is not sensitive,
|
||||
* changing it is.
|
||||
*/
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public String read(@HeaderParam("X-Api-Key") final String apiKey, @Context final HttpServletRequest servletRequest) {
|
||||
if (StringUtils.isNotBlank(apiKey)) {
|
||||
apiClientCredentialService.authenticate(apiKey, resolveClientIp(servletRequest));
|
||||
} else {
|
||||
context.authenticatedUser();
|
||||
}
|
||||
final List<Map<String, Object>> rows = jdbcTemplate.queryForList("SELECT menu_code, enabled FROM m_tenant_menu_access");
|
||||
final JsonArray out = new JsonArray();
|
||||
for (final Map<String, Object> row : rows) {
|
||||
final JsonObject o = new JsonObject();
|
||||
o.addProperty("menuCode", (String) row.get("menu_code"));
|
||||
o.addProperty("enabled", Boolean.TRUE.equals(row.get("enabled")));
|
||||
out.add(o);
|
||||
}
|
||||
return gson.toJson(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every tenant configured on this platform, for the operator's own tenant picker -- always the
|
||||
* master list from the tenant-store database, never the one tenant the caller's own
|
||||
* {@code Fineract-Platform-TenantId} header happened to resolve to. That header still has to name
|
||||
* a real tenant for the request to reach this handler at all (see
|
||||
* {@code TenantAwareBasicAuthenticationFilter}); which one is otherwise irrelevant here.
|
||||
*/
|
||||
@GET
|
||||
@Path("/tenants")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public String listTenants(@HeaderParam("X-Api-Key") final String apiKey, @Context final HttpServletRequest servletRequest) {
|
||||
requireApiKey(apiKey, servletRequest);
|
||||
final JsonArray out = new JsonArray();
|
||||
for (final FineractPlatformTenant tenant : tenantDetailsService.findAllTenants()) {
|
||||
final JsonObject o = new JsonObject();
|
||||
o.addProperty("identifier", tenant.getTenantIdentifier());
|
||||
o.addProperty("name", tenant.getName());
|
||||
out.add(o);
|
||||
}
|
||||
return gson.toJson(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets which menu codes the tenant named in {@code Fineract-Platform-TenantId} may show, from the
|
||||
* operator's own control screen. API key only -- see the class comment for why a normal session,
|
||||
* even a super user's, is never accepted here.
|
||||
*
|
||||
* Body: {@code [{"menuCode": "MENU_HRM", "enabled": false}, ...]}. Only codes already in
|
||||
* {@link #KNOWN_MENU_CODES} are written; anything else in the body is ignored rather than
|
||||
* rejected, so the operator's screen can always send its full known set without first checking
|
||||
* this deployment's exact backend version.
|
||||
*/
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public String save(@HeaderParam("X-Api-Key") final String apiKey, @Context final HttpServletRequest servletRequest,
|
||||
final String body) {
|
||||
requireApiKey(apiKey, servletRequest);
|
||||
|
||||
final JsonArray entries = gson.fromJson(body, JsonArray.class);
|
||||
if (entries == null) {
|
||||
throw apiError(400, "Request body must be a JSON array of {menuCode, enabled}.");
|
||||
}
|
||||
final Set<String> touched = new LinkedHashSet<>();
|
||||
for (final var el : entries) {
|
||||
final JsonObject entry = el.getAsJsonObject();
|
||||
if (!entry.has("menuCode") || entry.get("menuCode").isJsonNull()) {
|
||||
continue;
|
||||
}
|
||||
final String menuCode = entry.get("menuCode").getAsString();
|
||||
if (!KNOWN_MENU_CODES.contains(menuCode)) {
|
||||
continue;
|
||||
}
|
||||
final boolean enabled = entry.has("enabled") && !entry.get("enabled").isJsonNull() && entry.get("enabled").getAsBoolean();
|
||||
jdbcTemplate.update("UPDATE m_tenant_menu_access SET enabled = ? WHERE menu_code = ?", enabled, menuCode);
|
||||
touched.add(menuCode);
|
||||
}
|
||||
final JsonObject res = new JsonObject();
|
||||
res.addProperty("tenant", ThreadLocalContextUtil.getTenant().getTenantIdentifier());
|
||||
res.addProperty("updated", touched.size());
|
||||
return gson.toJson(res);
|
||||
}
|
||||
|
||||
/** No session fallback, unlike every other API-key-capable endpoint in this codebase -- see the class comment. */
|
||||
private void requireApiKey(final String apiKey, final HttpServletRequest servletRequest) {
|
||||
if (StringUtils.isBlank(apiKey)) {
|
||||
throw apiError(403, "This endpoint is only reachable with a platform operator API key.");
|
||||
}
|
||||
apiClientCredentialService.authenticate(apiKey, resolveClientIp(servletRequest));
|
||||
}
|
||||
|
||||
private String resolveClientIp(final HttpServletRequest request) {
|
||||
for (final String header : IP_HEADER_CANDIDATES) {
|
||||
final String value = request.getHeader(header);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
return value.split(",")[0].trim();
|
||||
}
|
||||
}
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
|
||||
private WebApplicationException apiError(final int httpStatus, final String message) {
|
||||
final JsonObject body = new JsonObject();
|
||||
body.addProperty("message", message);
|
||||
body.addProperty("defaultUserMessage", message);
|
||||
body.addProperty("httpStatus", httpStatus);
|
||||
return new WebApplicationException(
|
||||
Response.status(httpStatus).entity(gson.toJson(body)).type(MediaType.APPLICATION_JSON).build());
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,8 @@
|
||||
<include file="parts/0538_add_branch_pincode.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0539_seed_cash_exchange_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0540_unique_active_cash_settlement_per_cashier_day.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0541_create_tenant_menu_access.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0542_split_loan_origination_investment_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0546_aml_rule_direction_and_cash_scope.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0547_aml_rule_subject_scope.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0548_sponsor_dd_revoke_request.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!-- A bank's own role/permission screen cannot be trusted to keep a nav item hidden: any role
|
||||
holding ALL_FUNCTIONS (a super user, or a role misconfigured to carry it, as Clerk was found
|
||||
to be in two tenants this session) bypasses every MENU_.../SIDEBAR_... permission check on the
|
||||
frontend unconditionally. This table is the hard ceiling that check now also has to clear,
|
||||
so an operator onboarding a new bank can cap which top-nav sections it can EVER show, no
|
||||
matter what a future role in that bank's own tenant grants itself. Deliberately not editable
|
||||
from inside the tenant it governs (see TenantMenuAccessApiResource) - the whole point is that
|
||||
a bank's own admin cannot lift this ceiling on themselves.
|
||||
|
||||
One row per top-nav menu code, all enabled by default so every tenant that already exists
|
||||
keeps behaving exactly as it does today until an operator deliberately narrows one. -->
|
||||
<changeSet id="0541-1" author="cbs-nitesh">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<tableExists tableName="m_tenant_menu_access" />
|
||||
</not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_tenant_menu_access">
|
||||
<column name="menu_code" type="VARCHAR(64)">
|
||||
<constraints primaryKey="true" nullable="false" />
|
||||
</column>
|
||||
<column name="enabled" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false" />
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0541-2" author="cbs-nitesh">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_tenant_menu_access</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_INSTITUTION" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="SIDEBAR_CFCFRMS" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_TASKS" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_CASHIER" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_CLERK" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_BRANCH_OPS" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_CLEARING" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_DEPOSITS" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_ADVANCES" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_SHARE" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_NPA" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_GENERAL_LEDGER" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_SUB_LEDGER" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_ACCOUNTING" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_CKYC_MGMT" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_INSURANCE" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_DIGITAL" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_HRM" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_ADMIN" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_RBI" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_REPORTS" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_OTHERS" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_CONFIG_WIZARD" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_CHEQUEBOOK" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_LETTER_MGMT" /></insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!-- Two of the 27 top-nav items were sharing a permission code with their neighbour:
|
||||
Loan Origination rode on MENU_ADVANCES (Advances' own code) and Investment rode on
|
||||
MENU_RBI (RBI's own code), so neither could be shown or hidden independently, on a
|
||||
role or on the new per-tenant menu-access ceiling (0541) alike. Splitting them into their
|
||||
own codes here, and granting the new code to every role that already had the shared one,
|
||||
so this migration changes nothing about who sees what today. It only makes the two
|
||||
pairs independently controllable from this point on. -->
|
||||
<changeSet id="0542-1" author="cbs-nitesh">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'MENU_LOAN_ORIGINATION'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="heading_menu" />
|
||||
<column name="code" value="MENU_LOAN_ORIGINATION" />
|
||||
<column name="entity_name" value="MENU" />
|
||||
<column name="action_name" value="LOAN_ORIGINATION" />
|
||||
<column name="can_maker_checker" valueBoolean="false" />
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0542-2" author="cbs-nitesh">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'MENU_INVESTMENT'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="heading_menu" />
|
||||
<column name="code" value="MENU_INVESTMENT" />
|
||||
<column name="entity_name" value="MENU" />
|
||||
<column name="action_name" value="INVESTMENT" />
|
||||
<column name="can_maker_checker" valueBoolean="false" />
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<!-- Preserve today's visibility: any role that already sees Advances also keeps seeing
|
||||
Loan Origination, and any role that already sees RBI also keeps seeing Investment. -->
|
||||
<changeSet id="0542-3" author="cbs-nitesh" runOnChange="true">
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT rp.role_id, p2.id
|
||||
FROM m_role_permission rp
|
||||
JOIN m_permission p1 ON p1.id = rp.permission_id AND p1.code = 'MENU_ADVANCES'
|
||||
JOIN m_permission p2 ON p2.code = 'MENU_LOAN_ORIGINATION'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission existing
|
||||
WHERE existing.role_id = rp.role_id AND existing.permission_id = p2.id)
|
||||
</sql>
|
||||
<sql>
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT rp.role_id, p2.id
|
||||
FROM m_role_permission rp
|
||||
JOIN m_permission p1 ON p1.id = rp.permission_id AND p1.code = 'MENU_RBI'
|
||||
JOIN m_permission p2 ON p2.code = 'MENU_INVESTMENT'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission existing
|
||||
WHERE existing.role_id = rp.role_id AND existing.permission_id = p2.id)
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Extend the per-tenant menu-access ceiling (0541) to the two new codes, enabled by default
|
||||
so nothing already deployed there changes either. -->
|
||||
<changeSet id="0542-4" author="cbs-nitesh">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_tenant_menu_access" />
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_tenant_menu_access WHERE menu_code = 'MENU_LOAN_ORIGINATION'</sqlCheck>
|
||||
</and>
|
||||
</preConditions>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_LOAN_ORIGINATION" /></insert>
|
||||
<insert tableName="m_tenant_menu_access"><column name="menu_code" value="MENU_INVESTMENT" /></insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user