-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[BACK-3228] Add Patient FHIR resource to session note mapper
- Loading branch information
1 parent
d472499
commit a4cc856
Showing
7 changed files
with
295 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
.../src/main/java/org/tidepool/keycloak/extensions/broker/mappers/PatientRepresentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package org.tidepool.keycloak.extensions.broker.mappers; | ||
|
||
import org.hl7.fhir.r4.model.Patient; | ||
|
||
import java.time.Instant; | ||
|
||
public class PatientRepresentation { | ||
public String id; | ||
public String firstName; | ||
public String lastName; | ||
public String mrn; | ||
public Long timestamp; | ||
|
||
public PatientRepresentation(Patient patient) { | ||
id = patient.getId(); | ||
firstName = patient.getNameFirstRep().getGivenAsSingleString(); | ||
lastName = patient.getNameFirstRep().getFamily(); | ||
mrn = patient.getIdentifierFirstRep().getValue(); | ||
|
||
// Capture the time when the patient representation was instantiated | ||
// to allow for LRU pruning if needed | ||
timestamp = Instant.now().getEpochSecond(); | ||
} | ||
|
||
public String getId() { | ||
return id; | ||
} | ||
|
||
public void setId(String id) { | ||
this.id = id; | ||
} | ||
|
||
public String getFirstName() { | ||
return firstName; | ||
} | ||
|
||
public void setFirstName(String firstName) { | ||
this.firstName = firstName; | ||
} | ||
|
||
public String getLastName() { | ||
return lastName; | ||
} | ||
|
||
public void setLastName(String lastName) { | ||
this.lastName = lastName; | ||
} | ||
|
||
public String getMrn() { | ||
return mrn; | ||
} | ||
|
||
public void setMrn(String mrn) { | ||
this.mrn = mrn; | ||
} | ||
|
||
public Long getTimestamp() { | ||
return timestamp; | ||
} | ||
|
||
public void setTimestamp(Long timestamp) { | ||
this.timestamp = timestamp; | ||
} | ||
|
||
} | ||
|
30 changes: 30 additions & 0 deletions
30
...rc/main/java/org/tidepool/keycloak/extensions/broker/mappers/PatientsUserSessionNote.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.tidepool.keycloak.extensions.broker.mappers; | ||
|
||
import org.hl7.fhir.r4.model.Patient; | ||
import org.keycloak.util.JsonSerialization; | ||
|
||
import java.io.IOException; | ||
import java.util.HashMap; | ||
|
||
public class PatientsUserSessionNote extends HashMap<String, PatientRepresentation> { | ||
|
||
public PatientsUserSessionNote() { | ||
super(); | ||
} | ||
|
||
public void addPatient(String correlationId, Patient patient) { | ||
this.put(correlationId, new PatientRepresentation(patient)); | ||
} | ||
|
||
public String serializeAsString() throws IOException { | ||
return JsonSerialization.writeValueAsString(this); | ||
} | ||
|
||
public static PatientsUserSessionNote deserializeFromString(String value) throws IOException { | ||
if (value == null || value.isBlank()) { | ||
return new PatientsUserSessionNote(); | ||
} | ||
|
||
return JsonSerialization.readValue(value, PatientsUserSessionNote.class); | ||
} | ||
} |
150 changes: 150 additions & 0 deletions
150
...n/java/org/tidepool/keycloak/extensions/broker/mappers/PatientsUserSessionNoteMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package org.tidepool.keycloak.extensions.broker.mappers; | ||
|
||
import ca.uhn.fhir.rest.client.api.IGenericClient; | ||
import org.hl7.fhir.r4.model.Patient; | ||
import org.jboss.logging.Logger; | ||
import org.keycloak.broker.oidc.OIDCIdentityProvider; | ||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper; | ||
import org.keycloak.broker.provider.BrokeredIdentityContext; | ||
import org.keycloak.models.IdentityProviderMapperModel; | ||
import org.keycloak.models.IdentityProviderSyncMode; | ||
import org.keycloak.models.KeycloakSession; | ||
import org.keycloak.models.RealmModel; | ||
import org.keycloak.models.UserModel; | ||
import org.keycloak.models.UserSessionModel; | ||
import org.keycloak.protocol.oidc.OIDCLoginProtocol; | ||
import org.keycloak.provider.ProviderConfigProperty; | ||
import org.keycloak.representations.AccessTokenResponse; | ||
import org.keycloak.services.managers.AuthenticationManager; | ||
import org.tidepool.keycloak.extensions.broker.FHIRContext; | ||
import org.tidepool.keycloak.extensions.broker.SMARTIdentityProvider; | ||
import org.tidepool.keycloak.extensions.broker.SMARTIdentityProviderFactory; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
||
import static org.tidepool.keycloak.extensions.authenticator.SMARTIdentityProviderAuthenticator.CORRELATION_ID; | ||
|
||
|
||
public class PatientsUserSessionNoteMapper extends AbstractIdentityProviderMapper { | ||
|
||
private static final Logger LOG = Logger.getLogger(PatientsUserSessionNoteMapper.class); | ||
|
||
private static final String PATIENTS_NOTE_NAME = "smart/patients"; | ||
|
||
private static final String[] COMPATIBLE_PROVIDERS = { ANY_PROVIDER }; | ||
|
||
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>(); | ||
|
||
private static final Set<IdentityProviderSyncMode> IDENTITY_PROVIDER_SYNC_MODES = | ||
new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values())); | ||
|
||
public static final String PROVIDER_ID = "smart-patients-session-note-idp-mapper"; | ||
|
||
@Override | ||
public String[] getCompatibleProviders() { | ||
return COMPATIBLE_PROVIDERS; | ||
} | ||
|
||
@Override | ||
public String getDisplayCategory() { | ||
return "User Session"; | ||
} | ||
|
||
@Override | ||
public String getDisplayType() { | ||
return "Patient User Session Note Mapper"; | ||
} | ||
|
||
@Override | ||
public String getHelpText() { | ||
return "Adds the patient in context to the user session note."; | ||
} | ||
|
||
@Override | ||
public List<ProviderConfigProperty> getConfigProperties() { | ||
return CONFIG_PROPERTIES; | ||
} | ||
|
||
@Override | ||
public String getId() { | ||
return PROVIDER_ID; | ||
} | ||
|
||
@Override | ||
public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) { | ||
return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode); | ||
} | ||
|
||
@Override | ||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, | ||
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { | ||
addPatientToSessionNote(session, realm, context); | ||
} | ||
|
||
@Override | ||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, | ||
IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { | ||
addPatientToSessionNote(session, realm, context); | ||
} | ||
|
||
private void addPatientToSessionNote(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context) { | ||
String correlationId = context.getAuthenticationSession().getClientNote(CORRELATION_ID); | ||
if (correlationId.isBlank()) { | ||
LOG.warnf("Client correlationId is not defined for brokered user %s", context.getBrokerUserId()); | ||
return; | ||
} | ||
|
||
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) context.getContextData().get(OIDCIdentityProvider.FEDERATED_ACCESS_TOKEN_RESPONSE); | ||
Object patientId = accessTokenResponse.getOtherClaims().getOrDefault("patient", ""); | ||
if (!(patientId instanceof String) || ((String)patientId).isBlank()) { | ||
LOG.warnf("Patient id not found in access token response for brokered user %s", context.getBrokerUserId()); | ||
return; | ||
} | ||
|
||
String fhirVersion = (String) context.getContextData().get(SMARTIdentityProvider.FHIR_VERSION); | ||
String fhirBaseURL = (String) context.getContextData().get(SMARTIdentityProvider.FHIR_BASE_URL); | ||
|
||
IGenericClient client = FHIRContext.getFHIRClient(fhirVersion, fhirBaseURL, accessTokenResponse.getToken()); | ||
Patient patient = client.read().resource(Patient.class).withId((String)patientId).execute(); | ||
|
||
PatientsUserSessionNote patients; | ||
String noteValue; | ||
|
||
// There may be multiple authentication sessions for a single SSO session. | ||
// Retrieve the session notes from the user session, to make sure we are appending | ||
// to the list of patients associated to the SSO session, not to the current auth session. | ||
UserSessionModel userSession = this.getUserSession(session, realm); | ||
if (userSession != null) { | ||
noteValue = userSession.getNote(PATIENTS_NOTE_NAME); | ||
try { | ||
patients = PatientsUserSessionNote.deserializeFromString(noteValue); | ||
} catch (IOException e) { | ||
LOG.warnf("Unable to deserialize patient notes: %s", noteValue); | ||
return; | ||
} | ||
} else { | ||
patients = new PatientsUserSessionNote(); | ||
} | ||
|
||
patients.addPatient(correlationId, patient); | ||
try { | ||
context.setSessionNote(PATIENTS_NOTE_NAME, patients.serializeAsString()); | ||
} catch (IOException e) { | ||
LOG.warnf("Unable to serialize patient notes: %s", e.getMessage()); | ||
} | ||
} | ||
|
||
private UserSessionModel getUserSession(KeycloakSession session, RealmModel realm) { | ||
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, true); | ||
if (authResult == null) { | ||
return null; | ||
} | ||
return authResult.getSession(); | ||
} | ||
} | ||
|
1 change: 1 addition & 0 deletions
1
.../src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
org.tidepool.keycloak.extensions.broker.mappers.PatientsUserSessionNoteMapper |