Procházet zdrojové kódy

Merge pull request #13560 from overleaf/bg-group-policy-tests

add policies and tests for managed users

GitOrigin-RevId: 1f17e0911306b7dba8f4e2ab25a320a08c44702c
Brian Gough před 3 roky
rodič
revize
efdc2dfca4

+ 92 - 1
services/web/app/src/Features/Subscription/ManagedUsersPolicy.js

@@ -1,3 +1,93 @@
+const {
+  registerCapability,
+  registerPolicy,
+} = require('../Authorization/PermissionsManager')
+const SubscriptionLocator = require('./SubscriptionLocator')
+
+// 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 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-managing-group-subscription', { default: true })
+
+// Register the capability for a user to start a subscription.
+registerCapability('start-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(
+  'userCannotAddSecondaryEmail',
+  {
+    'add-secondary-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-managing-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 personal subscription.
+registerPolicy(
+  'userCannotHaveSubscription',
+  { 'start-subscription': false },
+  {
+    validator: async user => {
+      return !(await SubscriptionLocator.promises.getUserIndividualSubscription(
+        user
+      ))
+    },
+  }
+)
+
 /**
  * Returns the default group policy for managed users.
  * Managed users are users who are part of a group subscription, and are
@@ -14,7 +104,8 @@ function getDefaultPolicy() {
     userCannotAddSecondaryEmail: true,
     userCannotHaveSubscription: true,
     userCannotLeaveManagingGroupSubscription: true,
-    userCannotHaveThirdPartySSO: true,
+    userCannotHaveGoogleSSO: false, // we want to allow google SSO by default
+    userCannotHaveOtherThirdPartySSO: true,
   }
 }
 

+ 5 - 2
services/web/app/src/models/GroupPolicy.js

@@ -16,8 +16,11 @@ const GroupPolicySchema = new Schema(
     // User can't choose to leave the group subscription they are managed by
     userCannotLeaveManagingGroupSubscription: Boolean,
 
-    // User can't have Google/Twitter/ORCID SSO active on their account, nor can they link it to their account
-    userCannotHaveThirdPartySSO: Boolean,
+    // User can't have a Google SSO account, nor can they link it to their account
+    userCannotHaveGoogleSSO: Boolean,
+
+    // User can't have other third-party SSO (e.g. Twitter/ORCID/IEEE) active on their account, nor can they link it to their account
+    userCannotHaveOtherThirdPartySSO: Boolean,
   },
   { minimize: false }
 )

+ 22 - 0
services/web/test/acceptance/src/helpers/Subscription.js

@@ -1,7 +1,9 @@
 const { db, ObjectId } = require('../../../../app/src/infrastructure/mongodb')
 const { expect } = require('chai')
+const { promisify } = require('util')
 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 SubscriptionModel =
   require('../../../../app/src/models/Subscription').Subscription
 const DeletedSubscriptionModel =
@@ -68,6 +70,14 @@ class Subscription {
     ManagedUsersHandler.getGroupPolicyForUser(user, callback)
   }
 
+  getCapabilities(groupPolicy) {
+    return PermissionsManager.getUserCapabilities(groupPolicy)
+  }
+
+  getUserValidationStatus(user, groupPolicy, callback) {
+    PermissionsManager.getUserValidationStatus(user, groupPolicy, callback)
+  }
+
   enrollManagedUser(user, callback) {
     SubscriptionModel.findById(this._id).exec((error, subscription) => {
       if (error) {
@@ -108,4 +118,16 @@ class Subscription {
   }
 }
 
+Subscription.promises = class extends Subscription {}
+
+// promisify User class methods - works for methods with 0-1 output parameters,
+// otherwise we will need to implement the method manually instead
+const nonPromiseMethods = ['constructor', 'getCapabilities']
+Object.getOwnPropertyNames(Subscription.prototype).forEach(methodName => {
+  const method = Subscription.prototype[methodName]
+  if (typeof method === 'function' && !nonPromiseMethods.includes(methodName)) {
+    Subscription.promises.prototype[methodName] = promisify(method)
+  }
+})
+
 module.exports = Subscription