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.
 
 
 
 
okapidemo/his/openapiclient.go

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": externalAddr + "/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": externalAddr + "/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": externalAddr + "/external/fhir/Patient",
}),
Auth: &okapi.ProtocolAuthConfiguration{
Method: sharedmodel.AuthMethodDecozoBearerToken,
},
},
Push: &okapi.CallbackConfiguration{
Protocol: "https://hl7.org/fhir",
Configuration: toStruct(map[string]interface{}{
"url": externalAddr + "/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
}