Skip to content

Commit

Permalink
EPA-81: Added authorization related operations
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasrichner-oviva committed Mar 30, 2024
1 parent 3d72372 commit 5392abf
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.oviva.epa.client;

import com.oviva.epa.client.model.Card;
import com.oviva.epa.client.model.PinStatus;
import com.oviva.epa.client.model.RecordIdentifier;
import com.oviva.epa.client.model.WriteDocumentResponse;
import com.oviva.epa.client.model.*;
import de.gematik.epa.ihe.model.document.Document;
import de.gematik.epa.ihe.model.simple.AuthorInstitution;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand All @@ -20,6 +17,22 @@ public interface KonnektorService {
@NonNull
PinStatus verifySmcPin(@NonNull String cardHandle);

/** Determines the authorization state for a given electronic health record. */
@NonNull
List<AuthorizedApplication> getAuthorizationState(@NonNull RecordIdentifier recordIdentifier);

/**
* Returns a list of all electronic health records the Konnektor has access to. <br>
* <b>IMPORTANT: There is strict rate-limiting on this endpoint. Currently once per day.</b>
*
* <p>See also <b>gemILF_PS_ePA_V1.4.3</b>
*
* <p><i>"A_19008-01 - Einschränkung der Häufigkeit der Abfrage getAuthorizationList Das PS DARF
* den Request getAuthorizationList NICHT öfter als einmal pro Tag stellen"</i>
*/
@NonNull
List<AuthorizationEntry> getAuthorizationList();

@NonNull
WriteDocumentResponse writeDocument(
@NonNull RecordIdentifier recordIdentifier, @NonNull Document document);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ public List<Card> getCardsInfo() {
return wrap(() -> delegate.verifySmcPin(cardHandle));
}

@NonNull
@Override
public List<AuthorizedApplication> getAuthorizationState(
@NonNull RecordIdentifier recordIdentifier) {
return wrap(() -> delegate.getAuthorizationState(recordIdentifier));
}

@NonNull
@Override
public List<AuthorizationEntry> getAuthorizationList() {
return wrap(delegate::getAuthorizationList);
}

@Override
public @NonNull WriteDocumentResponse writeDocument(
@NonNull RecordIdentifier recordIdentifier, @NonNull Document document) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,25 @@
import de.gematik.epa.ihe.model.simple.SubmissionSetMetadata;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import javax.xml.datatype.XMLGregorianCalendar;
import oasis.names.tc.ebxml_regrep.xsd.rs._3.RegistryErrorList;
import oasis.names.tc.ebxml_regrep.xsd.rs._3.RegistryResponseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import telematik.ws.conn.cardservicecommon.xsd.v2_0.CardTypeType;
import telematik.ws.conn.connectorcommon.xsd.v5_0.ResultEnum;
import telematik.ws.conn.connectorcommon.xsd.v5_0.Status;
import telematik.ws.conn.exception.FaultMessageException;
import telematik.ws.conn.phrs.phrmanagementservice.xsd.v2_5.GetAuthorizationListResponse;
import telematik.ws.conn.phrs.phrmanagementservice.xsd.v2_5.GetAuthorizationStateResponse;

public class KonnektorServiceImpl implements KonnektorService {

private static final String STATUS_SUCCESS =
private static Logger log = LoggerFactory.getLogger(KonnektorServiceImpl.class);
private static final String REGISTRY_STATUS_SUCCESS =
"urn:oasis:names:tc:ebxml-regrep:ResponseStatusType:Success";
private final EventServiceClient eventServiceClient;
private final CardServiceClient cardServiceClient;
Expand Down Expand Up @@ -67,7 +76,6 @@ public List<AuthorInstitution> getAuthorInstitutions() {
@NonNull
@Override
public List<Card> getCardsInfo() {
// don't be fooled by the name, this returns a list of cards...
return eventServiceClient.getSmbInfo().getCards().getCard().stream()
.map(c -> new Card(c.getCardHandle(), c.getCardHolderName(), mapCardType(c.getCardType())))
.toList();
Expand All @@ -87,6 +95,78 @@ private Card.CardType mapCardType(CardTypeType t) {
return PinStatus.valueOf(response.getPinStatus().name());
}

@Override
@NonNull
public List<AuthorizedApplication> getAuthorizationState(
@NonNull RecordIdentifier recordIdentifier) {

var res =
phrManagementServiceClient.getAuthorizationState(
recordIdentifier.kvnr(), recordIdentifier.homeCommunityId());
validateAuthorizationStateResponse(res);

return res.getAuthorizationStatusList().getAuthorizedApplication().stream()
.map(e -> new AuthorizedApplication(e.getApplicationName(), parseDate(e.getValidTo())))
.toList();
}

private void validateAuthorizationStateResponse(GetAuthorizationStateResponse res) {
var result = parseResult(res.getStatus());
if (ResultEnum.OK.equals(result)) {
return;
}

if (ResultEnum.WARNING.equals(result)) {
log.atDebug().addKeyValue("response", res::toString).log("warning response from Konnektor");
return;
}

throw new KonnektorException(
"bad GetAuthorizationStateResponse: " + res.getStatus().toString());
}

@Override
@NonNull
public List<AuthorizationEntry> getAuthorizationList() {
var res = phrManagementServiceClient.getAuthorizationList();

validateAuthorizationListResponse(res);

return res.getAuthorizationList().getAuthorizationEntry().stream()
.map(
e ->
new AuthorizationEntry(
new RecordIdentifier(
e.getRecordIdentifier().getInsurantId().getExtension(),
e.getRecordIdentifier().getHomeCommunityId()),
parseDate(e.getValidTo())))
.toList();
}

private LocalDate parseDate(XMLGregorianCalendar encoded) {
return LocalDate.of(encoded.getYear(), encoded.getMonth(), encoded.getDay());
}

private void validateAuthorizationListResponse(GetAuthorizationListResponse res) {
var result = parseResult(res.getStatus());
if (ResultEnum.OK.equals(result)) {
return;
}

throw new KonnektorException("bad GetAuthorizationListResponse: " + res.getStatus().toString());
}

private ResultEnum parseResult(Status status) {
var str = status.getResult().toUpperCase(Locale.ROOT);
try {
// upper case before parsing, the Result allows 'Warning' while the enum does not
return ResultEnum.valueOf(str);
} catch (IllegalArgumentException e) {
// let's not fail on an unknown enum, response might still be useful
return ResultEnum.WARNING;
}
}

@Override
public @NonNull WriteDocumentResponse writeDocument(
@NonNull RecordIdentifier recordIdentifier, @NonNull Document document) {
Expand Down Expand Up @@ -146,7 +226,7 @@ private Card.CardType mapCardType(CardTypeType t) {

private void validateResponse(RegistryResponseType res) {

if (STATUS_SUCCESS.equals(res.getStatus())) {
if (REGISTRY_STATUS_SUCCESS.equals(res.getStatus())) {
return;
}
var errors =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.oviva.epa.client.internal.svc;

import com.oviva.epa.client.internal.svc.model.KonnektorContext;
import com.oviva.epa.client.internal.svc.utils.Models;
import telematik.ws.conn.connectorcontext.xsd.v2_0.ContextType;
import telematik.ws.conn.phrs.phrmanagementservice.wsdl.v2_5.PHRManagementServicePortType;
import telematik.ws.conn.phrs.phrmanagementservice.xsd.v2_5.ObjectFactory;
import telematik.ws.conn.phrs.phrmanagementservice.xsd.v2_5.*;
import telematik.ws.fd.phr.phrcommon.xsd.v1_1.InsurantIdType;
import telematik.ws.fd.phr.phrcommon.xsd.v1_1.RecordIdentifierType;

public class PhrManagementServiceClient {

Expand All @@ -17,14 +19,31 @@ public PhrManagementServiceClient(
this.konnektorContext = konnektorContext;
}

public GetAuthorizationStateResponse getAuthorizationState(String knvr, String homeCommunityId) {
var context = getContext();

var req =
new GetAuthorizationState()
.withContext(context)
.withUserAgent("PS_123/V1.1.0/gematik") // TODO where does that come from?
.withRecordIdentifier(
new RecordIdentifierType()
.withInsurantId(Models.fromKvnr(knvr))
.withHomeCommunityId(homeCommunityId));

return phrManagementService.getAuthorizationState(req);
}

public GetAuthorizationListResponse getAuthorizationList() {

var context = getContext();
var req = new GetAuthorizationList().withContext(context);
return phrManagementService.getAuthorizationList(req);
}

public String getHomeCommunityID(String kvnr) {

var context =
new ContextType()
.withClientSystemId(konnektorContext.clientSystemId())
.withMandantId(konnektorContext.mandantId())
.withUserId(konnektorContext.userId())
.withWorkplaceId(konnektorContext.workplaceId());
var context = getContext();

var insurantId =
new InsurantIdType().withExtension(kvnr).withRoot(new InsurantIdType().getRoot());
Expand All @@ -37,4 +56,12 @@ public String getHomeCommunityID(String kvnr) {

return phrManagementService.getHomeCommunityID(getHomeCommunityIDRequest).getHomeCommunityID();
}

private ContextType getContext() {
return new ContextType()
.withClientSystemId(konnektorContext.clientSystemId())
.withMandantId(konnektorContext.mandantId())
.withUserId(konnektorContext.userId())
.withWorkplaceId(konnektorContext.workplaceId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import com.oviva.epa.client.internal.svc.model.KonnektorContext;
import com.oviva.epa.client.internal.svc.phr.model.RecordIdentifier;
import com.oviva.epa.client.internal.svc.utils.Models;
import telematik.ws.conn.connectorcontext.xsd.v2_0.ContextType;
import telematik.ws.conn.phrs.phrservice.xsd.v2_0.ContextHeader;
import telematik.ws.fd.phr.phrcommon.xsd.v1_1.InsurantIdType;
import telematik.ws.fd.phr.phrcommon.xsd.v1_1.RecordIdentifierType;

public class ContextHeaderBuilder {
Expand All @@ -20,10 +20,6 @@ public static ContextHeaderBuilder fromKonnektorContext(KonnektorContext ctx) {
return new ContextHeaderBuilder(ctx);
}

private static InsurantIdType fromKvnr(String kvnr) {
return new InsurantIdType().withExtension(kvnr).withRoot(new InsurantIdType().getRoot());
}

private static ContextType buildKonnektorContext(KonnektorContext ctx) {
return new ContextType()
.withClientSystemId(ctx.clientSystemId())
Expand Down Expand Up @@ -52,7 +48,7 @@ public ContextHeader build() {

RecordIdentifierType buildRecordIdentifier(RecordIdentifier ctx) {
return new RecordIdentifierType()
.withInsurantId(fromKvnr(ctx.kvnr()))
.withInsurantId(Models.fromKvnr(ctx.kvnr()))
.withHomeCommunityId(ctx.homeCommunityId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.oviva.epa.client.internal.svc.utils;

import telematik.ws.fd.phr.phrcommon.xsd.v1_1.InsurantIdType;

public class Models {

private Models() {}

public static InsurantIdType fromKvnr(String kvnr) {
return new InsurantIdType().withExtension(kvnr).withRoot(new InsurantIdType().getRoot());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.oviva.epa.client.model;

import java.time.LocalDate;

public record AuthorizationEntry(RecordIdentifier recordIdentifier, LocalDate validTo) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.oviva.epa.client.model;

import java.time.LocalDate;

/**
* @param name the name of the application, e.g. 'ePA'
* @param validTo the date until when this authorization is valid
*/
public record AuthorizedApplication(String name, LocalDate validTo) {}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,30 @@ void testGetCardsInfo() throws Exception {
assertThat(pinStatus, equalTo(PinStatus.VERIFIED));
}

@Test
void getAuthorizationList() {

// IMPORTANT: This is strictly rate-limited to once a day!
var authorizations = konnektorService.getAuthorizationList();

// check whether our test KVNR is among them
assertTrue(authorizations.stream().anyMatch(a -> a.recordIdentifier().kvnr().equals(KVNR)));
}

@Test
void getAuthorizationState() {

// 1) get home community
var hcid = konnektorService.getHomeCommunityID(KVNR);
var recordIdentifier = new RecordIdentifier(KVNR, hcid);

// 2) get the authorization state
var authorizedApplications = konnektorService.getAuthorizationState(recordIdentifier);

// 3) check whether we're authorized for the ePA
assertTrue(authorizedApplications.stream().anyMatch(a -> "ePA".equals(a.name())));
}

@Test
void writeDocument() {

Expand Down

0 comments on commit 5392abf

Please sign in to comment.