diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java index c051b972827..e03259c05ab 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.util; import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; /* * #%L @@ -365,6 +366,22 @@ public class FhirTerser { for (String nextPath : nextParam.getPathsSplit()) { for (IBaseReference nextValue : getValues(theSource, nextPath, IBaseReference.class)) { String nextRef = nextValue.getReferenceElement().toUnqualifiedVersionless().getValue(); + + /* + * If the reference isn't an explicit resource ID, but instead is just + * a resource object, we'll calculate its ID and treat the target + * as that. + */ + if (isBlank(nextRef) && nextValue.getResource() != null) { + IBaseResource nextTarget = nextValue.getResource(); + IIdType nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless(); + if (!nextTargetId.hasResourceType()) { + String resourceType = myContext.getResourceDefinition(nextTarget).getName(); + nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null); + } + nextRef = nextTargetId.getValue(); + } + if (wantRef.equals(nextRef)) { return true; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java index 79b9d4094c3..858a20de253 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java @@ -66,6 +66,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar * Constructor */ public ResourceIndexedSearchParamDate() { + super(); } /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java index 8bd8f60b096..35e9684f82d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java @@ -200,10 +200,14 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc b.append("system", getSystem()); b.append("units", getUnits()); b.append("value", getValue()); + b.append("missing", isMissing()); return b.build(); } private static String toTruncatedString(BigDecimal theValue) { + if (theValue == null) { + return null; + } return theValue.setScale(0, RoundingMode.FLOOR).toPlainString(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java new file mode 100644 index 00000000000..71abdeb99bb --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java @@ -0,0 +1,164 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; +import ca.uhn.fhir.jpa.rp.r4.ObservationResourceProvider; +import ca.uhn.fhir.jpa.rp.r4.OrganizationResourceProvider; +import ca.uhn.fhir.jpa.rp.r4.PatientResourceProvider; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.validation.ResultSeverityEnum; +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.junit.*; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +public class EmptyIndexesR4Test extends BaseJpaR4Test { + private static RestfulServer myRestServer; + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static CloseableHttpClient ourHttpClient; + private static Server ourServer; + private static String ourServerBase; + private SimpleRequestHeaderInterceptor mySimpleHeaderInterceptor; + + @SuppressWarnings("deprecation") + @After + public void after() { + myRestServer.setUseBrowserFriendlyContentTypes(true); + ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); + myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); + } + + @Before + public void before() { + mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor(); + ourClient.registerInterceptor(mySimpleHeaderInterceptor); + myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + } + + @Before + public void beforeStartServer() throws Exception { + if (myRestServer == null) { + PatientResourceProvider patientRp = new PatientResourceProvider(); + patientRp.setDao(myPatientDao); + + QuestionnaireResourceProviderR4 questionnaireRp = new QuestionnaireResourceProviderR4(); + questionnaireRp.setDao(myQuestionnaireDao); + + ObservationResourceProvider observationRp = new ObservationResourceProvider(); + observationRp.setDao(myObservationDao); + + OrganizationResourceProvider organizationRp = new OrganizationResourceProvider(); + organizationRp.setDao(myOrganizationDao); + + RestfulServer restServer = new RestfulServer(ourCtx); + restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); + + restServer.setPlainProviders(mySystemProvider); + + int myPort = RandomServerPortProvider.findFreePort(); + ourServer = new Server(myPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ourServerBase = "http://localhost:" + myPort + "/fhir/context"; + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(restServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourCtx = FhirContext.forR4(); + restServer.setFhirContext(ourCtx); + + ourServer.setHandler(proxyHandler); + ourServer.start(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourHttpClient = builder.build(); + + ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000); + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.setLogRequestAndResponse(true); + myRestServer = restServer; + } + + myRestServer.setDefaultResponseEncoding(EncodingEnum.XML); + myRestServer.setPagingProvider(myPagingProvider); + } + + + @Test + public void testDontCreateNullIndexesWithOnlyString() { + Observation obs = new Observation(); + obs.getCode().setText("ZXCVBNM ASDFGHJKL QWERTYUIOPASDFGHJKL"); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + runInTransaction(()->{ + assertThat(myResourceIndexedSearchParamQuantityDao.findAll(), empty()); + assertThat(myResourceIndexedSearchParamTokenDao.findAll(), empty()); + }); + } + + @Test + public void testDontCreateNullIndexesWithOnlyToken() { + Observation obs = new Observation(); + obs.getCode().addCoding().setSystem("FOO").setCode("BAR"); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + runInTransaction(()->{ + assertThat(myResourceIndexedSearchParamQuantityDao.findAll(), empty()); + assertThat(myResourceIndexedSearchParamStringDao.findAll(), empty()); + // code and combo-code + assertThat(myResourceIndexedSearchParamTokenDao.findAll().toString(), myResourceIndexedSearchParamTokenDao.findAll(), hasSize(2)); + }); + } + + + @AfterClass + public static void afterClassClearContext() throws Exception { + ourServer.stop(); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java index aa3ce0dd9de..d9301b73a9b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java @@ -1,37 +1,11 @@ package ca.uhn.fhir.jpa.provider.r4; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.io.IOUtils; -import org.apache.http.Header; -import org.apache.http.client.methods.*; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Bundle.BundleType; -import org.hl7.fhir.r4.model.Bundle.HTTPVerb; -import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; -import ca.uhn.fhir.jpa.rp.r4.*; +import ca.uhn.fhir.jpa.rp.r4.ObservationResourceProvider; +import ca.uhn.fhir.jpa.rp.r4.OrganizationResourceProvider; +import ca.uhn.fhir.jpa.rp.r4.PatientResourceProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -45,6 +19,35 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.ResultSeverityEnum; +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.junit.*; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class SystemProviderR4Test extends BaseJpaR4Test { @@ -63,7 +66,7 @@ public class SystemProviderR4Test extends BaseJpaR4Test { myRestServer.setUseBrowserFriendlyContentTypes(true); ourClient.unregisterInterceptor(mySimpleHeaderInterceptor); } - + @Before public void before() { mySimpleHeaderInterceptor = new SimpleRequestHeaderInterceptor(); @@ -118,7 +121,7 @@ public class SystemProviderR4Test extends BaseJpaR4Test { ourClient.setLogRequestAndResponse(true); myRestServer = restServer; } - + myRestServer.setDefaultResponseEncoding(EncodingEnum.XML); myRestServer.setPagingProvider(myPagingProvider); } @@ -154,7 +157,7 @@ public class SystemProviderR4Test extends BaseJpaR4Test { myRestServer.unregisterInterceptor(interceptor); } - + @Test public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception { myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON); @@ -183,7 +186,7 @@ public class SystemProviderR4Test extends BaseJpaR4Test { myRestServer.unregisterInterceptor(interceptor); } - + @Test public void testEverythingType() throws Exception { HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything"); @@ -194,7 +197,7 @@ public class SystemProviderR4Test extends BaseJpaR4Test { http.close(); } } - + @Test public void testGetOperationDefinition() { OperationDefinition op = ourClient.read(OperationDefinition.class, "-s-get-resource-counts"); @@ -210,7 +213,8 @@ public class SystemProviderR4Test extends BaseJpaR4Test { ourLog.info(output); assertEquals(200, http.getStatusLine().getStatusCode()); } finally { - IOUtils.closeQuietly(http);; + IOUtils.closeQuietly(http); + ; } get = new HttpGet(ourServerBase + "/$perform-reindexing-pass"); @@ -220,7 +224,8 @@ public class SystemProviderR4Test extends BaseJpaR4Test { ourLog.info(output); assertEquals(200, http.getStatusLine().getStatusCode()); } finally { - IOUtils.closeQuietly(http);; + IOUtils.closeQuietly(http); + ; } } @@ -237,6 +242,7 @@ public class SystemProviderR4Test extends BaseJpaR4Test { assertThat(http.getFirstHeader("Content-Type").getValue(), containsString("application/fhir+json")); } + @Transactional(propagation = Propagation.NEVER) @Test public void testSuggestKeywords() throws Exception { @@ -320,7 +326,7 @@ public class SystemProviderR4Test extends BaseJpaR4Test { } } - + @Test public void testTransactionCount() throws Exception { for (int i = 0; i < 20; i++) { @@ -577,33 +583,33 @@ public class SystemProviderR4Test extends BaseJpaR4Test { //@formatter:off String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; //@formatter:off HttpPost req = new HttpPost(ourServerBase); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java index 01b20c93a37..61da96bb604 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java @@ -390,6 +390,69 @@ public class AuthorizationInterceptorR4Test { assertTrue(ourHitMethod); } + @Test + public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder().allow().read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/123")).andThen().denyAll() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + Patient patient; + CarePlan carePlan; + + // Unqualified + patient = new Patient(); + patient.setId("123"); + carePlan = new CarePlan(); + carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE); + carePlan.getSubject().setResource(patient); + + ourHitMethod = false; + ourReturn = Collections.singletonList(carePlan); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Qualified + patient = new Patient(); + patient.setId("Patient/123"); + carePlan = new CarePlan(); + carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE); + carePlan.getSubject().setResource(patient); + + ourHitMethod = false; + ourReturn = Collections.singletonList(carePlan); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Wrong one + patient = new Patient(); + patient.setId("456"); + carePlan = new CarePlan(); + carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE); + carePlan.getSubject().setResource(patient); + + ourHitMethod = false; + ourReturn = Collections.singletonList(carePlan); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + } + + @Test public void testBatchWhenOnlyTransactionAllowed() throws Exception { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { diff --git a/src/changes/changes.xml b/src/changes/changes.xml index d2072f60f07..45a4149caf4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -248,6 +248,12 @@ the number of threads allocated to reindexing may now be adjusted via a setting in the DaoConfig. + + AuthorizationInterceptor did not correctly grant access to resources + by compartment when the reference on the target resource that pointed + to the compartment owner was defined using a resource object (ResourceReference#setResource) + instead of a reference (ResourceReference#setReference). +