Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Når brugeren skal logges ud skal browseren flyttes til den saml/Logout på den server, der holder sessionen. 

Se kodestumpenden nedenfor.

Code Block
languagejs
import { Injectable } from '@angular/core';
import { NapAngularService } from 'nap-angular-sdk';
import {
  filterEvents,
  NAPEventCatalogue,
  NAPMessage
} from 'nap-typescript-sdk';
import { combineLatest, merge, Observable, Subject } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';
import { v4 as UUID } from 'uuid';
import { ConfigurationsService } from './configurations.service';

/**
 * Service to handle user session
 *
 * @export AuthService
 * @class AuthService
 */
@Injectable({
  providedIn: 'root',
})
export class AuthService {

  innerLogout = new Subject<undefined>();
  logOut$: Observable<boolean> = merge(
    this.innerLogout,
    this.napSDK.incoming$.pipe(
      filterEvents([NAPEventCatalogue.v1.SessionClose]),
    )).pipe(
      switchMap(() => this.logOutUrl$),
      tap(logOutUrl => {
        // Direct the window to the OIOSaml logout filter
        window.location.assign(logOutUrl);
        console.log('Will redirect:' + logOutUrl);
      }
      ),
      map(() => true)
    );

  private logOutUrl$: Observable<string> = combineLatest([
    this.configService.fetch(
      config => config.serverUrl
    ),
    this.configService.fetch(config => config.logOutEndpoint)
  ]).pipe(
    map(([serverUrl, logOutUrl]) => serverUrl + logOutUrl),
  )

  constructor(
    private napSDK: NapAngularService,
    private configService: ConfigurationsService
  ) {
  }

  /**
   * completes the current session and logs out the user
   */
  logout(): void {
    console.log('NapReferenceWeb Logging out');
    this.innerLogout.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/appointment/), 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.

Code Block
  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.incoming$.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 indikerer der er sket en uventet fejl i projektet som skal sendes til værtssystemet og vice versa.

Fejlen bliver sendt hvis kaldet til getAppointments() fejler.

Code Block
/**
 * Generetas Requests to get appoitnemnts for a given person and organization
 *
 * @export AppointmentService
 * @class AppointmentService
 */
@Injectable({
  providedIn: 'root'
})

export class AppointmentService {

  /**
   * The appointment endpoint
   *
   * @private
   * @memberof AppointmentService
   */
  private appointmentEndPoint$ = this.configurationService.fetch(
    config => config.appointmentsEndpoint
  );

  /**
   * The server url
   *
   * @private
   * @memberof AppointmentService
   */
  private serverUrl = this.configurationService.fetch(config => config.serverUrl);

  /**
   * A refresh indicator
   *
   * @private
   * @memberof AppointmentService
   */
  private serviceActivator = new BehaviorSubject(undefined);

  constructor(
    private http: HttpClient,
    private errorService: ErrorService,
    private configurationService: ConfigurationsService,
    private napSDK: NapAngularService
  ) {
  }

  /**
   * Make a HTTP GET to the serverurl/appointmentendpoint
   * @param patientIdentifier
   */
  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(NAPEventCatalogue.v1.MessageFactory.createSessionErrorMessage(UUID(), error.innerError?.message, error.errorMessage)); // indicate to the host that something went wrong
        return of(undefined);
      }),
    );
  }

  /**
   * Triggers a data update of the service
   */
  refresh(): void {
    this.serviceActivator.next(undefined);
  }
}

Nap-reference-facade

Nap-reference-facade er backend til nap-reference-web.

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)

I bygge processen lægges nap-reference-web ind i src/main/webapp, hvorfra disse er tilgængelig i kontekstroden. Se NAP Ref.Impl. - Installationsvejledning for mere information omkring Jenkins pibeline.

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

...

dk.digst.oiosaml2.java

...

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. - Driftsvejledning.

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

Sikkerhedsarkitekturen følger det skitserede i Sikkerhedsarkitektur for iNSP løsninger. Dermed er der åben for mulighederne for integration til udbredte procedure Sikker browser opstart, samt understøtter tilslutning til Nemlogin.

dk.digst.oiosaml2.java

Anvendes som autentifikationsfilter. For at et OIOSAML kan loades skal oiosaml-kofigurationer være placeret i oiosaml.home under deployment. 
Indstilling af oisaml.home og konifiguration af OIOSAML som vist i compose/development/docker-compose.yml med environmental variable og ved mounte konfigurationsfiler ind.

Code Block
languagejava
...
services:
 napreffacade:
    ...
    environment:
      # Set oiosaml.home + open debug socket
      - JAVA_OPTS=$JAVA_OPTS -Doiosaml.home=/pack/oiosaml -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=n

	...

Nedenfor er et eksempel på registrering af SPFilteret.

Code Block
languagejava
 
@WebListener
public class SpringLoader implements ServletContextListener {
    protected static final String ANNOTATION_CONFIG_CONTEXT = "ANNOTATION_CONFIG_APPLICATION_CONTEXT";

    /**
     * Initialize standalone spring context. Registers the spring configuration and finally register it at the servlet context
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        AnnotationConfigApplicationContext springContext = new AnnotationConfigApplicationContext();
        springContext.register(SpringContext.class);
        springContext.refresh();
        final ServletContext servletContext = servletContextEvent.getServletContext();
        servletContext.setAttribute(ANNOTATION_CONFIG_CONTEXT, springContext);
        ServletRegistration servletRegistration = servletContext.addServlet("SAMLDispatcherServlet", DispatcherServlet.class);
        servletRegistration.addMapping("/saml/*");
        FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("LoginFilter", SPFilter.class);
        filterRegistration.addMappingForUrlPatterns(null, false, "/api/*");
        filterRegistration.addMappingForUrlPatterns(null, false, "/saml/*");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        Object ctx = servletContextEvent.getServletContext().getAttribute(ANNOTATION_CONFIG_CONTEXT);
        if(ctx instanceof AnnotationConfigApplicationContext) {
            ((AnnotationConfigApplicationContext)ctx).close();
        }
    }
}

...

Der findes eksempel på hvordan OIOSAML konfigureres på NAP Ref.Impl. - Guide til udviklere.

SOSI idkort fra Samlassertion

Desuden er der implementeret et service specifikt autentifikationsfilter, der udtrækker det IDkort, som er indlejret i en SAMLassertion så det kan bruges i DGWS kald. 

Code Block
languagejava
@Provider
public class AuthFilter implements ContainerRequestFilter {
    static private final Logger log;
    public static final Response ACCESS_DENIED = Response.status(401).build();
    private static final String[][] XML_PATH_TO_SAML_ASSERTION = {
            {NameSpaces.WSA_1_0_SCHEMA, WSATags.metadata.getTagName()},
            {NameSpaces.LIBERTY_DISCOVERY_SCHEMA, LibertyDiscoveryTags.securityContext.getTagName()},
            {NameSpaces.LIBERTY_SECURITY_SCHEMA, LibertySecurityTags.token.getTagName()},
            {NameSpaces.SAML2ASSERTION_SCHEMA, SAMLTags.ASSERTION}
    };

    static {
        log = Logger.getLogger(AuthFilter.class);
    }

    @Override
    public void filter(ContainerRequestContext requestContext) {
        final RequestContext context = RequestContext.getContext();
        final UserAssertion userAssertion = UserAssertionHolder.get();
        context.setAssertion(userAssertion);
        context.setUserIdCard(getIdCardFromAssertion(userAssertion));
    }

    private UserIDCard getIdCardFromAssertion(UserAssertion ua) {
        UserAttribute attribute = ua.getAttribute(OIOSAMLAttributes.DISCOVERY_EPR);
        if (attribute == null) {
            if (log.isDebugEnabled()) log.debug("No embedded idcard in SAML assertion");
            return null;
        }

        if (!Attribute.URI_REFERENCE.equals(attribute.getFormat())) {
            if (log.isDebugEnabled()) log.debug("Attribute of name " + OIOSAMLAttributes.DISCOVERY_EPR +
                    " is not an embedded idcard - NameFormat mismatch (was + " + attribute.getFormat() +
                    ", expected " + Attribute.URI_REFERENCE);

            return null;
        }

        if (log.isDebugEnabled()) log.debug("Extracting idcard from SAML assertion");

        String attributeValue = attribute.getValue();
        InputSource inputSource = new InputSource(new StringReader(attributeValue));
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder documentBuilder;
        try {
            documentBuilder = documentBuilderFactory.newDocumentBuilder();
            Document document = documentBuilder.parse(inputSource);

            Element idCardElm = getDescendant(document.getDocumentElement());

            return (UserIDCard) new IDCardModelBuilder().buildModel(idCardElm);

        } catch (ParserConfigurationException | SAXException | IOException e) {
            log.error("Error parsing embedded idcard", e);
        }

        return null;
    }

    private Element getDescendant(Element elm) {
        for (String[] qName : AuthFilter.XML_PATH_TO_SAML_ASSERTION) {
            NodeList nodeList = elm.getElementsByTagNameNS(qName[0], qName[1]);
            if (nodeList == null || nodeList.getLength() == 0) {
                log.error("Path element not found: {" + qName[0] + "}" + qName[1]);
                return null;
            }
            Node child = nodeList.item(0);
            if (!(child instanceof Element)) {
                log.error("Path component {" + qName[0] + "}" + qName[1] + " is not an element; node=" + child);
                return null;
            }
            elm = (Element) nodeList.item(0);
        }

        return elm;
    }
}

dk.sosi.seal

Seal anvendes til at indlejre et SOSI IDkort i et XML dokument som er parseable for DGWS.

Nedenstående er eksempler på en SOAP interceptors, som sætter en DGWS header for client requests til DGWS.

...

languagejava

...

auth.service.ts i https://svn.nspop.dk/svn/components/nap/nap-reference-web/trunk/src/app/services/


Patient-Open og Patient-close

I komponenten appointment (src/app/appointment/), 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.

se appointment.component.ts i https://svn.nspop.dk/svn/components/nap/nap-reference-web/trunk/src/app/appointment/

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 indikerer der er sket en uventet fejl i projektet som skal sendes til værtssystemet og vice versa.

Fejlen bliver sendt hvis kaldet til getAppointments() fejler.

se appointment.service.ts i https://svn.nspop.dk/svn/components/nap/nap-reference-web/trunk/src/app/services/

Nap-reference-facade

Nap-reference-facade er backend til nap-reference-web.

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)

I bygge processen lægges nap-reference-web ind i src/main/webapp, hvorfra disse er tilgængelig i kontekstroden. Se NAP Ref.Impl. - Installationsvejledning for mere information omkring Jenkins pibeline.

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.
dk.digst.oiosaml2.java
Danske profilering af OASIS SAML 2.0 standarden.

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. - Driftsvejledning.

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

Sikkerhedsarkitekturen følger det skitserede i Sikkerhedsarkitektur for iNSP løsninger. Dermed er der åben for mulighederne for integration til udbredte procedure Sikker browser opstart, samt understøtter tilslutning til Nemlogin.

dk.digst.oiosaml2.java

Anvendes som autentifikationsfilter. For at et OIOSAML kan loades skal oiosaml-kofigurationer være placeret i oiosaml.home under deployment. 
Indstilling af oisaml.home og konifiguration af OIOSAML som vist i compose/development/docker-compose.yml med environmental variable og ved mounte konfigurationsfiler ind.

Code Block
languagejava
...
services:
 napreffacade:
    ...
    environment:
      # Set oiosaml.home + open debug socket
      - JAVA_OPTS=$JAVA_OPTS -Doiosaml.home=/pack/oiosaml -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=n

	...

For registreing af SPFIlter se SpringLoader.Java i https://svn.nspop.dk/svn/components/nap/nap-reference-facade/trunk/src/main/java/dk/sds/nsp/nap/reference/facade/

OIOSAML sørger for at validere den assertion der kommer fra en given iDP (https://www.nspop.dk/display/public/web/Sikkerhedsservices+%28STS%29+-+Leverancebeskrivelse  i dette tilfælde) og tilføjer denne til brugerens session.

Der findes eksempel på hvordan OIOSAML konfigureres på NAP Ref.Impl. - Guide til udviklere.

SOSI idkort fra Samlassertion

Desuden er der implementeret et service specifikt autentifikationsfilter, der udtrækker det IDkort, som er indlejret i en SAMLassertion så det kan bruges i DGWS kald. 

Se AuthFilter.java i https://svn.nspop.dk/svn/components/nap/nap-reference-facade/trunk/src/main/java/dk/sds/nsp/nap/reference/facade/filters/AuthFilter.java


dk.sosi.seal

Seal anvendes til at indlejre et SOSI IDkort i et XML dokument som er parseable for DGWS.

for et eksemple på en SOAP interceptors, som sætter en DGWS header for client requests til DGWS se https://svn.nspop.dk/svn/components/nap/nap-reference-facade/trunk/src/main/java/dk/sds/nsp/nap/reference/facade/soapinterceptor/.


Snitfladebeskrivelse og brug

...