-
-
- {t('invited_to_join')}
-
- {requireAcceptData.projectName || 'This project'}
-
+
+
+
+ {t(
+ 'your_name_and_email_address_will_be_visible_to_the_project_owner_and_other_editors'
+ )}
+
+
- {user && (
-
-
-
- {t('accepting_invite_as')} {user.email}
-
-
-
- )}
-
-
-
- sendPostRequest(true)}
- >
- {t('join_project')}
-
-
+
+
+ sendPostRequest(true)}
+ >
+ {t('ok_join_project')}
+
diff --git a/services/web/frontend/js/features/token-access/components/token-access-root.tsx b/services/web/frontend/js/features/token-access/components/token-access-root.tsx
index 1e8865c90f..b38b3fdab4 100644
--- a/services/web/frontend/js/features/token-access/components/token-access-root.tsx
+++ b/services/web/frontend/js/features/token-access/components/token-access-root.tsx
@@ -99,13 +99,9 @@ function TokenAccessRoot() {
// We don't want the full-size div and back link(?) on
// the new page, but we do this so the original page
- // doesn't change. When tearing down we can clean up
- // the DOM in the main return
- if (
- mode === 'requireAccept' &&
- requireAcceptData &&
- requireAcceptData.linkSharingChanges
- ) {
+ // doesn't change.
+ // TODO: clean up the DOM in the main return
+ if (mode === 'requireAccept' && requireAcceptData) {
return (
)}
-
- {mode === 'requireAccept' && requireAcceptData && (
-
- )}
)
}
diff --git a/services/web/frontend/js/shared/hooks/use-viewer-permissions.ts b/services/web/frontend/js/shared/hooks/use-viewer-permissions.ts
index aca2fede22..5f6e7f1f72 100644
--- a/services/web/frontend/js/shared/hooks/use-viewer-permissions.ts
+++ b/services/web/frontend/js/shared/hooks/use-viewer-permissions.ts
@@ -1,12 +1,8 @@
import { useEditorContext } from '../context/editor-context'
-import getMeta from '@/utils/meta'
function useViewerPermissions() {
const { permissionsLevel } = useEditorContext()
-
- const hasViewerPermissions =
- getMeta('ol-linkSharingWarning') && permissionsLevel === 'readOnly'
- return hasViewerPermissions
+ return permissionsLevel === 'readOnly'
}
export default useViewerPermissions
diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts
index 177d596cfd..b8c00d4adc 100644
--- a/services/web/frontend/js/utils/meta.ts
+++ b/services/web/frontend/js/utils/meta.ts
@@ -136,8 +136,6 @@ export interface Meta {
'ol-learnedWords': string[]
'ol-legacyEditorThemes': string[]
'ol-licenseQuantity': number | undefined
- 'ol-linkSharingEnforcement': boolean
- 'ol-linkSharingWarning': boolean
'ol-loadingText': string
'ol-managedGroupSubscriptions': ManagedGroupSubscription[]
'ol-managedInstitutions': ManagedInstitution[]
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 41b773d7e4..532fb398d4 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1048,7 +1048,6 @@
"invited_to_group_login_benefits": "As part of this group, you’ll have access to __appName__ premium features such as additional collaborators, greater maximum compile time, and real-time track changes.",
"invited_to_group_register": "To accept __inviterName__’s invitation you’ll need to create an account.",
"invited_to_group_register_benefits": "__appName__ is a collaborative online LaTeX editor, with thousands of ready-to-use templates and an array of LaTeX learning resources to help you get started.",
- "invited_to_join": "You have been invited to join",
"inviting": "Inviting",
"ip_address": "IP Address",
"is_email_affiliated": "Is your email affiliated with an institution? ",
@@ -2364,7 +2363,6 @@
"upgrade_my_plan": "Upgrade my plan",
"upgrade_now": "Upgrade now",
"upgrade_summary": "Upgrade summary",
- "upgrade_to_add_more_editors": "Upgrade to add more editors to your project",
"upgrade_to_add_more_editors_and_access_collaboration_features": "Upgrade to add more editors and access collaboration features like track changes and full project history.",
"upgrade_to_get_feature": "Upgrade to get __feature__, plus:",
"upgrade_to_track_changes": "Upgrade to track changes",
@@ -2519,8 +2517,6 @@
"you_can_manage_your_reference_manager_integrations_from_your_account_settings_page": "You can manage your reference manager integrations from your <0>account settings page0>.",
"you_can_now_enable_sso": "You can now enable SSO on your Group settings page.",
"you_can_now_log_in_sso": "You can now log in through your institution and if eligible you will receive <0>__appName__ Professional features0>.",
- "you_can_only_add_n_people_to_edit_a_project": "You can only add __count__ person to edit a project with you on your current plan. Upgrade to add more.",
- "you_can_only_add_n_people_to_edit_a_project_plural": "You can only add __count__ people to edit a project with you on your current plan. Upgrade to add more.",
"you_can_opt_in_and_out_of_the_program_at_any_time_on_this_page": "You can <0>opt in and out0> of the program at any time on this page",
"you_can_request_a_maximum_of_limit_fixes_per_day": "You can request a maximum of __limit__ fixes per day. Please try again tomorrow.",
"you_can_select_or_invite": "You can select or invite __count__ editor on your current plan, or upgrade to get more.",
diff --git a/services/web/test/acceptance/src/TokenAccessTests.mjs b/services/web/test/acceptance/src/TokenAccessTests.mjs
index 96d10c6414..41bcc0c3af 100644
--- a/services/web/test/acceptance/src/TokenAccessTests.mjs
+++ b/services/web/test/acceptance/src/TokenAccessTests.mjs
@@ -5,8 +5,6 @@ import request from './helpers/request.js'
import settings from '@overleaf/settings'
import { db } from '../../../app/src/infrastructure/mongodb.js'
import expectErrorResponse from './helpers/expectErrorResponse.mjs'
-import SplitTestHandler from '../../../app/src/Features/SplitTests/SplitTestHandler.js'
-import sinon from 'sinon'
const tryEditorAccess = (user, projectId, test, callback) =>
async.series(
@@ -115,23 +113,6 @@ const tryReadOnlyTokenAccept = (
)
}
-const tryReadAndWriteTokenAccept = (
- user,
- token,
- testPageLoad,
- testFormPost,
- callback
-) => {
- _doTryTokenAccept(
- `/${token}`,
- user,
- token,
- testPageLoad,
- testFormPost,
- callback
- )
-}
-
const _doTryTokenAccept = (
url,
user,
@@ -831,701 +812,191 @@ describe('TokenAccess', function () {
})
})
- describe('read-and-write token', function () {
+ describe('anonymous read-and-write token, disabled (feature is deprecated)', function () {
beforeEach(function (done) {
- this.projectName = `token-rw-test${Math.random()}`
- this.owner.createProject(this.projectName, (err, projectId) => {
- if (err != null) {
- return done(err)
- }
- this.projectId = projectId
- this.owner.makeTokenBased(this.projectId, err => {
+ this.owner.createProject(
+ `token-anon-rw-test${Math.random()}`,
+ (err, projectId) => {
if (err != null) {
return done(err)
}
- this.owner.getProject(this.projectId, (err, project) => {
+ this.projectId = projectId
+ this.owner.makeTokenBased(this.projectId, err => {
if (err != null) {
return done(err)
}
- this.tokens = project.tokens
- done()
+ this.owner.getProject(this.projectId, (err, project) => {
+ if (err != null) {
+ return done(err)
+ }
+ this.tokens = project.tokens
+ done()
+ })
})
- })
- })
+ }
+ )
})
- it('should allow the user to access project via read-and-write token url', function (done) {
+ it('should not allow the user to access read-and-write token', function (done) {
async.series(
[
- // deny access before the token is used
cb =>
tryEditorAccess(
- this.other1,
+ this.anon,
this.projectId,
expectErrorResponse.restricted.html,
cb
),
- // try token
cb =>
tryReadAndWriteTokenAccess(
- this.other1,
+ this.anon,
this.tokens.readAndWrite,
(response, body) => {
expect(response.statusCode).to.equal(200)
},
(response, body) => {
expect(response.statusCode).to.equal(200)
- expect(body.requireAccept.projectName).to.equal(
- this.projectName
- )
+ expect(body).to.deep.equal({
+ redirect: '/restricted',
+ anonWriteAccessDenied: true,
+ })
},
cb
),
- // deny access before the token is accepted
+ cb =>
+ tryAnonContentAccess(
+ this.anon,
+ this.projectId,
+ this.tokens.readAndWrite,
+ (response, body) => {
+ expect(response.statusCode).to.equal(403)
+ expect(body).to.equal('Forbidden')
+ },
+ cb
+ ),
+ cb =>
+ this.anon.login((err, response, body) => {
+ expect(err).to.not.exist
+ expect(response.statusCode).to.equal(200)
+ expect(body.redir).to.equal(`/${this.tokens.readAndWrite}`)
+ cb()
+ }),
+ ],
+ done
+ )
+ })
+
+ it('should deny access to access tokens', function (done) {
+ tryFetchProjectTokens(this.anon, this.projectId, (error, response) => {
+ expect(error).to.equal(null)
+ expect(response.statusCode).to.equal(403)
+ done()
+ })
+ })
+
+ it('should require login if project does not exist', function (done) {
+ async.series(
+ [
+ // delete project
+ cb => {
+ this.owner.deleteProject(this.projectId, cb)
+ },
+ cb =>
+ tryReadAndWriteTokenAccess(
+ this.anon,
+ this.tokens.readAndWrite,
+ (response, body) => {
+ expect(response.statusCode).to.equal(200)
+ },
+ (response, body) => {
+ expect(response.statusCode).to.equal(200)
+ expect(body).to.deep.equal({
+ redirect: '/restricted',
+ anonWriteAccessDenied: true,
+ })
+ },
+ cb
+ ),
+ cb =>
+ this.anon.login((err, response, body) => {
+ expect(err).to.not.exist
+ expect(response.statusCode).to.equal(200)
+ expect(body.redir).to.equal(`/${this.tokens.readAndWrite}`)
+ cb()
+ }),
+ ],
+ done
+ )
+ })
+
+ it('should save URL hash in redirect', function (done) {
+ const urlFragment = '#123456'
+ const tokenWithUrlFragment = `${this.tokens.readAndWrite}${urlFragment}`
+
+ async.series(
+ [
cb =>
tryEditorAccess(
- this.other1,
+ this.anon,
this.projectId,
expectErrorResponse.restricted.html,
cb
),
- // accept token
cb =>
- tryReadAndWriteTokenAccept(
- this.other1,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body.redirect).to.equal(`/project/${this.projectId}`)
- expect(body.tokenAccessGranted).to.equal('readAndWrite')
- },
- cb
- ),
- cb =>
- tryEditorAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- cb
- ),
- cb =>
- tryContentAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(body.privilegeLevel).to.equal('readAndWrite')
- expect(body.isRestrictedUser).to.equal(false)
- expect(body.isTokenMember).to.equal(true)
- expect(body.isInvitedMember).to.equal(false)
- expect(body.project.owner).to.have.all.keys(
- '_id',
- 'email',
- 'first_name',
- 'last_name',
- 'privileges',
- 'signUpDate'
- )
- },
- cb
- ),
- ],
- done
- )
- })
-
- it('fetching access tokens returns an empty object', function (done) {
- async.series(
- [
- cb =>
- tryReadAndWriteTokenAccept(
- this.other1,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body.redirect).to.equal(`/project/${this.projectId}`)
- expect(body.tokenAccessGranted).to.equal('readAndWrite')
- },
- cb
- ),
- cb => {
- tryFetchProjectTokens(
- this.other1,
- this.projectId,
- (error, response, body) => {
- expect(error).to.equal(null)
- expect(response.statusCode).to.equal(200)
- expect(body).to.deep.equal({})
- cb()
- }
- )
- },
- ],
- done
- )
- })
-
- describe('upgrading from a read-only token', function () {
- beforeEach(function (done) {
- this.owner.createProject(
- `token-rw-upgrade-test${Math.random()}`,
- (err, projectId) => {
- if (err != null) {
- return done(err)
- }
- this.projectId = projectId
- this.owner.makeTokenBased(this.projectId, err => {
- if (err != null) {
- return done(err)
- }
- this.owner.getProject(this.projectId, (err, project) => {
- if (err != null) {
- return done(err)
+ this.anon.request.get(
+ tokenWithUrlFragment,
+ (err, response, body) => {
+ if (err) {
+ return cb(err)
}
- this.tokens = project.tokens
- done()
- })
- })
- }
- )
- })
+ expect(response.statusCode).to.equal(200)
- it('should allow user to access project via read-only, then upgrade to read-write', function (done) {
- async.series(
- [
- // deny access before the token is used
- cb =>
- tryEditorAccess(
- this.other1,
- this.projectId,
- expectErrorResponse.restricted.html,
- cb
- ),
- cb => {
- // use read-only token
- tryReadOnlyTokenAccept(
- this.other1,
- this.tokens.readOnly,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body.redirect).to.equal(`/project/${this.projectId}`)
- expect(body.tokenAccessGranted).to.equal('readOnly')
- },
- cb
- )
- },
- cb => {
- tryEditorAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- cb
- )
- },
- cb => {
- // allow content access read-only
- tryContentAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(body.privilegeLevel).to.equal('readOnly')
- expect(body.isRestrictedUser).to.equal(true)
- expect(body.isTokenMember).to.equal(true)
- expect(body.isInvitedMember).to.equal(false)
- expect(body.project.owner).to.have.keys('_id')
- expect(body.project.owner).to.not.have.any.keys(
- 'email',
- 'first_name',
- 'last_name'
- )
- },
- cb
- )
- },
- //
- // Then switch to read-write token
- //
- cb =>
- tryReadAndWriteTokenAccept(
- this.other1,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body.redirect).to.equal(`/project/${this.projectId}`)
- expect(body.tokenAccessGranted).to.equal('readAndWrite')
- },
- cb
- ),
- cb =>
- tryEditorAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- cb
- ),
- cb =>
- tryContentAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(body.privilegeLevel).to.equal('readAndWrite')
- expect(body.isRestrictedUser).to.equal(false)
- expect(body.isTokenMember).to.equal(true)
- expect(body.isInvitedMember).to.equal(false)
- expect(body.project.owner).to.have.all.keys(
- '_id',
- 'email',
- 'first_name',
- 'last_name',
- 'privileges',
- 'signUpDate'
- )
- },
- cb
- ),
- ],
- done
- )
- })
- })
-
- describe('made private again', function () {
- beforeEach(function (done) {
- this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
- })
-
- it('should deny access to project', function (done) {
- async.series(
- [
- cb => {
- tryEditorAccess(
- this.other1,
- this.projectId,
- (response, body) => {},
- cb
- )
- },
- cb => {
- tryReadAndWriteTokenAccess(
- this.other1,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(404)
- },
- cb
- )
- },
- cb => {
- tryEditorAccess(
- this.other1,
- this.projectId,
- expectErrorResponse.restricted.html,
- cb
- )
- },
- cb => {
- tryContentAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(403)
- expect(body).to.equal('Forbidden')
- },
- cb
- )
- },
- ],
- done
- )
- })
-
- it('should deny access to access tokens', function (done) {
- tryFetchProjectTokens(
- this.other1,
- this.projectId,
- (error, response) => {
- expect(error).to.equal(null)
- expect(response.statusCode).to.equal(403)
- done()
- }
- )
- })
+ this.anon.request.post(
+ `${this.tokens.readAndWrite}/grant`,
+ {
+ json: {
+ token: this.tokens.readAndWrite,
+ tokenHashPrefix: urlFragment,
+ },
+ },
+ (err, response, body) => {
+ if (err) {
+ return cb(err)
+ }
+ expect(response.statusCode).to.equal(200)
+ expect(body).to.deep.equal({
+ redirect: '/restricted',
+ anonWriteAccessDenied: true,
+ })
+ cb()
+ }
+ )
+ }
+ ),
+ cb =>
+ tryAnonContentAccess(
+ this.anon,
+ this.projectId,
+ this.tokens.readAndWrite,
+ (response, body) => {
+ expect(response.statusCode).to.equal(403)
+ expect(body).to.equal('Forbidden')
+ },
+ cb
+ ),
+ cb =>
+ this.anon.login((err, response, body) => {
+ expect(err).to.not.exist
+ expect(response.statusCode).to.equal(200)
+ expect(body.redir).to.equal(`/${tokenWithUrlFragment}`)
+ cb()
+ }),
+ ],
+ done
+ )
})
})
- if (!settings.allowAnonymousReadAndWriteSharing) {
- describe('anonymous read-and-write token, disabled', function () {
- beforeEach(function (done) {
- this.owner.createProject(
- `token-anon-rw-test${Math.random()}`,
- (err, projectId) => {
- if (err != null) {
- return done(err)
- }
- this.projectId = projectId
- this.owner.makeTokenBased(this.projectId, err => {
- if (err != null) {
- return done(err)
- }
- this.owner.getProject(this.projectId, (err, project) => {
- if (err != null) {
- return done(err)
- }
- this.tokens = project.tokens
- done()
- })
- })
- }
- )
- })
-
- it('should not allow the user to access read-and-write token', function (done) {
- async.series(
- [
- cb =>
- tryEditorAccess(
- this.anon,
- this.projectId,
- expectErrorResponse.restricted.html,
- cb
- ),
- cb =>
- tryReadAndWriteTokenAccess(
- this.anon,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body).to.deep.equal({
- redirect: '/restricted',
- anonWriteAccessDenied: true,
- })
- },
- cb
- ),
- cb =>
- tryAnonContentAccess(
- this.anon,
- this.projectId,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(403)
- expect(body).to.equal('Forbidden')
- },
- cb
- ),
- cb =>
- this.anon.login((err, response, body) => {
- expect(err).to.not.exist
- expect(response.statusCode).to.equal(200)
- expect(body.redir).to.equal(`/${this.tokens.readAndWrite}`)
- cb()
- }),
- ],
- done
- )
- })
-
- it('should deny access to access tokens', function (done) {
- tryFetchProjectTokens(this.anon, this.projectId, (error, response) => {
- expect(error).to.equal(null)
- expect(response.statusCode).to.equal(403)
- done()
- })
- })
-
- it('should require login if project does not exist', function (done) {
- async.series(
- [
- // delete project
- cb => {
- this.owner.deleteProject(this.projectId, cb)
- },
- cb =>
- tryReadAndWriteTokenAccess(
- this.anon,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body).to.deep.equal({
- redirect: '/restricted',
- anonWriteAccessDenied: true,
- })
- },
- cb
- ),
- cb =>
- this.anon.login((err, response, body) => {
- expect(err).to.not.exist
- expect(response.statusCode).to.equal(200)
- expect(body.redir).to.equal(`/${this.tokens.readAndWrite}`)
- cb()
- }),
- ],
- done
- )
- })
-
- it('should save URL hash in redirect', function (done) {
- const urlFragment = '#123456'
- const tokenWithUrlFragment = `${this.tokens.readAndWrite}${urlFragment}`
-
- async.series(
- [
- cb =>
- tryEditorAccess(
- this.anon,
- this.projectId,
- expectErrorResponse.restricted.html,
- cb
- ),
- cb =>
- this.anon.request.get(
- tokenWithUrlFragment,
- (err, response, body) => {
- if (err) {
- return cb(err)
- }
- expect(response.statusCode).to.equal(200)
-
- this.anon.request.post(
- `${this.tokens.readAndWrite}/grant`,
- {
- json: {
- token: this.tokens.readAndWrite,
- tokenHashPrefix: urlFragment,
- },
- },
- (err, response, body) => {
- if (err) {
- return cb(err)
- }
- expect(response.statusCode).to.equal(200)
- expect(body).to.deep.equal({
- redirect: '/restricted',
- anonWriteAccessDenied: true,
- })
- cb()
- }
- )
- }
- ),
- cb =>
- tryAnonContentAccess(
- this.anon,
- this.projectId,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(403)
- expect(body).to.equal('Forbidden')
- },
- cb
- ),
- cb =>
- this.anon.login((err, response, body) => {
- expect(err).to.not.exist
- expect(response.statusCode).to.equal(200)
- expect(body.redir).to.equal(`/${tokenWithUrlFragment}`)
- cb()
- }),
- ],
- done
- )
- })
- })
- } else {
- describe('anonymous read-and-write token, enabled', function () {
- beforeEach(function (done) {
- this.owner.createProject(
- `token-anon-rw-test${Math.random()}`,
- (err, projectId) => {
- if (err != null) {
- return done(err)
- }
- this.projectId = projectId
- this.owner.makeTokenBased(this.projectId, err => {
- if (err != null) {
- return done(err)
- }
- this.owner.getProject(this.projectId, (err, project) => {
- if (err != null) {
- return done(err)
- }
- this.tokens = project.tokens
- done()
- })
- })
- }
- )
- })
-
- it('should allow the user to access project via read-and-write token url', function (done) {
- async.series(
- [
- cb =>
- tryEditorAccess(
- this.anon,
- this.projectId,
- expectErrorResponse.restricted.html,
- cb
- ),
- cb =>
- tryReadAndWriteTokenAccess(
- this.anon,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body.redirect).to.equal(`/project/${this.projectId}`)
- expect(body.grantAnonymousAccess).to.equal('readAndWrite')
- },
- cb
- ),
- cb =>
- tryEditorAccess(
- this.anon,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- cb
- ),
- cb =>
- tryAnonContentAccess(
- this.anon,
- this.projectId,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(body.privilegeLevel).to.equal('readAndWrite')
- },
- cb
- ),
- ],
- done
- )
- })
-
- describe('made private again', function () {
- beforeEach(function (done) {
- this.owner.makePrivate(this.projectId, () => setTimeout(done, 1000))
- })
-
- it('should not allow the user to access read-and-write token', function (done) {
- async.series(
- [
- cb =>
- tryEditorAccess(
- this.anon,
- this.projectId,
- expectErrorResponse.restricted.html,
- cb
- ),
- cb =>
- tryReadAndWriteTokenAccess(
- this.anon,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(404)
- },
- cb
- ),
- cb =>
- tryEditorAccess(
- this.anon,
- this.projectId,
- expectErrorResponse.restricted.html,
- cb
- ),
- cb =>
- tryAnonContentAccess(
- this.anon,
- this.projectId,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(403)
- expect(body).to.equal('Forbidden')
- },
- cb
- ),
- ],
- done
- )
- })
-
- it('should deny access to access tokens', function (done) {
- tryFetchProjectTokens(
- this.anon,
- this.projectId,
- (error, response) => {
- expect(error).to.equal(null)
- expect(response.statusCode).to.equal(403)
- done()
- }
- )
- })
- })
-
- it('should 404 if project does not exist', function (done) {
- async.series(
- [
- // delete project
- cb => {
- this.owner.deleteProject(this.projectId, cb)
- },
- cb =>
- tryReadAndWriteTokenAccess(
- this.anon,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body).to.deep.equal({
- v1Import: {
- status: 'mustLogin',
- },
- })
- },
- cb
- ),
- ],
- done
- )
- })
- })
- }
-
describe('private overleaf project', function () {
beforeEach(function (done) {
this.owner.createProject('overleaf-import', (err, projectId) => {
@@ -1826,19 +1297,7 @@ describe('TokenAccess', function () {
})
})
- describe('link sharing changes', function () {
- beforeEach(function () {
- this.getAssignmentForUser = sinon.stub(
- SplitTestHandler.promises,
- 'getAssignmentForUser'
- )
- this.getAssignmentForUser.resolves({ variant: 'default' })
- })
-
- afterEach(function () {
- this.getAssignmentForUser.restore()
- })
-
+ describe('sharing updates consent page for read-and-write token deprecation', function () {
describe('not a member of the project', function () {
beforeEach(function (done) {
this.projectName = `token-link-sharing-changes${Math.random()}`
@@ -1895,216 +1354,6 @@ describe('TokenAccess', function () {
})
})
- describe('read and write token member of project', function () {
- beforeEach(function (done) {
- this.projectName = `token-link-sharing-changes${Math.random()}`
- this.owner.createProject(this.projectName, (err, projectId) => {
- if (err != null) {
- return done(err)
- }
- this.projectId = projectId
- this.owner.makeTokenBased(this.projectId, err => {
- if (err != null) {
- return done(err)
- }
- this.owner.getProject(this.projectId, (err, project) => {
- if (err != null) {
- return done(err)
- }
- this.tokens = project.tokens
- // must do token accept before split test enabled
- // otherwise would be automatically added to named collaborators
- tryReadAndWriteTokenAccept(
- this.other1,
- this.tokens.readAndWrite,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- expect(body.redirect).to.equal(`/project/${this.projectId}`)
- expect(body.tokenAccessGranted).to.equal('readAndWrite')
- },
- done
- )
- })
- })
- })
- })
-
- describe('link sharing changes test not active', function () {
- it('should redirect to project, same permissions as before', function (done) {
- async.series(
- [
- cb => {
- trySharingUpdatesPage(
- this.other1,
- this.projectId,
- expectRedirectToProject,
- cb
- )
- },
- cb => {
- trySharingUpdatesJoin(
- this.other1,
- this.projectId,
- expectRedirectToProject,
- cb
- )
- },
- cb => {
- trySharingUpdatesView(
- this.other1,
- this.projectId,
- expectRedirectToProject,
- cb
- )
- },
- cb => {
- tryContentAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(body.privilegeLevel).to.equal('readAndWrite')
- expect(body.isRestrictedUser).to.equal(false)
- expect(body.isTokenMember).to.equal(true)
- expect(body.isInvitedMember).to.equal(false)
- expect(body.project.owner).to.have.all.keys(
- '_id',
- 'email',
- 'first_name',
- 'last_name',
- 'privileges',
- 'signUpDate'
- )
- },
- cb
- )
- },
- cb => {
- tryEditorAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- cb
- )
- },
- ],
- done
- )
- })
- })
-
- describe('link sharing changes test is active', function () {
- beforeEach(function () {
- this.getAssignmentForUser.resolves({ variant: 'active' })
- })
- it('should show sharing updates page', function (done) {
- trySharingUpdatesPage(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- done
- )
- })
-
- it('should allow join to named collaborator', function (done) {
- async.series(
- [
- cb => {
- trySharingUpdatesJoin(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(204)
- },
- cb
- )
- },
- cb => {
- tryContentAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(body.privilegeLevel).to.equal('readAndWrite')
- expect(body.isRestrictedUser).to.equal(false)
- expect(body.isTokenMember).to.equal(false)
- expect(body.isInvitedMember).to.equal(true) // now collaborator
- expect(body.project.owner).to.have.all.keys(
- '_id',
- 'email',
- 'first_name',
- 'last_name',
- 'privileges',
- 'signUpDate'
- )
- },
- cb
- )
- },
- cb => {
- tryEditorAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- cb
- )
- },
- ],
- done
- )
- })
-
- it('should allow move to anonymous viewer', function (done) {
- async.series(
- [
- cb => {
- trySharingUpdatesView(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(204)
- },
- cb
- )
- },
- cb => {
- tryContentAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(body.privilegeLevel).to.equal('readOnly')
- expect(body.isRestrictedUser).to.equal(true)
- expect(body.isTokenMember).to.equal(true)
- expect(body.isInvitedMember).to.equal(false)
- expect(body.project.owner).to.have.keys('_id')
- },
- cb
- )
- },
- cb => {
- tryEditorAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- cb
- )
- },
- ],
- done
- )
- })
- })
- })
-
describe('read-only token member of project', function () {
beforeEach(function (done) {
this.projectName = `token-link-sharing-changes${Math.random()}`
@@ -2140,72 +1389,66 @@ describe('TokenAccess', function () {
})
})
- describe('link sharing changes test is active', function () {
- beforeEach(function () {
- this.getAssignmentForUser.resolves({ variant: 'active' })
- })
-
- it('should redirect to project, same view permissions as before', function (done) {
- async.series(
- [
- cb => {
- trySharingUpdatesPage(
- this.other1,
- this.projectId,
- expectRedirectToProject,
- cb
- )
- },
- cb => {
- trySharingUpdatesJoin(
- this.other1,
- this.projectId,
- expectRedirectToProject,
- cb
- )
- },
- cb => {
- trySharingUpdatesView(
- this.other1,
- this.projectId,
- expectRedirectToProject,
- cb
- )
- },
- cb => {
- // allow content access read-only
- tryContentAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(body.privilegeLevel).to.equal('readOnly')
- expect(body.isRestrictedUser).to.equal(true)
- expect(body.isTokenMember).to.equal(true)
- expect(body.isInvitedMember).to.equal(false)
- expect(body.project.owner).to.have.keys('_id')
- expect(body.project.owner).to.not.have.any.keys(
- 'email',
- 'first_name',
- 'last_name'
- )
- },
- cb
- )
- },
- cb => {
- tryEditorAccess(
- this.other1,
- this.projectId,
- (response, body) => {
- expect(response.statusCode).to.equal(200)
- },
- cb
- )
- },
- ],
- done
- )
- })
+ it('should redirect to project', function (done) {
+ async.series(
+ [
+ cb => {
+ trySharingUpdatesPage(
+ this.other1,
+ this.projectId,
+ expectRedirectToProject,
+ cb
+ )
+ },
+ cb => {
+ trySharingUpdatesJoin(
+ this.other1,
+ this.projectId,
+ expectRedirectToProject,
+ cb
+ )
+ },
+ cb => {
+ trySharingUpdatesView(
+ this.other1,
+ this.projectId,
+ expectRedirectToProject,
+ cb
+ )
+ },
+ cb => {
+ // allow content access read-only
+ tryContentAccess(
+ this.other1,
+ this.projectId,
+ (response, body) => {
+ expect(body.privilegeLevel).to.equal('readOnly')
+ expect(body.isRestrictedUser).to.equal(true)
+ expect(body.isTokenMember).to.equal(true)
+ expect(body.isInvitedMember).to.equal(false)
+ expect(body.project.owner).to.have.keys('_id')
+ expect(body.project.owner).to.not.have.any.keys(
+ 'email',
+ 'first_name',
+ 'last_name'
+ )
+ },
+ cb
+ )
+ },
+ cb => {
+ tryEditorAccess(
+ this.other1,
+ this.projectId,
+ (response, body) => {
+ expect(response.statusCode).to.equal(200)
+ },
+ cb
+ )
+ },
+ ],
+ done
+ )
})
})
})
diff --git a/services/web/test/frontend/components/token-access/token-access-page.spec.tsx b/services/web/test/frontend/components/token-access/token-access-page.spec.tsx
index 96def25aa8..107d71acf4 100644
--- a/services/web/test/frontend/components/token-access/token-access-page.spec.tsx
+++ b/services/web/test/frontend/components/token-access/token-access-page.spec.tsx
@@ -29,13 +29,11 @@ describe('
', function () {
expect(interception.request.body.confirmedByUser).to.be.false
})
- cy.get('h1').should(
+ cy.get('.link-sharing-invite-header').should(
'have.text',
- ['You have been invited to join', 'Test Project'].join('')
+ ['You’re joining', 'Test Project', 'as test@example.com'].join('')
)
- cy.contains('You are accepting this invite as test@example.com')
-
cy.intercept(
{ method: 'post', url, times: 1 },
{
@@ -47,7 +45,7 @@ describe('
', function () {
cy.stub(location, 'replace').as('replaceLocation')
- cy.findByRole('button', { name: 'Join Project' }).click()
+ cy.findByRole('button', { name: 'OK, join project' }).click()
cy.wait('@confirmedGrantRequest').then(interception => {
expect(interception.request.body.confirmedByUser).to.be.true
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsControllerTests.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsControllerTests.mjs
index 944f9af7b8..e93563ab95 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsControllerTests.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsControllerTests.mjs
@@ -304,89 +304,77 @@ describe('CollaboratorsController', function () {
)
})
- describe('when link-sharing-warning test active', function () {
+ describe('when setting privilege level to readAndWrite', function () {
beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.resolves({
- variant: 'active',
+ this.req.body = { privilegeLevel: 'readAndWrite' }
+ })
+
+ describe('when owner can add new edit collaborators', function () {
+ beforeEach(function () {
+ this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
+ true
+ )
+ })
+
+ it('should set privilege level after checking collaborators can be added', function (done) {
+ this.res.sendStatus = status => {
+ expect(status).to.equal(204)
+ expect(
+ this.LimitationsManager.promises.canAddXEditCollaborators
+ ).to.have.been.calledWith(this.projectId, 1)
+ done()
+ }
+ this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
})
})
- describe('when setting privilege level to readAndWrite', function () {
+ describe('when owner cannot add edit collaborators', function () {
beforeEach(function () {
- this.req.body = { privilegeLevel: 'readAndWrite' }
+ this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
+ false
+ )
})
- describe('when owner can add new edit collaborators', function () {
- beforeEach(function () {
- this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
- true
- )
- })
-
- it('should set privilege level after checking collaborators can be added', function (done) {
- this.res.sendStatus = status => {
- expect(status).to.equal(204)
- expect(
- this.LimitationsManager.promises.canAddXEditCollaborators
- ).to.have.been.calledWith(this.projectId, 1)
- done()
- }
- this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
- })
- })
-
- describe('when owner cannot add edit collaborators', function () {
- beforeEach(function () {
- this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
- false
- )
- })
-
- it('should return a 403 if trying to set a new edit collaborator', function (done) {
- this.HttpErrorHandler.forbidden = sinon.spy((req, res) => {
- expect(req).to.equal(this.req)
- expect(res).to.equal(this.res)
- expect(
- this.LimitationsManager.promises.canAddXEditCollaborators
- ).to.have.been.calledWith(this.projectId, 1)
- expect(
- this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
- ).to.not.have.been.called
- done()
- })
- this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
+ it('should return a 403 if trying to set a new edit collaborator', function (done) {
+ this.HttpErrorHandler.forbidden = sinon.spy((req, res) => {
+ expect(req).to.equal(this.req)
+ expect(res).to.equal(this.res)
+ expect(
+ this.LimitationsManager.promises.canAddXEditCollaborators
+ ).to.have.been.calledWith(this.projectId, 1)
+ expect(
+ this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
+ ).to.not.have.been.called
+ done()
})
+ this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
})
})
+ })
- describe('when setting privilege level to readOnly', function () {
+ describe('when setting privilege level to readOnly', function () {
+ beforeEach(function () {
+ this.req.body = { privilegeLevel: 'readOnly' }
+ })
+
+ describe('when owner cannot add edit collaborators', function () {
beforeEach(function () {
- this.req.body = { privilegeLevel: 'readOnly' }
+ this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
+ false
+ )
})
- describe('when owner cannot add edit collaborators', function () {
- beforeEach(function () {
- this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
- false
- )
- })
-
- it('should always allow setting a collaborator to viewer even if user cant add edit collaborators', function (done) {
- this.res.sendStatus = status => {
- expect(status).to.equal(204)
- expect(this.LimitationsManager.promises.canAddXEditCollaborators)
- .to.not.have.been.called
- expect(
- this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
- ).to.have.been.calledWith(
- this.projectId,
- this.user._id,
- 'readOnly'
- )
- done()
- }
- this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
- })
+ it('should always allow setting a collaborator to viewer even if user cant add edit collaborators', function (done) {
+ this.res.sendStatus = status => {
+ expect(status).to.equal(204)
+ expect(this.LimitationsManager.promises.canAddXEditCollaborators).to
+ .not.have.been.called
+ expect(
+ this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
+ ).to.have.been.calledWith(this.projectId, this.user._id, 'readOnly')
+ done()
+ }
+ this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
})
})
})
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.mjs
index b806d5c773..1a6b0a5b63 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.mjs
@@ -230,228 +230,18 @@ describe('CollaboratorsInviteController', function () {
})
})
- describe('when in link-sharing-warning test', function (done) {
- beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.resolves({
- variant: 'active',
- })
- })
-
- describe('when all goes well', function (done) {
- beforeEach(async function () {
- this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
- .stub()
- .resolves(true)
- this.CollaboratorsInviteController._checkRateLimit = sinon
- .stub()
- .resolves(true)
-
- await this.CollaboratorsInviteController.inviteToProject(
- this.req,
- this.res
- )
- })
-
- it('should produce json response', function () {
- this.res.json.callCount.should.equal(1)
- expect(this.res.json.firstCall.args[0]).to.deep.equal({
- invite: this.inviteReducedData,
- })
- })
-
- it('should have called canAddXEditCollaborators', function () {
- this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
- 1
- )
- this.LimitationsManager.promises.canAddXEditCollaborators
- .calledWith(this.projectId)
- .should.equal(true)
- })
-
- it('should have called _checkShouldInviteEmail', function () {
- this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
- 1
- )
-
- this.CollaboratorsInviteController._checkShouldInviteEmail
- .calledWith(this.targetEmail)
- .should.equal(true)
- })
-
- it('should have called inviteToProject', function () {
- this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
- 1
- )
- this.CollaboratorsInviteHandler.promises.inviteToProject
- .calledWith(
- this.projectId,
- this.currentUser,
- this.targetEmail,
- this.privileges
- )
- .should.equal(true)
- })
-
- it('should have called emitToRoom', function () {
- this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
- this.EditorRealTimeController.emitToRoom
- .calledWith(this.projectId, 'project:membership:changed')
- .should.equal(true)
- })
-
- it('adds a project audit log entry', function () {
- this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
- this.projectId,
- 'send-invite',
- this.currentUser._id,
- this.req.ip,
- {
- inviteId: this.invite._id,
- privileges: this.privileges,
- }
- )
- })
- })
-
- describe('when the user is not allowed to add more edit collaborators', function () {
- beforeEach(function () {
- this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
- false
- )
- })
-
- describe('readAndWrite collaborator', function () {
- beforeEach(function (done) {
- this.privileges = 'readAndWrite'
- this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
- .stub()
- .resolves(true)
- this.CollaboratorsInviteController._checkRateLimit = sinon
- .stub()
- .resolves(true)
- this.res.callback = () => done()
- this.CollaboratorsInviteController.inviteToProject(
- this.req,
- this.res,
- this.next
- )
- })
-
- it('should produce json response without an invite', function () {
- this.res.json.callCount.should.equal(1)
- expect(this.res.json.firstCall.args[0]).to.deep.equal({
- invite: null,
- })
- })
-
- it('should not have called _checkShouldInviteEmail', function () {
- this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
- 0
- )
- this.CollaboratorsInviteController._checkShouldInviteEmail
- .calledWith(this.currentUser, this.targetEmail)
- .should.equal(false)
- })
-
- it('should not have called inviteToProject', function () {
- this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
- 0
- )
- })
- })
-
- describe('readOnly collaborator (always allowed)', function () {
- beforeEach(function (done) {
- this.req.body = {
- email: this.targetEmail,
- privileges: (this.privileges = 'readOnly'),
- }
- this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
- .stub()
- .resolves(true)
- this.CollaboratorsInviteController._checkRateLimit = sinon
- .stub()
- .resolves(true)
- this.res.callback = () => done()
- this.CollaboratorsInviteController.inviteToProject(
- this.req,
- this.res,
- this.next
- )
- })
-
- it('should produce json response', function () {
- this.res.json.callCount.should.equal(1)
- expect(this.res.json.firstCall.args[0]).to.deep.equal({
- invite: this.inviteReducedData,
- })
- })
-
- it('should not have called canAddXEditCollaborators', function () {
- this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
- 0
- )
- })
-
- it('should have called _checkShouldInviteEmail', function () {
- this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
- 1
- )
- this.CollaboratorsInviteController._checkShouldInviteEmail
- .calledWith(this.targetEmail)
- .should.equal(true)
- })
-
- it('should have called inviteToProject', function () {
- this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
- 1
- )
- this.CollaboratorsInviteHandler.promises.inviteToProject
- .calledWith(
- this.projectId,
- this.currentUser,
- this.targetEmail,
- this.privileges
- )
- .should.equal(true)
- })
-
- it('should have called emitToRoom', function () {
- this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
- this.EditorRealTimeController.emitToRoom
- .calledWith(this.projectId, 'project:membership:changed')
- .should.equal(true)
- })
-
- it('adds a project audit log entry', function () {
- this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
- this.projectId,
- 'send-invite',
- this.currentUser._id,
- this.req.ip,
- {
- inviteId: this.invite._id,
- privileges: this.privileges,
- }
- )
- })
- })
- })
- })
-
describe('when all goes well', function (done) {
- beforeEach(function (done) {
+ beforeEach(async function () {
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
- this.res.callback = () => done()
- this.CollaboratorsInviteController.inviteToProject(
+
+ await this.CollaboratorsInviteController.inviteToProject(
this.req,
- this.res,
- this.next
+ this.res
)
})
@@ -462,11 +252,11 @@ describe('CollaboratorsInviteController', function () {
})
})
- it('should have called canAddXCollaborators', function () {
- this.LimitationsManager.promises.canAddXCollaborators.callCount.should.equal(
+ it('should have called canAddXEditCollaborators', function () {
+ this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
1
)
- this.LimitationsManager.promises.canAddXCollaborators
+ this.LimitationsManager.promises.canAddXEditCollaborators
.calledWith(this.projectId)
.should.equal(true)
})
@@ -475,6 +265,7 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
1
)
+
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.targetEmail)
.should.equal(true)
@@ -515,81 +306,128 @@ describe('CollaboratorsInviteController', function () {
})
})
- describe('when the user is not allowed to add more collaborators', function () {
- beforeEach(function (done) {
- this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
- .stub()
- .resolves(true)
- this.CollaboratorsInviteController._checkRateLimit = sinon
- .stub()
- .resolves(true)
- this.LimitationsManager.promises.canAddXCollaborators.resolves(false)
- this.res.callback = () => done()
- this.CollaboratorsInviteController.inviteToProject(
- this.req,
- this.res,
- this.next
+ describe('when the user is not allowed to add more edit collaborators', function () {
+ beforeEach(function () {
+ this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
+ false
)
})
- it('should produce json response without an invite', function () {
- this.res.json.callCount.should.equal(1)
- expect(this.res.json.firstCall.args[0]).to.deep.equal({ invite: null })
+ describe('readAndWrite collaborator', function () {
+ beforeEach(function (done) {
+ this.privileges = 'readAndWrite'
+ this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
+ .stub()
+ .resolves(true)
+ this.CollaboratorsInviteController._checkRateLimit = sinon
+ .stub()
+ .resolves(true)
+ this.res.callback = () => done()
+ this.CollaboratorsInviteController.inviteToProject(
+ this.req,
+ this.res,
+ this.next
+ )
+ })
+
+ it('should produce json response without an invite', function () {
+ this.res.json.callCount.should.equal(1)
+ expect(this.res.json.firstCall.args[0]).to.deep.equal({
+ invite: null,
+ })
+ })
+
+ it('should not have called _checkShouldInviteEmail', function () {
+ this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
+ 0
+ )
+ this.CollaboratorsInviteController._checkShouldInviteEmail
+ .calledWith(this.currentUser, this.targetEmail)
+ .should.equal(false)
+ })
+
+ it('should not have called inviteToProject', function () {
+ this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
+ 0
+ )
+ })
})
- it('should not have called _checkShouldInviteEmail', function () {
- this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
- 0
- )
- this.CollaboratorsInviteController._checkShouldInviteEmail
- .calledWith(this.currentUser, this.targetEmail)
- .should.equal(false)
- })
+ describe('readOnly collaborator (always allowed)', function () {
+ beforeEach(function (done) {
+ this.req.body = {
+ email: this.targetEmail,
+ privileges: (this.privileges = 'readOnly'),
+ }
+ this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
+ .stub()
+ .resolves(true)
+ this.CollaboratorsInviteController._checkRateLimit = sinon
+ .stub()
+ .resolves(true)
+ this.res.callback = () => done()
+ this.CollaboratorsInviteController.inviteToProject(
+ this.req,
+ this.res,
+ this.next
+ )
+ })
- it('should not have called inviteToProject', function () {
- this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
- 0
- )
- })
- })
+ it('should produce json response', function () {
+ this.res.json.callCount.should.equal(1)
+ expect(this.res.json.firstCall.args[0]).to.deep.equal({
+ invite: this.inviteReducedData,
+ })
+ })
- describe('when canAddXCollaborators produces an error', function () {
- beforeEach(function (done) {
- this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
- .stub()
- .resolves(true)
- this.CollaboratorsInviteController._checkRateLimit = sinon
- .stub()
- .resolves(true)
- this.LimitationsManager.promises.canAddXCollaborators.rejects(
- new Error('woops')
- )
- this.next.callsFake(() => done())
- this.CollaboratorsInviteController.inviteToProject(
- this.req,
- this.res,
- this.next
- )
- })
+ it('should not have called canAddXEditCollaborators', function () {
+ this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
+ 0
+ )
+ })
- it('should call next with an error', function () {
- this.next.callCount.should.equal(1)
- this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
- })
+ it('should have called _checkShouldInviteEmail', function () {
+ this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
+ 1
+ )
+ this.CollaboratorsInviteController._checkShouldInviteEmail
+ .calledWith(this.targetEmail)
+ .should.equal(true)
+ })
- it('should not have called _checkShouldInviteEmail', function () {
- this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
- 0
- )
- this.CollaboratorsInviteController._checkShouldInviteEmail
- .calledWith(this.currentUser, this.targetEmail)
- .should.equal(false)
- })
+ it('should have called inviteToProject', function () {
+ this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
+ 1
+ )
+ this.CollaboratorsInviteHandler.promises.inviteToProject
+ .calledWith(
+ this.projectId,
+ this.currentUser,
+ this.targetEmail,
+ this.privileges
+ )
+ .should.equal(true)
+ })
- it('should not have called inviteToProject', function () {
- this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
- 0
- )
+ it('should have called emitToRoom', function () {
+ this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
+ this.EditorRealTimeController.emitToRoom
+ .calledWith(this.projectId, 'project:membership:changed')
+ .should.equal(true)
+ })
+
+ it('adds a project audit log entry', function () {
+ this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
+ this.projectId,
+ 'send-invite',
+ this.currentUser._id,
+ this.req.ip,
+ {
+ inviteId: this.invite._id,
+ privileges: this.privileges,
+ }
+ )
+ })
})
})
@@ -617,11 +455,11 @@ describe('CollaboratorsInviteController', function () {
expect(this.next).to.have.been.calledWith(sinon.match.instanceOf(Error))
})
- it('should have called canAddXCollaborators', function () {
- this.LimitationsManager.promises.canAddXCollaborators.callCount.should.equal(
+ it('should have called canAddXEditCollaborators', function () {
+ this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
1
)
- this.LimitationsManager.promises.canAddXCollaborators
+ this.LimitationsManager.promises.canAddXEditCollaborators
.calledWith(this.projectId)
.should.equal(true)
})
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.mjs
index e52d2a0a17..1a20d28699 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.mjs
@@ -492,6 +492,9 @@ describe('CollaboratorsInviteHandler', function () {
this.CollaboratorsHandler.promises.addUserIdToProject.resolves()
this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification =
sinon.stub().resolves()
+ this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
+ true
+ )
this.ProjectInvite.deleteOne.returns({ exec: sinon.stub().resolves() })
this.call = async () => {
await this.CollaboratorsInviteHandler.promises.acceptInvite(
@@ -503,11 +506,8 @@ describe('CollaboratorsInviteHandler', function () {
})
describe('when all goes well', function () {
- it('should have called CollaboratorsHandler.addUserIdToProject', async function () {
+ it('should add readAndWrite invitees to the project as normal', async function () {
await this.call()
- this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal(
- 1
- )
this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
this.projectId,
this.sendingUserId,
@@ -546,55 +546,29 @@ describe('CollaboratorsInviteHandler', function () {
})
})
- describe('when link-sharing-enforcement is active', function () {
+ describe('when the project has no more edit collaborator slots', function () {
beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.resolves({
- variant: 'active',
- })
+ this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
+ false
+ )
})
- describe('when the project has no more edit collaborator slots', function () {
- beforeEach(function () {
- this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
- false
- )
- })
-
- it('should add readAndWrite invitees to the project as readOnly (pendingEditor) users', async function () {
- await this.call()
- this.ProjectAuditLogHandler.promises.addEntry.should.have.been.calledWith(
- this.projectId,
- 'editor-moved-to-pending',
- null,
- null,
- { userId: this.userId.toString() }
- )
- this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
- this.projectId,
- this.sendingUserId,
- this.userId,
- 'readOnly',
- { pendingEditor: true }
- )
- })
- })
-
- describe('when the project has available edit collaborator slots', function () {
- beforeEach(function () {
- this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
- true
- )
- })
-
- it('should add readAndWrite invitees to the project as normal', async function () {
- await this.call()
- this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
- this.projectId,
- this.sendingUserId,
- this.userId,
- this.fakeInvite.privileges
- )
- })
+ it('should add readAndWrite invitees to the project as readOnly (pendingEditor) users', async function () {
+ await this.call()
+ this.ProjectAuditLogHandler.promises.addEntry.should.have.been.calledWith(
+ this.projectId,
+ 'editor-moved-to-pending',
+ null,
+ null,
+ { userId: this.userId.toString() }
+ )
+ this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
+ this.projectId,
+ this.sendingUserId,
+ this.userId,
+ 'readOnly',
+ { pendingEditor: true }
+ )
})
})
diff --git a/services/web/test/unit/src/Project/ProjectControllerTests.js b/services/web/test/unit/src/Project/ProjectControllerTests.js
index 9591947eac..ef85b4c0ed 100644
--- a/services/web/test/unit/src/Project/ProjectControllerTests.js
+++ b/services/web/test/unit/src/Project/ProjectControllerTests.js
@@ -1031,108 +1031,56 @@ describe('ProjectController', function () {
})
})
- describe('link sharing changes active', function () {
+ describe('when user is a read write token member (and not already a named editor)', function () {
beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
- async (userId, test) => {
- if (test === 'link-sharing-warning') {
- return { variant: 'active' }
- }
- }
+ this.CollaboratorsGetter.promises.userIsTokenMember.resolves(true)
+ this.CollaboratorsGetter.promises.userIsReadWriteTokenMember.resolves(
+ true
+ )
+ this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject.resolves(
+ false
)
})
- describe('when user is a read write token member (and not already a named editor)', function () {
- beforeEach(function () {
- this.CollaboratorsGetter.promises.userIsTokenMember.resolves(true)
- this.CollaboratorsGetter.promises.userIsReadWriteTokenMember.resolves(
- true
- )
- this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject.resolves(
- false
- )
- })
-
- it('should redirect to the sharing-updates page', function (done) {
- this.res.redirect = url => {
- expect(url).to.equal(`/project/${this.project_id}/sharing-updates`)
- done()
- }
- this.ProjectController.loadEditor(this.req, this.res)
- })
- })
-
- describe('when user is a read write token member but also a named editor', function () {
- beforeEach(function () {
- this.CollaboratorsGetter.promises.userIsTokenMember.resolves(true)
- this.CollaboratorsGetter.promises.userIsReadWriteTokenMember.resolves(
- true
- )
- this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject.resolves(
- true
- )
- })
-
- it('should not redirect to the sharing-updates page, and should load the editor', function (done) {
- this.res.render = (pageName, opts) => {
- done()
- }
- this.ProjectController.loadEditor(this.req, this.res)
- })
+ it('should redirect to the sharing-updates page', function (done) {
+ this.res.redirect = url => {
+ expect(url).to.equal(`/project/${this.project_id}/sharing-updates`)
+ done()
+ }
+ this.ProjectController.loadEditor(this.req, this.res)
})
})
- describe('link sharing enforcement', function () {
- describe('when not active (default)', function () {
- beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
- async (userId, test) => {
- if (test === 'link-sharing-warning') {
- return { variant: 'active' }
- } else if (test === 'link-sharing-enforcement') {
- return { variant: 'default' }
- }
- }
- )
- })
-
- it('should not call the collaborator limit enforcement check', function (done) {
- this.res.render = (pageName, opts) => {
- this.Modules.promises.hooks.fire.should.not.have.been.calledWith(
- 'enforceCollaboratorLimit'
- )
- done()
- }
- this.ProjectController.loadEditor(this.req, this.res)
- })
+ describe('when user is a read write token member but also a named editor', function () {
+ beforeEach(function () {
+ this.CollaboratorsGetter.promises.userIsTokenMember.resolves(true)
+ this.CollaboratorsGetter.promises.userIsReadWriteTokenMember.resolves(
+ true
+ )
+ this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject.resolves(
+ true
+ )
})
- describe('when active', function () {
- beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
- async (userId, test) => {
- if (test === 'link-sharing-warning') {
- return { variant: 'active' }
- } else if (test === 'link-sharing-enforcement') {
- return { variant: 'active' }
- }
- }
- )
- })
-
- it('should call the collaborator limit enforcement check', function (done) {
- this.res.render = (pageName, opts) => {
- this.Modules.promises.hooks.fire.should.have.been.calledWith(
- 'enforceCollaboratorLimit',
- this.project_id
- )
- done()
- }
- this.ProjectController.loadEditor(this.req, this.res)
- })
+ it('should not redirect to the sharing-updates page, and should load the editor', function (done) {
+ this.res.render = (pageName, opts) => {
+ done()
+ }
+ this.ProjectController.loadEditor(this.req, this.res)
})
})
+ it('should call the collaborator limit enforcement check', function (done) {
+ this.res.render = (pageName, opts) => {
+ this.Modules.promises.hooks.fire.should.have.been.calledWith(
+ 'enforceCollaboratorLimit',
+ this.project_id
+ )
+ done()
+ }
+ this.ProjectController.loadEditor(this.req, this.res)
+ })
+
describe('chatEnabled flag', function () {
it('should be set to false when the feature is disabled', function (done) {
this.Features.hasFeature = sinon.stub().withArgs('chat').returns(false)
diff --git a/services/web/test/unit/src/TokenAccess/TokenAccessControllerTests.mjs b/services/web/test/unit/src/TokenAccess/TokenAccessControllerTests.mjs
index 8cc6726df4..f8b43a2446 100644
--- a/services/web/test/unit/src/TokenAccess/TokenAccessControllerTests.mjs
+++ b/services/web/test/unit/src/TokenAccess/TokenAccessControllerTests.mjs
@@ -192,10 +192,22 @@ describe('TokenAccessController', function () {
})
describe('grantTokenAccessReadAndWrite', function () {
- describe('normal case', function () {
+ beforeEach(function () {
+ this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
+ true
+ )
+ })
+
+ describe('normal case (edit slot available)', function () {
beforeEach(function (done) {
+ this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
+ true
+ )
this.req.params = { token: this.token }
- this.req.body = { confirmedByUser: true, tokenHashPrefix: '#prefix' }
+ this.req.body = {
+ confirmedByUser: true,
+ tokenHashPrefix: '#prefix',
+ }
this.res.callback = done
this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
@@ -204,10 +216,15 @@ describe('TokenAccessController', function () {
)
})
- it('grants read and write access', function () {
+ it('adds the user as a read and write invited member', function () {
expect(
- this.TokenAccessHandler.promises.addReadAndWriteUserToProject
- ).to.have.been.calledWith(this.user._id, this.project._id)
+ this.CollaboratorsHandler.promises.addUserIdToProject
+ ).to.have.been.calledWith(
+ this.project._id,
+ undefined,
+ this.user._id,
+ PrivilegeLevels.READ_AND_WRITE
+ )
})
it('writes a project audit log', function () {
@@ -215,13 +232,32 @@ describe('TokenAccessController', function () {
this.ProjectAuditLogHandler.promises.addEntry
).to.have.been.calledWith(
this.project._id,
- 'join-via-token',
+ 'accept-via-link-sharing',
this.user._id,
this.req.ip,
{ privileges: 'readAndWrite' }
)
})
+ it('records a project-joined event for the user', function () {
+ expect(
+ this.AnalyticsManager.recordEventForUserInBackground
+ ).to.have.been.calledWith(this.user._id, 'project-joined', {
+ mode: 'read-write',
+ projectId: this.project._id.toString(),
+ })
+ })
+
+ it('emits a project membership changed event', function () {
+ expect(
+ this.EditorRealTimeController.emitToRoom
+ ).to.have.been.calledWith(
+ this.project._id,
+ 'project:membership:changed',
+ { members: true, invites: true }
+ )
+ })
+
it('checks token hash', function () {
expect(
this.TokenAccessHandler.checkTokenHashPrefix
@@ -235,262 +271,78 @@ describe('TokenAccessController', function () {
})
})
- describe('when project owner in link-sharing-warning split test', function () {
- beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
- async (userId, test) => {
- if (test === 'link-sharing-warning') {
- return { variant: 'active' }
- }
- }
+ describe('when there are no edit collaborator slots available', function () {
+ beforeEach(function (done) {
+ this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
+ false
)
- })
-
- it('tells the ui to show the link-sharing-warning variant', async function () {
this.req.params = { token: this.token }
- this.req.body = { tokenHashPrefix: '#prefix' }
- await this.TokenAccessController.grantTokenAccessReadAndWrite(
+ this.req.body = {
+ confirmedByUser: true,
+ tokenHashPrefix: '#prefix',
+ }
+ this.res.callback = done
+ this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
- {
- json: content => {
- expect(content).to.deep.equal({
- requireAccept: {
- linkSharingChanges: true,
- projectName: this.project.name,
- },
- })
- },
- }
+ this.res,
+ done
)
})
- describe('normal case', function () {
- beforeEach(function (done) {
- this.req.params = { token: this.token }
- this.req.body = { confirmedByUser: true, tokenHashPrefix: '#prefix' }
- this.res.callback = done
- this.TokenAccessController.grantTokenAccessReadAndWrite(
- this.req,
- this.res,
- done
- )
- })
+ it('adds the user as a read only invited member instead (pendingEditor)', function () {
+ expect(
+ this.CollaboratorsHandler.promises.addUserIdToProject
+ ).to.have.been.calledWith(
+ this.project._id,
+ undefined,
+ this.user._id,
+ PrivilegeLevels.READ_ONLY,
+ { pendingEditor: true }
+ )
+ })
- it('adds the user as a read and write invited member', function () {
- expect(
- this.CollaboratorsHandler.promises.addUserIdToProject
- ).to.have.been.calledWith(
- this.project._id,
- undefined,
- this.user._id,
- PrivilegeLevels.READ_AND_WRITE
- )
- })
+ it('writes a project audit log', function () {
+ expect(
+ this.ProjectAuditLogHandler.promises.addEntry
+ ).to.have.been.calledWith(
+ this.project._id,
+ 'accept-via-link-sharing',
+ this.user._id,
+ this.req.ip,
+ { privileges: 'readOnly', pendingEditor: true }
+ )
+ })
- it('writes a project audit log', function () {
- expect(
- this.ProjectAuditLogHandler.promises.addEntry
- ).to.have.been.calledWith(
- this.project._id,
- 'accept-via-link-sharing',
- this.user._id,
- this.req.ip,
- { privileges: 'readAndWrite' }
- )
- })
-
- it('records a project-joined event for the user', function () {
- expect(
- this.AnalyticsManager.recordEventForUserInBackground
- ).to.have.been.calledWith(this.user._id, 'project-joined', {
- mode: 'read-write',
- projectId: this.project._id.toString(),
- })
- })
-
- it('emits a project membership changed event', function () {
- expect(
- this.EditorRealTimeController.emitToRoom
- ).to.have.been.calledWith(
- this.project._id,
- 'project:membership:changed',
- { members: true, invites: true }
- )
- })
-
- it('checks token hash', function () {
- expect(
- this.TokenAccessHandler.checkTokenHashPrefix
- ).to.have.been.calledWith(
- this.token,
- '#prefix',
- 'readAndWrite',
- this.user._id,
- { projectId: this.project._id, action: 'continue' }
- )
+ it('records a project-joined event for the user', function () {
+ expect(
+ this.AnalyticsManager.recordEventForUserInBackground
+ ).to.have.been.calledWith(this.user._id, 'project-joined', {
+ mode: 'read-only',
+ projectId: this.project._id.toString(),
+ pendingEditor: true,
})
})
- describe('when the project owner is in the link-sharing-enforcement split test', function () {
- beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
- async (userId, test) => {
- if (test === 'link-sharing-warning') {
- return { variant: 'active' }
- } else if (test === 'link-sharing-enforcement') {
- return { variant: 'active' }
- }
- }
- )
- })
+ it('emits a project membership changed event', function () {
+ expect(
+ this.EditorRealTimeController.emitToRoom
+ ).to.have.been.calledWith(
+ this.project._id,
+ 'project:membership:changed',
+ { members: true, invites: true }
+ )
+ })
- describe('normal case (edit slot available)', function () {
- beforeEach(function (done) {
- this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
- true
- )
- this.req.params = { token: this.token }
- this.req.body = {
- confirmedByUser: true,
- tokenHashPrefix: '#prefix',
- }
- this.res.callback = done
- this.TokenAccessController.grantTokenAccessReadAndWrite(
- this.req,
- this.res,
- done
- )
- })
-
- it('adds the user as a read and write invited member', function () {
- expect(
- this.CollaboratorsHandler.promises.addUserIdToProject
- ).to.have.been.calledWith(
- this.project._id,
- undefined,
- this.user._id,
- PrivilegeLevels.READ_AND_WRITE
- )
- })
-
- it('writes a project audit log', function () {
- expect(
- this.ProjectAuditLogHandler.promises.addEntry
- ).to.have.been.calledWith(
- this.project._id,
- 'accept-via-link-sharing',
- this.user._id,
- this.req.ip,
- { privileges: 'readAndWrite' }
- )
- })
-
- it('records a project-joined event for the user', function () {
- expect(
- this.AnalyticsManager.recordEventForUserInBackground
- ).to.have.been.calledWith(this.user._id, 'project-joined', {
- mode: 'read-write',
- projectId: this.project._id.toString(),
- })
- })
-
- it('emits a project membership changed event', function () {
- expect(
- this.EditorRealTimeController.emitToRoom
- ).to.have.been.calledWith(
- this.project._id,
- 'project:membership:changed',
- { members: true, invites: true }
- )
- })
-
- it('checks token hash', function () {
- expect(
- this.TokenAccessHandler.checkTokenHashPrefix
- ).to.have.been.calledWith(
- this.token,
- '#prefix',
- 'readAndWrite',
- this.user._id,
- { projectId: this.project._id, action: 'continue' }
- )
- })
- })
-
- describe('when there are no edit collaborator slots available', function () {
- beforeEach(function (done) {
- this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
- false
- )
- this.req.params = { token: this.token }
- this.req.body = {
- confirmedByUser: true,
- tokenHashPrefix: '#prefix',
- }
- this.res.callback = done
- this.TokenAccessController.grantTokenAccessReadAndWrite(
- this.req,
- this.res,
- done
- )
- })
-
- it('adds the user as a read only invited member instead (pendingEditor)', function () {
- expect(
- this.CollaboratorsHandler.promises.addUserIdToProject
- ).to.have.been.calledWith(
- this.project._id,
- undefined,
- this.user._id,
- PrivilegeLevels.READ_ONLY,
- { pendingEditor: true }
- )
- })
-
- it('writes a project audit log', function () {
- expect(
- this.ProjectAuditLogHandler.promises.addEntry
- ).to.have.been.calledWith(
- this.project._id,
- 'accept-via-link-sharing',
- this.user._id,
- this.req.ip,
- { privileges: 'readOnly', pendingEditor: true }
- )
- })
-
- it('records a project-joined event for the user', function () {
- expect(
- this.AnalyticsManager.recordEventForUserInBackground
- ).to.have.been.calledWith(this.user._id, 'project-joined', {
- mode: 'read-only',
- projectId: this.project._id.toString(),
- pendingEditor: true,
- })
- })
-
- it('emits a project membership changed event', function () {
- expect(
- this.EditorRealTimeController.emitToRoom
- ).to.have.been.calledWith(
- this.project._id,
- 'project:membership:changed',
- { members: true, invites: true }
- )
- })
-
- it('checks token hash', function () {
- expect(
- this.TokenAccessHandler.checkTokenHashPrefix
- ).to.have.been.calledWith(
- this.token,
- '#prefix',
- 'readAndWrite',
- this.user._id,
- { projectId: this.project._id, action: 'continue' }
- )
- })
- })
+ it('checks token hash', function () {
+ expect(
+ this.TokenAccessHandler.checkTokenHashPrefix
+ ).to.have.been.calledWith(
+ this.token,
+ '#prefix',
+ 'readAndWrite',
+ this.user._id,
+ { projectId: this.project._id, action: 'continue' }
+ )
})
})
@@ -507,9 +359,16 @@ describe('TokenAccessController', function () {
)
})
- it("doesn't write a project audit log", function () {
- expect(this.ProjectAuditLogHandler.promises.addEntry).to.not.have.been
- .called
+ it('writes a project audit log', function () {
+ expect(
+ this.ProjectAuditLogHandler.promises.addEntry
+ ).to.have.been.calledWith(
+ this.project._id,
+ 'accept-via-link-sharing',
+ this.user._id,
+ this.req.ip,
+ { privileges: 'readAndWrite' }
+ )
})
it('checks token hash', function () {
@@ -537,10 +396,15 @@ describe('TokenAccessController', function () {
)
})
- it('grants read and write access', function () {
+ it('adds the user as a read and write invited member', function () {
expect(
- this.TokenAccessHandler.promises.addReadAndWriteUserToProject
- ).to.have.been.calledWith(this.user._id, this.project._id)
+ this.CollaboratorsHandler.promises.addUserIdToProject
+ ).to.have.been.calledWith(
+ this.project._id,
+ undefined,
+ this.user._id,
+ PrivilegeLevels.READ_AND_WRITE
+ )
})
it('checks the hash prefix', function () {
@@ -820,8 +684,13 @@ describe('TokenAccessController', function () {
.resolves(projectFromInternalStaff)
this.res.callback = () => {
expect(
- this.TokenAccessHandler.promises.addReadAndWriteUserToProject
- ).to.have.been.calledWith(admin._id, projectFromInternalStaff._id)
+ this.CollaboratorsHandler.promises.addUserIdToProject
+ ).to.have.been.calledWith(
+ projectFromInternalStaff._id,
+ undefined,
+ admin._id,
+ PrivilegeLevels.READ_AND_WRITE
+ )
}
this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
@@ -1151,138 +1020,77 @@ describe('TokenAccessController', function () {
this.req.params = { Project_id: this.project._id }
})
- describe('read only invited viewer gaining edit access via link sharing', function () {
- beforeEach(function (done) {
- this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
+ describe('when there are collaborator slots available', function () {
+ beforeEach(function () {
+ this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
true
)
- this.res.callback = done
- this.TokenAccessController.moveReadWriteToCollaborators(
- this.req,
- this.res,
- done
- )
})
- it('sets the privilege level to read and write for the invited viewer', function () {
- expect(
- this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
- ).to.have.been.calledWith(
- this.project._id,
- this.user._id,
- PrivilegeLevels.READ_AND_WRITE
- )
- expect(this.res.sendStatus).to.have.been.calledWith(204)
- })
- })
- describe('previously joined token access user moving to named collaborator', function () {
- beforeEach(function (done) {
- this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
- false
- )
- this.res.callback = done
- this.TokenAccessController.moveReadWriteToCollaborators(
- this.req,
- this.res,
- done
- )
- })
-
- it('sets the privilege level to read and write for the invited viewer', function () {
- expect(
- this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
- ).to.have.been.calledWith(this.user._id, this.project._id)
- expect(
- this.CollaboratorsHandler.promises.addUserIdToProject
- ).to.have.been.calledWith(
- this.project._id,
- undefined,
- this.user._id,
- PrivilegeLevels.READ_AND_WRITE
- )
- expect(this.res.sendStatus).to.have.been.calledWith(204)
- })
- })
-
- describe('when link-sharing-enforcement test is active', function () {
- beforeEach(function () {
- this.SplitTestHandler.promises.getAssignmentForUser.resolves({
- variant: 'active',
- })
- })
-
- describe('when there are collaborator slots available', function () {
- beforeEach(function () {
- this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
- true
- )
- })
-
- describe('previously joined token access user moving to named collaborator', function () {
- beforeEach(function (done) {
- this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
- false
- )
- this.res.callback = done
- this.TokenAccessController.moveReadWriteToCollaborators(
- this.req,
- this.res,
- done
- )
- })
-
- it('sets the privilege level to read and write for the invited viewer', function () {
- expect(
- this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
- ).to.have.been.calledWith(this.user._id, this.project._id)
- expect(
- this.CollaboratorsHandler.promises.addUserIdToProject
- ).to.have.been.calledWith(
- this.project._id,
- undefined,
- this.user._id,
- PrivilegeLevels.READ_AND_WRITE
- )
- expect(this.res.sendStatus).to.have.been.calledWith(204)
- })
- })
- })
-
- describe('when there are no edit collaborator slots available', function () {
- beforeEach(function () {
- this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
+ describe('previously joined token access user moving to named collaborator', function () {
+ beforeEach(function (done) {
+ this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
false
)
+ this.res.callback = done
+ this.TokenAccessController.moveReadWriteToCollaborators(
+ this.req,
+ this.res,
+ done
+ )
})
- describe('previously joined token access user moving to named collaborator', function () {
- beforeEach(function (done) {
- this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
- false
- )
- this.res.callback = done
- this.TokenAccessController.moveReadWriteToCollaborators(
- this.req,
- this.res,
- done
- )
- })
+ it('sets the privilege level to read and write for the invited viewer', function () {
+ expect(
+ this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
+ ).to.have.been.calledWith(this.user._id, this.project._id)
+ expect(
+ this.CollaboratorsHandler.promises.addUserIdToProject
+ ).to.have.been.calledWith(
+ this.project._id,
+ undefined,
+ this.user._id,
+ PrivilegeLevels.READ_AND_WRITE
+ )
+ expect(this.res.sendStatus).to.have.been.calledWith(204)
+ })
+ })
+ })
- it('sets the privilege level to read only for the invited viewer (pendingEditor)', function () {
- expect(
- this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
- ).to.have.been.calledWith(this.user._id, this.project._id)
- expect(
- this.CollaboratorsHandler.promises.addUserIdToProject
- ).to.have.been.calledWith(
- this.project._id,
- undefined,
- this.user._id,
- PrivilegeLevels.READ_ONLY,
- { pendingEditor: true }
- )
- expect(this.res.sendStatus).to.have.been.calledWith(204)
- })
+ describe('when there are no edit collaborator slots available', function () {
+ beforeEach(function () {
+ this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
+ false
+ )
+ })
+
+ describe('previously joined token access user moving to named collaborator', function () {
+ beforeEach(function (done) {
+ this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
+ false
+ )
+ this.res.callback = done
+ this.TokenAccessController.moveReadWriteToCollaborators(
+ this.req,
+ this.res,
+ done
+ )
+ })
+
+ it('sets the privilege level to read only for the invited viewer (pendingEditor)', function () {
+ expect(
+ this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
+ ).to.have.been.calledWith(this.user._id, this.project._id)
+ expect(
+ this.CollaboratorsHandler.promises.addUserIdToProject
+ ).to.have.been.calledWith(
+ this.project._id,
+ undefined,
+ this.user._id,
+ PrivilegeLevels.READ_ONLY,
+ { pendingEditor: true }
+ )
+ expect(this.res.sendStatus).to.have.been.calledWith(204)
})
})
})