fix: require issuance audit claims

master
chenyuan 4 weeks ago
parent a0d227342b
commit 372d00809b

@ -14,6 +14,8 @@ import com.yau.digitalrmb.issuance.interfaces.dto.DenominationItemRequest;
import com.yau.digitalrmb.issuance.interfaces.dto.DenominationPlanRequest; import com.yau.digitalrmb.issuance.interfaces.dto.DenominationPlanRequest;
import com.yau.digitalrmb.issuance.interfaces.dto.UpdateIssuanceRequest; import com.yau.digitalrmb.issuance.interfaces.dto.UpdateIssuanceRequest;
import com.yau.digitalrmb.shared.api.ApiResponse; import com.yau.digitalrmb.shared.api.ApiResponse;
import com.yau.digitalrmb.shared.api.ErrorCode;
import com.yau.digitalrmb.shared.exception.BusinessException;
import com.yau.digitalrmb.shared.web.TraceIdFilter; import com.yau.digitalrmb.shared.web.TraceIdFilter;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -65,11 +67,12 @@ public class CommercialBankIssuanceController {
} }
private IssuanceAuditActor actor(Jwt jwt){ private IssuanceAuditActor actor(Jwt jwt){
String username=jwt.getClaimAsString("preferred_username"); String username=jwt.getClaimAsString("preferred_username");
if(username==null||username.trim().isEmpty()) username=jwt.getSubject(); if(username==null||username.trim().isEmpty()) throw missingAuditClaim("preferred_username");
Object claim=jwt.getClaim("userId"); Object claim=jwt.getClaim("userId");
String userId=claim==null?jwt.getSubject():String.valueOf(claim); if(claim==null) throw missingAuditClaim("userId");
try{return new IssuanceAuditActor(Long.parseLong(userId),username);} try{return new IssuanceAuditActor(Long.parseLong(String.valueOf(claim)),username);}
catch(NumberFormatException exception){throw new IllegalArgumentException("current user id must be a number");} catch(NumberFormatException exception){throw new BusinessException(ErrorCode.UNAUTHORIZED,"current user id must be a number");}
} }
private BusinessException missingAuditClaim(String claim){return new BusinessException(ErrorCode.UNAUTHORIZED,"JWT missing required claim: "+claim);}
private <T> ApiResponse<T> ok(T data){return ApiResponse.success(data,MDC.get(TraceIdFilter.MDC_KEY));} private <T> ApiResponse<T> ok(T data){return ApiResponse.success(data,MDC.get(TraceIdFilter.MDC_KEY));}
} }

@ -42,6 +42,7 @@ public class JwtTokenService {
.subject(String.valueOf(platformUserId)) .subject(String.valueOf(platformUserId))
.issuedAt(issuedAt) .issuedAt(issuedAt)
.expiresAt(expiresAt) .expiresAt(expiresAt)
.claim("userId", platformUserId)
.claim("preferred_username", account) .claim("preferred_username", account)
.claim("roles", authorities) .claim("roles", authorities)
.build(); .build();

@ -29,22 +29,22 @@ class IssuanceControllerTest {
void commercialBankWorkflowIsVisibleToCentralBankEnd() throws Exception { void commercialBankWorkflowIsVisibleToCentralBankEnd() throws Exception {
String requestBody = "{\"bankCode\":\"BKCHCNBJ00001\",\"organizationId\":\"ORG_3A4B5C6D7E8F\",\"totalAmount\":50000.00,\"currency\":\"DC\",\"denominations\":[{\"denomination\":100,\"quantity\":400},{\"denomination\":50,\"quantity\":200}]}"; String requestBody = "{\"bankCode\":\"BKCHCNBJ00001\",\"organizationId\":\"ORG_3A4B5C6D7E8F\",\"totalAmount\":50000.00,\"currency\":\"DC\",\"denominations\":[{\"denomination\":100,\"quantity\":400},{\"denomination\":50,\"quantity\":200}]}";
String createResponse = mockMvc.perform(post("/api/v1/commercial-banks/issuance/requests") String createResponse = mockMvc.perform(post("/api/v1/commercial-banks/issuance/requests")
.with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001"))) .with(jwt().jwt(jwt -> jwt.subject("487").claim("userId", 487L).claim("preferred_username", "tzs001")))
.contentType(MediaType.APPLICATION_JSON).content(requestBody)) .contentType(MediaType.APPLICATION_JSON).content(requestBody))
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("DRAFT")) .andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("DRAFT"))
.andReturn().getResponse().getContentAsString(); .andReturn().getResponse().getContentAsString();
String requestId = JsonPath.read(createResponse, "$.data.id"); String requestId = JsonPath.read(createResponse, "$.data.id");
String base = "/api/v1/commercial-banks/issuance/requests/" + requestId; String base = "/api/v1/commercial-banks/issuance/requests/" + requestId;
mockMvc.perform(post(base + "/prepare-message").with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001")))) mockMvc.perform(post(base + "/prepare-message").with(jwt().jwt(jwt -> jwt.subject("487").claim("userId", 487L).claim("preferred_username", "tzs001"))))
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("MESSAGE_PREPARED")); .andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("MESSAGE_PREPARED"));
mockMvc.perform(post(base + "/digest").with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001")))) mockMvc.perform(post(base + "/digest").with(jwt().jwt(jwt -> jwt.subject("487").claim("userId", 487L).claim("preferred_username", "tzs001"))))
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("DIGESTED")); .andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("DIGESTED"));
mockMvc.perform(post(base + "/sign").with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001")))) mockMvc.perform(post(base + "/sign").with(jwt().jwt(jwt -> jwt.subject("487").claim("userId", 487L).claim("preferred_username", "tzs001"))))
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("SIGNED")); .andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("SIGNED"));
mockMvc.perform(post(base + "/package").with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001")))) mockMvc.perform(post(base + "/package").with(jwt().jwt(jwt -> jwt.subject("487").claim("userId", 487L).claim("preferred_username", "tzs001"))))
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("PACKAGED")); .andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("PACKAGED"));
mockMvc.perform(post(base + "/send").with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001")))) mockMvc.perform(post(base + "/send").with(jwt().jwt(jwt -> jwt.subject("487").claim("userId", 487L).claim("preferred_username", "tzs001"))))
.andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("SENT")); .andExpect(status().isOk()).andExpect(jsonPath("$.data.status").value("SENT"));
mockMvc.perform(get("/api/v1/central-banks/issuance/requests/{id}", requestId) mockMvc.perform(get("/api/v1/central-banks/issuance/requests/{id}", requestId)
@ -81,7 +81,7 @@ class IssuanceControllerTest {
String requestBody = "{\"totalAmount\":50000.00,\"items\":[{\"denomination\":100,\"quantity\":400},{\"denomination\":50,\"quantity\":100},{\"denomination\":20,\"quantity\":200},{\"denomination\":10,\"quantity\":50},{\"denomination\":5,\"quantity\":80},{\"denomination\":1,\"quantity\":100},{\"denomination\":0.5,\"quantity\":0},{\"denomination\":0.2,\"quantity\":0},{\"denomination\":0.1,\"quantity\":0},{\"denomination\":0.05,\"quantity\":0},{\"denomination\":0.01,\"quantity\":0}]}"; String requestBody = "{\"totalAmount\":50000.00,\"items\":[{\"denomination\":100,\"quantity\":400},{\"denomination\":50,\"quantity\":100},{\"denomination\":20,\"quantity\":200},{\"denomination\":10,\"quantity\":50},{\"denomination\":5,\"quantity\":80},{\"denomination\":1,\"quantity\":100},{\"denomination\":0.5,\"quantity\":0},{\"denomination\":0.2,\"quantity\":0},{\"denomination\":0.1,\"quantity\":0},{\"denomination\":0.05,\"quantity\":0},{\"denomination\":0.01,\"quantity\":0}]}";
mockMvc.perform(post("/api/v1/commercial-banks/issuance/denomination-plan/validate") mockMvc.perform(post("/api/v1/commercial-banks/issuance/denomination-plan/validate")
.with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001"))) .with(jwt().jwt(jwt -> jwt.subject("487").claim("userId", 487L).claim("preferred_username", "tzs001")))
.contentType(MediaType.APPLICATION_JSON).content(requestBody)) .contentType(MediaType.APPLICATION_JSON).content(requestBody))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.totalQuantity").value(930)) .andExpect(jsonPath("$.data.totalQuantity").value(930))
@ -94,13 +94,22 @@ class IssuanceControllerTest {
String requestBody = "{\"bankCode\":\"BKCHCNBJ00001\",\"organizationId\":\"ORG_3A4B5C6D7E8F\",\"totalAmount\":50000.00,\"currency\":\"DC\",\"denominations\":[{\"denomination\":100,\"quantity\":400},{\"denomination\":50,\"quantity\":100},{\"denomination\":20,\"quantity\":200},{\"denomination\":10,\"quantity\":50},{\"denomination\":5,\"quantity\":80},{\"denomination\":1,\"quantity\":100},{\"denomination\":0.5,\"quantity\":0},{\"denomination\":0.2,\"quantity\":0},{\"denomination\":0.1,\"quantity\":0},{\"denomination\":0.05,\"quantity\":0},{\"denomination\":0.01,\"quantity\":0}]}"; String requestBody = "{\"bankCode\":\"BKCHCNBJ00001\",\"organizationId\":\"ORG_3A4B5C6D7E8F\",\"totalAmount\":50000.00,\"currency\":\"DC\",\"denominations\":[{\"denomination\":100,\"quantity\":400},{\"denomination\":50,\"quantity\":100},{\"denomination\":20,\"quantity\":200},{\"denomination\":10,\"quantity\":50},{\"denomination\":5,\"quantity\":80},{\"denomination\":1,\"quantity\":100},{\"denomination\":0.5,\"quantity\":0},{\"denomination\":0.2,\"quantity\":0},{\"denomination\":0.1,\"quantity\":0},{\"denomination\":0.05,\"quantity\":0},{\"denomination\":0.01,\"quantity\":0}]}";
mockMvc.perform(post("/api/v1/commercial-banks/issuance/requests") mockMvc.perform(post("/api/v1/commercial-banks/issuance/requests")
.with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001"))) .with(jwt().jwt(jwt -> jwt.subject("487").claim("userId", 487L).claim("preferred_username", "tzs001")))
.contentType(MediaType.APPLICATION_JSON).content(requestBody)) .contentType(MediaType.APPLICATION_JSON).content(requestBody))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.data.status").value("DRAFT")) .andExpect(jsonPath("$.data.status").value("DRAFT"))
.andExpect(jsonPath("$.data.denominations.length()").value(6)); .andExpect(jsonPath("$.data.denominations.length()").value(6));
} }
@Test
void rejectsAnIssuanceOperationWhenAuditClaimsAreMissing() throws Exception {
String requestBody = "{\"bankCode\":\"BKCHCNBJ00001\",\"organizationId\":\"ORG_3A4B5C6D7E8F\",\"totalAmount\":50000.00,\"currency\":\"DC\",\"denominations\":[{\"denomination\":100,\"quantity\":500}]}";
mockMvc.perform(post("/api/v1/commercial-banks/issuance/requests")
.with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001")))
.contentType(MediaType.APPLICATION_JSON).content(requestBody))
.andExpect(jsonPath("$.code").value("UNAUTHORIZED"));
}
@Test @Test
void swaggerDocumentsBothChineseIssuanceEnds() throws Exception { void swaggerDocumentsBothChineseIssuanceEnds() throws Exception {
String apiDocs = new String(mockMvc.perform(get("/v3/api-docs")) String apiDocs = new String(mockMvc.perform(get("/v3/api-docs"))

Loading…
Cancel
Save