Sfoglia il codice sorgente

Merge pull request #26366 from overleaf/jel-group-csv

[web] Include in group members CSV if user is managed and/or linked to the group's SSO

GitOrigin-RevId: 449974917d98cf121ea46eaa58be4b3666d88268
Jessica Lawshe 1 anno fa
parent
commit
1768bef22a

+ 52 - 16
services/web/app/src/Features/UserMembership/UserMembershipController.mjs

@@ -13,6 +13,7 @@ import { Parser as CSVParser } from 'json2csv'
 import { expressify } from '@overleaf/promise-utils'
 import PlansLocator from '../Subscription/PlansLocator.js'
 import RecurlyClient from '../Subscription/RecurlyClient.js'
+import Modules from '../../infrastructure/Modules.js'
 
 async function manageGroupMembers(req, res, next) {
   const { entity: subscription, entityConfig } = req
@@ -121,6 +122,56 @@ async function _renderManagersPage(req, res, next, template) {
   })
 }
 
+async function exportCsv(req, res) {
+  let ssoEnabled
+  const { entity, entityConfig } = req
+  const fields = ['email', 'last_logged_in_at', 'last_active_at']
+
+  const { managedUsersEnabled } = entity
+
+  let users = await UserMembershipHandler.promises.getUsers(
+    entity,
+    entityConfig
+  )
+
+  if (entity.ssoConfig) {
+    const ssoEnabledResult = await Modules.promises.hooks.fire(
+      'hasGroupSSOEnabled',
+      entity
+    )
+    ssoEnabled = ssoEnabledResult?.[0]
+  }
+
+  if (managedUsersEnabled) {
+    fields.push('managed')
+  }
+
+  if (ssoEnabled) {
+    fields.push('sso')
+  }
+
+  if (managedUsersEnabled || ssoEnabled) {
+    users = users.map(user => {
+      if (managedUsersEnabled) {
+        user.managed =
+          user.enrollment?.managedBy?.toString() === entity._id.toString()
+      }
+
+      if (ssoEnabled) {
+        user.sso = !!user.enrollment?.sso?.some(
+          groupLinked =>
+            groupLinked.groupId.toString() === entity._id.toString()
+        )
+      }
+      return user
+    })
+  }
+
+  const csvParser = new CSVParser({ fields })
+
+  csvAttachment(res, csvParser.parse(users), 'Group.csv')
+}
+
 export default {
   manageGroupMembers: expressify(manageGroupMembers),
   manageGroupManagers: expressify(manageGroupManagers),
@@ -208,22 +259,7 @@ export default {
       }
     )
   },
-  exportCsv(req, res, next) {
-    const { entity, entityConfig } = req
-    const fields = ['email', 'last_logged_in_at', 'last_active_at']
-
-    UserMembershipHandler.getUsers(
-      entity,
-      entityConfig,
-      function (error, users) {
-        if (error != null) {
-          return next(error)
-        }
-        const csvParser = new CSVParser({ fields })
-        csvAttachment(res, csvParser.parse(users), 'Group.csv')
-      }
-    )
-  },
+  exportCsv: expressify(exportCsv),
   new(req, res, next) {
     res.render('user_membership/new', {
       entityName: req.params.name,

+ 115 - 2
services/web/test/unit/src/UserMembership/UserMembershipController.test.mjs

@@ -59,6 +59,48 @@ describe('UserMembershipController', function () {
         last_logged_in_at: '2020-05-20T10:41:11.407Z',
         last_active_at: '2021-05-20T10:41:11.407Z',
       },
+      {
+        _id: 'mock-member-id-3',
+        email: 'mock-email-3@foo.com',
+        last_logged_in_at: '2021-08-10T10:41:11.407Z',
+        last_active_at: '2021-08-20T10:41:11.407Z',
+        enrollment: {
+          managedBy: 'some-other-subscription-id',
+          enrolledAt: '2021-05-20T10:41:11.407Z',
+          sso: undefined,
+        },
+      },
+      {
+        _id: 'mock-member-id-4',
+        email: 'mock-email-4@foo.com',
+        last_logged_in_at: '2021-01-01T10:41:11.407Z',
+        last_active_at: '2021-01-02T10:41:11.407Z',
+        enrollment: {
+          managedBy: 'mock-subscription-id',
+          enrolledAt: '2021-01-02T10:41:11.407Z',
+          sso: undefined,
+        },
+      },
+      {
+        _id: 'mock-member-id-5',
+        email: 'mock-email-5@foo.com',
+        last_logged_in_at: '2023-01-01T10:41:11.407Z',
+        last_active_at: '2023-01-02T10:41:11.407Z',
+        enrollment: {
+          sso: [{ groupId: ctx.subscription._id }],
+        },
+      },
+      {
+        _id: 'mock-member-id-6',
+        email: 'mock-email-6@foo.com',
+        last_logged_in_at: '2024-01-01T10:41:11.407Z',
+        last_active_at: '2024-01-02T10:41:11.407Z',
+        enrollment: {
+          managedBy: 'mock-subscription-id',
+          enrolledAt: '2024-01-02T10:41:11.407Z',
+          sso: [{ groupId: ctx.subscription._id }],
+        },
+      },
     ]
 
     ctx.Settings = {
@@ -143,6 +185,17 @@ describe('UserMembershipController', function () {
       SSOConfig: ctx.SSOConfig,
     }))
 
+    ctx.Modules = {
+      promises: {
+        hooks: {
+          fire: sinon.stub(),
+        },
+      },
+    }
+    vi.doMock('../../../../app/src/infrastructure/Modules.js', () => ({
+      default: ctx.Modules,
+    }))
+
     ctx.UserMembershipController = (await import(modulePath)).default
   })
 
@@ -377,7 +430,7 @@ describe('UserMembershipController', function () {
 
     it('get users', function (ctx) {
       sinon.assert.calledWithMatch(
-        ctx.UserMembershipHandler.getUsers,
+        ctx.UserMembershipHandler.promises.getUsers,
         ctx.subscription,
         { modelName: 'Subscription' }
       )
@@ -398,7 +451,67 @@ describe('UserMembershipController', function () {
     it('should export the correct csv', function (ctx) {
       assertCalledWith(
         ctx.res.send,
-        '"email","last_logged_in_at","last_active_at"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z"\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z"'
+        '"email","last_logged_in_at","last_active_at"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z"\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z"\n"mock-email-3@foo.com","2021-08-10T10:41:11.407Z","2021-08-20T10:41:11.407Z"\n"mock-email-4@foo.com","2021-01-01T10:41:11.407Z","2021-01-02T10:41:11.407Z"\n"mock-email-5@foo.com","2023-01-01T10:41:11.407Z","2023-01-02T10:41:11.407Z"\n"mock-email-6@foo.com","2024-01-01T10:41:11.407Z","2024-01-02T10:41:11.407Z"'
+      )
+    })
+  })
+
+  describe('exportCsv when group is managed', function () {
+    beforeEach(function (ctx) {
+      ctx.req.entity = Object.assign(
+        { managedUsersEnabled: true },
+        ctx.subscription
+      )
+      ctx.req.entityConfig = EntityConfigs.groupManagers
+      ctx.res = new MockResponse()
+      ctx.UserMembershipController.exportCsv(ctx.req, ctx.res)
+    })
+
+    it('should export the correct csv', function (ctx) {
+      assertCalledWith(
+        ctx.res.send,
+        '"email","last_logged_in_at","last_active_at","managed"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z",false\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z",false\n"mock-email-3@foo.com","2021-08-10T10:41:11.407Z","2021-08-20T10:41:11.407Z",false\n"mock-email-4@foo.com","2021-01-01T10:41:11.407Z","2021-01-02T10:41:11.407Z",true\n"mock-email-5@foo.com","2023-01-01T10:41:11.407Z","2023-01-02T10:41:11.407Z",false\n"mock-email-6@foo.com","2024-01-01T10:41:11.407Z","2024-01-02T10:41:11.407Z",true'
+      )
+    })
+  })
+
+  describe('exportCsv when group has SSO', function () {
+    beforeEach(function (ctx) {
+      ctx.req.entity = Object.assign(
+        { ssoConfig: 'sso-config-id' },
+        ctx.subscription
+      )
+      ctx.req.entityConfig = EntityConfigs.groupManagers
+      ctx.Modules.promises.hooks.fire.resolves([true])
+      ctx.res = new MockResponse()
+      ctx.UserMembershipController.exportCsv(ctx.req, ctx.res)
+    })
+
+    it('should export the correct csv', function (ctx) {
+      assertCalledWith(
+        ctx.res.send,
+        '"email","last_logged_in_at","last_active_at","sso"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z",false\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z",false\n"mock-email-3@foo.com","2021-08-10T10:41:11.407Z","2021-08-20T10:41:11.407Z",false\n"mock-email-4@foo.com","2021-01-01T10:41:11.407Z","2021-01-02T10:41:11.407Z",false\n"mock-email-5@foo.com","2023-01-01T10:41:11.407Z","2023-01-02T10:41:11.407Z",true\n"mock-email-6@foo.com","2024-01-01T10:41:11.407Z","2024-01-02T10:41:11.407Z",true'
+      )
+    })
+  })
+
+  describe('exportCsv when group has SSO and managed users enabled', function () {
+    beforeEach(function (ctx) {
+      ctx.req.entity = Object.assign(
+        { managedUsersEnabled: true },
+        { ssoConfig: 'sso-config-id' },
+        ctx.subscription
+      )
+      ctx.req.entityConfig = EntityConfigs.groupManagers
+      ctx.Modules.promises.hooks.fire.resolves([true])
+      ctx.res = new MockResponse()
+      ctx.UserMembershipController.exportCsv(ctx.req, ctx.res)
+    })
+
+    it('should export the correct csv', function (ctx) {
+      assertCalledWith(
+        ctx.res.send,
+        '"email","last_logged_in_at","last_active_at","managed","sso"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z",false,false\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z",false,false\n"mock-email-3@foo.com","2021-08-10T10:41:11.407Z","2021-08-20T10:41:11.407Z",false,false\n"mock-email-4@foo.com","2021-01-01T10:41:11.407Z","2021-01-02T10:41:11.407Z",true,false\n"mock-email-5@foo.com","2023-01-01T10:41:11.407Z","2023-01-02T10:41:11.407Z",false,true\n"mock-email-6@foo.com","2024-01-01T10:41:11.407Z","2024-01-02T10:41:11.407Z",true,true'
       )
     })
   })