mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #25983 from overleaf/ii-managed-users-make-unmanaged-roles-access
[web] Prevent managers from removing/deleting themselves GitOrigin-RevId: 9287dc06bab8024bf03fecff678a4118a9456919
This commit is contained in:
@@ -162,6 +162,45 @@ const SubscriptionLocator = {
|
||||
}
|
||||
: null
|
||||
},
|
||||
|
||||
async getUserSubscriptionStatus(userId) {
|
||||
let usersSubscription = { personal: false, group: false }
|
||||
|
||||
if (!userId) {
|
||||
return usersSubscription
|
||||
}
|
||||
|
||||
const memberSubscriptions =
|
||||
await SubscriptionLocator.getMemberSubscriptions(userId)
|
||||
|
||||
const hasActiveGroupSubscription = memberSubscriptions.some(
|
||||
subscription =>
|
||||
subscription.recurlyStatus?.state === 'active' && subscription.groupPlan
|
||||
)
|
||||
if (hasActiveGroupSubscription) {
|
||||
// Member of a group plan
|
||||
usersSubscription = { ...usersSubscription, group: true }
|
||||
}
|
||||
|
||||
const personalSubscription =
|
||||
await SubscriptionLocator.getUsersSubscription(userId)
|
||||
|
||||
if (personalSubscription) {
|
||||
const hasActivePersonalSubscription =
|
||||
personalSubscription.recurlyStatus?.state === 'active'
|
||||
if (hasActivePersonalSubscription) {
|
||||
if (personalSubscription.groupPlan) {
|
||||
// Owner of a group plan
|
||||
usersSubscription = { ...usersSubscription, group: true }
|
||||
} else {
|
||||
// Owner of an individual plan
|
||||
usersSubscription = { ...usersSubscription, personal: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return usersSubscription
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -132,6 +132,9 @@ async function viewInvite(req, res, next) {
|
||||
logger.error({ err }, 'error getting subscription admin email')
|
||||
}
|
||||
|
||||
const usersSubscription =
|
||||
await SubscriptionLocator.promises.getUserSubscriptionStatus(userId)
|
||||
|
||||
return res.render('subscriptions/team/invite-managed', {
|
||||
inviterName: invite.inviterName,
|
||||
inviteToken: invite.token,
|
||||
@@ -141,6 +144,7 @@ async function viewInvite(req, res, next) {
|
||||
groupSSOActive,
|
||||
subscriptionId: subscription._id.toString(),
|
||||
user: sessionUser,
|
||||
usersSubscription,
|
||||
})
|
||||
} else {
|
||||
let currentManagedUserAdminEmail
|
||||
|
||||
@@ -31,8 +31,11 @@ async function manageGroupMembers(req, res, next) {
|
||||
)
|
||||
const ssoConfig = await SSOConfig.findById(subscription.ssoConfig).exec()
|
||||
const plan = PlansLocator.findLocalPlanInSettings(subscription.planCode)
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)?.toString()
|
||||
const isAdmin = subscription.admin_id.toString() === userId
|
||||
const isUserGroupManager =
|
||||
Boolean(subscription.manager_ids?.some(id => id.toString() === userId)) &&
|
||||
!isAdmin
|
||||
const recurlySubscription = subscription.recurlySubscription_id
|
||||
? await RecurlyClient.promises.getSubscription(
|
||||
subscription.recurlySubscription_id
|
||||
@@ -51,6 +54,7 @@ async function manageGroupMembers(req, res, next) {
|
||||
users,
|
||||
groupSize: subscription.membersLimit,
|
||||
managedUsersActive: subscription.managedUsersEnabled,
|
||||
isUserGroupManager,
|
||||
groupSSOActive: ssoConfig?.enabled,
|
||||
canUseFlexibleLicensing: plan?.canUseFlexibleLicensing,
|
||||
canUseAddSeatsFeature,
|
||||
|
||||
@@ -13,6 +13,7 @@ block append meta
|
||||
meta(name="ol-groupSSOActive" data-type="boolean" content=groupSSOActive)
|
||||
meta(name="ol-subscriptionId" data-type="string" content=subscriptionId)
|
||||
meta(name="ol-user" data-type="json" content=user)
|
||||
meta(name="ol-usersSubscription" data-type="json" content=usersSubscription)
|
||||
|
||||
block content
|
||||
main.content.content-alt.team-invite#invite-managed-root
|
||||
|
||||
@@ -10,6 +10,7 @@ block append meta
|
||||
meta(name="ol-groupName", data-type="string", content=name)
|
||||
meta(name="ol-groupSize", data-type="json", content=groupSize)
|
||||
meta(name="ol-managedUsersActive", data-type="boolean", content=managedUsersActive)
|
||||
meta(name="ol-isUserGroupManager", data-type="boolean", content=isUserGroupManager)
|
||||
meta(name="ol-groupSSOActive", data-type="boolean", content=groupSSOActive)
|
||||
meta(name="ol-canUseFlexibleLicensing", data-type="boolean", content=canUseFlexibleLicensing)
|
||||
meta(name="ol-canUseAddSeatsFeature", data-type="boolean", content=canUseAddSeatsFeature)
|
||||
|
||||
@@ -201,6 +201,8 @@
|
||||
"can_view_content": "",
|
||||
"cancel": "",
|
||||
"cancel_add_on": "",
|
||||
"cancel_any_existing_subscriptions": "",
|
||||
"cancel_any_existing_subscriptions_and_leave_any_group_subscriptions": "",
|
||||
"cancel_anytime": "",
|
||||
"cancel_my_account": "",
|
||||
"cancel_my_subscription": "",
|
||||
@@ -1142,7 +1144,7 @@
|
||||
"only_group_admin_or_managers_can_delete_your_account_2": "",
|
||||
"only_group_admin_or_managers_can_delete_your_account_3": "",
|
||||
"only_group_admin_or_managers_can_delete_your_account_4": "",
|
||||
"only_group_admin_or_managers_can_delete_your_account_5": "",
|
||||
"only_group_admin_or_managers_can_delete_your_account_8": "",
|
||||
"only_importer_can_refresh": "",
|
||||
"open_action_menu": "",
|
||||
"open_advanced_reference_search": "",
|
||||
|
||||
@@ -60,7 +60,8 @@ export default function DropdownButton({
|
||||
|
||||
const managedUsersActive = getMeta('ol-managedUsersActive')
|
||||
const groupSSOActive = getMeta('ol-groupSSOActive')
|
||||
|
||||
const userId = getMeta('ol-user_id')
|
||||
const isUserGroupManager = getMeta('ol-isUserGroupManager')
|
||||
const userPending = user.invite
|
||||
const isGroupSSOLinked =
|
||||
!userPending && user.enrollment?.sso?.some(sso => sso.groupId === groupId)
|
||||
@@ -238,7 +239,11 @@ export default function DropdownButton({
|
||||
</MenuItemButton>
|
||||
)
|
||||
}
|
||||
if (isUserManaged && !user.isEntityAdmin) {
|
||||
if (
|
||||
isUserManaged &&
|
||||
!user.isEntityAdmin &&
|
||||
(!isUserGroupManager || userId !== user._id)
|
||||
) {
|
||||
buttons.push(
|
||||
<MenuItemButton
|
||||
key="delete-user-action"
|
||||
@@ -272,7 +277,7 @@ export default function DropdownButton({
|
||||
|
||||
if (buttons.length === 0) {
|
||||
buttons.push(
|
||||
<DropdownListItem>
|
||||
<DropdownListItem key="no-actions-available">
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
|
||||
@@ -147,6 +147,7 @@ export interface Meta {
|
||||
'ol-isRegisteredViaGoogle': boolean
|
||||
'ol-isRestrictedTokenMember': boolean
|
||||
'ol-isSaas': boolean
|
||||
'ol-isUserGroupManager': boolean
|
||||
'ol-itm_campaign': string
|
||||
'ol-itm_content': string
|
||||
'ol-itm_referrer': string
|
||||
@@ -268,6 +269,7 @@ export interface Meta {
|
||||
'ol-users': ManagedUser[]
|
||||
'ol-usersBestSubscription': ProjectDashboardSubscription | undefined
|
||||
'ol-usersEmail': string | undefined
|
||||
'ol-usersSubscription': { personal: boolean; group: boolean }
|
||||
'ol-validationStatus': ValidationStatus
|
||||
'ol-wikiEnabled': boolean
|
||||
'ol-writefullCssUrl': string
|
||||
|
||||
@@ -263,6 +263,8 @@
|
||||
"can_view_content": "Can view content",
|
||||
"cancel": "Cancel",
|
||||
"cancel_add_on": "Cancel add-on",
|
||||
"cancel_any_existing_subscriptions": "Cancel any existing subscriptions. <0>This can be managed from the Subscription page.</0>",
|
||||
"cancel_any_existing_subscriptions_and_leave_any_group_subscriptions": "Cancel any existing subscriptions, and leave any group subscriptions other than the one managing your account. <0>This can be managed from the Subscription page.</0>",
|
||||
"cancel_anytime": "We’re confident that you’ll love __appName__, but if not, you can cancel anytime and request your money back, hassle free, within 30 days.",
|
||||
"cancel_my_account": "Cancel my subscription",
|
||||
"cancel_my_subscription": "Cancel my subscription",
|
||||
@@ -1498,7 +1500,7 @@
|
||||
"only_group_admin_or_managers_can_delete_your_account_2": "Only your group admin or group managers will be able to delete your account.",
|
||||
"only_group_admin_or_managers_can_delete_your_account_3": "Your group admin and group managers will be able to reassign ownership of your projects to another group member.",
|
||||
"only_group_admin_or_managers_can_delete_your_account_4": "Once you have become a managed user, you cannot change back. <0>Learn more about managed Overleaf accounts.</0>",
|
||||
"only_group_admin_or_managers_can_delete_your_account_5": "For more information, see the \"Managed Accounts\" section in our terms of use, which you agree to by clicking Accept invitation",
|
||||
"only_group_admin_or_managers_can_delete_your_account_8": "We’ll cancel the renewal of your subscription, reach out to Support to request a pro-rata refund. Your individual subscription will be terminated when your account becomes managed.",
|
||||
"only_importer_can_refresh": "Only the person who originally imported this __provider__ file can refresh it.",
|
||||
"open_action_menu": "Open __name__ action menu",
|
||||
"open_advanced_reference_search": "Open advanced reference search",
|
||||
|
||||
@@ -175,6 +175,7 @@ describe('DropdownButton', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-users', [user])
|
||||
win.metaAttributesCache.set('ol-isUserGroupManager', true)
|
||||
})
|
||||
mountDropDownComponent(user, subscriptionId)
|
||||
})
|
||||
@@ -637,6 +638,7 @@ describe('DropdownButton', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-users', [user])
|
||||
win.metaAttributesCache.set('ol-isUserGroupManager', true)
|
||||
})
|
||||
mountDropDownComponent(user, subscriptionId)
|
||||
})
|
||||
@@ -687,6 +689,7 @@ describe('DropdownButton', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-users', [user])
|
||||
win.metaAttributesCache.set('ol-isUserGroupManager', true)
|
||||
})
|
||||
mountDropDownComponent(user, subscriptionId)
|
||||
})
|
||||
|
||||
@@ -13,6 +13,11 @@ describe('Subscription Locator Tests', function () {
|
||||
exec: sinon.stub().resolves(),
|
||||
}),
|
||||
find: sinon.stub().returns({
|
||||
populate: sinon.stub().returns({
|
||||
populate: sinon.stub().returns({
|
||||
exec: sinon.stub().resolves([]),
|
||||
}),
|
||||
}),
|
||||
exec: sinon.stub().resolves(),
|
||||
}),
|
||||
}
|
||||
@@ -77,4 +82,110 @@ describe('Subscription Locator Tests', function () {
|
||||
subscription.should.equal(this.subscription)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getUserSubscriptionStatus', function () {
|
||||
it('should return no active personal or group subscription when no user is passed', async function () {
|
||||
const subscriptionStatus =
|
||||
await this.SubscriptionLocator.promises.getUserSubscriptionStatus(
|
||||
undefined
|
||||
)
|
||||
expect(subscriptionStatus).to.deep.equal({
|
||||
personal: false,
|
||||
group: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should return no active personal or group subscription when the user has no subscription', async function () {
|
||||
const subscriptionStatus =
|
||||
await this.SubscriptionLocator.promises.getUserSubscriptionStatus(
|
||||
this.user._id
|
||||
)
|
||||
expect(subscriptionStatus).to.deep.equal({
|
||||
personal: false,
|
||||
group: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should return active personal subscription', async function () {
|
||||
this.Subscription.findOne.returns({
|
||||
exec: sinon.stub().resolves({
|
||||
recurlyStatus: {
|
||||
state: 'active',
|
||||
},
|
||||
}),
|
||||
})
|
||||
const subscriptionStatus =
|
||||
await this.SubscriptionLocator.promises.getUserSubscriptionStatus(
|
||||
this.user._id
|
||||
)
|
||||
expect(subscriptionStatus).to.deep.equal({ personal: true, group: false })
|
||||
})
|
||||
|
||||
it('should return active group subscription when member of a group plan', async function () {
|
||||
this.Subscription.find.returns({
|
||||
populate: sinon.stub().returns({
|
||||
populate: sinon.stub().returns({
|
||||
exec: sinon.stub().resolves([
|
||||
{
|
||||
recurlyStatus: {
|
||||
state: 'active',
|
||||
},
|
||||
groupPlan: true,
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
const subscriptionStatus =
|
||||
await this.SubscriptionLocator.promises.getUserSubscriptionStatus(
|
||||
this.user._id
|
||||
)
|
||||
expect(subscriptionStatus).to.deep.equal({ personal: false, group: true })
|
||||
})
|
||||
|
||||
it('should return active group subscription when owner of a group plan', async function () {
|
||||
this.Subscription.findOne.returns({
|
||||
exec: sinon.stub().resolves({
|
||||
recurlyStatus: {
|
||||
state: 'active',
|
||||
},
|
||||
groupPlan: true,
|
||||
}),
|
||||
})
|
||||
const subscriptionStatus =
|
||||
await this.SubscriptionLocator.promises.getUserSubscriptionStatus(
|
||||
this.user._id
|
||||
)
|
||||
expect(subscriptionStatus).to.deep.equal({ personal: false, group: true })
|
||||
})
|
||||
|
||||
it('should return active personal and group subscription when has personal subscription and member of a group', async function () {
|
||||
this.Subscription.find.returns({
|
||||
populate: sinon.stub().returns({
|
||||
populate: sinon.stub().returns({
|
||||
exec: sinon.stub().resolves([
|
||||
{
|
||||
recurlyStatus: {
|
||||
state: 'active',
|
||||
},
|
||||
groupPlan: true,
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
this.Subscription.findOne.returns({
|
||||
exec: sinon.stub().resolves({
|
||||
recurlyStatus: {
|
||||
state: 'active',
|
||||
},
|
||||
}),
|
||||
})
|
||||
const subscriptionStatus =
|
||||
await this.SubscriptionLocator.promises.getUserSubscriptionStatus(
|
||||
this.user._id
|
||||
)
|
||||
expect(subscriptionStatus).to.deep.equal({ personal: true, group: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -184,6 +184,7 @@ describe('UserMembershipController', function () {
|
||||
expect(viewParams.users).to.deep.equal(ctx.users)
|
||||
expect(viewParams.groupSize).to.equal(ctx.subscription.membersLimit)
|
||||
expect(viewParams.managedUsersActive).to.equal(true)
|
||||
expect(viewParams.isUserGroupManager).to.equal(false)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user