MDMUI is a user-facing application designed to make it easy for administrators to accept or refute links created by MDM (Master Data Management). It includes the ability to search for a particular resource, view a list of all potential matches and potential duplicates, and assign tasks to users for workload management.
MDMUI currently supports FHIR R4 endpoints only.
NOTE: if you are using a reverse / web proxy, please see step 10 at the very bottom (last step).
MDM UI will create FHIR resources for its own workflow management, these will be tagged with the tag https://smilecdr.com/fhir/ns/mdm/ui/NamingSystem/tags|MDM_UI
.
It will create new resources in the following scenarios:
MDM UI depends on having certain components already configured in Smile. Please follow these steps to configure a fresh Smile install to work with MDMUI.
FHIR MDM Server
in the On this page
sidebar on the right of the page to navigate to MDM mode setting. Enable MDM mode
(this will allow MDM module to work).FHIR Subscription Persistence
in the On this page
sidebar on the right side of the page to navigate to Subscription setting. Enable Message Subscription
.StructureDefinition
, which will be used by the front application.SMART Authorization
in the On this page
sidebar. Disable Enforce approved scopes
to restrict permissions.Cross-Origin Resource Sharing
in the On this page
sidebar. Enable CORS
to future MDMUI app port (cannot use * here due to logout URL constraints) (ie. http://localhost:8080)./smartauth
/smartauth
to the endSMART Callback Script
section, add to the Post Authorization Script Text the following script: Post Authorization Text Script JS Script/json-admin
Cross-Origin Resource Sharing (CORS)
section. Enable CORS
to *
(for testing purposes) or MDMUI app url (ie. http://localhost:8080).OpenID Connect Security
./fhir-request
http://localhost:8000/fhir-request
or https://<base URL>/fhir-request/
)OpenID Connect Security
CORS
to * or MDM UI port (ie. http://localhost:8080)Add Client
button to add a new OIDC clientAuthorization Code
, Refresh Token
, and JWT Bearer Token
http://localhost:8080/
or other
port/context path of your choice (NOTE: this requires the context path as well, if one has been set --
http://localhost:8080/mdm-ui/
-- this context path can be set in step 8 when setting up MDM UI Module)cdr_all_user_authorities
launch/patient
launch/practitioner
offline_access
openid
profile
Create Client
to create the OIDC client.system|identifier
http://localhost:9000/json-admin
)MDM_UI
)http://localhost:9200/smartauth
)http://localhost:8080/mdm-ui/
- this is also set in OIDC Client)/smartauth
if this was the context path set) for smart auth with the correct logout url (ie. http://localhost:9200/smartauth/logout?cb=none&revoke=token&revoke=token_refresh
)8080
) - should match the redirect URL of the OIDC client and any CORS settings configured
above/mdm-ui/
) -- this should match the redirect URL above, and the
OIDC Client Authorized Redirect URLS savedFederated OAuth2/OIDC Login
in smart_auth module;Enabled
to Yes
Authorization Code
, Refresh Token
, and JWT Bearer Token
http://localhost:8080/
)Client Secrets
to Yes
cdr_all_user_authorities
launch/patient
launch/practitioner
offline_access
openid profile
Remember User Approved Scopes
to Yes
<base Url>/keycloak/auth/realms/master
)<base Url>/keycloak/auth/realms/master/protocol/openid-connect/auth
<base Url>/keycloak/auth/realms/master/protocol/openid-connect/token
<base Url>/keycloak/auth/realms/master/protocol/openid-connect/userinfo
<base Url>/keycloak/auth/realms/master/protocol/openid-connect/certs
openid profile
function onTokenGenerating(theUserSession, theAuthorizationRequestDetails) {
var userName = theUserSession.username;
Log.info('User Name : ' + userName);
theUserSession.addAuthority('FHIR_WRITE_ALL_OF_TYPE', 'AuditEvent');
theUserSession.addAuthority('FHIR_WRITE_ALL_OF_TYPE', 'Task');
theUserSession.addAuthority('ACCESS_ADMIN_JSON');
theUserSession.addAuthority('UPDATE_USER');
theUserSession.addAuthority('VIEW_USERS');
if (userName === 'DATA_STEWARD') {
theUserSession.addAuthority('ROLE_MDMUI_DATASTEWARD_FHIR');
theUserSession.addAuthority('VIEW_MODULE_CONFIG_FOR_MODULE', 'Master/mdm');
theAuthorizationRequestDetails.addAccessTokenClaim('user_role', 'data_steward');
} else if (userName === 'ADMIN') {
theUserSession.addAuthority('FHIR_EXTENDED_OPERATION_ON_SERVER', '$mdm-evaluate');
theUserSession.addAuthority('ROLE_MDMUI_ADMIN_FHIR');
theUserSession.addAuthority('MODULE_ADMIN_FOR_MODULE', 'Master/mdm');
theUserSession.addAuthority('VIEW_BATCH_JOBS');
theAuthorizationRequestDetails.addAccessTokenClaim('user_role', 'admin');
}
}
onAuthenticateSuccess
function, update username for admin to any username for user added that you want to be an admin; you can also add an else if condition for data stewards.function onTokenGenerating(theUserSession, theAuthorizationRequestDetails, theClientDetails) {
const userRole = theUserSession.getUserData('user_role');
Log.info('User Role : ' + userRole);
theAuthorizationRequestDetails.addAccessTokenClaim('user_role', userRole);
}
function onAuthenticateSuccess(theOutcome, theOutcomeFactory, theContext) {
const userRole = theContext.getClaim('user_role');
if (userRole === 'data_steward') {
addDataStewardAuthorities(theOutcome);
} else if (userRole === 'admin') {
addAdminAuthorities(theOutcome);
}
theOutcome.setUserData('user_role', userRole);
return theOutcome;
}
function addDataStewardAuthorities(outcome) {
addCommonAuthorities(outcome);
const readResourceTypes = ['CodeSystem'];
const readAndWriteResourceTypes = ['AuditEvent', 'Task'];
addReadAuthorities(outcome, readResourceTypes);
addReadAndWriteAuthorities(outcome, readAndWriteResourceTypes);
}
function addAdminAuthorities(outcome) {
addCommonAuthorities(outcome);
const readAndWriteResourceTypes = ['AuditEvent', 'Task'];
const readResourceTypes = ['SearchParameter', 'CodeSystem'];
addReadAndWriteAuthorities(outcome, readAndWriteResourceTypes);
addReadAuthorities(outcome, readResourceTypes);
addSmileSpecificAuthorities(outcome);
}
function addCommonAuthorities(outcome) {
const commonResourceTypes = ['Patient', 'Practitioner', 'StructureDefinition', 'Organization', 'PractitionerRole'];
addReadAndWriteAuthorities(outcome, commonResourceTypes);
addSmileSpecificAuthorities(outcome);
}
function addReadAndWriteAuthorities(outcome, resourceTypes) {
resourceTypes.forEach(resourceType => {
outcome.addAuthority('FHIR_READ_ALL_OF_TYPE', resourceType);
outcome.addAuthority('FHIR_WRITE_ALL_OF_TYPE', resourceType);
});
}
function addReadAuthorities(outcome, resourceTypes) {
resourceTypes.forEach(resourceType => {
outcome.addAuthority('FHIR_READ_ALL_OF_TYPE', resourceType);
});
}
function addSmileSpecificAuthorities(outcome) {
const smileSpecificAuthorities = [
'FHIR_OP_MDM_DUPLICATE_GOLDEN_RESOURCES',
'FHIR_OP_MDM_LINK_HISTORY',
'FHIR_OP_MDM_MERGE_GOLDEN_RESOURCES',
'FHIR_OP_MDM_NOT_DUPLICATE',
'FHIR_OP_MDM_QUERY_LINKS',
'FHIR_OP_MDM_UPDATE_LINK',
'ACCESS_ADMIN_JSON',
'CHANGE_OWN_DEFAULT_LAUNCH_CONTEXTS',
'VIEW_BATCH_JOBS',
'VIEW_MODULE_CONFIG',
'VIEW_USERS',
];
smileSpecificAuthorities.forEach(authority => {
outcome.addAuthority(authority);
});
}