Introduktion

Anvendere betragtes som udviklere af afprøvningsprojekter. Denne guide har til formål at give disse udvikler eksempler på en webapplikation og en facade. 

Guiden inkluderer instruktioner til opsætning af hele nap platformen samt selvstændige eksempler på kodeimplementationer.

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 injected javascript bridge. Via denne bro henter applikationen SAMLassertion og patientkonteksten 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ørt 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 Aftale dokumenter 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ærst system så som, nap-java-host Platformsservices (NAP) - Leverancebeskrivelse

Kom i gang med NAP

I følgende beskrives opsætningstrinene for at komme i gang med NAP platformen som anvender og udvikler af nap-projekter.


Præliminere opsætning:

Docker skal være installeret, da opsætningen foregår via docker-compose.

Et docker netværk kaldet nap_net skal laves (`docker network create nap_net`)

Komponenterne fra https://svn.nspop.dk/svn/components/nap/ skal hentes ned.

Start nap-host-java

OBS vi har ikke nogen distributionskanal for denne executeable endnu. Så foreløbigt: Kør `mvn install -Pgenerateexecutable` og start den executable det bliver generate i /target.

Login ved at trykke på localhost og bruge default login credentials. Dette vil trække en SAMLassertion fra STS på test1. Du kan bruge denne i 30 min.

Skriv et cpr nummer på en test person (Eksempelvis "2708599967", som er et hyppigt anvendt test cpr nummer). Du kan nu browse rundt i fanerne.

For at fanen "afprøvningsplatformen" skal fungere skal Nap-Compose og Nap-Lobby og startes, som er de næste trin i opsætning. Dette vil nemlig hoste lobbyen på localhost:8080/nap/lobby/web/, som er den url hosten kigger efter. 

Nap-Compose

Ideen med nap-compose er, at denne reverse proxy fungerer som en NSP loadbalancer med path rewriting.

For at starte denne reverse proxy køres `docker-compose up`. Dette vil åbne port 8080 på localhost.

Ideen med nap-compose er, at denne reverse proxy fungerer som en NSP loadbalancer med path rewriting.

For at starte denne reverse proxy køres `docker-compose up`. Dette vil åbne port 8080 på localhost.

Nap-Lobby

Nap-lobby er kataloget for de afprøvningsprojekter, der findes i nappen.

Kør `docker-compose up` fra compose/test. Med Nap-compose kørende, er denne nu tilgængelig på localhost:8080/nap/lobby/web.

For at at Lobby skal vise nogen projekter skal den have forbindelse til Nap-admin

Nap-admin

Kør `docker-compose up` fra compose/test. Med Nap-compose kørende, er denne nu tilgængelig på localhost:8080/nap/lobby/services/main.



Få din løsning ind i NAP-platformen

Denne reverse proxy konfigureres ved at opdatere nap-httpd.conf.


<VirtualHost *:8080> ServerName nap

ProxyPass /nap/lobby/web/services/main http://napadmin:8080

ProxyPassReverse /nap/lobby/web/services/main http://napadmin:8080

ProxyPass /nap/lobby/web http://naplobbyweb:8080

ProxyPassReverse /nap/lobby/web http://naplobbyweb:8080

ProxyPass /nap/reference/web/services/main http://napreffacade:8080

ProxyPassReverse /nap/reference/web/services/main http://napreffacade:8080

ProxyPass /nap/reference/web http://naprefweb:8080

ProxyPassReverse /nap/reference/web http://naprefweb:8080

ProxyPass /nap/test/web http://naptestweb:8080

ProxyPassReverse /nap/test/web http://naptestweb:8080

ProxyPass /nap/admin/web/services/main http://napadmin:8080

ProxyPassReverse /nap/admin/web/services/main http://napadmin:8080

ProxyPass /nap/admin/web http://napadminweb:8080

ProxyPassReverse /nap/admin/web http://napadminweb:8080

</VirtualHost>


Nap-reference-web

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

  1. Opsætning og struktur af angular projekt.
  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. Således er der ingen funktionalitet, hvis ikke der findes en NAPbridge på global scope, som når applikationen åbnes i en almindelig browser.

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. Således bliver komponenter, templates og style sheets genereret med `ng g c`.

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

Services

Services håndterer forretningslogikken og bliver genereret med `ng g s`. 

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 hiver Istanbul indtil 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 innerLogout: Subject<undefined> = new Subject();

  public logout$ = this.innerLogout.asObservable();

  public session$: Observable<NAPMessage | undefined> = merge(
    this.logout$,
    this.napSDK.incomming$.pipe(
      filterEvents([NAPEventCatalogue.v1.SessionOpen, NAPEventCatalogue.v1.SessionClose]),
      shareReplay(1),
      startWith(undefined),
    )
  );


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

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


  logout(): void {
    this.innerLogout.next(undefined);
  }
}


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 aftale - dokumenter
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, som der efterfølgende dependes på 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 bliver generet med Javadoc.

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-OrganizationSorSor nummer (bruger af Aftale servicen)

/isAlive

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

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
}