'use strict' const fs = require('fs') const minimist = require('minimist') const InstitutionsAPI = require('../../app/src/Features/Institutions/InstitutionsAPI') .promises const argv = minimist(process.argv.slice(2)) const commit = argv.commit !== undefined const ignoreNulls = !!argv['ignore-nulls'] if (!commit) { console.log('DOING DRY RUN. TO SAVE CHANGES PASS --commit') } const userEntitlements = loadUserEntitlements(argv['user-entitlements']) const cachedEntitlements = loadCachedEntitlements(argv['cached-entitlements']) syncUserEntitlements(userEntitlements, cachedEntitlements) .catch(err => console.error(err.stack)) .then(() => process.exit()) async function syncUserEntitlements(userEntitlements, cachedEntitlements) { // check for user entitlements in mongo but not in postgres for (const userEntitlement of Object.values(userEntitlements)) { // find any email(s) that are linked through sso for (const email of userEntitlement.emails) { if (!email.samlProviderId) { continue } // get samlIdentifiers entry for email const samlIdentifier = userEntitlement.samlIdentifiers.find( samlIdentifier => samlIdentifier.providerId === email.samlProviderId ) // validate that entitlement is cached if (samlIdentifier) { const cachedEntitlment = cachedEntitlements[email.email] // validate that record is correct if (cachedEntitlment) { if ( cachedEntitlment.hasEntitlement !== samlIdentifier.hasEntitlement ) { console.log( `cached entitlement mismatch for user ${userEntitlement.userId} mongo(${samlIdentifier.hasEntitlement}) postgres(${cachedEntitlment.hasEntitlement})` ) await syncUserEntitlement( userEntitlement.userId, email.email, samlIdentifier.hasEntitlement ) } } // there is not record in postgres at all else { console.log( `missing cached entitlement for user ${userEntitlement.userId}` ) await syncUserEntitlement( userEntitlement.userId, email.email, samlIdentifier.hasEntitlement ) } } // if identifier is missing for email this is internal inconsistency in mongo else { console.log(`missing samlIdentifier for user ${userEntitlement.userId}`) } } // find any samlIdentifier records missing email entry for (const samlIdentifier of userEntitlement.samlIdentifiers) { const email = userEntitlement.emails.find( email => email.samlProviderId === samlIdentifier.providerId ) if (!email) { console.log( `missing email entry for samlIdentifier for user ${userEntitlement.userId}` ) } } } // check for user entitlements in postgres but not in mongo for (const cachedEntitlment of Object.values(cachedEntitlements)) { if (!cachedEntitlment.hasEntitlement) { continue } const userEntitlement = userEntitlements[cachedEntitlment.userId] // validate that mongo has correct entitlement if (userEntitlement) { // find samlIdentifier for provider const samlIdentifier = userEntitlement.samlIdentifiers.find( samlIdentifier => samlIdentifier.providerId === cachedEntitlment.providerId ) if (!samlIdentifier || !samlIdentifier.hasEntitlement) { console.log( `cached entitlement mismatch for user ${userEntitlement.userId} mongo(false) postgres(true)` ) await syncUserEntitlement( userEntitlement.userId, cachedEntitlment.email, false ) } } // if the record does not exist it is probably because users without // entitlements were not exported else { console.log( `missing cached entitlement in mongo for user ${cachedEntitlment.userId}` ) } } } async function syncUserEntitlement(userId, email, hasEntitlement) { if (!commit) { return } try { if (hasEntitlement) { await InstitutionsAPI.addEntitlement(userId, email) } else { await InstitutionsAPI.removeEntitlement(userId, email) } } catch (err) { console.error( `error setting entitlement: ${userId}, ${email}, ${hasEntitlement} - ${err.message}` ) } } function loadUserEntitlements(userEntitlementsFilename) { const userEntitlementsFile = fs.readFileSync(userEntitlementsFilename, { encoding: 'utf8', }) const userEntitlements = {} for (const userEntitlementLine of userEntitlementsFile.split('\n')) { if (!userEntitlementLine) { continue } const userEntitlementExport = JSON.parse(userEntitlementLine) const userId = userEntitlementExport._id.$oid delete userEntitlementExport._id userEntitlementExport.userId = userId userEntitlements[userId] = userEntitlementExport } return userEntitlements } function loadCachedEntitlements(cachedEntitlementsFilename) { const cachedEntitlementsFile = fs.readFileSync(cachedEntitlementsFilename, { encoding: 'utf8', }) const cachedEntitlements = {} for (const cachedEntitlementLine of cachedEntitlementsFile.split('\n')) { // this is safe because comma is not an allowed value for any column const [ userId, email, hasEntitlement, providerId, ] = cachedEntitlementLine.split(',') let hasEntitlementBoolean if (ignoreNulls) { hasEntitlementBoolean = hasEntitlement === 't' } else { hasEntitlementBoolean = hasEntitlement === 't' ? true : hasEntitlement === 'f' ? false : null } cachedEntitlements[email] = { email, hasEntitlement: hasEntitlementBoolean, providerId, userId, } } return cachedEntitlements }