diff --git a/services/web/app/src/Features/Subscription/ManagedUsersPolicy.js b/services/web/app/src/Features/Subscription/ManagedUsersPolicy.js index ced66a5563..1cf0c99c6b 100644 --- a/services/web/app/src/Features/Subscription/ManagedUsersPolicy.js +++ b/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, } } diff --git a/services/web/app/src/models/GroupPolicy.js b/services/web/app/src/models/GroupPolicy.js index 08907564a0..c03dc7839a 100644 --- a/services/web/app/src/models/GroupPolicy.js +++ b/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 } ) diff --git a/services/web/test/acceptance/src/helpers/Subscription.js b/services/web/test/acceptance/src/helpers/Subscription.js index 2da569e7fc..51ac8ba905 100644 --- a/services/web/test/acceptance/src/helpers/Subscription.js +++ b/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