sync-user-entitlements.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. 'use strict'
  2. const fs = require('fs')
  3. const minimist = require('minimist')
  4. const InstitutionsAPI = require('../../app/src/Features/Institutions/InstitutionsAPI')
  5. .promises
  6. const argv = minimist(process.argv.slice(2))
  7. const commit = argv.commit !== undefined
  8. const ignoreNulls = !!argv['ignore-nulls']
  9. if (!commit) {
  10. console.log('DOING DRY RUN. TO SAVE CHANGES PASS --commit')
  11. }
  12. const userEntitlements = loadUserEntitlements(argv['user-entitlements'])
  13. const cachedEntitlements = loadCachedEntitlements(argv['cached-entitlements'])
  14. syncUserEntitlements(userEntitlements, cachedEntitlements)
  15. .catch(err => console.error(err.stack))
  16. .then(() => process.exit())
  17. async function syncUserEntitlements(userEntitlements, cachedEntitlements) {
  18. // check for user entitlements in mongo but not in postgres
  19. for (const userEntitlement of Object.values(userEntitlements)) {
  20. // find any email(s) that are linked through sso
  21. for (const email of userEntitlement.emails) {
  22. if (!email.samlProviderId) {
  23. continue
  24. }
  25. // get samlIdentifiers entry for email
  26. const samlIdentifier = userEntitlement.samlIdentifiers.find(
  27. samlIdentifier => samlIdentifier.providerId === email.samlProviderId
  28. )
  29. // validate that entitlement is cached
  30. if (samlIdentifier) {
  31. const cachedEntitlment = cachedEntitlements[email.email]
  32. // validate that record is correct
  33. if (cachedEntitlment) {
  34. if (
  35. cachedEntitlment.hasEntitlement !== samlIdentifier.hasEntitlement
  36. ) {
  37. console.log(
  38. `cached entitlement mismatch for user ${userEntitlement.userId} mongo(${samlIdentifier.hasEntitlement}) postgres(${cachedEntitlment.hasEntitlement})`
  39. )
  40. await syncUserEntitlement(
  41. userEntitlement.userId,
  42. email.email,
  43. samlIdentifier.hasEntitlement
  44. )
  45. }
  46. }
  47. // there is not record in postgres at all
  48. else {
  49. console.log(
  50. `missing cached entitlement for user ${userEntitlement.userId}`
  51. )
  52. await syncUserEntitlement(
  53. userEntitlement.userId,
  54. email.email,
  55. samlIdentifier.hasEntitlement
  56. )
  57. }
  58. }
  59. // if identifier is missing for email this is internal inconsistency in mongo
  60. else {
  61. console.log(`missing samlIdentifier for user ${userEntitlement.userId}`)
  62. }
  63. }
  64. // find any samlIdentifier records missing email entry
  65. for (const samlIdentifier of userEntitlement.samlIdentifiers) {
  66. const email = userEntitlement.emails.find(
  67. email => email.samlProviderId === samlIdentifier.providerId
  68. )
  69. if (!email) {
  70. console.log(
  71. `missing email entry for samlIdentifier for user ${userEntitlement.userId}`
  72. )
  73. }
  74. }
  75. }
  76. // check for user entitlements in postgres but not in mongo
  77. for (const cachedEntitlment of Object.values(cachedEntitlements)) {
  78. if (!cachedEntitlment.hasEntitlement) {
  79. continue
  80. }
  81. const userEntitlement = userEntitlements[cachedEntitlment.userId]
  82. // validate that mongo has correct entitlement
  83. if (userEntitlement) {
  84. // find samlIdentifier for provider
  85. const samlIdentifier = userEntitlement.samlIdentifiers.find(
  86. samlIdentifier =>
  87. samlIdentifier.providerId === cachedEntitlment.providerId
  88. )
  89. if (!samlIdentifier || !samlIdentifier.hasEntitlement) {
  90. console.log(
  91. `cached entitlement mismatch for user ${userEntitlement.userId} mongo(false) postgres(true)`
  92. )
  93. await syncUserEntitlement(
  94. userEntitlement.userId,
  95. cachedEntitlment.email,
  96. false
  97. )
  98. }
  99. }
  100. // if the record does not exist it is probably because users without
  101. // entitlements were not exported
  102. else {
  103. console.log(
  104. `missing cached entitlement in mongo for user ${cachedEntitlment.userId}`
  105. )
  106. }
  107. }
  108. }
  109. async function syncUserEntitlement(userId, email, hasEntitlement) {
  110. if (!commit) {
  111. return
  112. }
  113. try {
  114. if (hasEntitlement) {
  115. await InstitutionsAPI.addEntitlement(userId, email)
  116. } else {
  117. await InstitutionsAPI.removeEntitlement(userId, email)
  118. }
  119. } catch (err) {
  120. console.error(
  121. `error setting entitlement: ${userId}, ${email}, ${hasEntitlement} - ${err.message}`
  122. )
  123. }
  124. }
  125. function loadUserEntitlements(userEntitlementsFilename) {
  126. const userEntitlementsFile = fs.readFileSync(userEntitlementsFilename, {
  127. encoding: 'utf8'
  128. })
  129. const userEntitlements = {}
  130. for (const userEntitlementLine of userEntitlementsFile.split('\n')) {
  131. if (!userEntitlementLine) {
  132. continue
  133. }
  134. const userEntitlementExport = JSON.parse(userEntitlementLine)
  135. const userId = userEntitlementExport._id.$oid
  136. delete userEntitlementExport._id
  137. userEntitlementExport.userId = userId
  138. userEntitlements[userId] = userEntitlementExport
  139. }
  140. return userEntitlements
  141. }
  142. function loadCachedEntitlements(cachedEntitlementsFilename) {
  143. const cachedEntitlementsFile = fs.readFileSync(cachedEntitlementsFilename, {
  144. encoding: 'utf8'
  145. })
  146. const cachedEntitlements = {}
  147. for (const cachedEntitlementLine of cachedEntitlementsFile.split('\n')) {
  148. // this is safe because comma is not an allowed value for any column
  149. const [
  150. userId,
  151. email,
  152. hasEntitlement,
  153. providerId
  154. ] = cachedEntitlementLine.split(',')
  155. let hasEntitlementBoolean
  156. if (ignoreNulls) {
  157. hasEntitlementBoolean = hasEntitlement === 't'
  158. } else {
  159. hasEntitlementBoolean =
  160. hasEntitlement === 't' ? true : hasEntitlement === 'f' ? false : null
  161. }
  162. cachedEntitlements[email] = {
  163. email,
  164. hasEntitlement: hasEntitlementBoolean,
  165. providerId,
  166. userId
  167. }
  168. }
  169. return cachedEntitlements
  170. }