sync-user-entitlements.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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 ${
  39. userEntitlement.userId
  40. } mongo(${samlIdentifier.hasEntitlement}) postgres(${
  41. cachedEntitlment.hasEntitlement
  42. })`
  43. )
  44. await syncUserEntitlement(
  45. userEntitlement.userId,
  46. email.email,
  47. samlIdentifier.hasEntitlement
  48. )
  49. }
  50. }
  51. // there is not record in postgres at all
  52. else {
  53. console.log(
  54. `missing cached entitlement for user ${userEntitlement.userId}`
  55. )
  56. await syncUserEntitlement(
  57. userEntitlement.userId,
  58. email.email,
  59. samlIdentifier.hasEntitlement
  60. )
  61. }
  62. }
  63. // if identifier is missing for email this is internal inconsistency in mongo
  64. else {
  65. console.log(`missing samlIdentifier for user ${userEntitlement.userId}`)
  66. }
  67. }
  68. // find any samlIdentifier records missing email entry
  69. for (const samlIdentifier of userEntitlement.samlIdentifiers) {
  70. const email = userEntitlement.emails.find(
  71. email => email.samlProviderId === samlIdentifier.providerId
  72. )
  73. if (!email) {
  74. console.log(
  75. `missing email entry for samlIdentifier for user ${
  76. userEntitlement.userId
  77. }`
  78. )
  79. }
  80. }
  81. }
  82. // check for user entitlements in postgres but not in mongo
  83. for (const cachedEntitlment of Object.values(cachedEntitlements)) {
  84. if (!cachedEntitlment.hasEntitlement) {
  85. continue
  86. }
  87. const userEntitlement = userEntitlements[cachedEntitlment.userId]
  88. // validate that mongo has correct entitlement
  89. if (userEntitlement) {
  90. // find samlIdentifier for provider
  91. const samlIdentifier = userEntitlement.samlIdentifiers.find(
  92. samlIdentifier =>
  93. samlIdentifier.providerId === cachedEntitlment.providerId
  94. )
  95. if (!samlIdentifier || !samlIdentifier.hasEntitlement) {
  96. console.log(
  97. `cached entitlement mismatch for user ${
  98. userEntitlement.userId
  99. } mongo(false) postgres(true)`
  100. )
  101. await syncUserEntitlement(
  102. userEntitlement.userId,
  103. cachedEntitlment.email,
  104. false
  105. )
  106. }
  107. }
  108. // if the record does not exist it is probably because users without
  109. // entitlements were not exported
  110. else {
  111. console.log(
  112. `missing cached entitlement in mongo for user ${
  113. cachedEntitlment.userId
  114. }`
  115. )
  116. }
  117. }
  118. }
  119. async function syncUserEntitlement(userId, email, hasEntitlement) {
  120. if (!commit) {
  121. return
  122. }
  123. try {
  124. if (hasEntitlement) {
  125. await InstitutionsAPI.addEntitlement(userId, email)
  126. } else {
  127. await InstitutionsAPI.removeEntitlement(userId, email)
  128. }
  129. } catch (err) {
  130. console.error(
  131. `error setting entitlement: ${userId}, ${email}, ${hasEntitlement} - ${
  132. err.message
  133. }`
  134. )
  135. }
  136. }
  137. function loadUserEntitlements(userEntitlementsFilename) {
  138. const userEntitlementsFile = fs.readFileSync(userEntitlementsFilename, {
  139. encoding: 'utf8'
  140. })
  141. const userEntitlements = {}
  142. for (const userEntitlementLine of userEntitlementsFile.split('\n')) {
  143. if (!userEntitlementLine) {
  144. continue
  145. }
  146. const userEntitlementExport = JSON.parse(userEntitlementLine)
  147. const userId = userEntitlementExport._id.$oid
  148. delete userEntitlementExport._id
  149. userEntitlementExport.userId = userId
  150. userEntitlements[userId] = userEntitlementExport
  151. }
  152. return userEntitlements
  153. }
  154. function loadCachedEntitlements(cachedEntitlementsFilename) {
  155. const cachedEntitlementsFile = fs.readFileSync(cachedEntitlementsFilename, {
  156. encoding: 'utf8'
  157. })
  158. const cachedEntitlements = {}
  159. for (const cachedEntitlementLine of cachedEntitlementsFile.split('\n')) {
  160. // this is safe because comma is not an allowed value for any column
  161. const [
  162. userId,
  163. email,
  164. hasEntitlement,
  165. providerId
  166. ] = cachedEntitlementLine.split(',')
  167. let hasEntitlementBoolean
  168. if (ignoreNulls) {
  169. hasEntitlementBoolean = hasEntitlement === 't'
  170. } else {
  171. hasEntitlementBoolean =
  172. hasEntitlement === 't' ? true : hasEntitlement === 'f' ? false : null
  173. }
  174. cachedEntitlements[email] = {
  175. email,
  176. hasEntitlement: hasEntitlementBoolean,
  177. providerId,
  178. userId
  179. }
  180. }
  181. return cachedEntitlements
  182. }