Merge dev-latest into feature/bugfix
This commit is contained in:
+5
-3
@@ -165,9 +165,11 @@ public class SecurityConfig {
|
||||
// envelope's RSA signature/encryption (NpciCryptoUtil) inside the handler.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/npci/**")).permitAll()
|
||||
// Client account summary, third-party callers — a partner integration calls this
|
||||
// with an X-Api-Key header instead of a Fineract username/password (issued via
|
||||
// POST /v1/apiclients, super-user only). Same posture as IMPS/NPCI above: the
|
||||
// outer gate stays open for this one narrow GET path, and
|
||||
// with an X-Api-Key header instead of a Fineract username/password. The key itself
|
||||
// is issued by inserting directly into m_api_client in the tenant-store database
|
||||
// (see ApiClientAdminApiResource — its POST /v1/apiclients is deliberately disabled,
|
||||
// since any bank's own super user reaching it could mint a platform-wide key). Same
|
||||
// posture as IMPS/NPCI above: the outer gate stays open for this one narrow GET path, and
|
||||
// ClientAllAccountsApiResource#retrieveByClientNo is what actually authenticates
|
||||
// the caller (ApiClientCredentialService checks the key's hash, active flag and
|
||||
// IP allowlist) — a request with no key and no Fineract login still gets nothing,
|
||||
|
||||
+20
-14
@@ -20,7 +20,6 @@ package org.apache.fineract.infrastructure.security.apiclient;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
@@ -35,17 +34,26 @@ import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Admin-only management of the third-party API keys {@link ApiClientCredentialService} authenticates.
|
||||
* This resource itself requires a normal Fineract login, same as everything else -- ordinary
|
||||
* username/password, restricted to a super user (ALL_FUNCTIONS). Only the endpoints that use a minted
|
||||
* key to skip login (e.g. GET /v1/clientaccounts) are the ones exempted in SecurityConfig; issuing that
|
||||
* key in the first place is not something to hand to just anyone.
|
||||
* List/deactivate/reactivate still require a normal Fineract login restricted to a super user
|
||||
* (ALL_FUNCTIONS) -- but minting a NEW key ({@link #create}) is disabled here entirely, on purpose.
|
||||
*
|
||||
* {@code ALL_FUNCTIONS} is a per-TENANT role grant -- and {@code TenantMenuAccessApiResource}'s whole
|
||||
* point is that a bank's own super user (or a role simply misconfigured to carry ALL_FUNCTIONS, as the
|
||||
* "Clerk" role was found to in two tenants this session) must never be able to lift its own menu-access
|
||||
* ceiling. If this endpoint minted keys for any ALL_FUNCTIONS holder, that same bank super user could
|
||||
* call {@code POST /v1/apiclients} and hand themselves a brand-new platform-operator credential -- one
|
||||
* that authenticates against every tenant, not just their own -- completely bypassing the ceiling this
|
||||
* key mechanism exists to enforce. A super user being able to read/deactivate the client list is fine
|
||||
* (visibility and revocation are not privilege escalation); being able to mint a fresh one is exactly
|
||||
* the escalation path being closed. New keys are issued the same way this fix issued the first one: a
|
||||
* direct insert into {@code m_api_client} (see {@link ApiClientCredentialService#createClient}), by
|
||||
* whoever already has direct database access -- never over HTTP.
|
||||
*/
|
||||
@Path("/v1/apiclients")
|
||||
@Component
|
||||
@@ -55,20 +63,18 @@ public class ApiClientAdminApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final ApiClientCredentialService credentialService;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
private final Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
|
||||
/** Disabled -- see the class comment. Minting a key is a direct-database operation only. */
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public String create(final String body) {
|
||||
final AppUser user = requireSuperUser();
|
||||
final JsonElement root = fromJsonHelper.parse(body);
|
||||
final String clientName = fromJsonHelper.extractStringNamed("clientName", root);
|
||||
final String allowedIps = fromJsonHelper.extractStringNamed("allowedIps", root);
|
||||
|
||||
final Map<String, Object> result = credentialService.createClient(clientName, allowedIps, user.getId());
|
||||
return gson.toJson(result);
|
||||
requireSuperUser();
|
||||
throw new WebApplicationException(Response.status(403)
|
||||
.entity("{\"message\":\"Minting a new platform API key over HTTP is disabled. "
|
||||
+ "Insert directly into m_api_client in the tenant-store database instead.\"}")
|
||||
.type(MediaType.APPLICATION_JSON).build());
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
+1
@@ -32,4 +32,5 @@
|
||||
<include file="parts/0009_set_and_encrypt_ro_if_not_exists.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0010_set_datetime_precision.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0011_create_api_client.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0012_seed_default_platform_api_client.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<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">
|
||||
|
||||
<!-- Seeds ONE fixed, well-known platform-operator API key on every deployment, so
|
||||
#/platform-admin/menu-access works out of the box the first time the app is stood up,
|
||||
without anyone needing shell/DB access to mint the first key by hand. It is deliberately
|
||||
the SAME raw value on every deployment/tenant (requested behaviour) rather than a random
|
||||
one generated per install -- the trade-off, on purpose: this value is visible to anyone who
|
||||
can read this source file, exactly like a default admin password shipped in a seed script.
|
||||
It is a bootstrap convenience, not a secret. Rotate it for any deployment that is not purely
|
||||
local/dev: insert a new row with a real random key's hash (see ApiClientCredentialService's
|
||||
hashing) and either deactivate or DELETE this row -- POST /v1/apiclients is intentionally
|
||||
disabled (see ApiClientAdminApiResource), so rotation is a direct m_api_client write, same
|
||||
as this seed.
|
||||
|
||||
Raw key (only shown here, since it is fixed and not a secret to rotate away from):
|
||||
cbsx_default_platform_operator_key_2026
|
||||
api_key_hash below is SHA-256 of that exact string, hex-encoded, lowercase -- matching
|
||||
ApiClientCredentialService#sha256Hex. -->
|
||||
<changeSet id="0012_seed_default_platform_api_client" author="cbs-nitesh">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_api_client"/>
|
||||
<sqlCheck expectedResult="0">
|
||||
SELECT COUNT(*) FROM m_api_client
|
||||
WHERE api_key_hash = '9d6d4dfa7a1953e33e0acde4fd775aad11689ec540340c7224796447118a3edd'
|
||||
</sqlCheck>
|
||||
</and>
|
||||
</preConditions>
|
||||
<insert tableName="m_api_client">
|
||||
<column name="client_name" value="Default Platform Operator (rotate before real production use)"/>
|
||||
<column name="api_key_hash" value="9d6d4dfa7a1953e33e0acde4fd775aad11689ec540340c7224796447118a3edd"/>
|
||||
<column name="api_key_prefix" value="cbsx_default"/>
|
||||
<column name="allowed_ips"/>
|
||||
<column name="created_by"/>
|
||||
<column name="created_by_tenant" value="ALL"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -145,7 +145,6 @@
|
||||
<include file="parts/0583_save_rbi_form_a_return_job.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0584_form9_slr_assets.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0582_create_tds_config.xml" relativeToChangelogFile="true"/>
|
||||
<<<<<<< HEAD
|
||||
<!-- Our local 0575 Loan Originator checker-permission migration collided with dev-latest's own
|
||||
0575_rbi_reserve_custom_roles.xml once merged (it was only stashed/uncommitted locally, so it
|
||||
never appeared on either side of this merge as a real conflict - the collision only became
|
||||
@@ -156,7 +155,5 @@
|
||||
<include file="parts/0584_add_manage_survey_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0585_add_loan_cob_catchup_permission.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0586_add_system_module_checker_permissions.xml" relativeToChangelogFile="true"/>
|
||||
=======
|
||||
<include file="parts/0585_fix_current_product_od_scheme_pending_columns.xml" relativeToChangelogFile="true"/>
|
||||
>>>>>>> dev-latest
|
||||
<include file="parts/0587_fix_current_product_od_scheme_pending_columns.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@
|
||||
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">
|
||||
|
||||
<changeSet author="CBS" id="0585-1-fix-saving-product-temp-od-min-limit">
|
||||
<changeSet author="CBS" id="0587-1-fix-saving-product-temp-od-min-limit">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_saving_product_temp"/>
|
||||
@@ -46,7 +46,7 @@
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="CBS" id="0585-2-fix-saving-product-temp-od-excess-drawing">
|
||||
<changeSet author="CBS" id="0587-2-fix-saving-product-temp-od-excess-drawing">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_saving_product_temp"/>
|
||||
Reference in New Issue
Block a user