Skip to content
Merged

2.12 #1417

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6500288
feat(egov-enc-service): add idempotent POST /crypto/v1/_generatekey e…
Chakshu Jun 3, 2026
a570506
fix(egov-localization): reduce memory usage with module-scoped querie…
Chakshu Feb 26, 2026
743d657
fix(boundary-service): fix tenant filtering and tree assembly in rela…
Chakshu Mar 1, 2026
005b030
fix(egov-user): add tenant-aware encryption and localization overloads
Chakshu Feb 26, 2026
630d0fe
fix(egov-user): prevent NPE in _updatenovalidate and profile _update
Chakshu Feb 27, 2026
af89544
fix(egov-user): migrate all encryption call sites to tenant-aware ove…
Chakshu May 31, 2026
5789262
fix(workflow-v2): persist assignees on self-loop ASSIGN transitions
Chakshu Jun 10, 2026
9fdb7ea
feat: add JDK 21 runtime base image for Spring Boot 3.x services
Chakshu Mar 24, 2026
cad3c52
feat: add JDK 8→21 hybrid build for egov-user
Chakshu Mar 24, 2026
c99bd9b
feat: migrate all JVM services to JDK 21 runtime
vinothrallapalli-eGov Jun 5, 2026
17cda5c
fix(tests): fix build failures in egov-workflow-v2 and egov-url-short…
vinothrallapalli-eGov Jun 5, 2026
99c9fe3
Merge pull request #1379 from KDwevedi/212-fix/egov-enc-generatekey
vinothrallapalli-eGov Jul 20, 2026
af826e1
Merge pull request #1380 from KDwevedi/212-fix/egov-localization-oom
vinothrallapalli-eGov Jul 20, 2026
79970ba
Merge pull request #1381 from KDwevedi/212-fix/boundary-tenantid-filter
vinothrallapalli-eGov Jul 20, 2026
68c4a27
Merge pull request #1382 from KDwevedi/212-fix/workflow-assign-assignee
vinothrallapalli-eGov Jul 20, 2026
5dc0a9b
Merge pull request #1383 from KDwevedi/212-fix/egov-user-updatenovali…
vinothrallapalli-eGov Jul 20, 2026
302460a
Merge branch '2.12' into 212-fix/egov-user-tenant-aware-enc
vinothrallapalli-eGov Jul 20, 2026
669c538
Merge pull request #1384 from KDwevedi/212-fix/egov-user-tenant-aware…
vinothrallapalli-eGov Jul 20, 2026
87e13fe
Merge pull request #1385 from KDwevedi/212-feat/jdk21-runtime
vinothrallapalli-eGov Jul 20, 2026
628f85c
Merge pull request #1408 from egovernments/master
vinothrallapalli-eGov Aug 20, 2026
b30ff42
Merge pull request #1409 from egovernments/maven-jdk21
vinothrallapalli-eGov Aug 21, 2026
68f707e
Merge pull request #1411 from egovernments/maven-jdk21
vinothrallapalli-eGov Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@ private String buildQuery(BoundaryRelationshipSearchCriteria boundaryRelationshi
}
}

if(boundaryRelationshipSearchCriteria.getIsSearchForRootNode()) {
QueryUtil.addClauseIfRequired(builder, preparedStmtList);
builder.append(" parent IS NULL ");
}
// When isSearchForRootNode is true, fetch ALL nodes for the tenant+hierarchy
// so the enricher can build the full tree. Do NOT restrict to parent IS NULL,
// as that returns only root nodes with no children to assemble.

if(!CollectionUtils.isEmpty(boundaryRelationshipSearchCriteria.getCurrentBoundaryCodes())) {
QueryUtil.addClauseIfRequired(builder, preparedStmtList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import digit.service.BoundaryRelationshipService;
import digit.web.models.*;
import org.egov.common.contract.request.RequestInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -35,13 +34,13 @@ public ResponseEntity<BoundaryRelationshipResponse> create(@Valid @RequestBody B

/**
* Request handler for serving boundary relationships search request.
* @param boundaryRelationshipSearchCriteria
* @param requestInfo
* @param body wrapper containing RequestInfo and BoundaryRelationshipSearchCriteria
* @return
*/
@RequestMapping(value = "/_search", method = RequestMethod.POST)
public ResponseEntity<BoundarySearchResponse> search(@Valid @ModelAttribute BoundaryRelationshipSearchCriteria boundaryRelationshipSearchCriteria, @RequestBody RequestInfo requestInfo) {
BoundarySearchResponse boundarySearchResponse = boundaryRelationshipService.getBoundaryRelationships(boundaryRelationshipSearchCriteria, requestInfo);
public ResponseEntity<BoundarySearchResponse> search(@Valid @RequestBody BoundaryRelationshipSearchRequest body) {
BoundarySearchResponse boundarySearchResponse = boundaryRelationshipService.getBoundaryRelationships(
body.getBoundaryRelationshipSearchCriteria(), body.getRequestInfo());
return new ResponseEntity<>(boundarySearchResponse, HttpStatus.OK);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package digit.web.models;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.egov.common.contract.request.RequestInfo;
import org.springframework.validation.annotation.Validated;

import jakarta.validation.Valid;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Data;
import lombok.Builder;

/**
* Wrapper request for boundary relationship search — contains RequestInfo and search criteria.
*/
@Validated
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BoundaryRelationshipSearchRequest {

@JsonProperty("RequestInfo")
@Valid
private RequestInfo requestInfo = null;

@JsonProperty("BoundaryRelationship")
@Valid
private BoundaryRelationshipSearchCriteria boundaryRelationshipSearchCriteria = null;

}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,65 @@ public RotateKeyResponse rotateAllKeys() throws Exception {
return new RotateKeyResponse(true);
}

/**
* Idempotently provision a symmetric + asymmetric key for a single tenantId.
*
* The default key-generation path (init() + checkIfTenantExists) only fires
* for tenants reachable via MDMS search under STATE_LEVEL_TENANT_ID — brand
* new state roots (which are not yet under any existing root's
* tenant.tenants list) get a "Tenant Id not found" 500 on first encrypt
* because no key exists for them.
*
* Callers that provision new tenants (e.g. MCP tenant_bootstrap) hit this
* BEFORE the first encrypt request for the new tenant. Re-issuing for an
* existing tenant is a no-op — the existing keyId is returned, no rotation.
*
* Synchronized to prevent two concurrent generates for the same fresh
* tenant from both inserting (the underlying generateKeys does not have
* an INSERT ... ON CONFLICT — duplicate rows would violate the keyId PK).
*/
public synchronized org.egov.enc.web.models.GenerateKeyResponse generateKeyForTenant(String tenantId)
throws Exception {
if (tenantId == null || tenantId.trim().isEmpty()) {
throw new CustomException("INVALID_TENANT_ID", "tenantId must be non-empty");
}
final String normalized = tenantId.trim();

// Idempotency: if the tenant already has an active key in the store,
// return its keyId — do NOT generate a duplicate. This is the no-op
// path that lets callers issue this freely without worrying about state.
keyStore.refreshKeys();
if (keyStore.getTenantIds().contains(normalized)) {
org.egov.enc.models.SymmetricKey existing = keyStore.getSymmetricKey(normalized);
return org.egov.enc.web.models.GenerateKeyResponse.builder()
.tenantId(normalized)
.created(false)
.keyId(existing != null ? existing.getId() : null)
.build();
}

// Generate the key pair and persist. Reuses the same private path
// that init() and rotateAll() use — symmetric + asymmetric inserts
// in one shot; failure halfway throws and the caller can retry.
ArrayList<String> tenants = new ArrayList<>();
tenants.add(normalized);
generateKeys(tenants);

// Refresh in-memory caches so the next encrypt for this tenant
// resolves directly without going through the MDMS-discovery fallback.
keyStore.refreshKeys();
keyIdGenerator.refreshKeyIds();

org.egov.enc.models.SymmetricKey created = keyStore.getSymmetricKey(normalized);
log.info("Generated keys for tenantId={} (keyId={})", normalized,
created != null ? created.getId() : "?");
return org.egov.enc.web.models.GenerateKeyResponse.builder()
.tenantId(normalized)
.created(true)
.keyId(created != null ? created.getId() : null)
.build();
}

public RotateKeyResponse rotateKey(RotateKeyRequest rotateKeyRequest) throws Exception {
int status;
status = keyRepository.deactivateSymmetricKeyForGivenTenant(rotateKeyRequest.getTenantId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,22 @@ public ResponseEntity<RotateKeyResponse> cryptoRotateKeys(@Valid @RequestBody Ro
return new ResponseEntity<RotateKeyResponse>(keyManagementService.rotateKey(rotateKeyRequest), HttpStatus.OK);
}

/**
* Provision a symmetric + asymmetric key pair for a tenantId that doesn't
* have one yet. Idempotent — returns the existing keyId without rotating
* if the tenant already has a key.
*
* Required for new-state-root provisioning flows (MCP tenant_bootstrap,
* etc.) where the default MDMS-driven key discovery doesn't pick up the
* new tenant. Without this, the first encrypt for a brand-new tenant
* fails with "Tenant Id not found".
*/
@RequestMapping(value = "/crypto/v1/_generatekey", method = RequestMethod.POST)
public ResponseEntity<GenerateKeyResponse> cryptoGenerateKey(
@Valid @RequestBody GenerateKeyRequest generateKeyRequest) throws Exception {
return new ResponseEntity<>(
keyManagementService.generateKeyForTenant(generateKeyRequest.getTenantId()),
HttpStatus.OK);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.egov.enc.web.models;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import jakarta.validation.constraints.NotNull;

/**
* Request body for POST /crypto/v1/_generatekey.
*
* Generates a symmetric + asymmetric key pair for the given tenantId if one
* doesn't already exist. Idempotent — re-issuing for an existing tenant
* returns the current keyId without rotating.
*
* Use case: callers that provision new tenants (e.g. MCP tenant_bootstrap)
* need a key to exist BEFORE the first encrypt request for that tenant.
* The default key-generation path (init() + checkIfTenantExists) only fires
* for tenants reachable via MDMS search under STATE_LEVEL_TENANT_ID, which
* excludes brand-new state roots.
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class GenerateKeyRequest {

@NotNull
@JsonProperty("tenantId")
private String tenantId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.egov.enc.web.models;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

/**
* Response from POST /crypto/v1/_generatekey.
* created=true → a new key was generated and persisted
* created=false → tenant already had a key; this is a no-op
*
* The `keyId` is always populated on success (whether newly generated or
* pre-existing) so callers can correlate downstream encrypt requests.
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class GenerateKeyResponse {

@JsonProperty("tenantId")
private String tenantId;

@JsonProperty("created")
private boolean created;

@JsonProperty("keyId")
private Integer keyId;
}
Loading
Loading