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);
// 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);
});
}