Introduktion

Denne guide er primært målrettet til anvender/udvikler, som skal implementere en ny afprøvningsløsning og den har til formål, at give et eksempel på en eksisterede web applikation og facade. 

Guiden beskriver en web applikation som bruger en backend, som laver kald til Dokumentdelingsservice (DDS), og som bruger sikkerhed i henhold til NAP konteksten. 

Der er beskrevet instruktioner til opsætning af et web projekt og en backend, som overholder givne NSP standarder. Guiden indeholder eksempler på kodeimplementationer, men ellers er projekterne tilgængelig på svn.nspop.dk og kan bruges som inspiration til et afprøvningsprojekt. 

nap-reference-web

nap-reference-facade 

Dokumentation

Hvis der er behov for yderligere dokumentation omkring hele NAP platformen, henvises til NAP Platform - Guide til anvendere

Derudover findes der yderligere diagrammer og NAP SDK'erne på NAP SDK - Design og Arkitektur beskrivelse.

 nap-reference opbygning.

nap-reference-web er en web applikation, som anvender nap-typescript-sdk og nap-angular-sdk. 

Dette kan køres indlejret i et værtssytem (eks. nap-java-host), således der vil blive kommunikere igennem SDK'erne. Igennem kommunikationen vil der blive håndteret sikkerhed igennem anvendelse af en SAML assertion og patient konteksten vil kunne blive brugt igennem det indlejret værtssystem.

Derefter tilføjes SAML assertion som en header, samt en hardcoded SOR værdi som X-Organization Sor header, til kaldet af dokumentationdelingsservicen, som går igennem nap-reference facade.  


nap-reference-facade er en java applikation, som anvender SEAL.java til at validere den SAMLAssertion der bliver sendt ind. Den står for at hente relevante aftale dokumenter (org.openehealth.ipf.commons) fra den valgte patient. For at formatere aftaledokumenterne fra XML til objekter, bruges dk.s4.hl7.builders. Før der returneres, bliver kun relevant information sendt tilbage til nap-reference-web. 


Nap-reference-implementering

Nap-reference-web er en web applikation, som implementerer nap-typescript-sdk og nap-angular-sdk. Som konsekvens af dette, kan denne køres indlejret i et værtssytem (eks. nap-java-host) og kommunikerer med værtssystemet over en indlejret javascript bro. Igennem denne bro henter applikationen en SAMLassertion og patient konteksten fra værtssytemet, hvorefter den kalder den tilhørende facade (nap-ref-facade) og forsøger at hente aftaledokumenter den valgte patient i værtssystemet. Kaldet indeholder den overførte SAMLassertion som en authentication header, og en hardcoded SOR værdi som X-OrganizationSor header. 

Nap-reference-facade er en javaapplikation, som implementerer SEAL.java til at validerer SAMLassertion, org.openehealth.ipf.commons til at hente aftaledokumenter fra Dokumentdelingsservice (DDS), og dk.s4.hl7.builders til at formatere aftaledokumenter fra XML til objekter, hvorfra den hiver den relevante information ud og sender tilbage til klienten.

Funktionaliteten af Nap-ref-web illustreres bedst, når applikation er indlejret i et værstsystem, f.eks. nap-java-host Platformsservices (NAP) - Leverancebeskrivelse


Nap-reference-web

Nap-reference-web er bygget med Angular version 9 og fungerer som illustration på:

  1. Projekt opsætning overholdende https://www.nspop.dk/display/public/web/Husregler+for+udvikling+til+NSP.
  2. Brug af NAP SDK

  3. GUI for NAP Projekter.

Applikationens hovedfunktionalitet er at vise aftaledokumenter for den patient, der er i kontekst i det værtssystem, den er indlejret. Der skal tilføjes en NAP Bridge på den givne applikations global scope, for at funktionaliteten fungere.

Diagram over NAP Bridge virkmåde, kan ses her.


LOGGING → husregler


Opsætning og struktur af et angular projekt

Komponenter

Da angular frameworket lægger sig op af model-view-controller pattern er dette også implementeret i nap-reference-web.

Derfor er der simple komponenter til at visning aftaler, hjælpinformation, fejl osv. 

Services

Services håndterer forretningslogikken.

Derfor er der simple services til håndtering af authentication, applikationsfejl og konfigurering osv.

Dependencies

Der ligger en .npmrc, som sætter npm registry til https://nexus.nspop.dk/nexus/repository/nsp-npm/

Konfiguration

Nap-reference-web benytter sig af en konfigurationsfil (assets/configurtation.json), som loades via configuration-servicen. Denne konfigurationsfil bliver således overskrevet i de forskellige docker-compose setups.

Da NSP web applikationer kan blive deployet på vilkårlige paths, er det vigtigt at applikationen fungerer med relative paths. Detter er opnået ved hashrouting.

@NgModule({
  imports: [RouterModule.forRoot(routes, { useHash: true })],
  exports: [RouterModule]
})
export class AppRoutingModule { }


Testing

Nap-reference-web benytter sig af testframeworket Karma, da det kommer default med angular. Karma bruger Istanbul til at genere test-coverage rapporter. Testene køres i en headless chromium browser.

Dokumentation

Kodedokumentationen bliver generet med TSDoc.

Brug af NAP SDK

Nap-reference-web implementerer version 1 af eventkataloget.

Session-Open og Session-close (security)

Session-open starter en trusted session hvor brugerens SAMLassertion overføres. Session-close lukker sessionen og brugerens SAMLassertion slettes. Denne funktionalitet findes i auth-servicen.


export class AuthService {
 private sessionMessageSubject = new BehaviorSubject<NAPMessage | undefined>(
    undefined
  );

  /**
   * The current session
   *
   * @memberof AuthService
   */
  public session$ = this.sessionMessageSubject.asObservable();

  constructor(private napSDK: NapAngularService) {
    this.napSDK.incomming$
      .pipe(
        filterEvents([
          NAPEventCatalogue.v1.SessionOpen,
          NAPEventCatalogue.v1.SessionClose,
        ])
      )
      .subscribe((napMessage) => {
        if (napMessage.event.type === NAPEventCatalogue.v1.SessionOpen) {
          this.sessionMessageSubject.next(napMessage);
        } else {
          this.sessionMessageSubject.next(undefined);
        }
      });

    const napMsg: NAPMessage = {
      date: new Date().toISOString(),
      id: UUID(),
      event: { type: NAPEventCatalogue.v1.SessionOpen },
    };

    // Ask for the SAMLassertion in host
    this.napSDK.sendMessage(napMsg);
  }

  /**
   * Tricker logout event
   */
  logout(): void {
    this.sessionMessageSubject.next(undefined);
    const napMsg: NAPMessage = {
      date: new Date().toISOString(),
      id: UUID(),
      event: { type: NAPEventCatalogue.v1.SessionClose },
    };
    this.napSDK.sendMessage(napMsg);
  }
}


Patient-Open og Patient-close

Patient-open sender den brugervalgte patient journal og patient-close lukker den bruger valgte patient (set fra det indlejrede systems perspektiv). Et eksempel på dette kan ses i appointment.component


  constructor(
    private napSDK: NapAngularService,
    private appointmentService: AppointmentService,
    private authService: AuthService
  ) {
    const napMsg: NAPMessage = {
      date: new Date().toISOString(),
      id: UUID(),
      event: {
        type: NAPEventCatalogue.v1.PatientOpen,
      },
    };
    this.napSDK.sendMessage(napMsg);
  }

  public currentPatient$: Observable<NAPPatientInfo | undefined> = this.napSDK.incomming$.pipe(
    filterEvent(NAPEventCatalogue.v1.PatientOpen),
    map(message => FHIRValueGetter.getPatientInfo(message))
  );


Session-Error

Session-error sendes, hvis der sker en uventet fejl i projektet skal dette sendes til værtssystemet og vice versa. Et eksempel på dette findes i appointment-service:


  private appointmentEndPoint$ = this.configurationService.fetch(
    config => config.appointmentsEndpoint
  );
  private serverUrl = this.configurationService.fetch(config => config.serverUrl);
  
  private createNapErrorMessage(error: GenericAppError): NAPMessage {
    return {
      date: new Date().toISOString(),
      id: UUID(),
      event: {
        type: NAPEventCatalogue.v1.SessionError,
        context: [
          {
            resource: {
              resourceType: FHIRValueSetter.FHIRResourceType.Basic,
              code: {
                coding: [
                  {
                    code: NAPEventCatalogue.v1.SessionError,
                    system: FHIRValueSetter.FHIRSystem.NAP,
                  },
                ],
              },
              identifier: [
                {
                  system: FHIRValueSetter.FHIRIdentifierSystem.NAPErrorMessage,
                  value: error.innerError?.message ? error.innerError?.message : '',
                },
                {
                  system: FHIRValueSetter.FHIRIdentifierSystem.NAPErrorDescription,
                  value: error.errorMessage,
                },
              ],
            },
          },
        ],
      }
    }
  }

  public getAppointments(patientIdentifier: string | undefined): Observable<any[] | undefined> {
    return combineLatest(
      [
        this.serverUrl,
        this.appointmentEndPoint$,
        this.serviceActivator
      ]
    ).pipe(
      switchMap(([serverUrl, endpointPath, _]) => this.http.get<any[]>(serverUrl + endpointPath + '/' + patientIdentifier)),
      catchError(error => {
        this.errorService.postError(error);
        this.napSDK.sendMessage(this.createNapErrorMessage(error)); // indicate to the host that something went wrong
        return of(undefined);
      }),
    );
  }


Nap-reference-facade

Nap-reference-facade er bygget med java 8 og fungerer som illustration på:

  1. Projekt opsætning overholdende https://www.nspop.dk/display/public/web/Husregler+for+udvikling+til+NSP.
  2. Sikkerhed i NAP kontekst
  3. Brug af Dokumentdelingsservice (DDS)

Opsætning og struktur 

Dependencies

Dependencies er hentet fra https://nexus.nspop.dk/nexus/content/groups/public og de dependencies, som stilles til rådighed af wildfly8 platformen er angivet med scope provided.

For at kunne kalde Dokumentdelingsservice (DDS) er følgende dependencies anvendt:

dk.sosi.seal

Java-bibliotek til understøttelse af "Den Gode Webservice" og validering af SAMLassertion, se http://digitaliser.dk/group/374971

dk.s4.hl7.builders

Dansk profileret XML converter af hl7 clinical documents

org.openehealth.ipf.commons
Dansk profileret bibliotek til Cross Enterprise Document sharing. Bruges til at integrere Dokumentdelingsservice (DDS). Du kan læse mere på https://github.com/KvalitetsIT/aftaledeling/tree/master/dgws-eksempel/src/main/java/dk/sds/appointment.

Konfiguration

Alt konfiguration foregår ved at loade filer fra et selv-defineret et wildfly module ind i classpath under deployment.
Dette gør wildfly ved at modulet defineres i jboss-deployment-structure.xml. 

Testing

Testene bliver eksekveret af maven-surefire-plugin med test frameworket junit. Test coverage bliver målt af Jacoco.

Dokumentation

Kodedokumentationen overholder javadoc standarden således dette kan generes med et værktøj fra fx den IDE man bruger.

Sikkerhed

dk.sosi.seal bliver brugt til at verificere SAMLassertion.

/**
     * @param headers Map of request headers
     * @return Saml assertion if its valid or null in case of invalid SAML assertion
     * @throws AuthenticationException If ant authentication exception occure
     */
    public OIOSAMLAssertion extractAndValidate(MultivaluedMap<String, String> headers) throws AuthenticationException {
        final List<String> authentication = headers.get(AUTHENTICATION_PROPERTY);
        try {
            String xml = new String(Base64.decode(authentication.get(0)), StandardCharsets.UTF_8);

            Document doc = parseXml(xml);

            Element encryptedAssertionElm = doc.getDocumentElement();

            PrivateKey privateKeyForAudience = certificateVault.getSystemCredentialPair().getPrivateKey();

            // decrypt the xml for the assertion and parse it
            final Element element = EncryptionUtil.decryptAndDetach(encryptedAssertionElm, privateKeyForAudience);
            OIOSAMLAssertion assertion = new OIOSAMLAssertion(element);

            log.debug("extracted: \n" + assertion.getUID());
            validateAssertion(assertion);

            return assertion;
        } catch (IOException | ParserConfigurationException | SAXException e) {
            throw new AuthenticationException("Could not validate authentication header", e);
        }
    }


Snitfladebeskrivelse og brug

Nap-reference-facade har 2 snitflader som bliver beskrevet i følgende.

/aftaler/{cpr}

Den eneste aftage af denne service er nap-reference-web. 

Headers
KeyValue
AuthenticationBASE64 encoded SAMLassertion
X-OrganizationSor

Sor nummer (bruges af dokumentdelingsservicen)

/isAlive

Bruges af loadbalanceren for at tjekke at servicen er deployet. Returnerer en html side med deployment info.

Da denne service ikke er afhængige af database eller andre interne services, returnere den altid statuskode 200, hvis aplikationen kører.

Eksempel på request/response

http://nap/web/reference/services/main/aftaler/124567890

{
    "title": "Ekkokardiografi",
    "orgName": "Skejby Sygehus",
    "indication": "Har ondt i hjertet somme tider",
    "date": 1591142400
}