Introduktion

Denne guide er primært målrettet til anvendere, som skal implementere en ny afprøvningsløsning og har til formål, at give eksempler på kode og opsætning af web applikation og facade til NAP. 

Guiden beskriver en web applikation som bruger en backend, som interagerer med 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(Husregler for webløsninger og https://www.nspop.dk/display/public/web/Husregler+for+udvikling+til+NSP)

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 

Sammenhæng med øvrige dokumenter

Dette dokument er en del af den samlede dokumentation for NAP Ref.impl..

Dokumentet er udformet, så det i videst muligt omfang opfylder sit formål uafhængigt af de øvrige dokumenter.

Ønskes mere information omkring arkitektur og design findes dette på NAP Ref.Impl. - Design og Arkitektur beskrivelse.

Ønskes mere information omkring installationsvejledning til anvender kan findes på NAP Ref.Impl. - Installationsvejledning.

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


 NAP Ref.Impl. Opbygning

nap-reference-web er en web applikation, som anvender version 1.0.0 af nap-typescript-sdk og nap-angular-sdk. Applikationen kan køres indlejret i et værtssytem (eks. nap-java-host), hvori der tilføjes en NAP Bridge på global scope. Således kan der kommunikeres med SDK'erne. Denne kommunikation håndterer sikkerhed via anvendelse af en SAMLassertion, og giver muligheden for overførelse af patientkonteksten til det indlejret værtssystem.

Derefter tilføjes SAML assertion som en header, samt en SOR værdi i en "X-OrganizationSor" header (denne værdi er hardcoded i dette tilfælde), til kaldet til nap-reference facade, der efterfølgende kalder dokumentationdelingsservicen.

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

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 NAP SDK - Guide til anvendere

  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 i (f.eks. nap-java-host). Derfor af funktionalitet i applikationen begrænset, hvis den åbnes uden at være framet.

Opsætning og struktur af et angular projekt

Komponenter

Angular frameworket lægger sig op af MVC (model-view-controller) design mønstre, og dette er implementeret i nap-reference-web.

Der er simple komponenter til at visning af aftaler, hjælpinformation, fejl osv. 

Services

Services håndterer forretningslogikken.

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

Dependencies

Der er brugt dependencies, som er hentet igennem NSP nexus. Der ligger en .npmrc i projektet, som sætter npm registry til https://nexus.nspop.dk/nexus/repository/nsp-npm/ således dependencies fra package.json hentes herfra. 

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.

For opbygning af tests, henvises til at kigge i projektets .spec filer. 

Dokumentation

Koden er dokumenteret efter TSDoc standard og kan derfor generes med TSDoc

Logging

Der bliver logget til konsollen i tilfælde af fejl - endvidere sendes logiske fejl med NAP SDK'et, som beskrevet nedenfor. 

Brug af NAP SDK

Nap-reference-web implementerer version 1 af eventkataloget, og da det er et angular projekt, er der gjort brug af nap-angular-sdk'et. 

nap-reference-web gør brug af det udstillet interface i nap-angular-sdk, og nedenfor er der eksempler på hvordan det er blevet brugt. 

Session-Open og Session-close (security)

Der er lavet en AuthService, som sørger for at håndtere sessioner. Nedenstående funktionalitet findes i src/app/services/auth.service.ts.

I constructoren lytter man på indkommende beskeder fra NAP SDK'et, og filtrere de beskeder, således det kun er beskeder af typen SessionOpen og SessionClose som er relevante. I constructoren bliver der ligeledes sendt en besked til NAP SDK'et om man gerne vil anmode om at starte en ny trusted session (SessionOpen), hvor brugeres SAMLAssertion overføres.

Hvis der logges ud, sendes en besked med SessionClose, og brugerens SAMLAssertion slettes.

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

I komponenten appointment (src/app/appoinment/), vises de forskellige aftaler for den givne patient.

For at få den nuværende patient, sendes en besked med typen PatientOpen igennem SDK'et. Dette gøres i constructoren.

Herefter lyttes der på indkommende beskeder med typen PatientOpen. Der gøres brug af en hjælpe metode i nap-typescript-sdk, FHIRValueGetter.getPatientInfo(), som konvertere den indkommende besked til et objekt som indeholder patient oplysningerne.  

Hvis den nuværende patient skal lukkes, set fra det indlejrede systems perspektiv, sendes der en besked med typen PatientClose.


  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

I appointment service (src/app/service/appointment.service.ts) vises et eksempel på fejlhåndtering som skal gå igennem NAP SDK'et.

createNapErrorMessage() opbygger en besked med typen SessionError, som indikere der er sket en uventet fejl i projektet og som skal sendes til værtssystemet og vice versa.

Fejlen bliver sendt hvis kaldet til getAppointments() fejler.


  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 Sikkerhedsarkitektur for iNSP løsninger
  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 wildfly modul og ind i classpath under deployment.
Dette gør wildfly ved at modulet defineres i jboss-deployment-structure.xml. 

Der findes flere information om hvordan nap-refernece-facade konfigureres på NAP Ref.Impl. - Guide til udviklere.

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
}