mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-16 09:55:09 +00:00
6203 subscriptionvalidatinginterceptor allows invalid url for rest hook endpoint (#6225)
* use SearchParamater validator in package installer (#6112) * Ensure ' ' is treated as '+' in timezones with offsets. (#6115) * Use lockless mode when adding index on Azure Sql server (#6100) * Use lockless mode when adding index on Azure Sql server Use try-catch for Online add-index on Sql Server. This avoids having to map out the entire matrix of Sql Server product names and ONLINE index support. Warnings in docs, and cleanups * make consent service dont call willSeeResource on children if parent resource is AUTHORIZED or REJECT (#6127) * fix hfj search migration task (#6143) * fix migration task * changelog * changelog * code review * spotless --------- Co-authored-by: jdar <justin.dar@smiledigitalhealth.com> * Enhance migration for MSSQL to change the collation for HFJ_RESOURCE.FHIR_ID to case sensitive (#6135) * MSSQL: Migrate HFJ_RESOURCE.FHIR_ID to new collation: SQL_Latin1_General_CP1_CS_AS * Spotless. * Enhance test. Fix case in ResourceSearchView to defend against future migration to case insensitive collation. * Remove TODOs. Add comment to ResourceSearchView explaining why all columns are uppercase. Changelog. * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6146-mssql-hfj-resource-fhir-id-colllation.yaml Code reviewer suggestion Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> * Code review fixes: Make changes conditional on the collation including _CI_, otherwise, leave it alone. --------- Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> * Common API for FHIR Data Access (#6141) * Add initial interface for common FHIR API * Fix formatting * Update javadocs * Address code review comments * Add path value to _id search parameter and other missing search param… (#6128) * Add path value to _id search parameter and other missing search parameters to IAnyResource. * Adjust tests and remove now unnecessary addition of meta parameters which are now provided by IAnyResource * Revert unneeded change * _security param is not token but uri * Add tests for new defined resource-level standard parameters * Adjust test --------- Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com> * update to online (#6157) * SEARCH_UUID should be non-null (#6165) Avoid using constants in migrations because it creates false history. * Handle 400 and 404 codes returned by remote terminology operation. (#6151) * Handle 400 and 404 codes returned by remote terminology operation. * Some simplification * Adjust changelog * Add a comment to explain alternate solution which can be reused. * fix concepts with no display element for $apply-codesystem-delta-add and $apply-codesystem-delta-remove (#6164) * allow transaction with update conditional urls (#6155) * Revert "Add path value to _id search parameter and other missing search param…" (#6171) This reverts commit 2275eba1a0f379b6ccb2d1b467fa22bd9febcf54. * 7 2 2 mb (#6160) * Enhance RuleBuilder code to support multiple instances (#5852) * Overhaul bulk export permissions. * Overhaul bulk export permissions. * Small tweak to rule builder. * Cleanup validation. * Cleanup validation. * Code review feedback. * Postgres terminology service hard coded column names migration (#5866) * updating parent pids column name * updating name of the fullTestField Search * updating name of the fullTestField Search * fixing typo. * failing test. * - Moving FullTextField annotation from getter method and adding it to the newly added VC property of the entity; - reverting the name of the FullTextField entity to its previous name of 'myParentPids'; - reverting the name of the lucene index to search on in the terminology service. - updating the changelog; * making spotless happy --------- Co-authored-by: peartree <etienne.poirier@smilecdr.com> * 5879 back porting fix for issue 5877 (attempting to update a tokenparam with a value greater than 200 characters raises an sqlexception) to release rel_7_2 (#5881) * initial failing test. * solution * adding changelog * spotless * moving changelog from 7_4_0 to 7_2_0 and deleting 7_4_0 folder. --------- Co-authored-by: peartree <etienne.poirier@smilecdr.com> * Expose BaseRequestPartitionHelperSvc validateAndNormalize methods (#5811) * Expose BaseRequestPartitionHelperSvc validate and normalize methods * Compilation errors * change mock test to jpa test * change mock test to jpa test * validateAndNormalizePartitionIds * validateAndNormalizePartitionNames * validateAndNormalizePartitionIds validation + bug fix * validateAndNormalizePartitionNames validation * fix test * version bump * Ensure a non-numeric FHIR ID doesn't result in a NumberFormatException when processing survivorship rules (#5883) * Add failing test as well as commented out potential solution. * Fix for NumberFormatException. * Add conditional test for survivorship rules. * Spotless. * Add changelog. * Code review feedback. * updating documentation (#5889) * Ensure temp file ends with "." and then suffix. (#5894) * bugfix to https://github.com/hapifhir/hapi-fhir-jpaserver-starter/issues/675 (#5892) Co-authored-by: Jens Kristian Villadsen <jenskristianvilladsen@gmail.com> * Enhance mdm interceptor (#5899) * Add MDM Transaction Context for further downstream processing giving interceptors a better chance of figuring out what happened. * Added javadoc * Cahngelog * spotless --------- Co-authored-by: Jens Kristian Villadsen <jenskristianvilladsen@gmail.com> * Fix BaseHapiFhirResourceDao $meta method to use HapiTransactionService instead of @Transaction (#5896) * Try making ResourceTable.myTags EAGER instead of LAZY and see if it breaks anything. * Try making ResourceTable.myTags EAGER instead of LAZY and see if it breaks anything. * Ensure BaseHapiFhirResourceDao#metaGetOperation uses HapiTransactionService instead of @Transactional in order to resolve megascale $meta bug. * Add changelog. * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/5898-ld-megascale-meta-operation-fails-hapi-0389.yaml Commit code reviewer suggestion. Co-authored-by: Tadgh <garygrantgraham@gmail.com> --------- Co-authored-by: Tadgh <garygrantgraham@gmail.com> * Fix query chained on sort bug where we over-filter results (#5903) * Failing test. * Ensure test cleanup doesn't fail by deleting Patients before Practitioners. * Implement fix. * Spotless. * Clean up unit test and add changelog. Fix unit test. * Fix changelog file. * Apply suggestions from code review Apply code review suggestions. Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> * Spotless --------- Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> * cve fix (#5906) Co-authored-by: Long Ma <long@smilecdr.com> * Fixing issues with postgres LOB migration. (#5895) * Fixing issues with postgres LOB migration. * addressing code review comments for audit/transaction logs. * test and implementation for BinaryStorageEntity migration post code review. * test and implementation for BinaryStorageEntity migration post code review. * test and implementation for TermConcept migration post code review. * applying spotless * test and implementation for TermConceptProperty migration post code review. * test and implementation for TermValueSetConcept migration post code review. * fixing migration version * fixing migration task * changelog * fixing changelog * Minor renames * addressing comments and suggestions from second code review. * passing tests * fixing more tests --------- Co-authored-by: peartree <etienne.poirier@smilecdr.com> Co-authored-by: Tadgh <garygrantgraham@gmail.com> * 6051 bulk export security errors (#5915) * Enhance RuleBuilder code to support multiple instances (#5852) * Overhaul bulk export permissions. * Overhaul bulk export permissions. * Small tweak to rule builder. * Cleanup validation. * Cleanup validation. * Code review feedback. * Postgres terminology service hard coded column names migration (#5866) * updating parent pids column name * updating name of the fullTestField Search * updating name of the fullTestField Search * fixing typo. * failing test. * - Moving FullTextField annotation from getter method and adding it to the newly added VC property of the entity; - reverting the name of the FullTextField entity to its previous name of 'myParentPids'; - reverting the name of the lucene index to search on in the terminology service. - updating the changelog; * making spotless happy --------- Co-authored-by: peartree <etienne.poirier@smilecdr.com> * 5879 back porting fix for issue 5877 (attempting to update a tokenparam with a value greater than 200 characters raises an sqlexception) to release rel_7_2 (#5881) * initial failing test. * solution * adding changelog * spotless * moving changelog from 7_4_0 to 7_2_0 and deleting 7_4_0 folder. --------- Co-authored-by: peartree <etienne.poirier@smilecdr.com> * Expose BaseRequestPartitionHelperSvc validateAndNormalize methods (#5811) * Expose BaseRequestPartitionHelperSvc validate and normalize methods * Compilation errors * change mock test to jpa test * change mock test to jpa test * validateAndNormalizePartitionIds * validateAndNormalizePartitionNames * validateAndNormalizePartitionIds validation + bug fix * validateAndNormalizePartitionNames validation * fix test * version bump * Ensure a non-numeric FHIR ID doesn't result in a NumberFormatException when processing survivorship rules (#5883) * Add failing test as well as commented out potential solution. * Fix for NumberFormatException. * Add conditional test for survivorship rules. * Spotless. * Add changelog. * Code review feedback. * updating documentation (#5889) * Ensure temp file ends with "." and then suffix. (#5894) * bugfix to https://github.com/hapifhir/hapi-fhir-jpaserver-starter/issues/675 (#5892) Co-authored-by: Jens Kristian Villadsen <jenskristianvilladsen@gmail.com> * Enhance mdm interceptor (#5899) * Add MDM Transaction Context for further downstream processing giving interceptors a better chance of figuring out what happened. * Added javadoc * Cahngelog * spotless --------- Co-authored-by: Jens Kristian Villadsen <jenskristianvilladsen@gmail.com> * Fix BaseHapiFhirResourceDao $meta method to use HapiTransactionService instead of @Transaction (#5896) * Try making ResourceTable.myTags EAGER instead of LAZY and see if it breaks anything. * Try making ResourceTable.myTags EAGER instead of LAZY and see if it breaks anything. * Ensure BaseHapiFhirResourceDao#metaGetOperation uses HapiTransactionService instead of @Transactional in order to resolve megascale $meta bug. * Add changelog. * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/5898-ld-megascale-meta-operation-fails-hapi-0389.yaml Commit code reviewer suggestion. Co-authored-by: Tadgh <garygrantgraham@gmail.com> --------- Co-authored-by: Tadgh <garygrantgraham@gmail.com> * Fix query chained on sort bug where we over-filter results (#5903) * Failing test. * Ensure test cleanup doesn't fail by deleting Patients before Practitioners. * Implement fix. * Spotless. * Clean up unit test and add changelog. Fix unit test. * Fix changelog file. * Apply suggestions from code review Apply code review suggestions. Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> * Spotless --------- Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> * cve fix (#5906) Co-authored-by: Long Ma <long@smilecdr.com> * Fixing issues with postgres LOB migration. (#5895) * Fixing issues with postgres LOB migration. * addressing code review comments for audit/transaction logs. * test and implementation for BinaryStorageEntity migration post code review. * test and implementation for BinaryStorageEntity migration post code review. * test and implementation for TermConcept migration post code review. * applying spotless * test and implementation for TermConceptProperty migration post code review. * test and implementation for TermValueSetConcept migration post code review. * fixing migration version * fixing migration task * changelog * fixing changelog * Minor renames * addressing comments and suggestions from second code review. * passing tests * fixing more tests --------- Co-authored-by: peartree <etienne.poirier@smilecdr.com> Co-authored-by: Tadgh <garygrantgraham@gmail.com> * refactor bulk export rule, add concept of appliestoallpatients, fix tests * spotless * Cahgnelog, tests * more tests * refactor style checks --------- Co-authored-by: Luke deGruchy <luke.degruchy@smilecdr.com> Co-authored-by: Etienne Poirier <33007955+epeartree@users.noreply.github.com> Co-authored-by: peartree <etienne.poirier@smilecdr.com> Co-authored-by: Nathan Doef <n.doef@protonmail.com> Co-authored-by: TipzCM <leif.stawnyczy@gmail.com> Co-authored-by: dotasek <david.otasek@smilecdr.com> Co-authored-by: Jens Kristian Villadsen <jenskristianvilladsen@gmail.com> Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> Co-authored-by: longma1 <32119004+longma1@users.noreply.github.com> Co-authored-by: Long Ma <long@smilecdr.com> * Convert a few nulls to aggressive denies * Change chain sort syntax for MS SQL (#5917) * Change sort type on chains * Change sort type on chains * Test for MS SQL * Comments * Version bump * Updating version to: 7.2.1 post release. * Fix queries with chained sort with Lucene by checking supported SortSpecs (#5958) * First commit with very rough solution. * Solidify solutions for both requirements. Add new tests. Enhance others. * Spotless. * Add new chained sort spec algorithm. Add new Msg.codes. Finalize tests. Update docs. Add changelog. * pom remove the snapshot * Updating version to: 7.2.2 post release. * cherry-picked pr 6051 * changelog fix * cherry-picked 6027 * docs and changelog * merge fix for issue with infinite cache refresh loop * Use lockless mode when adding index on Azure Sql server (#6100) (#6129) * Use lockless mode when adding index on Azure Sql server Use try-catch for Online add-index on Sql Server. This avoids having to map out the entire matrix of Sql Server product names and ONLINE index support. Warnings in docs, and cleanups * added fix for 6133 * failing Test * Add fix * spotless * Remove useless file * Fix claeaner * cleanup * Remove dead class * Changelog * test description * Add test. Fix broken logic. * fix quantity search parameter test to pass * reverted test testDirectPathWholeResourceNotIndexedWorks in FhirResourceDaoR4SearchWithElasticSearchIT * spotless * cleanup mistake during merge * added missing imports * fix more mergeback oopsies * bump to 7.3.13-snapshot --------- Co-authored-by: Luke deGruchy <luke.degruchy@smilecdr.com> Co-authored-by: Etienne Poirier <33007955+epeartree@users.noreply.github.com> Co-authored-by: peartree <etienne.poirier@smilecdr.com> Co-authored-by: Nathan Doef <n.doef@protonmail.com> Co-authored-by: TipzCM <leif.stawnyczy@gmail.com> Co-authored-by: dotasek <david.otasek@smilecdr.com> Co-authored-by: Jens Kristian Villadsen <jenskristianvilladsen@gmail.com> Co-authored-by: Tadgh <garygrantgraham@gmail.com> Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> Co-authored-by: Long Ma <long@smilecdr.com> Co-authored-by: markiantorno <markiantorno@gmail.com> * Patient validate operation with remote terminology service enabled returns 400 bad request (#6124) * Patient $validate operation with Remote Terminology Service enabled returns 400 Bad Request - failing test * Patient $validate operation with Remote Terminology Service enabled returns 400 Bad Request - implementation * - Changing method accessibility from default to public to allow method overwriting. (#6172) Co-authored-by: peartree <etienne.poirier@smilecdr.com> * applying Taha Attari's fix on branch merging to rel_7_4 (#6177) Co-authored-by: peartree <etienne.poirier@smilecdr.com> * Automated Migration Testing (HAPI-FHIR) V7_4_0 (#6170) * Automated Migration Testing (HAPI-FHIR) - updated test migration scripts for 7_4_0 * Automated Migration Testing (HAPI-FHIR) - updated test migration scripts for 7_2_0 * To provide the target resource partitionId and partitionDate in the resourceLinlk (#6149) * initial POC. * addressing comments from first code review * Adding tests * adding changelog and spotless * fixing tests * spotless --------- Co-authored-by: peartree <etienne.poirier@smilecdr.com> * applying patch (#6190) Co-authored-by: peartree <etienne.poirier@smilecdr.com> * cve for 08 release (#6197) Co-authored-by: Long Ma <long@smilecdr.com> * Search param path missing for _id param (#6175) * Add path tp _id search param and definitions for _lastUpdated _tag, _profile and _security * Add tests and changelog * Increase snapshot version * Irrelevant change to force new build --------- Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com> * Reverting to core fhir-test-cases 1.1.14; (#6194) re-enabling FhirPatchCoreTest Co-authored-by: peartree <etienne.poirier@smilecdr.com> * initial failing test * providing capability to tailor resthook endpoint url validation through supplied regex * adding necessary import. * conformance with spotless * adding tests * adding test for payload * spotless * adding changelogs * passing all tests * passing all tests * no-op commit to kickstart the pipeline. * - slight modification to the solution; - spotless; * pre-code review submission. * moving validator instantiation to the submitter configuration so it is create along side the subscriptionValidatorInterceptor * last min tweeking before submission for review. * fixing spotless post merging in master --------- Co-authored-by: Emre Dincturk <74370953+mrdnctrk@users.noreply.github.com> Co-authored-by: Luke deGruchy <luke.degruchy@smilecdr.com> Co-authored-by: Michael Buckley <michaelabuckley@gmail.com> Co-authored-by: jdar8 <69840459+jdar8@users.noreply.github.com> Co-authored-by: jdar <justin.dar@smiledigitalhealth.com> Co-authored-by: Luke deGruchy <luke.degruchy@smiledigitalhealth.com> Co-authored-by: JP <jonathan.i.percival@gmail.com> Co-authored-by: jmarchionatto <60409882+jmarchionatto@users.noreply.github.com> Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com> Co-authored-by: TipzCM <leif.stawnyczy@gmail.com> Co-authored-by: Martha Mitran <marthamitran@gmail.com> Co-authored-by: longma1 <32119004+longma1@users.noreply.github.com> Co-authored-by: peartree <etienne.poirier@smilecdr.com> Co-authored-by: Nathan Doef <n.doef@protonmail.com> Co-authored-by: dotasek <david.otasek@smilecdr.com> Co-authored-by: Jens Kristian Villadsen <jenskristianvilladsen@gmail.com> Co-authored-by: Tadgh <garygrantgraham@gmail.com> Co-authored-by: Long Ma <long@smilecdr.com> Co-authored-by: markiantorno <markiantorno@gmail.com> Co-authored-by: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com>
This commit is contained in:
parent
48d8fac6ea
commit
5e48e38b1d
@ -0,0 +1,5 @@
|
||||
---
|
||||
type: fix
|
||||
issue: 6203
|
||||
title: "Previously, the SubscriptionValidatingInterceptor would allow the creation/update of a REST hook subscription
|
||||
where the endpoint URL property is not prefixed with http[s]. This issue is fixed."
|
@ -27,9 +27,13 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public abstract class BaseSubscriptionSettings {
|
||||
public static final String DEFAULT_EMAIL_FROM_ADDRESS = "noreply@unknown.com";
|
||||
public static final String DEFAULT_WEBSOCKET_CONTEXT_PATH = "/websocket";
|
||||
public static final String DEFAULT_RESTHOOK_ENDPOINTURL_VALIDATION_REGEX =
|
||||
"((((http?|https?)://))([-%()_.!~*';/?:@&=+$,A-Za-z0-9])+)";
|
||||
|
||||
private final Set<Subscription.SubscriptionChannelType> mySupportedSubscriptionTypes = new HashSet<>();
|
||||
private String myEmailFromAddress = DEFAULT_EMAIL_FROM_ADDRESS;
|
||||
@ -45,6 +49,13 @@ public abstract class BaseSubscriptionSettings {
|
||||
*/
|
||||
private boolean myAllowOnlyInMemorySubscriptions = false;
|
||||
|
||||
/**
|
||||
* @since 7.6.0
|
||||
*
|
||||
* Regex To perform validation on the endpoint URL for Subscription of type RESTHOOK.
|
||||
*/
|
||||
private String myRestHookEndpointUrlValidationRegex = DEFAULT_RESTHOOK_ENDPOINTURL_VALIDATION_REGEX;
|
||||
|
||||
/**
|
||||
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
||||
* to the server matching these types will be activated.
|
||||
@ -235,4 +246,32 @@ public abstract class BaseSubscriptionSettings {
|
||||
public void setTriggerSubscriptionsForNonVersioningChanges(boolean theTriggerSubscriptionsForNonVersioningChanges) {
|
||||
myTriggerSubscriptionsForNonVersioningChanges = theTriggerSubscriptionsForNonVersioningChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the regex expression to perform endpoint URL validation If rest-hook subscriptions are supported.
|
||||
* Default value is {@link #DEFAULT_RESTHOOK_ENDPOINTURL_VALIDATION_REGEX}.
|
||||
* @since 7.6.0
|
||||
*/
|
||||
public String getRestHookEndpointUrlValidationRegex() {
|
||||
return myRestHookEndpointUrlValidationRegex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the regex expression that will be used to validate the endpoint URL.
|
||||
* Set to NULL or EMPTY for no endpoint URL validation.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*/
|
||||
public void setRestHookEndpointUrlValidationRegex(String theRestHookEndpointUrlValidationgRegex) {
|
||||
myRestHookEndpointUrlValidationRegex = theRestHookEndpointUrlValidationgRegex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an endpoint validation Regex was set for URL validation.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*/
|
||||
public boolean hasRestHookEndpointUrlValidationRegex() {
|
||||
return isNotBlank(myRestHookEndpointUrlValidationRegex);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.subscription.config;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionQueryValidator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
@ -30,6 +30,10 @@ import ca.uhn.fhir.jpa.subscription.model.config.SubscriptionModelConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionSubmitInterceptorLoader;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionValidatingInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.IChannelTypeValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.RegexEndpointUrlValidationStrategy;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.RestHookChannelValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionChannelTypeValidatorFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.svc.ResourceModifiedSubmitterSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.triggering.ISubscriptionTriggeringSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.triggering.SubscriptionTriggeringSvcImpl;
|
||||
@ -43,6 +47,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.RestHookChannelValidator.noOpEndpointUrlValidationStrategy;
|
||||
|
||||
/**
|
||||
* This Spring config should be imported by a system that submits resources to the
|
||||
* matching queue for processing
|
||||
@ -103,4 +111,23 @@ public class SubscriptionSubmitterConfig {
|
||||
return new AsyncResourceModifiedSubmitterSvc(
|
||||
theIResourceModifiedMessagePersistenceSvc, theResourceModifiedConsumer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IChannelTypeValidator restHookChannelValidator(SubscriptionSettings theSubscriptionSettings) {
|
||||
RestHookChannelValidator.IEndpointUrlValidationStrategy iEndpointUrlValidationStrategy =
|
||||
noOpEndpointUrlValidationStrategy;
|
||||
|
||||
if (theSubscriptionSettings.hasRestHookEndpointUrlValidationRegex()) {
|
||||
String endpointUrlValidationRegex = theSubscriptionSettings.getRestHookEndpointUrlValidationRegex();
|
||||
iEndpointUrlValidationStrategy = new RegexEndpointUrlValidationStrategy(endpointUrlValidationRegex);
|
||||
}
|
||||
|
||||
return new RestHookChannelValidator(iEndpointUrlValidationStrategy);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SubscriptionChannelTypeValidatorFactory subscriptionChannelTypeValidatorFactory(
|
||||
List<IChannelTypeValidator> theValidorList) {
|
||||
return new SubscriptionChannelTypeValidatorFactory(theValidorList);
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,10 @@ import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyE
|
||||
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.IChannelTypeValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionChannelTypeValidatorFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||
@ -87,6 +89,9 @@ public class SubscriptionValidatingInterceptor {
|
||||
@Autowired
|
||||
private SubscriptionQueryValidator mySubscriptionQueryValidator;
|
||||
|
||||
@Autowired
|
||||
private SubscriptionChannelTypeValidatorFactory mySubscriptionChannelTypeValidatorFactory;
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
|
||||
public void resourcePreCreate(
|
||||
IBaseResource theResource, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
|
||||
@ -149,7 +154,7 @@ public class SubscriptionValidatingInterceptor {
|
||||
break;
|
||||
}
|
||||
|
||||
validatePermissions(theSubscription, subscription, theRequestDetails, theRequestPartitionId, thePointcut);
|
||||
validatePermissions(theSubscription, theRequestDetails, theRequestPartitionId, thePointcut);
|
||||
|
||||
mySubscriptionCanonicalizer.setMatchingStrategyTag(theSubscription, null);
|
||||
|
||||
@ -167,7 +172,7 @@ public class SubscriptionValidatingInterceptor {
|
||||
|
||||
try {
|
||||
SubscriptionMatchingStrategy strategy = mySubscriptionStrategyEvaluator.determineStrategy(subscription);
|
||||
if (!(SubscriptionMatchingStrategy.IN_MEMORY == strategy)
|
||||
if (SubscriptionMatchingStrategy.IN_MEMORY != strategy
|
||||
&& mySubscriptionSettings.isOnlyAllowInMemorySubscriptions()) {
|
||||
throw new InvalidRequestException(
|
||||
Msg.code(2367)
|
||||
@ -236,7 +241,6 @@ public class SubscriptionValidatingInterceptor {
|
||||
|
||||
protected void validatePermissions(
|
||||
IBaseResource theSubscription,
|
||||
CanonicalSubscription theCanonicalSubscription,
|
||||
RequestDetails theRequestDetails,
|
||||
RequestPartitionId theRequestPartitionId,
|
||||
Pointcut thePointcut) {
|
||||
@ -319,27 +323,11 @@ public class SubscriptionValidatingInterceptor {
|
||||
protected void validateChannelType(CanonicalSubscription theSubscription) {
|
||||
if (theSubscription.getChannelType() == null) {
|
||||
throw new UnprocessableEntityException(Msg.code(20) + "Subscription.channel.type must be populated");
|
||||
} else if (theSubscription.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) {
|
||||
validateChannelPayload(theSubscription);
|
||||
validateChannelEndpoint(theSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected void validateChannelEndpoint(CanonicalSubscription theResource) {
|
||||
if (isBlank(theResource.getEndpointUrl())) {
|
||||
throw new UnprocessableEntityException(
|
||||
Msg.code(21) + "Rest-hook subscriptions must have Subscription.channel.endpoint defined");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected void validateChannelPayload(CanonicalSubscription theResource) {
|
||||
if (!isBlank(theResource.getPayloadString())
|
||||
&& EncodingEnum.forContentType(theResource.getPayloadString()) == null) {
|
||||
throw new UnprocessableEntityException(Msg.code(1985) + "Invalid value for Subscription.channel.payload: "
|
||||
+ theResource.getPayloadString());
|
||||
}
|
||||
IChannelTypeValidator iChannelTypeValidator =
|
||||
mySubscriptionChannelTypeValidatorFactory.getValidatorForChannelType(theSubscription.getChannelType());
|
||||
iChannelTypeValidator.validateChannelType(theSubscription);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@ -371,4 +359,10 @@ public class SubscriptionValidatingInterceptor {
|
||||
mySubscriptionStrategyEvaluator = theSubscriptionStrategyEvaluator;
|
||||
mySubscriptionQueryValidator = new SubscriptionQueryValidator(myDaoRegistry, theSubscriptionStrategyEvaluator);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setSubscriptionChannelTypeValidatorFactoryForUnitTest(
|
||||
SubscriptionChannelTypeValidatorFactory theSubscriptionChannelTypeValidatorFactory) {
|
||||
mySubscriptionChannelTypeValidatorFactory = theSubscriptionChannelTypeValidatorFactory;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
package ca.uhn.fhir.jpa.subscription.submit.interceptor.validator;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
|
||||
|
||||
public interface IChannelTypeValidator {
|
||||
|
||||
void validateChannelType(CanonicalSubscription theSubscription);
|
||||
|
||||
CanonicalSubscriptionChannelType getSubscriptionChannelType();
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package ca.uhn.fhir.jpa.subscription.submit.interceptor.validator;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import jakarta.annotation.Nonnull;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
public class RegexEndpointUrlValidationStrategy implements RestHookChannelValidator.IEndpointUrlValidationStrategy {
|
||||
|
||||
private final Pattern myEndpointUrlValidationPattern;
|
||||
|
||||
public RegexEndpointUrlValidationStrategy(@Nonnull String theEndpointUrlValidationRegex) {
|
||||
try {
|
||||
myEndpointUrlValidationPattern = Pattern.compile(theEndpointUrlValidationRegex);
|
||||
} catch (PatternSyntaxException e) {
|
||||
throw new IllegalArgumentException(
|
||||
Msg.code(2546) + " invalid synthax for provided regex " + theEndpointUrlValidationRegex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateEndpointUrl(String theEndpointUrl) {
|
||||
Matcher matcher = myEndpointUrlValidationPattern.matcher(theEndpointUrl);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
throw new UnprocessableEntityException(
|
||||
Msg.code(2545) + "Failed validation for endpoint URL: " + theEndpointUrl);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package ca.uhn.fhir.jpa.subscription.submit.interceptor.validator;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import jakarta.annotation.Nonnull;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/**
|
||||
*
|
||||
* Definition of a REST Hook channel validator that perform checks on the channel payload and endpoint URL.
|
||||
*
|
||||
* The channel payload will always evaluate in the same manner where endpoint URL validation can be extended beyond the
|
||||
* minimal validation perform by this class.
|
||||
*
|
||||
* At a minimum, this class ensures that the provided URL is not blank or null. Supplemental validation(s) should be
|
||||
* encapsulated into a {@link IEndpointUrlValidationStrategy} and provided with the arg constructor.
|
||||
*
|
||||
*/
|
||||
public class RestHookChannelValidator implements IChannelTypeValidator {
|
||||
|
||||
private final IEndpointUrlValidationStrategy myEndpointUrlValidationStrategy;
|
||||
|
||||
/**
|
||||
* Constructor for a validator where the endpoint URL will
|
||||
*/
|
||||
public RestHookChannelValidator() {
|
||||
this(noOpEndpointUrlValidationStrategy);
|
||||
}
|
||||
|
||||
public RestHookChannelValidator(@Nonnull IEndpointUrlValidationStrategy theEndpointUrlValidationStrategy) {
|
||||
myEndpointUrlValidationStrategy = theEndpointUrlValidationStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateChannelType(CanonicalSubscription theSubscription) {
|
||||
validateChannelPayload(theSubscription);
|
||||
validateChannelEndpoint(theSubscription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalSubscriptionChannelType getSubscriptionChannelType() {
|
||||
return CanonicalSubscriptionChannelType.RESTHOOK;
|
||||
}
|
||||
|
||||
protected void validateChannelEndpoint(@Nonnull CanonicalSubscription theCanonicalSubscription) {
|
||||
String endpointUrl = theCanonicalSubscription.getEndpointUrl();
|
||||
|
||||
if (isBlank(endpointUrl)) {
|
||||
throw new UnprocessableEntityException(
|
||||
Msg.code(21) + "Rest-hook subscriptions must have Subscription.channel.endpoint defined");
|
||||
}
|
||||
|
||||
myEndpointUrlValidationStrategy.validateEndpointUrl(endpointUrl);
|
||||
}
|
||||
|
||||
protected void validateChannelPayload(CanonicalSubscription theResource) {
|
||||
if (!isBlank(theResource.getPayloadString())
|
||||
&& EncodingEnum.forContentType(theResource.getPayloadString()) == null) {
|
||||
throw new UnprocessableEntityException(Msg.code(1985) + "Invalid value for Subscription.channel.payload: "
|
||||
+ theResource.getPayloadString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A concrete instantiation of this interface should provide tailored validation of an endpoint URL
|
||||
* throwing {@link RuntimeException} upon validation failure.
|
||||
*/
|
||||
public interface IEndpointUrlValidationStrategy {
|
||||
void validateEndpointUrl(String theEndpointUrl);
|
||||
}
|
||||
|
||||
public static final IEndpointUrlValidationStrategy noOpEndpointUrlValidationStrategy = theEndpointUrl -> {};
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package ca.uhn.fhir.jpa.subscription.submit.interceptor.validator;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SubscriptionChannelTypeValidatorFactory {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionChannelTypeValidatorFactory.class);
|
||||
|
||||
private final Map<CanonicalSubscriptionChannelType, IChannelTypeValidator> myValidators =
|
||||
new EnumMap<>(CanonicalSubscriptionChannelType.class);
|
||||
|
||||
public SubscriptionChannelTypeValidatorFactory(@Nonnull List<IChannelTypeValidator> theValidorList) {
|
||||
theValidorList.forEach(this::addChannelTypeValidator);
|
||||
}
|
||||
|
||||
public IChannelTypeValidator getValidatorForChannelType(CanonicalSubscriptionChannelType theChannelType) {
|
||||
return myValidators.getOrDefault(theChannelType, getNoopValidatorForChannelType(theChannelType));
|
||||
}
|
||||
|
||||
public void addChannelTypeValidator(IChannelTypeValidator theValidator) {
|
||||
myValidators.put(theValidator.getSubscriptionChannelType(), theValidator);
|
||||
}
|
||||
|
||||
private IChannelTypeValidator getNoopValidatorForChannelType(CanonicalSubscriptionChannelType theChannelType) {
|
||||
return new IChannelTypeValidator() {
|
||||
@Override
|
||||
public void validateChannelType(CanonicalSubscription theSubscription) {
|
||||
ourLog.debug(
|
||||
"No validator for channel type {} was registered, will perform no-op validation.",
|
||||
theChannelType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CanonicalSubscriptionChannelType getSubscriptionChannelType() {
|
||||
return theChannelType;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.subscription.submit.interceptor;
|
||||
package ca.uhn.fhir.jpa.subscription.submit.interceptor.validator;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
@ -23,7 +23,7 @@ import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher;
|
||||
import ca.uhn.fhir.jpa.subscription.config.SubscriptionConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
@ -25,7 +25,7 @@ import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
@ -15,7 +15,7 @@ import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender;
|
||||
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.subscription.api.IResourceModifiedMessagePersistenceSvc;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
@ -4,15 +4,19 @@ import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
||||
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
|
||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator;
|
||||
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer;
|
||||
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.IChannelTypeValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.RegexEndpointUrlValidationStrategy;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.RestHookChannelValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionChannelTypeValidatorFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionQueryValidator;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||
@ -29,11 +33,13 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
@ -49,6 +55,8 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ -70,6 +78,9 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
private IFhirResourceDao<SubscriptionTopic> mySubscriptionTopicDao;
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
@SpyBean
|
||||
private SubscriptionChannelTypeValidatorFactory mySubscriptionChannelTypeValidatorFactory;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
setFhirContext(FhirVersionEnum.R4B);
|
||||
@ -79,8 +90,9 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("subscriptionByFhirVersion345")
|
||||
public void testEmptySub(IBaseResource theSubscription) {
|
||||
try {
|
||||
setFhirContext(theSubscription);
|
||||
|
||||
try {
|
||||
mySubscriptionValidatingInterceptor.resourcePreCreate(theSubscription, null, null);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
@ -92,8 +104,9 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("subscriptionByFhirVersion34") // R5 subscriptions don't have criteria
|
||||
public void testEmptyCriteria(IBaseResource theSubscription) {
|
||||
try {
|
||||
initSubscription(theSubscription);
|
||||
|
||||
try {
|
||||
mySubscriptionValidatingInterceptor.resourcePreCreate(theSubscription, null, null);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
@ -105,9 +118,10 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("subscriptionByFhirVersion34")
|
||||
public void testBadCriteria(IBaseResource theSubscription) {
|
||||
try {
|
||||
initSubscription(theSubscription);
|
||||
SubscriptionUtil.setCriteria(myFhirContext, theSubscription, "Patient");
|
||||
|
||||
try {
|
||||
mySubscriptionValidatingInterceptor.resourcePreCreate(theSubscription, null, null);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
@ -118,9 +132,10 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("subscriptionByFhirVersion34")
|
||||
public void testBadChannel(IBaseResource theSubscription) {
|
||||
try {
|
||||
initSubscription(theSubscription);
|
||||
SubscriptionUtil.setCriteria(myFhirContext, theSubscription, "Patient?");
|
||||
|
||||
try {
|
||||
mySubscriptionValidatingInterceptor.resourcePreCreate(theSubscription, null, null);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
@ -131,10 +146,11 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("subscriptionByFhirVersion345")
|
||||
public void testEmptyEndpoint(IBaseResource theSubscription) {
|
||||
try {
|
||||
initSubscription(theSubscription);
|
||||
SubscriptionUtil.setCriteria(myFhirContext, theSubscription, "Patient?");
|
||||
SubscriptionUtil.setChannelType(myFhirContext, theSubscription, "message");
|
||||
|
||||
try {
|
||||
mySubscriptionValidatingInterceptor.resourcePreCreate(theSubscription, null, null);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
@ -223,8 +239,27 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
SimpleBundleProvider simpleBundleProvider = new SimpleBundleProvider(List.of(topic));
|
||||
when(mySubscriptionTopicDao.search(any(), any())).thenReturn(simpleBundleProvider);
|
||||
mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub, null, null, Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED);
|
||||
|
||||
verify(mySubscriptionChannelTypeValidatorFactory, times(1)).getValidatorForChannelType(CanonicalSubscriptionChannelType.MESSAGE);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"acme.corp",
|
||||
"https://acme.corp/badstuff-%%$^&& iuyi",
|
||||
"ftp://acme.corp"})
|
||||
public void testRestHookEndpointValidation_whenProvidedWithBadURLs(String theBadUrl) {
|
||||
try {
|
||||
Subscription subscriptionWithBadEndpoint = createSubscription();
|
||||
subscriptionWithBadEndpoint.getChannel().setEndpoint(theBadUrl);
|
||||
|
||||
mySubscriptionValidatingInterceptor.validateSubmittedSubscription(subscriptionWithBadEndpoint, null, null, Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED);
|
||||
fail("");
|
||||
} catch (Exception e) {
|
||||
verify(mySubscriptionChannelTypeValidatorFactory, times(1)).getValidatorForChannelType(CanonicalSubscriptionChannelType.RESTHOOK);
|
||||
assertThat(e.getMessage()).startsWith(Msg.code(2545));
|
||||
}
|
||||
}
|
||||
|
||||
private void initSubscription(IBaseResource theSubscription) {
|
||||
setFhirContext(theSubscription);
|
||||
@ -298,6 +333,19 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
SubscriptionQueryValidator subscriptionQueryValidator(DaoRegistry theDaoRegistry, SubscriptionStrategyEvaluator theSubscriptionStrategyEvaluator) {
|
||||
return new SubscriptionQueryValidator(theDaoRegistry, theSubscriptionStrategyEvaluator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IChannelTypeValidator restHookChannelValidator() {
|
||||
String regex = new SubscriptionSettings().getRestHookEndpointUrlValidationRegex();
|
||||
RegexEndpointUrlValidationStrategy regexEndpointUrlValidationStrategy = new RegexEndpointUrlValidationStrategy(regex);
|
||||
return new RestHookChannelValidator(regexEndpointUrlValidationStrategy);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SubscriptionChannelTypeValidatorFactory subscriptionChannelTypeValidatorFactory(
|
||||
List<IChannelTypeValidator> theValidorList) {
|
||||
return new SubscriptionChannelTypeValidatorFactory(theValidorList);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@ -307,7 +355,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
subscription.setCriteria("Patient?");
|
||||
final Subscription.SubscriptionChannelComponent channel = subscription.getChannel();
|
||||
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
|
||||
channel.setEndpoint("channel");
|
||||
channel.setEndpoint("http://acme.corp/");
|
||||
return subscription;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,130 @@
|
||||
package ca.uhn.fhir.jpa.subscription.submit.interceptor.validator;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
|
||||
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.REQUESTED;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class RestHookChannelValidatorTest {
|
||||
private final FhirContext myCtx = FhirContext.forR4();
|
||||
private final SubscriptionSettings mySubscriptionSettings = new SubscriptionSettings();
|
||||
private final SubscriptionCanonicalizer mySubscriptionCanonicalizer= new SubscriptionCanonicalizer(myCtx, mySubscriptionSettings);
|
||||
|
||||
private final String NO_PAYLOAD = StringUtils.EMPTY;
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("urlAndExpectedEvaluationResultProvider")
|
||||
public void testRestHookChannelValidation_withUrl(String theUrl, boolean theExpectedValidationResult){
|
||||
RegexEndpointUrlValidationStrategy regexEndpointUrlValidationStrategy = new RegexEndpointUrlValidationStrategy(SubscriptionSettings.DEFAULT_RESTHOOK_ENDPOINTURL_VALIDATION_REGEX);
|
||||
RestHookChannelValidator restHookChannelValidator = new RestHookChannelValidator(regexEndpointUrlValidationStrategy);
|
||||
|
||||
CanonicalSubscription subscription = createSubscription(theUrl, NO_PAYLOAD);
|
||||
doValidateUrlAndAssert(restHookChannelValidator, subscription, theExpectedValidationResult);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("urlAndExpectedEvaluationResultProviderForNoUrlValidation")
|
||||
public void testRestHookChannelValidation_withNoUrlValidation(String theUrl, boolean theExpectedValidationResult){
|
||||
RestHookChannelValidator restHookChannelValidator = new RestHookChannelValidator();
|
||||
|
||||
CanonicalSubscription subscription = createSubscription(theUrl, NO_PAYLOAD);
|
||||
doValidateUrlAndAssert(restHookChannelValidator, subscription, theExpectedValidationResult);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("payloadAndExpectedEvaluationResultProvider")
|
||||
public void testRestHookChannelValidation_withPayload(String thePayload, boolean theExpectedValidationResult){
|
||||
RestHookChannelValidator restHookChannelValidator = new RestHookChannelValidator();
|
||||
|
||||
CanonicalSubscription subscription = createSubscription("https://acme.org", thePayload);
|
||||
doValidatePayloadAndAssert(restHookChannelValidator, subscription, theExpectedValidationResult);
|
||||
}
|
||||
|
||||
private void doValidatePayloadAndAssert(RestHookChannelValidator theRestHookChannelValidator, CanonicalSubscription theSubscription, boolean theExpectedValidationResult) {
|
||||
boolean validationResult = true;
|
||||
|
||||
try {
|
||||
theRestHookChannelValidator.validateChannelPayload(theSubscription);
|
||||
} catch (Exception e){
|
||||
validationResult = false;
|
||||
}
|
||||
|
||||
if( validationResult != theExpectedValidationResult){
|
||||
String message = String.format("Validation result for payload %s was expected to be %b but was %b", theSubscription.getEndpointUrl(), theExpectedValidationResult, validationResult);
|
||||
fail(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void doValidateUrlAndAssert(RestHookChannelValidator theRestHookChannelValidator, CanonicalSubscription theSubscription, boolean theExpectedValidationResult) {
|
||||
boolean validationResult = true;
|
||||
|
||||
try {
|
||||
theRestHookChannelValidator.validateChannelEndpoint(theSubscription);
|
||||
} catch (Exception e){
|
||||
validationResult = false;
|
||||
}
|
||||
|
||||
if( validationResult != theExpectedValidationResult){
|
||||
String message = String.format("Validation result for URL %s was expected to be %b but was %b", theSubscription.getEndpointUrl(), theExpectedValidationResult, validationResult);
|
||||
fail(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private CanonicalSubscription createSubscription(String theUrl, String thePayload) {
|
||||
final Subscription subscription = new Subscription();
|
||||
subscription.setStatus(REQUESTED);
|
||||
subscription.setCriteria("Patient?");
|
||||
final Subscription.SubscriptionChannelComponent channel = subscription.getChannel();
|
||||
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
|
||||
channel.setEndpoint(theUrl);
|
||||
channel.setPayload(thePayload);
|
||||
return mySubscriptionCanonicalizer.canonicalize(subscription);
|
||||
}
|
||||
|
||||
static Stream<Arguments> urlAndExpectedEvaluationResultProvider() {
|
||||
return Stream.of(
|
||||
Arguments.of("http://www.acme.corp/fhir", true),
|
||||
Arguments.of("http://acme.corp/fhir", true),
|
||||
Arguments.of("http://acme.corp:8000/fhir", true),
|
||||
Arguments.of("http://acme.corp:8000/fhir/", true),
|
||||
Arguments.of("http://acme.corp/fhir/", true),
|
||||
Arguments.of("https://foo.bar.com", true),
|
||||
Arguments.of("http://localhost:8000", true),
|
||||
Arguments.of("http://localhost:8000/", true),
|
||||
Arguments.of("http://localhost:8000/fhir", true),
|
||||
Arguments.of("http://localhost:8000/fhir/", true),
|
||||
Arguments.of("acme.corp", false),
|
||||
Arguments.of("https://acme.corp/badstuff-%%$^&& iuyi", false),
|
||||
Arguments.of("ftp://acme.corp", false));
|
||||
}
|
||||
|
||||
static Stream<Arguments> urlAndExpectedEvaluationResultProviderForNoUrlValidation() {
|
||||
return Stream.of(
|
||||
Arguments.of(null, false),
|
||||
Arguments.of("", false),
|
||||
Arguments.of(" ", false),
|
||||
Arguments.of("something", true));
|
||||
}
|
||||
|
||||
static Stream<Arguments> payloadAndExpectedEvaluationResultProvider() {
|
||||
return Stream.of(
|
||||
Arguments.of(null, true),
|
||||
Arguments.of("", true),
|
||||
Arguments.of(" ", true),
|
||||
Arguments.of("application/json", true),
|
||||
Arguments.of("garbage/fhir", false));
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
|
||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy;
|
||||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator;
|
||||
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionValidatingInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.RegexEndpointUrlValidationStrategy;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.RestHookChannelValidator;
|
||||
import ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.SubscriptionChannelTypeValidatorFactory;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
|
||||
@ -29,9 +31,13 @@ import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static ca.uhn.fhir.jpa.subscription.submit.interceptor.validator.RestHookChannelValidator.IEndpointUrlValidationStrategy;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
@ -57,6 +63,9 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||
@Mock
|
||||
private SubscriptionSettings mySubscriptionSettings;
|
||||
|
||||
private SubscriptionChannelTypeValidatorFactory mySubscriptionChannelTypeValidatorFactory;
|
||||
|
||||
private SubscriptionCanonicalizer mySubscriptionCanonicalizer;
|
||||
|
||||
@BeforeEach
|
||||
@ -69,6 +78,11 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
mySvc.setFhirContext(myCtx);
|
||||
mySvc.setSubscriptionSettingsForUnitTest(mySubscriptionSettings);
|
||||
mySvc.setRequestPartitionHelperSvcForUnitTest(myRequestPartitionHelperSvc);
|
||||
|
||||
IEndpointUrlValidationStrategy iEndpointUrlValidationStrategy = new RegexEndpointUrlValidationStrategy(SubscriptionSettings.DEFAULT_RESTHOOK_ENDPOINTURL_VALIDATION_REGEX);
|
||||
mySubscriptionChannelTypeValidatorFactory = new SubscriptionChannelTypeValidatorFactory(List.of(new RestHookChannelValidator(iEndpointUrlValidationStrategy)));
|
||||
|
||||
mySvc.setSubscriptionChannelTypeValidatorFactoryForUnitTest(mySubscriptionChannelTypeValidatorFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -85,7 +99,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testValidate_RestHook_Populated() {
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(true);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(true);
|
||||
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
@ -99,7 +113,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testValidate_RestHook_ResourceTypeNotSupported() {
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(false);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(false);
|
||||
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
@ -118,7 +132,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testValidate_RestHook_MultitypeResourceTypeNotSupported() {
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(false);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(false);
|
||||
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
@ -137,7 +151,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testValidate_RestHook_NoEndpoint() {
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(true);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(true);
|
||||
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
@ -156,7 +170,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testValidate_RestHook_NoType() {
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(true);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(true);
|
||||
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
@ -174,7 +188,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testValidate_RestHook_NoPayload() {
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(true);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(true);
|
||||
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
@ -203,7 +217,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testValidate_Cross_Partition_Subscription() {
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(true);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(true);
|
||||
when(mySubscriptionSettings.isCrossPartitionSubscriptionEnabled()).thenReturn(true);
|
||||
when(myRequestPartitionHelperSvc.determineCreatePartitionForRequest(isA(RequestDetails.class), isA(Subscription.class), eq("Subscription"))).thenReturn(RequestPartitionId.defaultPartition());
|
||||
|
||||
@ -222,8 +236,8 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
// is invalid
|
||||
assertDoesNotThrow(() -> mySvc.resourcePreCreate(subscription, requestDetails, null));
|
||||
Mockito.verify(mySubscriptionSettings, times(1)).isCrossPartitionSubscriptionEnabled();
|
||||
Mockito.verify(myDaoRegistry, times(1)).isResourceTypeSupported(eq("Patient"));
|
||||
Mockito.verify(myRequestPartitionHelperSvc, times(1)).determineCreatePartitionForRequest(isA(RequestDetails.class), isA(Subscription.class), eq("Subscription"));
|
||||
Mockito.verify(myDaoRegistry, times(1)).isResourceTypeSupported("Patient");
|
||||
Mockito.verify(myRequestPartitionHelperSvc, times(1)).determineCreatePartitionForRequest(isA(RequestDetails.class), isA(Subscription.class),eq("Subscription"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -275,7 +289,7 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testValidate_Cross_Partition_System_Subscription_Without_Setting() {
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(true);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(true);
|
||||
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
@ -292,14 +306,14 @@ public class SubscriptionValidatingInterceptorTest {
|
||||
// is invalid
|
||||
mySvc.resourcePreCreate(subscription, requestDetails, null);
|
||||
Mockito.verify(mySubscriptionSettings, never()).isCrossPartitionSubscriptionEnabled();
|
||||
Mockito.verify(myDaoRegistry, times(1)).isResourceTypeSupported(eq("Patient"));
|
||||
Mockito.verify(myRequestPartitionHelperSvc, never()).determineCreatePartitionForRequest(isA(RequestDetails.class), isA(Patient.class), eq("Patient"));
|
||||
Mockito.verify(myDaoRegistry, times(1)).isResourceTypeSupported("Patient");
|
||||
Mockito.verify(myRequestPartitionHelperSvc, never()).determineCreatePartitionForRequest(isA(RequestDetails.class), isA(Patient.class),eq("Patient"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionUpdate() {
|
||||
// setup
|
||||
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(true);
|
||||
when(myDaoRegistry.isResourceTypeSupported("Patient")).thenReturn(true);
|
||||
when(mySubscriptionSettings.isCrossPartitionSubscriptionEnabled()).thenReturn(true);
|
||||
lenient()
|
||||
.when(myRequestPartitionHelperSvc.determineReadPartitionForRequestForRead(isA(RequestDetails.class), isA(String.class), isA(IIdType.class)))
|
||||
|
Loading…
x
Reference in New Issue
Block a user