You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
436 lines
11 KiB
436 lines
11 KiB
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"log"
|
|
"regexp"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
"gorm.io/gorm"
|
|
"src.whiteboxsystems.nl/decozo/okapi"
|
|
"src.whiteboxsystems.nl/decozo/okapidemo/cryptoutil"
|
|
"src.whiteboxsystems.nl/decozo/okapidemo/his/model"
|
|
"src.whiteboxsystems.nl/decozo/okapidemo/sharedmodel"
|
|
)
|
|
|
|
func toStruct(m map[string]interface{}) *structpb.Struct {
|
|
s, err := structpb.NewStruct(m)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func getUnauthenticatedClient(addr string) (okapi.OkAPIClient, error) {
|
|
opts := []grpc.DialOption{
|
|
grpc.WithTransportCredentials(
|
|
credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}), // Don't do this in production
|
|
),
|
|
}
|
|
|
|
conn, err := grpc.Dial(addr, opts...)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return okapi.NewOkAPIClient(conn), nil
|
|
}
|
|
|
|
func getAuthenticatedClient(serviceProvider *model.ServiceProvider, cert tls.Certificate) (okapi.OkAPIClient, error) {
|
|
opts := []grpc.DialOption{
|
|
grpc.WithTransportCredentials(
|
|
credentials.NewTLS(&tls.Config{
|
|
InsecureSkipVerify: true,
|
|
Certificates: []tls.Certificate{cert},
|
|
}),
|
|
),
|
|
}
|
|
conn, err := grpc.Dial(serviceProvider.Addr, opts...)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return okapi.NewOkAPIClient(conn), nil
|
|
}
|
|
|
|
type PSKAuth struct {
|
|
psk string
|
|
insecure bool
|
|
}
|
|
|
|
func (ma PSKAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
|
return map[string]string{
|
|
"authorization": ma.psk,
|
|
}, nil
|
|
}
|
|
|
|
func (ma PSKAuth) RequireTransportSecurity() bool {
|
|
return !ma.insecure
|
|
}
|
|
|
|
func makePSKAuth(psk string, insecure bool) *PSKAuth {
|
|
return &PSKAuth{psk, insecure}
|
|
}
|
|
|
|
func (srv *HISServer) register(addr string) (*model.ServiceProvider, error) {
|
|
client, err := getUnauthenticatedClient(addr)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
jwkBytes, err := cryptoutil.PublicKeyToJWKJson(cryptoutil.ExtractPublicKey(srv.clientCert.PrivateKey))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
auth := &okapi.XISAuthConfiguration{
|
|
Method: okapi.XISAuthMethod_mTLS,
|
|
Configuration: &okapi.XISAuthConfiguration_MtlsConfiguration{
|
|
MtlsConfiguration: &okapi.MTLSConfigurationParams{
|
|
PublicKey: string(jwkBytes),
|
|
},
|
|
},
|
|
}
|
|
|
|
resp, err := client.Register(context.Background(), &okapi.RegisterRequest{
|
|
OrganisationIdentifier: "00009999",
|
|
OrganisationIdentifierType: "https://vektis.nl/agbz",
|
|
OrganisationDisplayName: "Praktijk de oude berg",
|
|
Auth: auth,
|
|
})
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
connection := &model.ServiceProvider{
|
|
Addr: addr,
|
|
AuthConfig: &sharedmodel.XISAuthConfig{
|
|
Method: int32(okapi.XISAuthMethod_mTLS),
|
|
Raw: string(jwkBytes),
|
|
},
|
|
State: model.ConnectionStatePending,
|
|
Reference: resp.Reference,
|
|
}
|
|
|
|
meta, _ := srv.listMeta(connection)
|
|
|
|
connection.Supplier = meta.SupplierDisplayName
|
|
connection.System = meta.ProductName
|
|
|
|
if err := srv.data.Create(connection).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return connection, nil
|
|
}
|
|
|
|
func (srv *HISServer) activate(serviceProvider *model.ServiceProvider, psk string) (*model.ServiceProvider, error) {
|
|
client, err := getAuthenticatedClient(serviceProvider, srv.clientCert)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, err = client.CompleteRegistration(context.Background(), &okapi.CompleteRegistrationRequest{
|
|
Reference: serviceProvider.Reference,
|
|
AuthorizationToken: psk,
|
|
})
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
meta, err := srv.listMeta(serviceProvider)
|
|
|
|
if err != nil {
|
|
return serviceProvider, fmt.Errorf("Failed to retreive metadata: %v ", err)
|
|
}
|
|
|
|
serviceProvider.State = model.ConnectionStateCompleted
|
|
serviceProvider.Supplier = meta.SupplierDisplayName
|
|
serviceProvider.System = meta.ProductName
|
|
|
|
if err := srv.data.Save(serviceProvider).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return serviceProvider, err
|
|
|
|
}
|
|
|
|
func (srv *HISServer) listMeta(serviceProvider *model.ServiceProvider) (*okapi.GetMetadataResponse, error) {
|
|
client, err := getUnauthenticatedClient(serviceProvider.Addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := client.GetMetadata(context.Background(), &okapi.GetMetadataRequest{})
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (srv *HISServer) listServices(serviceProvider *model.ServiceProvider) (*okapi.ListServicesResponse, error) {
|
|
client, err := getAuthenticatedClient(serviceProvider, srv.clientCert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := client.ListServices(context.Background(), &okapi.ListServicesRequest{})
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (srv *HISServer) enableService(serviceProvider *model.ServiceProvider, serviceId string) error {
|
|
client, err := getAuthenticatedClient(serviceProvider, srv.clientCert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
moddedService := &model.Service{}
|
|
moddedServiceErr := srv.data.Where("service_provider_id = ? and service_id = ?", serviceProvider.ID, serviceId).First(moddedService).Error
|
|
|
|
if moddedServiceErr != nil && moddedServiceErr != gorm.ErrRecordNotFound {
|
|
return moddedServiceErr
|
|
}
|
|
|
|
if moddedServiceErr == nil {
|
|
return fmt.Errorf("Service already activivated")
|
|
}
|
|
|
|
meta, _ := srv.listServices(serviceProvider)
|
|
var serviceDefinition *okapi.Service
|
|
|
|
for _, sd := range meta.Services {
|
|
if sd.Id == serviceId {
|
|
serviceDefinition = sd
|
|
}
|
|
}
|
|
|
|
if serviceDefinition == nil {
|
|
return fmt.Errorf("Invalid service: %v", serviceId)
|
|
}
|
|
|
|
var resp *okapi.EnableServiceResponse
|
|
|
|
if m, _ := regexp.MatchString("wbx:*", serviceId); m { // Whitebox
|
|
resp, err = client.EnableService(context.Background(), &okapi.EnableServiceRequest{
|
|
ServiceId: serviceId,
|
|
Fetch: &okapi.CallbackConfiguration{
|
|
Protocol: "https://whiteboxsystems.nl/protospecs/whitebox-fetch/http",
|
|
Configuration: toStruct(map[string]interface{}{
|
|
"url": externalApiAddr + "/external/api",
|
|
}),
|
|
Auth: &okapi.ProtocolAuthConfiguration{
|
|
Method: sharedmodel.AuthMethodDecozoMTLS,
|
|
},
|
|
},
|
|
Push: &okapi.CallbackConfiguration{
|
|
Protocol: "https://whiteboxsystems.nl/protospecs/whitebox-push/http",
|
|
Configuration: toStruct(map[string]interface{}{
|
|
"url": externalApiAddr + "/external/api",
|
|
}),
|
|
Auth: &okapi.ProtocolAuthConfiguration{
|
|
Method: sharedmodel.AuthMethodDecozoMTLS,
|
|
},
|
|
},
|
|
})
|
|
} else { // DVZA / FHIR
|
|
resp, err = client.EnableService(context.Background(), &okapi.EnableServiceRequest{
|
|
ServiceId: serviceId,
|
|
Fetch: &okapi.CallbackConfiguration{
|
|
Protocol: "https://hl7.org/fhir",
|
|
Configuration: toStruct(map[string]interface{}{
|
|
"url": externalApiAddr + "/external/fhir/Patient",
|
|
}),
|
|
Auth: &okapi.ProtocolAuthConfiguration{
|
|
Method: sharedmodel.AuthMethodDecozoBearerToken,
|
|
},
|
|
},
|
|
Push: &okapi.CallbackConfiguration{
|
|
Protocol: "https://hl7.org/fhir",
|
|
Configuration: toStruct(map[string]interface{}{
|
|
"url": externalApiAddr + "/external/fhir/Patient",
|
|
}),
|
|
Auth: &okapi.ProtocolAuthConfiguration{
|
|
Method: sharedmodel.AuthMethodDecozoBearerToken,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return err
|
|
}
|
|
|
|
authConfig := sharedmodel.NewAuthConfig(resp.Fetch.Auth)
|
|
|
|
return srv.data.Create(&model.Service{
|
|
ServiceProviderID: serviceProvider.ID,
|
|
ServiceID: serviceDefinition.Id,
|
|
Name: serviceDefinition.Name,
|
|
Description: serviceDefinition.Name,
|
|
SubscriptionPolicy: serviceDefinition.SubscriptionPolicy,
|
|
ConsentPolicy: serviceDefinition.ConsentPolicy,
|
|
AuthConfig: authConfig,
|
|
}).Error
|
|
}
|
|
|
|
func (srv *HISServer) disableService(serviceProvider *model.ServiceProvider, serviceId string) error {
|
|
client, err := getAuthenticatedClient(serviceProvider, srv.clientCert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
moddedService := &model.Service{}
|
|
moddedServiceErr := srv.data.Where("service_provider_id = ? and service_id = ?", serviceProvider.ID, serviceId).First(moddedService).Error
|
|
|
|
if moddedServiceErr != nil && moddedServiceErr != gorm.ErrRecordNotFound {
|
|
return moddedServiceErr
|
|
}
|
|
|
|
if moddedServiceErr != nil {
|
|
return fmt.Errorf("Service not active")
|
|
}
|
|
|
|
meta, _ := srv.listServices(serviceProvider)
|
|
var serviceDefinition *okapi.Service
|
|
|
|
for _, sd := range meta.Services {
|
|
if sd.Id == serviceId {
|
|
serviceDefinition = sd
|
|
}
|
|
}
|
|
|
|
if serviceDefinition == nil {
|
|
return fmt.Errorf("Invalid service: %v", serviceId)
|
|
}
|
|
|
|
_, err = client.DisableService(context.Background(), &okapi.DisableServiceRequest{
|
|
ServiceId: serviceId,
|
|
})
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return err
|
|
}
|
|
|
|
subs := []model.Patient{}
|
|
|
|
srv.data.Model(moddedService).Association("Subscriptions").Find(&subs)
|
|
srv.data.Model(moddedService).Association("Subscriptions").Delete(subs)
|
|
srv.unsubscribePatients(serviceProvider, moddedService, subs)
|
|
return srv.data.Unscoped().Delete(moddedService).Error
|
|
}
|
|
|
|
func (srv *HISServer) subscribePatients(serviceProvider *model.ServiceProvider, service *model.Service, patients []model.Patient) (*okapi.CreateOrUpdatePatientRegistrationsResponse, error) {
|
|
client, err := getAuthenticatedClient(serviceProvider, srv.clientCert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
subs := []*okapi.PatientRegistrationCreateOrUpdateData{}
|
|
|
|
for _, pat := range patients {
|
|
subs = append(subs, &okapi.PatientRegistrationCreateOrUpdateData{
|
|
Id: pat.ExternalId,
|
|
Subject: &okapi.PatientMeta{
|
|
Identifier: &okapi.Identifier{
|
|
Type: "http://fhir.nl/fhir/NamingSystem/bsn",
|
|
Value: pat.ExternalId,
|
|
},
|
|
Name: &okapi.Name{
|
|
Display: pat.Name,
|
|
},
|
|
Address: &okapi.Address{},
|
|
Birthdate: pat.Birthdate,
|
|
},
|
|
CallbackProtocolData: toStruct(map[string]interface{}{
|
|
"patientID": pat.PatientID,
|
|
}),
|
|
})
|
|
}
|
|
|
|
req := &okapi.CreateOrUpdatePatientRegistrationsRequest{
|
|
ServiceId: service.ServiceID,
|
|
Registrations: subs,
|
|
}
|
|
|
|
resp, err := client.CreateOrUpdatePatientRegistrations(context.Background(), req)
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (srv *HISServer) unsubscribePatients(serviceProvider *model.ServiceProvider, service *model.Service, patients []model.Patient) (*okapi.RemovePatientRegistrationsResponse, error) {
|
|
client, err := getAuthenticatedClient(serviceProvider, srv.clientCert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
subs := []string{}
|
|
|
|
for _, pat := range patients {
|
|
subs = append(subs, pat.ExternalId)
|
|
}
|
|
|
|
req := &okapi.RemovePatientRegistrationsRequest{
|
|
ServiceId: service.ServiceID,
|
|
Registrations: subs,
|
|
}
|
|
|
|
resp, err := client.RemovePatientRegistrations(context.Background(), req)
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (srv *HISServer) listPatientRegistrations(serviceProvider *model.ServiceProvider, service *model.Service) ([]*okapi.PatientRegistrationData, error) {
|
|
client, err := getAuthenticatedClient(serviceProvider, srv.clientCert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &okapi.ListPatientRegistrationsRequest{
|
|
ServiceId: service.ServiceID,
|
|
}
|
|
|
|
resp, err := client.ListPatientRegistrations(context.Background(), req)
|
|
|
|
if err != nil {
|
|
log.Printf("Err in request: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
return resp.PatientRegistrationData, nil
|
|
}
|
|
|