diff --git a/bin/create-xlsx.sh b/bin/create-xlsx.sh index 97a0f0ba8b..0411031537 100755 --- a/bin/create-xlsx.sh +++ b/bin/create-xlsx.sh @@ -27,7 +27,7 @@ az acr login --name hmctsprod --subscription DCD-CNP-PROD docker run --rm --name "json2xlsx" \ -v "$run_dir/build/definitions/${case_type}:/tmp/ccd-input" \ -v "$run_dir/build/definitions:/tmp/ccd-output" \ - hmctsprod.azurecr.io/ccd/definition-processor:pr-348-64dd53f \ + hmctsprod.azurecr.io/ccd/definition-processor:pr-348-7561002 \ json2xlsx -D /tmp/ccd-input -o /tmp/ccd-output/"${ccd_definition_file}" done diff --git a/build.gradle b/build.gradle index 43e5c2019c..42e304ef72 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ plugins { id 'org.flywaydb.flyway' version "$flywayVersion" id 'org.sonarqube' version '7.3.1.8318' id 'net.serenity-bdd.serenity-gradle-plugin' version "$serenityBddVersion" - id 'hmcts.ccd.sdk' version 'HDPI-6258-generate-accessType-accessTypeRole-json-74.1-20933aed' + id 'hmcts.ccd.sdk' version 'HDPI-6258-generate-accessType-accessTypeRole-json-99.1-ee757057' id 'com.github.hmcts.rse-cft-lib' version '0.19.2220' id 'io.freefair.lombok' version '9.5.0' id 'au.com.dius.pact' version '4.7.3' diff --git a/charts/pcs-api/Chart.yaml b/charts/pcs-api/Chart.yaml index eb09050671..42a9d67769 100644 --- a/charts/pcs-api/Chart.yaml +++ b/charts/pcs-api/Chart.yaml @@ -3,7 +3,7 @@ appVersion: "1.0" description: A Helm chart for pcs-api App name: pcs-api home: https://github.com/hmcts/pcs-api -version: 0.0.76 +version: 0.0.79 maintainers: - name: HMCTS pcs team dependencies: diff --git a/charts/pcs-api/values.ccd.preview.template.yaml b/charts/pcs-api/values.ccd.preview.template.yaml index 275d3b704c..15c4e4ca55 100644 --- a/charts/pcs-api/values.ccd.preview.template.yaml +++ b/charts/pcs-api/values.ccd.preview.template.yaml @@ -113,6 +113,7 @@ ccd: ELASTIC_SEARCH_HOSTS: "http://{{ .Release.Name }}-es-master:9200" CCD_S2S_AUTHORISED_SERVICES_CASE_USER_ROLES: "aac_manage_case_assignment,pcs_api,pcs_frontend" CASE_DOCUMENT_AM_URL: http://${SERVICE_NAME}-cdam + ENABLE_CASE_GROUP_ACCESS_FILTERING: true ingressHost: ccd-data-store-api-${SERVICE_FQDN} autoscaling: enabled: false @@ -342,6 +343,10 @@ am-org-role-mapping-service: REFRESH_BULK_ASSIGNMENT_INCLUDE_BOOKINGS: true DB_FEATURE_FLAG_ENABLE: st_cic_wa_1_0 ORM_ENV: pr + CCD_DEF_STORE_APP_URL: http://${SERVICE_NAME}-ccd-definition-store + PROFESSIONAL_REF_APP_URL: http://rd-professional-api-aat.service.core-compute-aat.internal + PROFESSIONAL_SCHEDULING_ENABLED: true + PROFESSIONAL_REFRESH_API_ENABLED: true aac-manage-case-assignment: enabled: true diff --git a/config/owasp/suppressions.xml b/config/owasp/suppressions.xml index bac56b8542..c2f8fd7718 100644 --- a/config/owasp/suppressions.xml +++ b/config/owasp/suppressions.xml @@ -30,5 +30,11 @@ CVE-2025-7962 + + + CVE-2025-37731 + CVE-2025-68384 + CVE-2025-68390 + diff --git a/docs/group-access/refresh.sh b/docs/group-access/refresh.sh new file mode 100755 index 0000000000..4417ef197f --- /dev/null +++ b/docs/group-access/refresh.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Trigger PRM refresh for a professional user on a PCS PR preview, then show RAS result. +# Requires HMCTS VPN (the S2S lease URL is internal). jq required. +# +# export IDAM_CLIENT_SECRET= # from the Postman env or pcs-aat vault +# ./refresh.sh [pr-number] +# ./refresh.sh 3b18bab9-33a3-420f-b37e-b63e73862828 2179 # johnwoo on pr-2179 +set -euo pipefail + +USER_ID="${1:?usage: refresh.sh [pr-number]}" +PR="${2:-2179}" +CLIENT_SECRET="${IDAM_CLIENT_SECRET:?export IDAM_CLIENT_SECRET (pcs-frontend) first}" +IDAM_USER="${IDAM_USER:-johnwoo@johnwoo.com}" +IDAM_PASS="${IDAM_PASS:-Testing12345}" + +IDAM=https://idam-api.aat.platform.hmcts.net +S2S=http://rpe-service-auth-provider-aat.service.core-compute-aat.internal +ORM=https://am-org-role-mapping-service-pcs-api-pr-${PR}.preview.platform.hmcts.net +RAS=https://am-role-assignment-service-pcs-api-pr-${PR}.preview.platform.hmcts.net + +echo "== auth ==" +TOKEN=$(curl -s -X POST "$IDAM/o/token" \ + -d grant_type=password -d "username=$IDAM_USER" -d "password=$IDAM_PASS" \ + -d client_id=pcs-frontend -d "client_secret=$CLIENT_SECRET" -d 'scope=openid profile roles' \ + | jq -r .access_token) +[ -n "$TOKEN" ] && [ "$TOKEN" != null ] || { echo "IDAM login failed"; exit 1; } +S2S_ORM=$(curl -s -X POST "$S2S/testing-support/lease" -H 'Content-Type: application/json' -d '{"microservice":"am_org_role_mapping_service"}') +S2S_CCD=$(curl -s -X POST "$S2S/testing-support/lease" -H 'Content-Type: application/json' -d '{"microservice":"ccd_data"}') +echo " ok (idam ${#TOKEN}, s2sOrm ${#S2S_ORM}, s2sCcd ${#S2S_CCD})" + +echo "== PRM refresh $USER_ID ==" +curl -s -o /dev/null -w ' -> HTTP %{http_code}\n' -X POST \ + "$ORM/am/role-mapping/professional/refresh?userId=$USER_ID" \ + -H "Authorization: Bearer $TOKEN" -H "ServiceAuthorization: Bearer $S2S_ORM" + +sleep 5 +echo "== RAS assignments for $USER_ID ==" +curl -s "$RAS/am/role-assignments/actors/$USER_ID" \ + -H "Authorization: Bearer $TOKEN" -H "ServiceAuthorization: Bearer $S2S_CCD" | jq . +# 422 on refresh + empty RAS = catalogue blocker (AM). Row present = AM added the role -> works. diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/CaseType.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/CaseType.java index 0606ee91f3..c297f46be6 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/CaseType.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/CaseType.java @@ -12,9 +12,11 @@ import java.util.Arrays; import java.util.EnumSet; +import java.util.List; import static java.lang.System.getenv; import static java.util.Optional.ofNullable; +import static uk.gov.hmcts.ccd.sdk.api.Permission.CRU; import static uk.gov.hmcts.reform.pcs.ccd.ShowConditions.NEVER_SHOW; import static uk.gov.hmcts.reform.pcs.ccd.domain.State.AWAITING_SUBMISSION_TO_HMCTS; @@ -124,30 +126,51 @@ public void configure(final ConfigBuilder builder .accessMandatory(true) .accessDefault(true) .display(false) + .description("Access to create cases") .hintText("Access to create cases") .displayOrder(1) .liveTo("01/01/2027"); - builder.accessType("prof-org-access") - .organisationProfileId("LOCALAUTH_PROFILE") - .accessMandatory(false) - .accessDefault(false) - .display(true) - .description("Can manage all cases associated with this organisation") - .hintText("Assign to Users to enable access to all cases associated with this organisation") - .displayOrder(2) - .liveTo("01/01/2027"); builder.accessTypeRole("create-cases") .organisationProfileId("LOCALAUTH_PROFILE") .organisationalRoleName(UserRole.SOLICITOR.getRole()) .liveTo("01/01/2027"); - builder.accessTypeRole("prof-org-access") - .organisationProfileId("LOCALAUTH_PROFILE") - .groupRoleName(UserRole.SOLICITOR.getRole()) - .caseAssignedRoleField(UserRole.PROFESSIONA_USER.getRole()) - .groupAccessEnabled(true) - .caseAccessGroupIdTemplate("PCS:PCS:prof-org-access:solicitor:$ORGID$") - .liveTo("01/01/2027"); + + // SDK keeps only the first row per access type, so LOCALAUTH must lead. + List groupAccessOrgProfiles = List.of( + "LOCALAUTH_PROFILE", + "SOLICITOR_PROFILE", + "OTHER_REALT_PROFILE", + "OTHER_PROP_PROFILE", + "OTHER_NFP_PROFILE", + "OTHER_CHARITY_PROFILE" + ); + + for (String orgProfile : groupAccessOrgProfiles) { + builder.accessType("prof-org-access") + .organisationProfileId(orgProfile) + .accessMandatory(false) + .accessDefault(false) + .display(true) + .description("Can manage all cases associated with this organisation") + .hintText("Assign to Users to enable access to all cases associated with this organisation") + .displayOrder(2) + .liveTo("01/01/2027"); + + builder.accessTypeRole("prof-org-access") + .organisationProfileId(orgProfile) + .groupRoleName(UserRole.SOLICITOR.getRole()) + .caseAssignedRoleField(UserRole.PROFESSIONAL_USER.getRole()) + .groupAccessEnabled(true) + // Uppercase service prefix required; def store rejects lowercase. + .caseAccessGroupIdTemplate("PCS:PCS:prof-org-access:solicitor:$ORGID$") + .liveTo("01/01/2027"); + } + + // State ACLs, else the data store filters matched cases out. + for (State state : State.values()) { + builder.grant(state, CRU, AccessProfile.SOLICITOR, AccessProfile.PROFESSIONAL_USER); + } buildCaseListView(builder); diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseView.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseView.java index 8b0e6086bf..66ffd8ec7c 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseView.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseView.java @@ -151,6 +151,7 @@ private SubmittedCase getSubmittedCase(long caseReference) { .build(); setDerivedProperties(pcsCase, pcsCaseEntity); + setGroupAccessFields(pcsCase); partiesView.setCaseFields(pcsCase, pcsCaseEntity); claimView.setCaseFields(pcsCase, pcsCaseEntity); @@ -199,6 +200,26 @@ private LocalDate getClaimIssueDateLocal(PcsCaseEntity pcsCaseEntity) { .orElse(null); } + // INTERIM: hardcoded WK8GIHE stamp for preview. TODO derive from the creator's org. + private void setGroupAccessFields(PCSCase pcsCase) { + uk.gov.hmcts.ccd.sdk.type.CaseAccessGroup caseAccessGroup = + uk.gov.hmcts.ccd.sdk.type.CaseAccessGroup.builder() + .caseAccessGroupId("PCS:PCS:prof-org-access:solicitor:WK8GIHE") + .caseAccessGroupType("CCD:all-cases-access") + .build(); + + ListValue wrapped = + ListValue.builder() + .id(UUID.nameUUIDFromBytes(caseAccessGroup.getCaseAccessGroupId().getBytes()).toString()) + .value(caseAccessGroup) + .build(); + + uk.gov.hmcts.reform.pcs.ccd.domain.GroupAccessFields + groupAccessFields = new uk.gov.hmcts.reform.pcs.ccd.domain.GroupAccessFields<>(); + groupAccessFields.setCaseAccessGroups(List.of(wrapped)); + pcsCase.setGroupAccessFields(groupAccessFields); + } + private void setDerivedProperties(PCSCase pcsCase, PcsCaseEntity pcsCaseEntity) { boolean pcqIdSet = findPartyForCurrentUser(pcsCaseEntity) .map(party -> party.getPcqId() != null) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/AccessProfile.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/AccessProfile.java index f2fb204fb9..c26177bc12 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/AccessProfile.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/AccessProfile.java @@ -21,6 +21,8 @@ public enum AccessProfile implements HasRole { DEFENDANT_SOLICITOR("[DEFENDANTSOLICITOR]", CRU), PCS_CASE_WORKER("caseworker-pcs", Set.of(R)), PCS_SOLICITOR("caseworker-pcs-solicitor", CRU), + SOLICITOR("[SOLICITOR]", CRU), + PROFESSIONAL_USER("professional-user", CRU), JUDGE("judge", CRU), FEE_PAID_JUDGE("fee-paid-judge", CRU), diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/UserRole.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/UserRole.java index 107ec3a477..b18b7fc0a0 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/UserRole.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/UserRole.java @@ -24,7 +24,7 @@ public enum UserRole implements HasRole { CITIZEN("citizen", CRU, IDAM), SOLICITOR("[SOLICITOR]", CRU, RAS), - PROFESSIONA_USER("professional-user", CRU, RAS), + PROFESSIONAL_USER("professional-user", CRU, RAS), DEFENDANT("[DEFENDANT]", CRU, RAS), CLAIMANT_SOLICITOR("[CLAIMANTSOLICITOR]", CRU, RAS), DEFENDANT_SOLICITOR("[DEFENDANTSOLICITOR]", CRU, RAS), diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/GroupAccessFields.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/GroupAccessFields.java index 4094c350f5..0d5ebc5639 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/GroupAccessFields.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/GroupAccessFields.java @@ -2,19 +2,22 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; +import lombok.Data; import uk.gov.hmcts.ccd.sdk.api.CCD; import uk.gov.hmcts.ccd.sdk.api.HasRole; import uk.gov.hmcts.ccd.sdk.type.CaseAccessGroup; import uk.gov.hmcts.ccd.sdk.type.ChangeOrganisationRequest; +import uk.gov.hmcts.ccd.sdk.type.ListValue; import uk.gov.hmcts.ccd.sdk.type.Organisation; import uk.gov.hmcts.ccd.sdk.type.OrganisationPolicy; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.AccessProfile; +@Data public class GroupAccessFields { @JsonProperty("CaseAccessGroups") @CCD - private List caseAccessGroups; + private List> caseAccessGroups; @JsonProperty("OrganisationField") @CCD diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/CaseTypeTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/CaseTypeTest.java index c2a0cdf347..7bef9c4009 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/CaseTypeTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/CaseTypeTest.java @@ -201,6 +201,10 @@ private void stubBuilderForConfigure() { when(builder.searchCasesFields()).thenReturn(searchCasesBuilder); when(builder.searchResultFields()).thenReturn(searchBuilder); when(builder.workBasketResultFields()).thenReturn(searchBuilder); + when(builder.accessType(anyString())) + .thenReturn(AccessType.AccessTypeBuilder.builder("accessTypeId")); + when(builder.accessTypeRole(anyString())) + .thenReturn(AccessTypeRole.AccessTypeRoleBuilder.builder("accessTypeId")); when(builder.tab("nextSteps", "Next steps")).thenReturn(TabBuilder.builder(PCSCase.class, utils)); when(builder.tab("summary", "Summary")).thenReturn(TabBuilder.builder(PCSCase.class, utils)); when(builder.tab("CaseHistory", "History")).thenReturn(TabBuilder.builder(PCSCase.class, utils));