Skip to content
Merged
19 changes: 9 additions & 10 deletions component/pdp/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ func capabilityForScope(ctx context.Context, scope string) (fhir.CapabilityState
}

func evalCapabilityPolicy(ctx context.Context, input PolicyInput) (PolicyInput, PolicyResult) {
// Skip capability checking for requests that don't target a specific resource type (e.g., /metadata, /)
if input.Resource.Type == nil {
if !input.Action.Properties.ConnectionData.FHIRRest.isFHIRRest {
return input, Allow()
}

Expand All @@ -63,7 +62,7 @@ func evalCapabilityPolicy(ctx context.Context, input PolicyInput) (PolicyInput,
}

result := evalInteraction(statement, input)
input.Context.FHIRCapabilityChecked = result.Allow
input.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked = result.Allow
return input, result
}

Expand All @@ -88,9 +87,9 @@ func evalInteraction(
fhir.TypeRestfulInteractionSearchType,
}

props := input.Action.Properties
fhirData := input.Action.Properties.ConnectionData.FHIRRest

if !slices.Contains(supported, props.InteractionType) {
if !slices.Contains(supported, fhirData.InteractionType) {
return Deny(
ResultReason{
Code: TypeResultCodeNotImplemented,
Expand All @@ -110,7 +109,7 @@ func evalInteraction(
allowInteraction := false
for _, des := range resourceDescriptions {
for _, inter := range des.Interaction {
if inter.Code == props.InteractionType {
if inter.Code == fhirData.InteractionType {
allowInteraction = true
}
}
Expand All @@ -126,15 +125,15 @@ func evalInteraction(

allowParams := false
rejectedSearchParams := make([]string, 0, 10)
if props.InteractionType == fhir.TypeRestfulInteractionSearchType {
if fhirData.InteractionType == fhir.TypeRestfulInteractionSearchType {
allowedParams := make([]string, 0, 10)
for _, des := range resourceDescriptions {
for _, param := range des.SearchParam {
allowedParams = append(allowedParams, param.Name)
}
}

for paramName := range props.SearchParams {
for paramName := range fhirData.SearchParams {
if !slices.Contains(allowedParams, paramName) {
rejectedSearchParams = append(rejectedSearchParams, paramName)
}
Expand All @@ -156,7 +155,7 @@ func evalInteraction(
}

rejectedIncludes := make([]string, 0, len(allowedIncludes))
for _, inc := range props.Include {
for _, inc := range fhirData.Include {
if !slices.Contains(allowedIncludes, inc) {
rejectedIncludes = append(rejectedIncludes, inc)
}
Expand All @@ -177,7 +176,7 @@ func evalInteraction(
}

rejectedRevincludes := make([]string, 0, len(allowedRevincludes))
for _, inc := range props.Revinclude {
for _, inc := range fhirData.Revinclude {
if !slices.Contains(allowedRevincludes, inc) {
rejectedRevincludes = append(rejectedRevincludes, inc)
}
Expand Down
102 changes: 69 additions & 33 deletions component/pdp/capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ func TestComponent_reject_interaction(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionUpdate,
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionUpdate,
},
},
},
},
Context: PolicyContext{
Expand All @@ -36,7 +40,7 @@ func TestComponent_reject_interaction(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.False(t, resp.Allow)
assert.False(t, inp.Context.FHIRCapabilityChecked)
assert.False(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}

func TestComponent_allow_interaction(t *testing.T) {
Expand All @@ -55,8 +59,12 @@ func TestComponent_allow_interaction(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionHistoryType,
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionHistoryType,
},
},
},
},
Context: PolicyContext{
Expand All @@ -66,7 +74,7 @@ func TestComponent_allow_interaction(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.True(t, resp.Allow)
assert.True(t, inp.Context.FHIRCapabilityChecked)
assert.True(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}

func TestComponent_allow_search_param(t *testing.T) {
Expand All @@ -85,9 +93,13 @@ func TestComponent_allow_search_param(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionSearchType,
SearchParams: map[string]string{"_since": "2024-01-01"},
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionSearchType,
SearchParams: map[string]string{"_since": "2024-01-01"},
},
},
},
},
Context: PolicyContext{
Expand All @@ -97,7 +109,7 @@ func TestComponent_allow_search_param(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.True(t, resp.Allow)
assert.True(t, inp.Context.FHIRCapabilityChecked)
assert.True(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}

func TestComponent_reject_search_param(t *testing.T) {
Expand All @@ -116,9 +128,13 @@ func TestComponent_reject_search_param(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionSearchType,
SearchParams: map[string]string{"_foo": "bar", "_since": "2024-01-01"},
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionSearchType,
SearchParams: map[string]string{"_foo": "bar", "_since": "2024-01-01"},
},
},
},
},
Context: PolicyContext{
Expand All @@ -128,7 +144,7 @@ func TestComponent_reject_search_param(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.False(t, resp.Allow)
assert.False(t, inp.Context.FHIRCapabilityChecked)
assert.False(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}

func TestComponent_reject_interaction_type(t *testing.T) {
Expand All @@ -144,8 +160,12 @@ func TestComponent_reject_interaction_type(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionSearchSystem,
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionSearchSystem,
},
},
},
},
Context: PolicyContext{
Expand All @@ -155,7 +175,7 @@ func TestComponent_reject_interaction_type(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.False(t, resp.Allow)
assert.False(t, inp.Context.FHIRCapabilityChecked)
assert.False(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}

func TestComponent_allow_include(t *testing.T) {
Expand All @@ -174,9 +194,13 @@ func TestComponent_allow_include(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionRead,
Include: []string{"Location:organization"},
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionRead,
Include: []string{"Location:organization"},
},
},
},
},
Context: PolicyContext{
Expand All @@ -186,7 +210,7 @@ func TestComponent_allow_include(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.True(t, resp.Allow)
assert.True(t, inp.Context.FHIRCapabilityChecked)
assert.True(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}

func TestComponent_reject_include(t *testing.T) {
Expand All @@ -205,9 +229,13 @@ func TestComponent_reject_include(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionRead,
Include: []string{"Endpoint:organization"},
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionRead,
Include: []string{"Endpoint:organization"},
},
},
},
},
Context: PolicyContext{
Expand All @@ -217,7 +245,7 @@ func TestComponent_reject_include(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.False(t, resp.Allow)
assert.False(t, inp.Context.FHIRCapabilityChecked)
assert.False(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}

func TestComponent_reject_revinclude(t *testing.T) {
Expand All @@ -236,9 +264,13 @@ func TestComponent_reject_revinclude(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionRead,
Include: []string{"Location:organization"},
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionRead,
Include: []string{"Location:organization"},
},
},
},
},
Context: PolicyContext{
Expand All @@ -248,7 +280,7 @@ func TestComponent_reject_revinclude(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.False(t, resp.Allow)
assert.False(t, inp.Context.FHIRCapabilityChecked)
assert.False(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}

func TestComponent_allow_revinclude(t *testing.T) {
Expand All @@ -267,9 +299,13 @@ func TestComponent_allow_revinclude(t *testing.T) {
},
Action: PolicyAction{
Properties: PolicyActionProperties{
ContentType: "application/fhir+json",
InteractionType: fhir.TypeRestfulInteractionRead,
Revinclude: []string{"Location:organization"},
ConnectionData: PolicyConnectionData{
FHIRRest: FhirConnectionData{
isFHIRRest: true,
InteractionType: fhir.TypeRestfulInteractionRead,
Revinclude: []string{"Location:organization"},
},
},
},
},
Context: PolicyContext{
Expand All @@ -279,5 +315,5 @@ func TestComponent_allow_revinclude(t *testing.T) {

inp, resp := evalCapabilityPolicy(context.Background(), input)
assert.True(t, resp.Allow)
assert.True(t, inp.Context.FHIRCapabilityChecked)
assert.True(t, inp.Action.Properties.ConnectionData.FHIRRest.CapabilityChecked)
}
1 change: 1 addition & 0 deletions component/pdp/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func TestHandleMainPolicy_Integration(t *testing.T) {
Context: PDPContext{
DataHolderOrganizationId: "00000002",
DataHolderFacilityType: "TODO",
ConnectionTypeCode: "hl7-fhir-rest",
},
},
}
Expand Down
29 changes: 18 additions & 11 deletions component/pdp/fhirreq.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,13 +374,20 @@ func NewPolicyInput(request PDPRequest) (PolicyInput, PolicyResult) {
policyInput.Context.DataHolderFacilityType = request.Input.Context.DataHolderFacilityType
policyInput.Context.PatientBSN = request.Input.Context.PatientBSN

contentType := request.Input.Request.Header.Get("Content-Type")
policyInput.Action.Properties.ContentType = contentType
isFhirAPI := request.Input.Context.ConnectionTypeCode == "hl7-fhir-rest"
if !isFhirAPI {
// This is not a FHIR request
return policyInput, Allow()
}
policyInput.Action.Properties.ConnectionData.FHIRRest.isFHIRRest = true

tokens, ok := parseRequestPath(request.Input.Request)
if !ok {
// This is not a FHIR request
return policyInput, Allow()
reason := ResultReason{
Code: TypeResultCodeUnexpectedInput,
Description: "unexpected input, unable to parse fhir request",
}
return policyInput, Deny(reason)
}

if tokens.ResourceType != nil {
Expand All @@ -393,14 +400,14 @@ func NewPolicyInput(request PDPRequest) (PolicyInput, PolicyResult) {
}
}

policyInput.Action.Properties.InteractionType = tokens.Interaction
policyInput.Action.Properties.ConnectionData.FHIRRest.InteractionType = tokens.Interaction

if tokens.OperationName != "" {
policyInput.Action.Properties.Operation = &tokens.OperationName
policyInput.Action.Properties.ConnectionData.FHIRRest.Operation = &tokens.OperationName
}

var rawParams url.Values
hasFormData := contentType == "application/x-www-form-urlencoded"
hasFormData := request.Input.Request.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
interWithBody := []fhir.TypeRestfulInteraction{
fhir.TypeRestfulInteractionSearchType,
fhir.TypeRestfulInteractionOperation,
Expand All @@ -424,9 +431,9 @@ func NewPolicyInput(request PDPRequest) (PolicyInput, PolicyResult) {
}

params := groupParams(rawParams)
policyInput.Action.Properties.Include = params.Include
policyInput.Action.Properties.Revinclude = params.Revinclude
policyInput.Action.Properties.SearchParams = params.SearchParams
policyInput.Action.Properties.ConnectionData.FHIRRest.Include = params.Include
policyInput.Action.Properties.ConnectionData.FHIRRest.Revinclude = params.Revinclude
policyInput.Action.Properties.ConnectionData.FHIRRest.SearchParams = params.SearchParams

// Read patient resource ID from request
result := Allow()
Expand All @@ -437,7 +444,7 @@ func NewPolicyInput(request PDPRequest) (PolicyInput, PolicyResult) {
Description: "patient_id: " + err.Error(),
})
} else {
policyInput.Context.PatientID = patientId
policyInput.Action.Properties.ConnectionData.FHIRRest.PatientID = patientId
}

// Read patient BSN from request
Expand Down
Loading
Loading