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));