Просмотр исходного кода

Merge pull request #15726 from overleaf/ab-managed-users-module-cleanup

[web] Managed users / Group SSO module cleanup

GitOrigin-RevId: a24d3278667059248d9563afe44cafca5f87a7c0
Brian Gough 2 лет назад
Родитель
Сommit
1ff830027f

+ 28 - 17
services/web/app/src/Features/Authorization/PermissionsController.js

@@ -4,7 +4,7 @@ const {
   getUserCapabilities,
   getUserCapabilities,
   getUserRestrictions,
   getUserRestrictions,
 } = require('./PermissionsManager')
 } = require('./PermissionsManager')
-const ManagedUsersHandler = require('../Subscription/ManagedUsersHandler')
+const Modules = require('../../infrastructure/Modules')
 
 
 /**
 /**
  * Function that returns middleware to add an `assertPermission` function to the request object to check if the user has a specific capability.
  * Function that returns middleware to add an `assertPermission` function to the request object to check if the user has a specific capability.
@@ -26,19 +26,26 @@ function useCapabilities() {
       return next()
       return next()
     }
     }
     try {
     try {
-      // get the group policy applying to the user
-      const { groupPolicy, managedBy, isManagedGroupAdmin } =
-        await ManagedUsersHandler.promises.getEnrollmentForUser(req.user)
-      // attach the subscription ID to the request object
-      req.managedBy = managedBy
-      // attach the subscription admin status to the request object
-      req.isManagedGroupAdmin = isManagedGroupAdmin
-      // attach the new capabilities to the request object
-      for (const cap of getUserCapabilities(groupPolicy)) {
-        req.capabilitySet.add(cap)
+      const result = (
+        await Modules.promises.hooks.fire(
+          'getManagedUsersEnrollmentForUser',
+          req.user
+        )
+      )[0]
+      if (result) {
+        // get the group policy applying to the user
+        const { groupPolicy, managedBy, isManagedGroupAdmin } = result
+        // attach the subscription ID to the request object
+        req.managedBy = managedBy
+        // attach the subscription admin status to the request object
+        req.isManagedGroupAdmin = isManagedGroupAdmin
+        // attach the new capabilities to the request object
+        for (const cap of getUserCapabilities(groupPolicy)) {
+          req.capabilitySet.add(cap)
+        }
+        // also attach the user's restrictions (the capabilities they don't have)
+        req.userRestrictions = getUserRestrictions(groupPolicy)
       }
       }
-      // also attach the user's restrictions (the capabilities they don't have)
-      req.userRestrictions = getUserRestrictions(groupPolicy)
       next()
       next()
     } catch (error) {
     } catch (error) {
       if (error instanceof UserNotFoundError) {
       if (error instanceof UserNotFoundError) {
@@ -69,10 +76,14 @@ function requirePermission(...requiredCapabilities) {
       return next(new Error('no user'))
       return next(new Error('no user'))
     }
     }
     try {
     try {
-      // get the group policy applying to the user
-      const { groupPolicy, managedUsersEnabled } =
-        await ManagedUsersHandler.promises.getEnrollmentForUser(req.user)
-
+      const result =
+        (
+          await Modules.promises.hooks.fire(
+            'getManagedUsersEnrollmentForUser',
+            req.user
+          )
+        )[0] || {}
+      const { groupPolicy, managedUsersEnabled } = result
       if (!managedUsersEnabled) {
       if (!managedUsersEnabled) {
         return next()
         return next()
       }
       }

+ 0 - 210
services/web/app/src/Features/Subscription/ManagedUsersHandler.js

@@ -1,210 +0,0 @@
-const { callbackify } = require('util')
-const { Subscription } = require('../../models/Subscription')
-const { GroupPolicy } = require('../../models/GroupPolicy')
-const { User } = require('../../models/User')
-const ManagedUsersPolicy = require('./ManagedUsersPolicy')
-const OError = require('@overleaf/o-error')
-const settings = require('@overleaf/settings')
-const {
-  UserNotFoundError,
-  SubscriptionNotFoundError,
-} = require('../Errors/Errors')
-const UserGetter = require('../User/UserGetter')
-const UserUpdater = require('../User/UserUpdater')
-const EmailHandler = require('../Email/EmailHandler')
-const logger = require('@overleaf/logger')
-
-/**
- * This module contains functions for handling managed users in a
- * group subscription.
- */
-
-/**
- * Enables managed users for a given subscription by creating a new
- * group policy with default settings for managed users and updating
- * the subscription to use the new policy.
- * @async
- * @function
- * @param {string} subscriptionId - The ID of the subscription to enable
- *   managed users for.
- * @returns {Promise<void>} - A Promise that resolves when the subscription
- *   has been updated with the new group policy.
- */
-async function enableManagedUsers(subscriptionId) {
-  const subscription = await Subscription.findById(subscriptionId).exec()
-
-  // create a new Group policy with the default settings for managed users
-  const policy = ManagedUsersPolicy.getDefaultPolicy()
-  const groupPolicy = new GroupPolicy(policy)
-  await groupPolicy.save()
-  // update the subscription to use the new policy
-  subscription.groupPolicy = groupPolicy._id
-  subscription.managedUsersEnabled = true
-  await subscription.save()
-
-  await _sendEmailToGroupMembers(subscriptionId)
-}
-
-/**
- * Disables managed users for a given subscription by removing the
- * group policy and deleting enrolment information for all managed users.
- * @async
- * @function
- * @param {string} subscriptionId - The ID of the subscription to disable
- *   managed users for.
- * @returns {Promise<void>} - A Promise that resolves when the subscription and
- *   users have been updated.
- */
-async function disableManagedUsers(subscriptionId) {
-  const subscription = await Subscription.findById(subscriptionId).exec()
-  for (const userId of subscription.member_ids || []) {
-    const user = await UserGetter.promises.getUser(userId, { enrollment: 1 })
-    if (
-      user.enrollment?.managedBy?.toString() === subscription._id.toString()
-    ) {
-      await UserUpdater.promises.updateUser(userId, {
-        $unset: { enrollment: 1 },
-      })
-    }
-  }
-
-  subscription.groupPolicy = undefined
-  subscription.managedUsersEnabled = false
-  await subscription.save()
-}
-
-/**
- * Retrieves the group policy for a user enrolled in a managed group.
- * @async
- * @function
- * @param {Object} requestedUser - The user object to retrieve the group policy for.
- * @returns {Promise<Object>} - A Promise that resolves with the group policy
- *   and subscription objects for the user's enrollment, or null if it does not exist.
- */
-async function getEnrollmentForUser(requestedUser) {
-  // Don't rely on the user being populated, it may be a session user without
-  // the enrollment property. Force the user to be loaded from mongo.
-  const user = await User.findById(requestedUser._id, 'enrollment')
-  if (!user) {
-    throw new UserNotFoundError({ info: { userId: requestedUser._id } })
-  }
-  // Now we are sure the user exists and we have the full contents
-  if (user.enrollment?.managedBy == null) {
-    return {}
-  }
-  // retrieve the subscription and the group policy (without the _id field)
-  const subscription = await Subscription.findById(user.enrollment.managedBy)
-    .populate('groupPolicy', '-_id')
-    .exec()
-  if (!subscription) {
-    throw new SubscriptionNotFoundError({
-      info: { subscriptionId: user.enrollment.managedBy, userId: user._id },
-    })
-  }
-
-  // check whether the user is an admin of the subscription
-  const isManagedGroupAdmin = user._id.equals(subscription.admin_id)
-
-  // return the group policy as a plain object (without the __v field)
-  const groupPolicy = subscription.groupPolicy.toObject({
-    versionKey: false,
-  })
-
-  return {
-    groupPolicy,
-    managedUsersEnabled: subscription.managedUsersEnabled,
-    managedBy: user.enrollment.managedBy,
-    isManagedGroupAdmin,
-  }
-}
-
-async function enrollInSubscription(userId, subscription) {
-  // check whether the user is already enrolled in a subscription
-  const user = await User.findOne(
-    {
-      _id: userId,
-      'enrollment.managedBy': { $exists: true },
-    },
-    { _id: 1 }
-  ).exec()
-  if (user != null) {
-    throw new OError('User is already enrolled in a subscription', {
-      userId,
-      subscriptionId: subscription._id,
-    })
-  }
-  // update the user to be enrolled in the subscription
-  const updatedUser = await User.findOneAndUpdate(
-    { _id: userId, 'enrollment.managedBy': { $exists: false } },
-    {
-      enrollment: {
-        managedBy: subscription._id,
-        enrolledAt: new Date(),
-      },
-    },
-    { new: true }
-  ).exec()
-  // check whether the enrollment succeeded
-  if (
-    !updatedUser ||
-    !subscription.equals(updatedUser?.enrollment?.managedBy)
-  ) {
-    throw new OError('Failed to enroll user in subscription', {
-      userId,
-      subscriptionId: subscription._id,
-    })
-  }
-}
-
-/**
- * Send email to all group members, irregardless of the member status.
- * @async
- * @function
- * @param {string} subscriptionId - The ID of the subscription to enable
- *   managed users for.
- * @returns {Promise<void>} - A Promise that resolves when all the `sendEmail` function has been sent,
- * irregardless of whether they're successful or failed.
- */
-async function _sendEmailToGroupMembers(subscriptionId) {
-  const EMAIL_DELAY_IN_MS = 0
-
-  const subscription = await Subscription.findById(subscriptionId)
-    .populate('member_ids', 'email')
-    .populate('admin_id', ['first_name', 'last_name', 'email'])
-    .exec()
-
-  // On failure, log the error and carry on so that one email failing does not prevent other emails sending
-  for (const recipient of subscription.member_ids) {
-    try {
-      const opts = {
-        to: recipient.email,
-        admin: subscription.admin_id,
-        groupName: subscription.teamName,
-        acceptInviteUrl: `${settings.siteUrl}/subscription/${subscriptionId}/enrollment/`,
-      }
-      EmailHandler.sendDeferredEmail(
-        'surrenderAccountForManagedUsers',
-        opts,
-        EMAIL_DELAY_IN_MS
-      )
-    } catch (err) {
-      logger.error(
-        { err, userId: recipient._id },
-        'could not send notification email to surrender account'
-      )
-    }
-  }
-}
-
-module.exports = {
-  promises: {
-    enableManagedUsers,
-    disableManagedUsers,
-    getEnrollmentForUser,
-    enrollInSubscription,
-  },
-  enableManagedUsers: callbackify(enableManagedUsers),
-  getEnrollmentForUser: callbackify(getEnrollmentForUser),
-  enrollInSubscription: callbackify(enrollInSubscription),
-  disableManagedUsers: callbackify(disableManagedUsers),
-}

+ 0 - 173
services/web/app/src/Features/Subscription/ManagedUsersPolicy.js

@@ -1,173 +0,0 @@
-const {
-  registerCapability,
-  registerPolicy,
-} = require('../Authorization/PermissionsManager')
-const { getUsersSubscription, getGroupSubscriptionsMemberOf } =
-  require('./SubscriptionLocator').promises
-const { subscriptionIsCanceledOrExpired } = require('./RecurlyClient')
-
-// This file defines the capabilities and policies that are used to
-// determine what managed users can and cannot do.
-
-// Register the capability for a user to delete their own account.
-registerCapability('delete-own-account', { default: true })
-
-// Register the capability for a user to add a secondary email to their account.
-registerCapability('add-secondary-email', { default: true })
-
-// Register the capability for a user to add an affiliation to their account.
-registerCapability('add-affiliation', { default: true })
-
-// Register the capability for a user to endorse an email address.
-registerCapability('endorse-email', { default: true })
-
-// Register the capability for a user to sign in with Google to their account
-registerCapability('link-google-sso', { default: true })
-
-// Register the capability for a user to link other third party SSO to their account
-registerCapability('link-other-third-party-sso', { default: true })
-
-// Register the capability for a user to leave a managed group subscription.
-registerCapability('leave-group-subscription', { default: true })
-
-// Register the capability for a user to start a subscription.
-registerCapability('start-subscription', { default: true })
-
-// Register the capability for a user to join a subscription.
-registerCapability('join-subscription', { default: true })
-
-// Register the capability for a user to reactivate a subscription.
-registerCapability('reactivate-subscription', { default: true })
-
-// Register a policy to prevent a user deleting their own account.
-registerPolicy('userCannotDeleteOwnAccount', {
-  'delete-own-account': false,
-})
-
-// Register a policy to prevent a user having secondary email addresses on their account.
-registerPolicy(
-  'userCannotHaveSecondaryEmail',
-  {
-    'add-secondary-email': false,
-    'add-affiliation': false,
-    'endorse-email': false,
-  },
-  {
-    validator: async ({ user }) => {
-      // return true if the user does not have any secondary emails
-      return user.emails.length === 1
-    },
-  }
-)
-
-// Register a policy to prevent a user leaving the group subscription they are managed by.
-registerPolicy('userCannotLeaveManagingGroupSubscription', {
-  'leave-group-subscription': false,
-})
-
-// Register a policy to prevent a user having third-party SSO linked to their account.
-registerPolicy(
-  'userCannotHaveGoogleSSO',
-  { 'link-google-sso': false },
-  {
-    // return true if the user does not have Google SSO linked
-    validator: async ({ user }) =>
-      !user.thirdPartyIdentifiers?.some(
-        identifier => identifier.providerId === 'google'
-      ),
-  }
-)
-
-// Register a policy to prevent a user having third-party SSO linked to their account.
-registerPolicy(
-  'userCannotHaveOtherThirdPartySSO',
-  { 'link-other-third-party-sso': false },
-  {
-    // return true if the user does not have any other third party SSO linked
-    validator: async ({ user }) =>
-      !user.thirdPartyIdentifiers?.some(
-        identifier => identifier.providerId !== 'google'
-      ),
-  }
-)
-
-// Register a policy to prevent a user having an active subscription or
-// being a member of another group subscription.
-registerPolicy(
-  'userCannotHaveSubscription',
-  {
-    'start-subscription': false,
-    'join-subscription': false,
-    'reactivate-subscription': false,
-  },
-  {
-    validator: async ({ user, subscription }) => {
-      const usersSubscription = await getUsersSubscription(user)
-
-      // The user can be enrolled if:
-      // 1. they do not have a subscription and are not a member of another subscription (apart from the managed group subscription)
-      // 2. they have a subscription and it is canceled or expired
-      // 3. they have a subscription and it is the subscription they are trying to join as a managed user
-      // The last case is to allow the admin to join their own subscription as a managed user
-
-      const userHasSubscription =
-        Boolean(usersSubscription) &&
-        !subscriptionIsCanceledOrExpired(usersSubscription)
-
-      const userIsThisGroupAdmin =
-        Boolean(usersSubscription) &&
-        usersSubscription._id.toString() === subscription._id.toString()
-
-      const userMemberOfSubscriptions = await getGroupSubscriptionsMemberOf(
-        user
-      )
-
-      const isMemberOfOtherSubscriptions = userMemberOfSubscriptions.some(
-        sub => {
-          // ignore the subscription of the managed group itself
-          if (sub._id.toString() === subscription._id.toString()) {
-            return false
-          }
-          // ignore the user's own subscription
-          if (
-            usersSubscription &&
-            sub._id.toString() === usersSubscription._id.toString()
-          ) {
-            return false
-          }
-          return true
-        }
-      )
-
-      return (
-        (!userHasSubscription || userIsThisGroupAdmin) &&
-        !isMemberOfOtherSubscriptions
-      )
-    },
-  }
-)
-
-/**
- * Returns the default group policy for managed users.
- * Managed users are users who are part of a group subscription, and are
- * managed by the group policy. Managed users have limited functionality.
- * This method returns an object with boolean values for each policy that
- * indicates whether the policy is enforced or not.
- *
- * @returns {Object} An object with boolean values for each policy that indicates whether it is enforced or not.
- * @function
- */
-function getDefaultPolicy() {
-  return {
-    userCannotDeleteOwnAccount: true,
-    userCannotHaveSecondaryEmail: true,
-    userCannotHaveSubscription: true,
-    userCannotLeaveManagingGroupSubscription: true,
-    userCannotHaveGoogleSSO: false, // we want to allow google SSO by default
-    userCannotHaveOtherThirdPartySSO: true,
-  }
-}
-
-module.exports = {
-  getDefaultPolicy,
-}

+ 0 - 7
services/web/app/src/Features/Subscription/SubscriptionLocator.js

@@ -2,7 +2,6 @@ const { promisify } = require('util')
 const { Subscription } = require('../../models/Subscription')
 const { Subscription } = require('../../models/Subscription')
 const { DeletedSubscription } = require('../../models/DeletedSubscription')
 const { DeletedSubscription } = require('../../models/DeletedSubscription')
 const logger = require('@overleaf/logger')
 const logger = require('@overleaf/logger')
-const { SSOConfig } = require('../../models/SSOConfig')
 require('./GroupPlansData') // make sure dynamic group plans are loaded
 require('./GroupPlansData') // make sure dynamic group plans are loaded
 
 
 const SubscriptionLocator = {
 const SubscriptionLocator = {
@@ -133,11 +132,6 @@ const SubscriptionLocator = {
   },
   },
 }
 }
 
 
-async function hasSSOEnabled(subscription) {
-  const ssoConfig = await SSOConfig.findById(subscription.ssoConfig).exec()
-  return !!ssoConfig?.enabled
-}
-
 SubscriptionLocator.promises = {
 SubscriptionLocator.promises = {
   getUsersSubscription: promisify(SubscriptionLocator.getUsersSubscription),
   getUsersSubscription: promisify(SubscriptionLocator.getUsersSubscription),
   getUserIndividualSubscription: promisify(
   getUserIndividualSubscription: promisify(
@@ -170,6 +164,5 @@ SubscriptionLocator.promises = {
   hasRecurlyGroupSubscription: promisify(
   hasRecurlyGroupSubscription: promisify(
     SubscriptionLocator.hasRecurlyGroupSubscription
     SubscriptionLocator.hasRecurlyGroupSubscription
   ),
   ),
-  hasSSOEnabled,
 }
 }
 module.exports = SubscriptionLocator
 module.exports = SubscriptionLocator

+ 6 - 6
services/web/app/src/Features/Subscription/TeamInvitesController.js

@@ -84,9 +84,9 @@ async function viewInvite(req, res, next) {
       personalSubscription.recurlySubscription_id &&
       personalSubscription.recurlySubscription_id &&
       personalSubscription.recurlySubscription_id !== ''
       personalSubscription.recurlySubscription_id !== ''
 
 
-    const groupSSOActive = await SubscriptionLocator.promises.hasSSOEnabled(
-      subscription
-    )
+    const groupSSOActive = (
+      await Modules.promises.hooks.fire('hasGroupSSOEnabled', subscription)
+    )?.[0]
 
 
     if (subscription?.groupPolicy) {
     if (subscription?.groupPolicy) {
       if (!subscription.populated('groupPolicy')) {
       if (!subscription.populated('groupPolicy')) {
@@ -194,9 +194,9 @@ async function acceptInvite(req, res, next) {
     token,
     token,
     userId
     userId
   )
   )
-  const groupSSOActive = await SubscriptionLocator.promises.hasSSOEnabled(
-    subscription
-  )
+  const groupSSOActive = (
+    await Modules.promises.hooks.fire('hasGroupSSOEnabled', subscription)
+  )?.[0]
 
 
   res.json({ groupSSOActive })
   res.json({ groupSSOActive })
 }
 }

+ 3 - 3
services/web/app/src/Features/Subscription/TeamInvitesHandler.js

@@ -2,6 +2,7 @@ const logger = require('@overleaf/logger')
 const crypto = require('crypto')
 const crypto = require('crypto')
 
 
 const settings = require('@overleaf/settings')
 const settings = require('@overleaf/settings')
+const Modules = require('../../infrastructure/Modules')
 const { ObjectId } = require('mongodb')
 const { ObjectId } = require('mongodb')
 
 
 const { Subscription } = require('../../models/Subscription')
 const { Subscription } = require('../../models/Subscription')
@@ -11,7 +12,6 @@ const UserGetter = require('../User/UserGetter')
 const SubscriptionLocator = require('./SubscriptionLocator')
 const SubscriptionLocator = require('./SubscriptionLocator')
 const SubscriptionUpdater = require('./SubscriptionUpdater')
 const SubscriptionUpdater = require('./SubscriptionUpdater')
 const LimitationsManager = require('./LimitationsManager')
 const LimitationsManager = require('./LimitationsManager')
-const ManagedUsersHandler = require('./ManagedUsersHandler')
 
 
 const EmailHandler = require('../Email/EmailHandler')
 const EmailHandler = require('../Email/EmailHandler')
 const EmailHelper = require('../Helpers/EmailHelper')
 const EmailHelper = require('../Helpers/EmailHelper')
@@ -22,7 +22,6 @@ const {
   callbackifyMultiResult,
   callbackifyMultiResult,
 } = require('@overleaf/promise-utils')
 } = require('@overleaf/promise-utils')
 const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
 const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
-const Modules = require('../../infrastructure/Modules')
 
 
 async function getInvite(token) {
 async function getInvite(token) {
   const subscription = await Subscription.findOne({
   const subscription = await Subscription.findOne({
@@ -74,7 +73,8 @@ async function acceptInvite(token, userId) {
   await SubscriptionUpdater.promises.addUserToGroup(subscription._id, userId)
   await SubscriptionUpdater.promises.addUserToGroup(subscription._id, userId)
 
 
   if (subscription.managedUsersEnabled) {
   if (subscription.managedUsersEnabled) {
-    await ManagedUsersHandler.promises.enrollInSubscription(
+    await Modules.promises.hooks.fire(
+      'enrollInManagedSubscription',
       userId,
       userId,
       subscription
       subscription
     )
     )

+ 0 - 99
services/web/frontend/stories/subscription/managed-users/manage-group-settings.stories.tsx

@@ -1,99 +0,0 @@
-import GroupSettings from '../../../../modules/managed-users/frontend/js/components/group-settings'
-import { useMeta } from '../../hooks/use-meta'
-import useFetchMock from '../../hooks/use-fetch-mock'
-
-export const GroupSettingsWithManagedUsersDisabledAndNoSSOFeature = () => {
-  useMeta({
-    'ol-managedUsersEnabled': false,
-    'ol-hasGroupSSOFeature': false,
-  })
-  return <GroupSettings />
-}
-
-export const GroupSettingsWithManagedUsersDisabledAndSSOFeature = () => {
-  useFetchMock(fetchMock =>
-    fetchMock.get('express:/manage/groups/:id/settings/sso', {})
-  )
-  useMeta({
-    'ol-managedUsersEnabled': false,
-    'ol-hasGroupSSOFeature': true,
-  })
-  return <GroupSettings />
-}
-
-export const GroupSettingsWithManagedUsersEnabledAndNoSSOFeature = () => {
-  useMeta({
-    'ol-managedUsersEnabled': true,
-    'ol-hasGroupSSOFeature': false,
-  })
-  return <GroupSettings />
-}
-
-export const GroupSettingsWithManagedUsersEnabledAndSSOFeatureNotConfigured =
-  () => {
-    useMeta({
-      'ol-managedUsersEnabled': true,
-      'ol-hasGroupSSOFeature': true,
-    })
-    useFetchMock(fetchMock => {
-      fetchMock.get(
-        'express:/manage/groups/:id/settings/sso',
-        {},
-        {
-          delay: 500,
-        }
-      )
-    })
-    return <GroupSettings />
-  }
-
-export const GroupSettingsWithManagedUsersEnabledAndSSOFeatureConfigured =
-  () => {
-    const config = {
-      entryPoint: 'http://idp.example.com/entry_point',
-      certificate:
-        'X1JQa2tWQmYzYlN1aUZORVhzZGpURVp3c0U4T3J3bWtjYVZsQ2h4MkRyRUpOVGtxV2hXcG9KbG1WZ2hYclB1YUVNeFVjM0pFZW5Zd1dQRzB5bldxWm5xYm5IdEJ5d1VGQlQ2RWJ1bHdQeJ0VmpoMkFUeHlIaE5KUVBqYm1iUlB1ckZjQnZzRzlZWW5RZVpYU3pKd3V3Z1l3cE5ZeE9XZkx5ZlVJZGVKQk5JkFUeHlIaE5KUV',
-      signatureAlgorithm: 'sha256',
-      userIdAttribute: 'email',
-      enabled: true,
-    }
-    useFetchMock(fetchMock => {
-      fetchMock.get('express:/manage/groups/:id/settings/sso', config, {
-        delay: 500,
-      })
-    })
-
-    useMeta({
-      'ol-managedUsersEnabled': true,
-      'ol-hasGroupSSOFeature': true,
-    })
-    return <GroupSettings />
-  }
-
-export const GroupSettingsWithManagedUsersDisabledAndSSOFeatureConfigured =
-  () => {
-    const config = {
-      entryPoint: 'http://idp.example.com/entry_point',
-      certificate:
-        'X1JQa2tWQmYzYlN1aUZORVhzZGpURVp3c0U4T3J3bWtjYVZsQ2h4MkRyRUpOVGtxV2hXcG9KbG1WZ2hYclB1YUVNeFVjM0pFZW5Zd1dQRzB5bldxWm5xYm5IdEJ5d1VGQlQ2RWJ1bHdQeJ0VmpoMkFUeHlIaE5KUVBqYm1iUlB1ckZjQnZzRzlZWW5RZVpYU3pKd3V3Z1l3cE5ZeE9XZkx5ZlVJZGVKQk5JkFUeHlIaE5KUV',
-      signatureAlgorithm: 'sha256',
-      userIdAttribute: 'email',
-      enabled: false,
-    }
-    useFetchMock(fetchMock => {
-      fetchMock.get('express:/manage/groups/:id/settings/sso', config, {
-        delay: 500,
-      })
-    })
-
-    useMeta({
-      'ol-managedUsersEnabled': true,
-      'ol-hasGroupSSOFeature': true,
-    })
-    return <GroupSettings />
-  }
-
-export default {
-  title: 'Subscription / Managed Users',
-  component: GroupSettings,
-}

+ 0 - 37
services/web/frontend/stories/subscription/sso/disable-modal.stories.tsx

@@ -1,37 +0,0 @@
-import SSODisableModal, {
-  type SSODisableModalProps,
-} from '../../../../modules/managed-users/frontend/js/components/modals/sso-disable-modal'
-import useFetchMock from '../../hooks/use-fetch-mock'
-import { useMeta } from '../../hooks/use-meta'
-
-export const DisableSSOModalDefault = (args: SSODisableModalProps) => {
-  useMeta({ 'ol-groupId': '123' })
-  useFetchMock(fetchMock => {
-    fetchMock.post('express:/manage/groups/:id/settings/disableSSO', 200, {
-      delay: 500,
-    })
-  })
-  return <SSODisableModal {...args} />
-}
-
-export const DisableSSOModalError = (args: SSODisableModalProps) => {
-  useMeta({ 'ol-groupId': '123' })
-  useFetchMock(fetchMock => {
-    fetchMock.post('express:/manage/groups/:id/settings/enableSSO', 500, {
-      delay: 500,
-    })
-  })
-  return <SSODisableModal {...args} />
-}
-
-export default {
-  title: 'Subscription / SSO / Disable Modal',
-  component: SSODisableModal,
-  args: {
-    show: true,
-  },
-  argTypes: {
-    handleHide: { action: 'close modal' },
-    onDisableSSO: { action: 'callback' },
-  },
-}

+ 0 - 37
services/web/frontend/stories/subscription/sso/enable-modal.stories.tsx

@@ -1,37 +0,0 @@
-import SSOEnableModal, {
-  type SSOEnableModalProps,
-} from '../../../../modules/managed-users/frontend/js/components/modals/sso-enable-modal'
-import useFetchMock from '../../hooks/use-fetch-mock'
-import { useMeta } from '../../hooks/use-meta'
-
-export const EnableSSOModalDefault = (args: SSOEnableModalProps) => {
-  useMeta({ 'ol-groupId': '123' })
-  useFetchMock(fetchMock => {
-    fetchMock.post('express:/manage/groups/:id/settings/enableSSO', 200, {
-      delay: 500,
-    })
-  })
-  return <SSOEnableModal {...args} />
-}
-
-export const EnableSSOModalError = (args: SSOEnableModalProps) => {
-  useMeta({ 'ol-groupId': '123' })
-  useFetchMock(fetchMock => {
-    fetchMock.post('express:/manage/groups/:id/settings/enableSSO', 500, {
-      delay: 500,
-    })
-  })
-  return <SSOEnableModal {...args} />
-}
-
-export default {
-  title: 'Subscription / SSO / Enable Modal',
-  component: SSOEnableModal,
-  args: {
-    show: true,
-  },
-  argTypes: {
-    handleHide: { action: 'close modal' },
-    onEnableSSO: { action: 'callback' },
-  },
-}

+ 0 - 27
services/web/frontend/stories/subscription/sso/sso-link-interstitial.stories.tsx

@@ -1,27 +0,0 @@
-import { useMeta } from '../../hooks/use-meta'
-import SSOLinkConfirm from '../../../../modules/managed-users/frontend/js/components/sso-link-confirm'
-
-export const LinkConfirmInterstitial = () => {
-  return <SSOLinkConfirm />
-}
-
-export const LinkConfirmInterstitialWithError = () => {
-  useMeta({ 'ol-error': 'SAMLInvalidSignatureError' })
-  return <SSOLinkConfirm />
-}
-
-export default {
-  title: 'Subscription / SSO / Link',
-  component: SSOLinkConfirm,
-  decorators: [
-    (Story: any) => {
-      useMeta({ 'ol-groupId': '123' })
-      useMeta({ 'ol-email': 'user@example.com' })
-      return (
-        <div className="container">
-          <Story />
-        </div>
-      )
-    },
-  ],
-}

+ 1 - 1
services/web/frontend/stylesheets/_style_includes.less

@@ -111,7 +111,7 @@
 // module styles
 // module styles
 // TODO: find a way for modules to add styles dynamically
 // TODO: find a way for modules to add styles dynamically
 @import 'modules/symbol-palette.less';
 @import 'modules/symbol-palette.less';
-@import 'modules/managed-users.less';
+@import 'modules/group-settings.less';
 @import 'modules/git-bridge-modal.less';
 @import 'modules/git-bridge-modal.less';
 @import 'modules/admin-panel.less';
 @import 'modules/admin-panel.less';
 @import 'modules/overleaf-integration.less';
 @import 'modules/overleaf-integration.less';

+ 1 - 1
services/web/frontend/stylesheets/main-style.less

@@ -144,5 +144,5 @@
 @import 'modules/symbol-palette.less';
 @import 'modules/symbol-palette.less';
 @import 'modules/admin-panel.less';
 @import 'modules/admin-panel.less';
 @import 'modules/git-bridge-modal.less';
 @import 'modules/git-bridge-modal.less';
-@import 'modules/managed-users.less';
+@import 'modules/group-settings.less';
 @import 'modules/overleaf-integration.less';
 @import 'modules/overleaf-integration.less';

+ 0 - 0
services/web/frontend/stylesheets/modules/managed-users.less → services/web/frontend/stylesheets/modules/group-settings.less


+ 16 - 5
services/web/test/acceptance/src/helpers/Subscription.js

@@ -2,13 +2,13 @@ const { db, ObjectId } = require('../../../../app/src/infrastructure/mongodb')
 const { expect } = require('chai')
 const { expect } = require('chai')
 const { promisify } = require('util')
 const { promisify } = require('util')
 const SubscriptionUpdater = require('../../../../app/src/Features/Subscription/SubscriptionUpdater')
 const SubscriptionUpdater = require('../../../../app/src/Features/Subscription/SubscriptionUpdater')
-const ManagedUsersHandler = require('../../../../app/src/Features/Subscription/ManagedUsersHandler')
 const PermissionsManager = require('../../../../app/src/Features/Authorization/PermissionsManager')
 const PermissionsManager = require('../../../../app/src/Features/Authorization/PermissionsManager')
-const SSOConfigManager = require('../../../../modules/managed-users/app/src/SSOConfigManager')
+const SSOConfigManager = require('../../../../modules/group-settings/app/src/sso/SSOConfigManager')
 const SubscriptionModel =
 const SubscriptionModel =
   require('../../../../app/src/models/Subscription').Subscription
   require('../../../../app/src/models/Subscription').Subscription
 const DeletedSubscriptionModel =
 const DeletedSubscriptionModel =
   require('../../../../app/src/models/DeletedSubscription').DeletedSubscription
   require('../../../../app/src/models/DeletedSubscription').DeletedSubscription
+const Modules = require('../../../../app/src/infrastructure/Modules')
 
 
 class Subscription {
 class Subscription {
   constructor(options = {}) {
   constructor(options = {}) {
@@ -78,11 +78,17 @@ class Subscription {
   }
   }
 
 
   enableManagedUsers(callback) {
   enableManagedUsers(callback) {
-    ManagedUsersHandler.enableManagedUsers(this._id, callback)
+    Modules.hooks.fire('enableManagedUsers', this._id, callback)
   }
   }
 
 
   getEnrollmentForUser(user, callback) {
   getEnrollmentForUser(user, callback) {
-    ManagedUsersHandler.getEnrollmentForUser(user, callback)
+    Modules.hooks.fire(
+      'getManagedUsersEnrollmentForUser',
+      user,
+      (error, [enrollment]) => {
+        callback(error, enrollment)
+      }
+    )
   }
   }
 
 
   getCapabilities(groupPolicy) {
   getCapabilities(groupPolicy) {
@@ -98,7 +104,12 @@ class Subscription {
       if (error) {
       if (error) {
         return callback(error)
         return callback(error)
       }
       }
-      ManagedUsersHandler.enrollInSubscription(user._id, subscription, callback)
+      Modules.hooks.fire(
+        'enrollInManagedSubscription',
+        user._id,
+        subscription,
+        callback
+      )
     })
     })
   }
   }
 
 

+ 1 - 1
services/web/test/frontend/features/group-management/components/sso/group-settings-sso.spec.tsx

@@ -1,4 +1,4 @@
-import GroupSettingsSSORoot from '../../../../../../modules/managed-users/frontend/js/components/sso/group-settings-sso-root'
+import GroupSettingsSSORoot from '../../../../../../modules/group-settings/frontend/js/components/sso/group-settings-sso-root'
 
 
 function GroupSettingsSSOComponent() {
 function GroupSettingsSSOComponent() {
   return (
   return (

+ 2 - 8
services/web/test/unit/src/Subscription/TeamInvitesHandlerTests.js

@@ -76,12 +76,6 @@ describe('TeamInvitesHandler', function () {
       },
       },
     }
     }
 
 
-    this.ManagedUsersHandler = {
-      promises: {
-        enrollInSubscription: sinon.stub().resolves(),
-      },
-    }
-
     this.newToken = 'bbbbbbbbb'
     this.newToken = 'bbbbbbbbb'
 
 
     this.crypto = {
     this.crypto = {
@@ -128,7 +122,6 @@ describe('TeamInvitesHandler', function () {
         './SubscriptionUpdater': this.SubscriptionUpdater,
         './SubscriptionUpdater': this.SubscriptionUpdater,
         './LimitationsManager': this.LimitationsManager,
         './LimitationsManager': this.LimitationsManager,
         '../Email/EmailHandler': this.EmailHandler,
         '../Email/EmailHandler': this.EmailHandler,
-        './ManagedUsersHandler': this.ManagedUsersHandler,
         '../Notifications/NotificationsBuilder': this.NotificationsBuilder,
         '../Notifications/NotificationsBuilder': this.NotificationsBuilder,
         '../../infrastructure/Modules': (this.Modules = {
         '../../infrastructure/Modules': (this.Modules = {
           promises: { hooks: { fire: sinon.stub().resolves() } },
           promises: { hooks: { fire: sinon.stub().resolves() } },
@@ -393,7 +386,8 @@ describe('TeamInvitesHandler', function () {
 
 
         this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
         this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
           sinon.assert.calledWith(
           sinon.assert.calledWith(
-            this.ManagedUsersHandler.promises.enrollInSubscription,
+            this.Modules.promises.hooks.fire,
+            'enrollInManagedSubscription',
             this.user.id,
             this.user.id,
             this.subscription
             this.subscription
           )
           )