12.6.1Master Data Management User Interface (MDMUI)
Premium

 
⚠️ This guide assumes that MDM is already installed and configured. It focuses on enabling a user-facing interface to review and manage potential matches and duplicates.
MDM v2.0 will require a license, effective May 2024 Release onwards. MDM UI requires MDM as a dependency.

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).

12.6.1.1MDM UI Resources

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:

  • Since MDM UI actions can involve inspecting patient data to distinguish matches, these actions are all audited. All action performed by a user in the UI will create an Audit Event to support privacy and security management activities.
  • A Task resource will be created to track each POSSIBLE_MATCH and POSSIBLE_DUPLICATE link created by MDM that requires workflow management

12.6.2Setup

 

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.

12.6.2.1Add Subscription Matcher module

  • NOTE: On a fresh install, this is already available and configured - this step can be skipped
  • Add module of type Subscription Matcher (All FHIR Versions)
  • Select dependency FHIR Storage module as FHIR Storage (R4 RDBMS)
  • Create and start module

12.6.2.2Add MDM Module

  • Add module of type mdm
  • Set scripts for matching and survivorship -- NOTE: this is not required for setting up MDM, but is required for creating Golden records and match rules
  • Select dependency Subscription Matcher from step 1 above
  • Start module

12.6.2.3Configure FHIR Storage R4 RDBMS (persistence) module:

  • Click on 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).
  • Click on FHIR Subscription Persistence in the On this page sidebar on the right side of the page to navigate to Subscription setting. Enable Message Subscription.
  • NOTE: Starting Smile CDR v2024.02, enable Seed Base Validation resources. This will seed resources like StructureDefinition, which will be used by the front application.
  • Save and restart persistence module.

12.6.2.4Configure SMART outbound module (smart_auth):

  • Click on SMART Authorization in the On this page sidebar. Disable Enforce approved scopes to restrict permissions.
  • Click on 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).
  • For ease of use and to differentiate paths, we recommend setting:
    • HTTP Listener > Context Path -- change to /smartauth
    • OpenID Connect (OIDC) > Issuer URL -- add /smartauth to the end
  • In the SMART Callback Script section, add to the Post Authorization Script Text the following script: Post Authorization Text Script JS Script
  • NOTE: If adding an external IdP, also see step 9 below.
  • NOTE: We currently only support SMART Inbound in federated mode.
  • Save and restart module.

12.6.2.5Configure JSON Admin API (admin_json) endpoint

  • For ease of use and to differentiate paths, we recommend setting:
    • HTTP Listener > Context Path -- change to /json-admin
  • Go to the Cross-Origin Resource Sharing (CORS) section. Enable CORS to * (for testing purposes) or MDMUI app url (ie. http://localhost:8080).
  • In dependencies, enable OpenID Connect Security.
  • Add dependency for OpenID Connect Authentication to smart_auth
  • Save and restart module.

12.6.2.6Configure FHIR REST endpoint

  • Add module FHIR REST Endpoint (All FHIR Versions)
  • For ease of use and to differentiate paths, we recommend setting:
    • HTTP Listener > Context Path -- change to /fhir-request
  • Set value for Fixed Value for Endpoint Base URL to the externally visible URL for this endpoint ( e.g. http://localhost:8000/fhir-request or https://<base URL>/fhir-request/)
  • Under HTTP Listener configure a Listener Port (any port not in use e.g. 8000)
  • Enable OpenID Connect Security
  • Enable CORS to * or MDM UI port (ie. http://localhost:8080)
  • Add dependency for OpenID Connect Authentication to smart_auth
  • Add dependency for FHIR Storage Module (any FHIR version) to persistence (FHIR Storage (R4 RDBMS))
  • Start module

12.6.2.7Configure OIDC client

  • Under Users & Authorization OpenID Connect Clients, use the Add Client button to add a new OIDC client
  • Enter a Client ID and Client Name of your choice (e.g. "MDM_UI")
  • Select Authorized Grant Types: Authorization Code, Refresh Token, and JWT Bearer Token
  • Set Authorized Redirect URLs to the base URL for your new MDM UI instance, e.g. 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)
  • Set Scopes to: cdr_all_user_authorities launch/patient launch/practitioner offline_access openid profile
  • Click on Create Client to create the OIDC client.

12.6.2.8Add MDM UI Module

  • Add module of type MDM UI
  • Set Organization Identifier - a new Organization resource will be created if one does not exist with the given system|identifier
  • Set JSON admin URL (JSON admin endpoint, ie. if using localhost then http://localhost:9000/json-admin)
  • Set OIDC client ID (should match Client ID of OIDC client created in step 7, ie. MDM_UI)
  • Set issuer url (this is your issuer url where the application will retrieve the token, ie. if smart auth configured on port 9200 with context path /smartauth, then use http://localhost:9200/smartauth)
  • Set redirect URL of MDM UI application (ie. if you set port 8080 and context path /mdm-ui/ then use http://localhost:8080/mdm-ui/ - this is also set in OIDC Client)
  • Set logout url (this is your smartauth URL, remember to set /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)
  • Set HTTP Listener > Listener Port (e.g. 8080) - should match the redirect URL of the OIDC client and any CORS settings configured above
  • NOTE: Recommended to also set context path (ie. /mdm-ui/) -- this should match the redirect URL above, and the OIDC Client Authorized Redirect URLS saved
  • Select all dependencies
  • Start module

12.6.2.9Add External IdP (Optional - if using SMART outbaund without federated mode, skip to next step)

  • Toggle Federated OAuth2/OIDC Login in smart_auth module;
  • Add client for external IdP under 'Users & Authorization' > 'OpenID Connect clients'...
    • Add field for Client ID and Client Name
    • Toggle Enabled to Yes
    • Add for 'Authorized Grant Types' the three options Authorization Code, Refresh Token, and JWT Bearer Token
    • Add Authorized Redirect URLs (ie. http://localhost:8080/)
    • Toggle Client Secrets to Yes
    • Generate a secret and copy value - this will go to the server in the next step so keep it available
    • In 'SMART Scopes' field 'Scopes', add the following cdr_all_user_authorities launch/patient launch/practitioner offline_access openid profile
    • Toggle Remember User Approved Scopes to Yes
  • Add server for external IdP under 'Users & Authorization' > 'OpenID Connect Servers'...
    • Add a Name
    • Add Issuer url (ie. Keycloak would use <base Url>/keycloak/auth/realms/master)
    • Add the 'Client ID' from the Client just created
    • Add the 'Client Secret' from the Secret you just generated in the Client above
    • Add the relevant fields to the 'Federated OAuth2/OIDC Login' portion, see keycloak example below:
      • Registration ID - alphanumeric number
      • Authorization URL - <base Url>/keycloak/auth/realms/master/protocol/openid-connect/auth
      • Token URL - <base Url>/keycloak/auth/realms/master/protocol/openid-connect/token
      • UserInfo URL - <base Url>/keycloak/auth/realms/master/protocol/openid-connect/userinfo
      • JWKSet URL - <base Url>/keycloak/auth/realms/master/protocol/openid-connect/certs
      • Request Scopes - openid profile

12.6.2.10Use the MDM UI app

  • You should now be ready to view MDM UI at the port and context path specified in step 7 above! ( ie. if using local host, then use http://localhost:8080/mdm-ui/).
  • If you are using a web proxy to expose ports externally you will need to configure the following endpoints for external access:
    • MDM UI port and context path
    • FHIR endpoint
    • Smart outbound security module
    • JSON Admin API
  • You will also need to set in Smart_auth module
    • HTTP Listener
    • Enable "Respect Forward Headers"
    • Enable "HTTPS Forwarding Assume"

12.6.2.11Post Authorization Text Script JS Script

  • NOTE: Updated for November 2024 Pre-25 release - for any releases prior to that, please see the next bullet point. This sample script uses a simple check for hard-coded username - the two common uses are ADMIN and DATA STEWARD. We recommend you use directory services (LDAP, OIDC, etc.) to implement these mappings in a production environment.
The following permissions are required for proper MDM UI operation. Please see the full list at Smile CDR Roles & Permissions.

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

  • NOTE: [Updated - for any release after November 2024 Pre-24, please use the script above this bullet point] for 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);
    });
}