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/certgen" "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 } // defer serviceProvider.Close() 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 := certgen.PublicKeyToJWKJson(certgen.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 }