Skip to content

Commit 4e1c3cb

Browse files
committed
Merge branch 'aw/add-exception-class' of https://github.com/CMSgov/dpc-app into aw/add-exception-class
2 parents 24444db + 23d0b1d commit 4e1c3cb

File tree

7 files changed

+78
-47
lines changed

7 files changed

+78
-47
lines changed

dpc-api/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@
244244
<dependency>
245245
<groupId>io.netty</groupId>
246246
<artifactId>netty-codec-http</artifactId>
247-
<version>4.2.5.Final</version>
247+
<version>4.2.8.Final</version>
248248
</dependency>
249249
<dependency>
250250
<groupId>io.swagger</groupId>

dpc-api/src/main/java/gov/cms/dpc/api/auth/macaroonauth/MacaroonsAuthenticator.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import ca.uhn.fhir.rest.client.api.IGenericClient;
44
import gov.cms.dpc.api.auth.DPCAuthCredentials;
55
import gov.cms.dpc.api.auth.OrganizationPrincipal;
6+
import gov.cms.dpc.api.auth.annotations.PathAuthorizer;
67
import gov.cms.dpc.fhir.DPCIdentifierSystem;
78
import gov.cms.dpc.fhir.DPCResourceType;
89
import io.dropwizard.auth.Authenticator;
910
import jakarta.inject.Inject;
1011
import jakarta.inject.Named;
12+
import jakarta.ws.rs.NotFoundException;
1113
import org.hl7.fhir.dstu3.model.Bundle;
1214
import org.hl7.fhir.dstu3.model.Organization;
1315
import org.slf4j.Logger;
@@ -33,39 +35,42 @@ public MacaroonsAuthenticator(@Named("attribution") IGenericClient client) {
3335
@Override
3436
public Optional<OrganizationPrincipal> authenticate(DPCAuthCredentials credentials) {
3537
logger.debug("Performing token authentication");
38+
PathAuthorizer pathAuthorizer = credentials.getPathAuthorizer();
3639

3740
// If we don't have a path authorizer, just return the principal
3841
final OrganizationPrincipal principal = new OrganizationPrincipal(credentials.getOrganization());
39-
if (credentials.getPathAuthorizer() == null) {
42+
if (pathAuthorizer == null) {
4043
logger.debug("No path authorizer is present, returning principal");
4144
return Optional.of(principal);
4245
}
4346

4447
// If we're an organization, we just check the org ID against the path value and see if it matches
45-
if (credentials.getPathAuthorizer().type() == DPCResourceType.Organization) {
48+
if (pathAuthorizer.type() == DPCResourceType.Organization) {
4649
return validateOrganization(principal, credentials);
4750
}
4851

4952
// Otherwise, try to lookup the matching resource
50-
logger.debug("Looking up resource {} in path authorizer. With value: {}", credentials.getPathAuthorizer().type(), credentials.getPathAuthorizer().pathParam());
53+
logger.debug("Looking up resource {} in path authorizer. With value: {}", pathAuthorizer.type(), pathAuthorizer.pathParam());
5154
Map<String, List<String>> searchParams = new HashMap<>();
5255
searchParams.put("_id", Collections.singletonList(credentials.getPathValue()));
5356
searchParams.put("organization", Collections.singletonList(credentials.getOrganization().getId()));
5457

5558
// Special handling of Group resources, which use tags instead of resource properties.
56-
if (credentials.getPathAuthorizer().type() == DPCResourceType.Group) {
59+
if (pathAuthorizer.type() == DPCResourceType.Group) {
5760
searchParams.put("_tag", Collections.singletonList(String.format("%s|%s", DPCIdentifierSystem.DPC.getSystem(), credentials.getOrganization().getId())));
5861
}
5962
final Bundle bundle = this.client
6063
.search()
61-
.forResource(credentials.getPathAuthorizer().type().toString())
64+
.forResource(pathAuthorizer.type().toString())
6265
.whereMap(searchParams)
6366
.returnBundle(Bundle.class)
6467
.encodedJson()
6568
.execute();
6669

70+
// We're using a path authorizer. If the bundle is empty we want to force a 404 Not Found response instead of
71+
// 401 Unauthorized that would be returned for a standard auth failure.
6772
if (bundle.getEntry().isEmpty()) {
68-
return Optional.empty();
73+
throw new NotFoundException(String.format("%s not found", pathAuthorizer.type().toString()));
6974
}
7075

7176
return Optional.of(principal);

dpc-api/src/test/java/gov/cms/dpc/api/auth/macaroonauth/MacaroonsAuthenticatorUnitTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
import gov.cms.dpc.api.auth.annotations.PathAuthorizer;
88
import gov.cms.dpc.fhir.DPCIdentifierSystem;
99
import gov.cms.dpc.fhir.DPCResourceType;
10+
import jakarta.ws.rs.NotFoundException;
1011
import org.hl7.fhir.dstu3.model.Bundle;
1112
import org.hl7.fhir.dstu3.model.Organization;
1213
import org.hl7.fhir.instance.model.api.IBaseBundle;
1314
import org.junit.jupiter.api.Test;
1415

1516
import java.util.*;
1617

17-
import static org.junit.Assert.assertSame;
18+
import static org.junit.Assert.*;
1819
import static org.junit.jupiter.api.Assertions.assertTrue;
1920
import static org.mockito.Mockito.*;
2021

@@ -107,7 +108,9 @@ void test_authenticate_resource_not_authorized() {
107108
when(whereMapQuery.returnBundle(Bundle.class).encodedJson()).thenReturn(bundleQuery);
108109
when(bundleQuery.execute()).thenReturn(bundle);
109110

110-
assertTrue(macaroonsAuthenticator.authenticate(dpcAuthCredentials).isEmpty());
111+
NotFoundException exception = assertThrows(NotFoundException.class,
112+
() -> macaroonsAuthenticator.authenticate(dpcAuthCredentials));
113+
assertEquals(String.format("%s not found", DPCResourceType.Patient.name()), exception.getMessage());
111114
}
112115

113116
@Test

dpc-api/src/test/java/gov/cms/dpc/api/resources/v1/GroupResourceTest.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import ca.uhn.fhir.rest.api.MethodOutcome;
55
import ca.uhn.fhir.rest.client.api.IGenericClient;
66
import ca.uhn.fhir.rest.gclient.ICreateTyped;
7-
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
87
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
8+
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
99
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
1010
import gov.cms.dpc.api.APITestHelpers;
1111
import gov.cms.dpc.api.AbstractSecureApplicationTest;
@@ -133,7 +133,7 @@ void testMissingProvenance() throws IOException {
133133
}
134134

135135
@Test
136-
public void testInvalidInputsWhenCreatingGroup() throws IOException {
136+
void testInvalidInputsWhenCreatingGroup() throws IOException {
137137
final IParser parser = ctx.newJsonParser();
138138
IGenericClient client = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), ORGANIZATION_TOKEN, PUBLIC_KEY_ID, PRIVATE_KEY);
139139
APITestHelpers.setupPatientTest(client, parser);
@@ -253,7 +253,7 @@ void testCreateInvalidGroup() throws IOException, URISyntaxException {
253253
}
254254

255255
@Test
256-
public void testCreateGroupReturnsAppropriateHeaders() throws IOException {
256+
void testCreateGroupReturnsAppropriateHeaders() throws IOException {
257257
final IParser parser = ctx.newJsonParser();
258258
IGenericClient client = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), ORGANIZATION_TOKEN, PUBLIC_KEY_ID, PRIVATE_KEY);
259259
APITestHelpers.setupPatientTest(client, parser);
@@ -340,7 +340,7 @@ public void testCreateGroupReturnsAppropriateHeaders() throws IOException {
340340
}
341341

342342
@Test
343-
public void testProvenanceHeaderAndGroupProviderMatch() {
343+
void testProvenanceHeaderAndGroupProviderMatch() {
344344
IGenericClient client = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), ORGANIZATION_TOKEN, PUBLIC_KEY_ID, PRIVATE_KEY);
345345
Practitioner practitioner = APITestHelpers.createPractitionerResource(NPIUtil.generateNPI(), ORGANIZATION_ID);
346346

@@ -401,7 +401,7 @@ public void testProvenanceHeaderAndGroupProviderMatch() {
401401
}
402402

403403
@Test
404-
public void testGroupCanOnlyBeRetrievedByOwner() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
404+
void testGroupCanOnlyBeRetrievedByOwner() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
405405
final TestOrganizationContext orgAContext = registerAndSetupNewOrg();
406406
final TestOrganizationContext orgBContext = registerAndSetupNewOrg();
407407
final IGenericClient orgAClient = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), orgAContext.getClientToken(), UUID.fromString(orgAContext.getPublicKeyId()), orgAContext.getPrivateKey());
@@ -432,7 +432,7 @@ public void testGroupCanOnlyBeRetrievedByOwner() throws GeneralSecurityException
432432
assertEquals(orgBGroup.getId(), foundGroup.getId(), "Returned group ID should have been the same as requested ID");
433433

434434

435-
assertThrows(AuthenticationException.class, () ->
435+
assertThrows(ResourceNotFoundException.class, () ->
436436
orgBClient.read()
437437
.resource(Group.class)
438438
.withId(orgAGroup.getId())
@@ -442,7 +442,7 @@ public void testGroupCanOnlyBeRetrievedByOwner() throws GeneralSecurityException
442442
}
443443

444444
@Test
445-
public void testOrgCanOnlyDeleteTheirOwnGroup() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
445+
void testOrgCanOnlyDeleteTheirOwnGroup() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
446446
final TestOrganizationContext orgAContext = registerAndSetupNewOrg();
447447
final TestOrganizationContext orgBContext = registerAndSetupNewOrg();
448448
final IGenericClient orgAClient = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), orgAContext.getClientToken(), UUID.fromString(orgAContext.getPublicKeyId()), orgAContext.getPrivateKey());
@@ -455,7 +455,7 @@ public void testOrgCanOnlyDeleteTheirOwnGroup() throws GeneralSecurityException,
455455
Group orgBGroup = createAndSubmitGroup(orgBContext.getOrgId(), orgBPractitioner, orgBClient, Collections.emptyList());
456456

457457
Provenance provenance = APITestHelpers.createProvenance(orgBContext.getOrgId(), orgBPractitioner.getId(), Collections.emptyList());
458-
assertThrows(AuthenticationException.class, () ->
458+
assertThrows(ResourceNotFoundException.class, () ->
459459
orgBClient.delete()
460460
.resource(orgAGroup)
461461
.encodedJson()
@@ -472,7 +472,7 @@ public void testOrgCanOnlyDeleteTheirOwnGroup() throws GeneralSecurityException,
472472
}
473473

474474
@Test
475-
public void testOrgCanOnlyUpdateTheirOwnGroup() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
475+
void testOrgCanOnlyUpdateTheirOwnGroup() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
476476
final TestOrganizationContext orgAContext = registerAndSetupNewOrg();
477477
final TestOrganizationContext orgBContext = registerAndSetupNewOrg();
478478
final IGenericClient orgAClient = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), orgAContext.getClientToken(), UUID.fromString(orgAContext.getPublicKeyId()), orgAContext.getPrivateKey());
@@ -485,7 +485,7 @@ public void testOrgCanOnlyUpdateTheirOwnGroup() throws GeneralSecurityException,
485485
Group orgBGroup = createAndSubmitGroup(orgBContext.getOrgId(), orgBPractitioner, orgBClient, Collections.emptyList());
486486

487487
Provenance provenance = APITestHelpers.createProvenance(orgBContext.getOrgId(), orgBPractitioner.getId(), Collections.emptyList());
488-
assertThrows(AuthenticationException.class, () ->
488+
assertThrows(ResourceNotFoundException.class, () ->
489489
orgBClient.update().resource(orgAGroup)
490490
.encodedJson()
491491
.withAdditionalHeader("X-Provenance", ctx.newJsonParser().encodeResourceToString(provenance))
@@ -501,7 +501,7 @@ public void testOrgCanOnlyUpdateTheirOwnGroup() throws GeneralSecurityException,
501501
}
502502

503503
@Test
504-
public void testOrgCanOnlyListTheirOwnGroups() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
504+
void testOrgCanOnlyListTheirOwnGroups() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
505505
final TestOrganizationContext orgAContext = registerAndSetupNewOrg();
506506
final TestOrganizationContext orgBContext = registerAndSetupNewOrg();
507507
final IGenericClient orgAClient = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), orgAContext.getClientToken(), UUID.fromString(orgAContext.getPublicKeyId()), orgAContext.getPrivateKey());
@@ -525,7 +525,7 @@ public void testOrgCanOnlyListTheirOwnGroups() throws GeneralSecurityException,
525525

526526

527527
@Test
528-
public void testOrgCanOnlyCreateGroupWithPatientsTheyManage() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
528+
void testOrgCanOnlyCreateGroupWithPatientsTheyManage() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
529529
final TestOrganizationContext orgAContext = registerAndSetupNewOrg();
530530
final TestOrganizationContext orgBContext = registerAndSetupNewOrg();
531531
final IGenericClient orgAClient = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), orgAContext.getClientToken(), UUID.fromString(orgAContext.getPublicKeyId()), orgAContext.getPrivateKey());
@@ -553,7 +553,7 @@ public void testOrgCanOnlyCreateGroupWithPatientsTheyManage() throws GeneralSecu
553553
}
554554

555555
@Test
556-
public void testOrgCanOnlyUpdateGroupWithPatientsTheyManage() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
556+
void testOrgCanOnlyUpdateGroupWithPatientsTheyManage() throws GeneralSecurityException, IOException, URISyntaxException, ParseException {
557557
final TestOrganizationContext orgAContext = registerAndSetupNewOrg();
558558
final TestOrganizationContext orgBContext = registerAndSetupNewOrg();
559559
final IGenericClient orgAClient = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), orgAContext.getClientToken(), UUID.fromString(orgAContext.getPublicKeyId()), orgAContext.getPrivateKey());
@@ -582,7 +582,7 @@ public void testOrgCanOnlyUpdateGroupWithPatientsTheyManage() throws GeneralSecu
582582

583583

584584
@Test
585-
public void testOrgCanOnlyAddPatientsTheyManageToGroup() throws IOException, URISyntaxException, GeneralSecurityException, ParseException {
585+
void testOrgCanOnlyAddPatientsTheyManageToGroup() throws IOException, URISyntaxException, GeneralSecurityException, ParseException {
586586
final TestOrganizationContext orgAContext = registerAndSetupNewOrg();
587587
final TestOrganizationContext orgBContext = registerAndSetupNewOrg();
588588
final IGenericClient orgAClient = APIAuthHelpers.buildAuthenticatedClient(ctx, getBaseURL(), orgAContext.getClientToken(), UUID.fromString(orgAContext.getPublicKeyId()), orgAContext.getPrivateKey());

dpc-api/src/test/java/gov/cms/dpc/api/resources/v1/PatientResourceTest.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
import ca.uhn.fhir.rest.gclient.IReadExecutable;
1111
import ca.uhn.fhir.rest.gclient.IUpdateExecutable;
1212
import ca.uhn.fhir.rest.param.StringParam;
13-
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
14-
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
15-
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
16-
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
13+
import ca.uhn.fhir.rest.server.exceptions.*;
1714
import com.codahale.metrics.MetricRegistry;
1815
import gov.cms.dpc.api.APITestHelpers;
1916
import gov.cms.dpc.api.AbstractSecureApplicationTest;
@@ -166,7 +163,7 @@ void ensurePatientsExist() throws IOException, URISyntaxException, GeneralSecuri
166163
.withId(foundPatient.getId())
167164
.encodedJson();
168165

169-
assertThrows(AuthenticationException.class, fetchRequest::execute, "Should not be authorized");
166+
assertThrows(ResourceNotFoundException.class, fetchRequest::execute, "Should not be authorized");
170167

171168
// Search, and find nothing
172169
final Bundle otherSpecificSearch = client
@@ -210,8 +207,7 @@ void testPatientRemoval() throws IOException, URISyntaxException, GeneralSecurit
210207
.withId(patient.getId())
211208
.encodedJson();
212209

213-
// TODO: DPC-433, this really should be NotFound, but we can't disambiguate between the two cases
214-
assertThrows(AuthenticationException.class, fetchRequest::execute, "Should not have found the resource");
210+
assertThrows(ResourceNotFoundException.class, fetchRequest::execute, "Should not have found the resource");
215211

216212
// Search again
217213
final Bundle secondSearch = fetchPatients(client);
@@ -712,7 +708,7 @@ void testGetPatientByUUID() throws GeneralSecurityException, IOException, URISyn
712708

713709
assertNotNull(APITestHelpers.getResourceById(orgAClient, Patient.class,orgAPatient.getId()), "Org should be able to retrieve their own patient.");
714710
assertNotNull(APITestHelpers.getResourceById(orgBClient,Patient.class, orgBPatient.getId()), "Org should be able to retrieve their own patient.");
715-
assertThrows(AuthenticationException.class, () -> APITestHelpers.getResourceById(orgAClient,Patient.class, orgBPatient.getId()), "Expected auth error when retrieving another org's patient.");
711+
assertThrows(ResourceNotFoundException.class, () -> APITestHelpers.getResourceById(orgAClient,Patient.class, orgBPatient.getId()), "Expected auth error when retrieving another org's patient.");
716712
}
717713

718714
@Test
@@ -825,7 +821,7 @@ void testDeletePatient() throws GeneralSecurityException, IOException, URISyntax
825821
//Setup org B with a patient
826822
final Patient orgBPatient = (Patient) APITestHelpers.createResource(orgBClient, APITestHelpers.createPatientResource("4S41C00AA00", orgBContext.getOrgId())).getResource();
827823

828-
assertThrows(AuthenticationException.class, () -> APITestHelpers.deleteResourceById(orgAClient, DPCResourceType.Patient, orgBPatient.getIdElement().getIdPart()), "Expected auth error when deleting another org's patient.");
824+
assertThrows(ResourceNotFoundException.class, () -> APITestHelpers.deleteResourceById(orgAClient, DPCResourceType.Patient, orgBPatient.getIdElement().getIdPart()), "Expected auth error when deleting another org's patient.");
829825
APITestHelpers.deleteResourceById(orgBClient, DPCResourceType.Patient, orgBPatient.getIdElement().getIdPart());
830826
}
831827

@@ -842,17 +838,17 @@ void testPatientPathAuthorization() throws GeneralSecurityException, IOException
842838
//Test GET /Patient/{id}
843839
APITestHelpers.getResourceById(orgAClient,Patient.class,orgAPatient.getIdElement().getIdPart());
844840
APITestHelpers.getResourceById(orgBClient,Patient.class,orgBPatient.getIdElement().getIdPart());
845-
assertThrows(AuthenticationException.class, () -> APITestHelpers.getResourceById(orgBClient,Patient.class,orgAPatient.getIdElement().getIdPart()), "Expected auth error when accessing another org's patient");
841+
assertThrows(ResourceNotFoundException.class, () -> APITestHelpers.getResourceById(orgBClient,Patient.class,orgAPatient.getIdElement().getIdPart()), "Expected auth error when accessing another org's patient");
846842

847843
//Test PUT /Patient/{id}
848844
APITestHelpers.updateResource(orgAClient,orgAPatient.getIdElement().getIdPart(),orgAPatient);
849845
APITestHelpers.updateResource(orgBClient,orgBPatient.getIdElement().getIdPart(),orgBPatient);
850-
assertThrows(AuthenticationException.class, () -> APITestHelpers.updateResource(orgBClient,orgAPatient.getIdElement().getIdPart(),orgAPatient), "Expected auth error when updating another org's patient");
846+
assertThrows(ResourceNotFoundException.class, () -> APITestHelpers.updateResource(orgBClient,orgAPatient.getIdElement().getIdPart(),orgAPatient), "Expected auth error when updating another org's patient");
851847

852848
//Test PUT /Patient/{id}
853849
APITestHelpers.updateResource(orgAClient,orgAPatient.getIdElement().getIdPart(),orgAPatient);
854850
APITestHelpers.updateResource(orgBClient,orgBPatient.getIdElement().getIdPart(),orgBPatient);
855-
assertThrows(AuthenticationException.class, () -> APITestHelpers.updateResource(orgBClient,orgAPatient.getIdElement().getIdPart(),orgAPatient), "Expected auth error when updating another org's patient");
851+
assertThrows(ResourceNotFoundException.class, () -> APITestHelpers.updateResource(orgBClient,orgAPatient.getIdElement().getIdPart(),orgAPatient), "Expected auth error when updating another org's patient");
856852

857853

858854
//Test Get /Patient/{id}/$everything
@@ -867,7 +863,7 @@ void testPatientPathAuthorization() throws GeneralSecurityException, IOException
867863
assertEquals(64, result.getTotal(), "Should have 64 entries in Bundle");
868864

869865
final String orgAPractitionerId = orgAPractitioner.getIdElement().getIdPart();
870-
assertThrows(AuthenticationException.class, () ->
866+
assertThrows(ResourceNotFoundException.class, () ->
871867
APITestHelpers.getPatientEverything(orgBClient, orgAPatient.getIdElement().getIdPart(), generateProvenance(orgAContext.getOrgId(), orgAPractitionerId))
872868
, "Expected auth error when export another org's patient's data");
873869
}

0 commit comments

Comments
 (0)