Skip to content

Commit

Permalink
added nuts-pxp to setup, implemented in demo
Browse files Browse the repository at this point in the history
  • Loading branch information
woutslakhorst committed May 27, 2024
1 parent 78bfbc4 commit 336fb25
Show file tree
Hide file tree
Showing 25 changed files with 775 additions and 3,356 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ server.config.yaml
demo-ehr.db

nuts-demo-ehr

6 changes: 2 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ gen-api:
-import-mapping='../common/ssi_types.yaml:github.com/nuts-foundation/nuts-demo-ehr/nuts/client/common' \
-exclude-schemas SearchVCRequest,CredentialSubject \
-o nuts/client/discovery/generated.go https://nuts-node.readthedocs.io/en/latest/_static/discovery/v1.yaml
oapi-codegen -generate client,types -package vcr \
-import-mapping='../common/ssi_types.yaml:github.com/nuts-foundation/nuts-demo-ehr/nuts/client/common' \
-exclude-schemas SearchVCRequest,CredentialSubject \
-o nuts/client/vcr/generated.go https://nuts-node.readthedocs.io/en/latest/_static/vcr/vcr_v2.yaml
oapi-codegen -generate client,types -package didman \
-import-mapping='../common/ssi_types.yaml:github.com/nuts-foundation/nuts-demo-ehr/nuts/client/common' \
-o nuts/client/didman/generated.go -exclude-schemas OrganizationSearchResult https://nuts-node.readthedocs.io/en/latest/_static/didman/v1.yaml
Expand All @@ -34,3 +30,5 @@ gen-api:
oapi-codegen -generate client,types -package iam \
-import-mapping='../common/ssi_types.yaml:github.com/nuts-foundation/nuts-demo-ehr/nuts/client/common' \
-o nuts/client/iam/generated.go https://nuts-node.readthedocs.io/en/latest/_static/auth/v2.yaml
oapi-codegen -generate client,types -package pip \
-o nutspxp/client/pip/generated.go https://raw.githubusercontent.com/nuts-foundation/nuts-pxp/main/oas/pip.yaml
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Config struct {
Verbosity string `koanf:"verbosity"`
HTTPPort int `koanf:"port"`
NutsNodeAddress string `koanf:"nutsnodeaddr"`
NutsPIPAddress string `koanf:"nutspipaddr"`
FHIR FHIR `koanf:"fhir"`
SharedCarePlanning SharedCarePlanning `koanf:"sharedcareplanning"`
CustomersFile string `koanf:"customersfile"`
Expand Down
24 changes: 24 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ services:
- "./docker-compose/right/config/pep/nginx.conf:/etc/nginx/nginx.conf"
- "./docker-compose/lb/socket:/tmp/sockets"
- "./docker-compose/right/config/pep/oauth2.js:/etc/nginx/js/oauth2.js"
pip-left: &pip
image: nutsfoundation/nuts-pxp:main
environment:
NUTS_CONFIGFILE: /nuts/config.yaml
ports:
- 8080:8080
networks:
demo:
ipv4_address: 172.90.10.13
volumes:
- "./docker-compose/left/data/nutspxp:/nuts/data"
- "./docker-compose/left/config/nutspxp/policies:/nuts/policies"
- "./docker-compose/left/config/nutspxp/config.yaml:/nuts/config.yaml"
pip-right:
<<: *pip
ports:
- 8081:8080
networks:
demo:
ipv4_address: 172.90.10.14
volumes:
- "./docker-compose/right/data/nutspxp:/nuts/data"
- "./docker-compose/right/config/nutspxp/policies:/nuts/policies"
- "./docker-compose/left/config/nutspxp/config.yaml:/nuts/config.yaml"
node-left: &node
image: nutsfoundation/nuts-node:master
environment:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose/left/config/nutspxp/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
policy_dir: ./policies
sql:
connection: sqlite:file:./data/sqlite.db?_pragma=foreign_keys(1)&journal_mode(WAL)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package eOverdracht.receiver

import rego.v1

# Only owner can update the pet's information
# Ownership information is provided as part of OPA's input
default allow := false

allow if {
input.request.method = "POST"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package eOverdracht.sender

import rego.v1

# Only owner can update the pet's information
# Ownership information is provided as part of OPA's input
default allow := false

# eOVerdracht expects the following data: path to [actions] mapping

# example when http methods are stored in nuts-pxp
allow if {
input.request.method in input.external[input.request.path]
}
2 changes: 1 addition & 1 deletion docker-compose/left/config/pep/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ http {
proxy_pass http://unix:/tmp/authz.sock;
}

location = /_oauth2_delegated {
location /_oauth2_delegated {
internal;
js_content oauth2.introspectAccessToken;
}
Expand Down
2 changes: 1 addition & 1 deletion docker-compose/left/config/pep/oauth2.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function introspectAccessToken(r) {
r.subrequest("/_oauth2_introspect",
{ method: "POST", body: token},
function(reply) {
if (reply.status == 200) {
if (reply.status === 200) {
var introspection = JSON.parse(reply.responseBody);
if (introspection.active) {
//dpop(r, introspection.cnf)
Expand Down
Binary file modified docker-compose/left/data/node/sqlite.db
Binary file not shown.
3 changes: 3 additions & 0 deletions docker-compose/right/config/nutspxp/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
policy_dir: ./policies
sql:
connection: sqlite:file:./data/sqlite.db?_pragma=foreign_keys(1)&journal_mode(WAL)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package eOverdracht.receiver

import rego.v1

# Only owner can update the pet's information
# Ownership information is provided as part of OPA's input
default allow := false

allow if {
input.request.method = "POST"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package eOverdracht.sender

import rego.v1

# Only owner can update the pet's information
# Ownership information is provided as part of OPA's input
default allow := false

# eOVerdracht expects the following data: path to [actions] mapping

# example when http methods are stored in nuts-pxp
allow if {
input.request.method in input.external[input.request.path]
}
Binary file modified docker-compose/right/data/node/sqlite.db
Binary file not shown.
176 changes: 73 additions & 103 deletions domain/episode/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,13 @@ import (
"errors"
"fmt"
"github.com/nuts-foundation/nuts-demo-ehr/domain/acl"
"github.com/nuts-foundation/nuts-demo-ehr/nuts/client"
"github.com/sirupsen/logrus"
"net/url"
"strings"

"github.com/monarko/fhirgo/STU3/resources"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-demo-ehr/domain/fhir"
"github.com/nuts-foundation/nuts-demo-ehr/domain/fhir/zorginzage"
reports "github.com/nuts-foundation/nuts-demo-ehr/domain/reports"
"github.com/nuts-foundation/nuts-demo-ehr/domain/types"
"github.com/nuts-foundation/nuts-demo-ehr/nuts/client"
"github.com/nuts-foundation/nuts-demo-ehr/nuts/registry"
"github.com/sirupsen/logrus"
"net/url"
)

type Service interface {
Expand All @@ -31,41 +26,15 @@ func ssnURN(ssn string) string {
return fmt.Sprintf("urn:oid:2.16.840.1.113883.2.4.6.3:%s", ssn)
}

func parseAuthCredentialSubject(authCredential vc.VerifiableCredential) (*registry.NutsAuthorizationCredentialSubject, error) {
subject := make([]registry.NutsAuthorizationCredentialSubject, 0)

if err := authCredential.UnmarshalCredentialSubject(&subject); err != nil {
return nil, fmt.Errorf("invalid content for NutsAuthorizationCredential credentialSubject: %w", err)
}

return &subject[0], nil
}

type service struct {
factory fhir.Factory
nutsClient *client.HTTPClient
aclRepository *acl.Repository
registry registry.OrganizationRegistry
vcr registry.VerifiableCredentialRegistry
}

func NewService(factory fhir.Factory, nutsClient *client.HTTPClient, registry registry.OrganizationRegistry, vcr registry.VerifiableCredentialRegistry, aclRepository *acl.Repository) Service {
return &service{factory: factory, nutsClient: nutsClient, registry: registry, vcr: vcr, aclRepository: aclRepository}
}

func parseEpisodeOfCareID(authCredential vc.VerifiableCredential) (string, error) {
subject, err := parseAuthCredentialSubject(authCredential)
if err != nil {
return "", err
}

for _, resource := range subject.Resources {
if strings.HasPrefix(resource.Path, "/EpisodeOfCare/") {
return resource.Path[len("/EpisodeOfCare/"):], nil
}
}

return "", errors.New("no episode found in credential")
func NewService(factory fhir.Factory, nutsClient *client.HTTPClient, registry registry.OrganizationRegistry, aclRepository *acl.Repository) Service {
return &service{factory: factory, nutsClient: nutsClient, registry: registry, aclRepository: aclRepository}
}

func (service *service) Create(ctx context.Context, customerID int, patientID string, request types.CreateEpisodeRequest) (*types.Episode, error) {
Expand Down Expand Up @@ -186,71 +155,72 @@ func (service *service) GetCollaborations(ctx context.Context, customerDID, doss
}

func (service *service) GetReports(ctx context.Context, customerDID, patientSSN string) ([]types.Report, error) {
credentials, err := service.vcr.FindAuthorizationCredentials(
ctx,
&registry.VCRSearchParams{
PurposeOfUse: zorginzage.ServiceName,
SubjectID: customerDID,
Subject: ssnURN(patientSSN),
},
)
if err != nil {
return nil, err
}

if len(credentials) == 0 {
return []types.Report{}, nil
}

// TODO: loop over all credentials
issuer := credentials[0].Issuer.String()

fhirServer, err := service.registry.GetCompoundServiceEndpoint(ctx, issuer, zorginzage.ServiceName, "fhir")
if err != nil {
return nil, fmt.Errorf("error while looking up authorizer's FHIR server (did=%s): %w", issuer, err)
}

issuerOrg, err := service.registry.Get(ctx, issuer)
if err != nil {
return nil, fmt.Errorf("error while searching organization :%w", err)
}

// TODO: Should be user access token?
accessToken, err := service.nutsClient.RequestServiceAccessToken(ctx, customerDID, issuer, zorginzage.ServiceName)
if err != nil {
return nil, err
}

episodeOfCareID, err := parseEpisodeOfCareID(credentials[0])
if err != nil {
return nil, err
}

fhirClient := fhir.NewFactory(fhir.WithURL(fhirServer), fhir.WithAuthToken(accessToken))()

fhirEpisode := &fhir.EpisodeOfCare{}
err = fhirClient.ReadOne(ctx, "/EpisodeOfCare/"+episodeOfCareID, fhirEpisode)
if err != nil {
return nil, fmt.Errorf("could not retrieve episode of care: %w", err)
}
episode := zorginzage.ToEpisode(fhirEpisode)

observations := []resources.Observation{}
if err := fhirClient.ReadMultiple(ctx, "/Observation", map[string]string{
"context": fmt.Sprintf("EpisodeOfCare/%s", episodeOfCareID),
//"subject": fmt.Sprintf("Patient/%s", patientSSN),
}, &observations); err != nil {
return nil, err
}

results := make([]types.Report, len(observations))

for _, observation := range observations {
domainObservation := reports.ConvertToDomain(&observation, fhir.FromStringPtr(observation.Subject.ID))
domainObservation.Source = issuerOrg.Details.Name
domainObservation.EpisodeName = &episode.Diagnosis
results = append(results, domainObservation)
}

return results, nil
return nil, errors.New("not implemented")
//credentials, err := service.vcr.FindAuthorizationCredentials(
// ctx,
// &registry.VCRSearchParams{
// PurposeOfUse: zorginzage.ServiceName,
// SubjectID: customerDID,
// Subject: ssnURN(patientSSN),
// },
//)
//if err != nil {
// return nil, err
//}
//
//if len(credentials) == 0 {
// return []types.Report{}, nil
//}
//
//// TODO: loop over all credentials
//issuer := credentials[0].Issuer.String()
//
//fhirServer, err := service.registry.GetCompoundServiceEndpoint(ctx, issuer, zorginzage.ServiceName, "fhir")
//if err != nil {
// return nil, fmt.Errorf("error while looking up authorizer's FHIR server (did=%s): %w", issuer, err)
//}
//
//issuerOrg, err := service.registry.Get(ctx, issuer)
//if err != nil {
// return nil, fmt.Errorf("error while searching organization :%w", err)
//}
//
//// TODO: Should be user access token?
//accessToken, err := service.nutsClient.RequestServiceAccessToken(ctx, customerDID, issuer, zorginzage.ServiceName)
//if err != nil {
// return nil, err
//}
//
//episodeOfCareID, err := parseEpisodeOfCareID(credentials[0])
//if err != nil {
// return nil, err
//}
//
//fhirClient := fhir.NewFactory(fhir.WithURL(fhirServer), fhir.WithAuthToken(accessToken))()
//
//fhirEpisode := &fhir.EpisodeOfCare{}
//err = fhirClient.ReadOne(ctx, "/EpisodeOfCare/"+episodeOfCareID, fhirEpisode)
//if err != nil {
// return nil, fmt.Errorf("could not retrieve episode of care: %w", err)
//}
//episode := zorginzage.ToEpisode(fhirEpisode)
//
//observations := []resources.Observation{}
//if err := fhirClient.ReadMultiple(ctx, "/Observation", map[string]string{
// "context": fmt.Sprintf("EpisodeOfCare/%s", episodeOfCareID),
// //"subject": fmt.Sprintf("Patient/%s", patientSSN),
//}, &observations); err != nil {
// return nil, err
//}
//
//results := make([]types.Report, len(observations))
//
//for _, observation := range observations {
// domainObservation := reports.ConvertToDomain(&observation, fhir.FromStringPtr(observation.Subject.ID))
// domainObservation.Source = issuerOrg.Details.Name
// domainObservation.EpisodeName = &episode.Diagnosis
// results = append(results, domainObservation)
//}
//
//return results, nil
}
3 changes: 0 additions & 3 deletions domain/notification/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,19 @@ type handler struct {
localFHIRClientFactory fhir.Factory
transferService receiver.TransferService
registry registry.OrganizationRegistry
vcr registry.VerifiableCredentialRegistry
}

func NewHandler(
nutsClient *nutsClient.HTTPClient,
localFHIRClientFactory fhir.Factory,
transferReceiverService receiver.TransferService,
registry registry.OrganizationRegistry,
vcr registry.VerifiableCredentialRegistry,
) Handler {
return &handler{
nutsClient: nutsClient,
localFHIRClientFactory: localFHIRClientFactory,
transferService: transferReceiverService,
registry: registry,
vcr: vcr,
}
}

Expand Down
4 changes: 1 addition & 3 deletions domain/transfer/receiver/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,15 @@ type service struct {
localFHIRClientFactory fhir.Factory // client for interacting with the local FHIR server
customerRepo customers.Repository
registry registry.OrganizationRegistry
vcr registry.VerifiableCredentialRegistry
}

func NewTransferService(nutsClient *client.HTTPClient, localFHIRClientFactory fhir.Factory, transferRepository TransferRepository, customerRepository customers.Repository, organizationRegistry registry.OrganizationRegistry, vcr registry.VerifiableCredentialRegistry, notifier transfer.Notifier) TransferService {
func NewTransferService(nutsClient *client.HTTPClient, localFHIRClientFactory fhir.Factory, transferRepository TransferRepository, customerRepository customers.Repository, organizationRegistry registry.OrganizationRegistry, notifier transfer.Notifier) TransferService {
return &service{
nutsClient: nutsClient,
localFHIRClientFactory: localFHIRClientFactory,
transferRepo: transferRepository,
customerRepo: customerRepository,
registry: organizationRegistry,
vcr: vcr,
notifier: notifier,
}
}
Expand Down
Loading

0 comments on commit 336fb25

Please sign in to comment.