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-adminCross-Origin Resource Sharing (CORS) section. Enable CORS to * (for testing purposes) or MDMUI app url (ie. http://localhost:8080).OpenID Connect Security./fhir-requesthttp://localhost:8000/fhir-request or https://<base URL>/fhir-request/)OpenID Connect SecurityCORS to * or MDM UI port (ie. http://localhost:8080)Add Client button to add a new OIDC clientAuthorization Code, Refresh Token, and JWT Bearer Tokenhttp://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 profileCreate Client to create the OIDC client.system|identifierhttp://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 YesAuthorization Code, Refresh Token, and JWT Bearer Tokenhttp://localhost:8080/)Client Secrets to Yescdr_all_user_authorities launch/patient launch/practitioner offline_access openid profileRemember 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/certsopenid profile
function onTokenGenerating(theUserSession, theAuthorizationRequestDetails) {
var userName = theUserSession.username;
Log.info('User Name : ' + userName);
// Allows writing AuditEvent resources, which are used to log user actions in MDM UI (e.g., match decisions)
theUserSession.addAuthority('FHIR_WRITE_ALL_OF_TYPE', 'AuditEvent');
// Allows writing Task resources, used by MDM UI to track review actions (e.g., POSSIBLE_MATCH or DUPLICATE review tasks)
theUserSession.addAuthority('FHIR_WRITE_ALL_OF_TYPE', 'Task');
// Grants access to the Admin JSON API, which MDM UI uses to fetch module configuration and metadata
theUserSession.addAuthority('ACCESS_ADMIN_JSON');
// Allows updating user records via internal Admin UI (if applicable)
theUserSession.addAuthority('UPDATE_USER');
// Allows viewing the list of users via internal Admin UI
theUserSession.addAuthority('VIEW_USERS');
if (userName === 'DATA_STEWARD') {
// Enables Data Steward mode in the MDM UI, allowing users to resolve MDM match links
theUserSession.addAuthority('ROLE_MDMUI_DATASTEWARD_FHIR');
// Allows viewing the MDM module configuration (but not editing it)
theUserSession.addAuthority('VIEW_MODULE_CONFIG_FOR_MODULE', 'Master/mdm');
// Adds a claim to the access token indicating the role of the user, used by the frontend UI
theAuthorizationRequestDetails.addAccessTokenClaim('user_role', 'data_steward');
} else if (userName === 'ADMIN') {
// Allows executing the $mdm-evaluate operation to re-run MDM matching logic on demand
theUserSession.addAuthority('FHIR_EXTENDED_OPERATION_ON_SERVER', '$mdm-evaluate');
// Enables Admin mode in the MDM UI, which includes access to config pages and job visibility
theUserSession.addAuthority('ROLE_MDMUI_ADMIN_FHIR');
// Grants full administrative privileges for the MDM module, including modifying match rules and scripts
theUserSession.addAuthority('MODULE_ADMIN_FOR_MODULE', 'Master/mdm');
// Allows viewing all batch jobs in the system, such as match evaluation runs
theUserSession.addAuthority('VIEW_BATCH_JOBS');
// Adds a claim to the access token indicating the role of the user, used by the frontend UI
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);
});
}