Merge pull request #2105 from overleaf/ta-user-membership-refactor

UserMembershipAuthorization Refactor

GitOrigin-RevId: 7711cda4a134823cbacee42731319fbb8aa648d0
This commit is contained in:
Timothée Alby
2019-09-24 08:58:05 +00:00
committed by sharelatex
parent 44d3b8b92e
commit a23ecc9bf8
14 changed files with 427 additions and 930 deletions
@@ -1,339 +0,0 @@
/* eslint-disable
handle-callback-err,
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const sinon = require('sinon')
const chai = require('chai')
const { expect } = require('chai')
const modulePath =
'../../../../app/src/Features/UserMembership/UserMembershipAuthorization.js'
const SandboxedModule = require('sandboxed-module')
const MockRequest = require('../helpers/MockRequest')
const EntityConfigs = require('../../../../app/src/Features/UserMembership/UserMembershipEntityConfigs')
const Errors = require('../../../../app/src/Features/Errors/Errors')
describe('UserMembershipAuthorization', function() {
beforeEach(function() {
this.req = new MockRequest()
this.req.params.id = 'mock-entity-id'
this.user = { _id: 'mock-user-id' }
this.subscription = { _id: 'mock-subscription-id' }
this.AuthenticationController = {
getSessionUser: sinon.stub().returns(this.user)
}
this.UserMembershipHandler = {
getEntity: sinon.stub().yields(null, this.subscription),
getEntityWithoutAuthorizationCheck: sinon
.stub()
.yields(null, this.subscription)
}
this.AuthorizationMiddleware = {
redirectToRestricted: sinon.stub().yields(),
ensureUserIsSiteAdmin: sinon.stub().yields()
}
return (this.UserMembershipAuthorization = SandboxedModule.require(
modulePath,
{
globals: {
console: console
},
requires: {
'../Authentication/AuthenticationController': this
.AuthenticationController,
'../Authorization/AuthorizationMiddleware': this
.AuthorizationMiddleware,
'./UserMembershipHandler': this.UserMembershipHandler,
'./EntityConfigs': EntityConfigs,
'../Errors/Errors': Errors,
request: (this.request = sinon.stub().yields(null, null, {})),
'logger-sharelatex': {
log() {},
warn() {},
err() {}
}
}
}
))
})
describe('requireAccessToEntity', function() {
it('get entity', function(done) {
return this.UserMembershipAuthorization.requireGroupMetricsAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.calledWithMatch(
this.UserMembershipHandler.getEntity,
this.req.params.id,
{ modelName: 'Subscription' },
this.user
)
expect(this.req.entity).to.equal(this.subscription)
expect(this.req.entityConfig).to.exist
return done()
}
)
})
it('handle entity not found as non-admin', function(done) {
this.UserMembershipHandler.getEntity.yields(null, null)
this.UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(
null,
null
)
return this.UserMembershipAuthorization.requireGroupMetricsAccess(
this.req,
null,
error => {
expect(error).to.exist
expect(error).to.be.instanceof(Error)
expect(error.constructor.name).to.equal('NotFoundError')
sinon.assert.called(this.UserMembershipHandler.getEntity)
expect(this.req.entity).to.not.exist
return done()
}
)
})
it('handle entity not found an admin can create', function(done) {
this.user.isAdmin = true
this.UserMembershipHandler.getEntity.yields(null, null)
this.UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(
null,
null
)
return this.UserMembershipAuthorization.requirePublisherMetricsAccess(
this.req,
{
redirect: path => {
expect(path).to.exist
expect(path).to.match(/create/)
return done()
}
}
)
})
it('handle entity not found a non-admin can create', function(done) {
this.user.staffAccess = { institutionManagement: true }
this.UserMembershipHandler.getEntity.yields(null, null)
this.UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(
null,
null
)
return this.UserMembershipAuthorization.requirePublisherMetricsAccess(
this.req,
{
redirect: path => {
expect(path).to.exist
expect(path).to.match(/create/)
return done()
}
}
)
})
it('handle entity not found an admin cannot create', function(done) {
this.user.isAdmin = true
this.UserMembershipHandler.getEntity.yields(null, null)
this.UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(
null,
null
)
return this.UserMembershipAuthorization.requireGroupMetricsAccess(
this.req,
null,
error => {
expect(error).to.exist
expect(error).to.be.instanceof(Error)
expect(error.constructor.name).to.equal('NotFoundError')
return done()
}
)
})
it('handle entity no access', function(done) {
this.UserMembershipHandler.getEntity.yields(null, null)
return this.UserMembershipAuthorization.requireGroupMetricsAccess(
this.req,
null,
error => {
sinon.assert.called(this.AuthorizationMiddleware.redirectToRestricted)
return done()
}
)
})
it('handle anonymous user', function(done) {
this.AuthenticationController.getSessionUser.returns(null)
return this.UserMembershipAuthorization.requireGroupMetricsAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.called(this.AuthorizationMiddleware.redirectToRestricted)
sinon.assert.notCalled(this.UserMembershipHandler.getEntity)
expect(this.req.entity).to.not.exist
return done()
}
)
})
it('checks user is staff if required', function(done) {
return this.UserMembershipAuthorization.requireInstitutionManagementStaffAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.called(this.AuthorizationMiddleware.redirectToRestricted)
sinon.assert.notCalled(this.UserMembershipHandler.getEntity)
expect(this.req.entity).to.not.exist
return done()
}
)
})
})
describe('requireEntityAccess', function() {
it('handle team access', function(done) {
return this.UserMembershipAuthorization.requireTeamMetricsAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.calledWithMatch(
this.UserMembershipHandler.getEntity,
this.req.params.id,
{ fields: { primaryKey: 'overleaf.id' } }
)
return done()
}
)
})
it('handle group access', function(done) {
return this.UserMembershipAuthorization.requireGroupMetricsAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.calledWithMatch(
this.UserMembershipHandler.getEntity,
this.req.params.id,
{ translations: { title: 'group_account' } }
)
return done()
}
)
})
it('handle group managers access', function(done) {
return this.UserMembershipAuthorization.requireGroupManagersManagementAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.calledWithMatch(
this.UserMembershipHandler.getEntity,
this.req.params.id,
{ translations: { subtitle: 'managers_management' } }
)
return done()
}
)
})
it('handle institution access', function(done) {
return this.UserMembershipAuthorization.requireInstitutionMetricsAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.calledWithMatch(
this.UserMembershipHandler.getEntity,
this.req.params.id,
{ modelName: 'Institution' }
)
return done()
}
)
})
it('handle template with brand access', function(done) {
const templateData = {
id: 123,
title: 'Template Title',
brand: { slug: 'brand-slug' }
}
this.request.yields(
null,
{ statusCode: 200 },
JSON.stringify(templateData)
)
return this.UserMembershipAuthorization.requireTemplateMetricsAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.calledWithMatch(
this.UserMembershipHandler.getEntity,
'brand-slug',
{ modelName: 'Publisher' }
)
return done()
}
)
})
it('handle template without brand access', function(done) {
const templateData = {
id: 123,
title: 'Template Title',
brand: null
}
this.request.yields(
null,
{ statusCode: 200 },
JSON.stringify(templateData)
)
return this.UserMembershipAuthorization.requireTemplateMetricsAccess(
this.req,
null,
error => {
expect(error).to.not.exist
sinon.assert.notCalled(this.UserMembershipHandler.getEntity)
sinon.assert.calledOnce(
this.AuthorizationMiddleware.ensureUserIsSiteAdmin
)
return done()
}
)
})
it('handle graph access', function(done) {
this.req.query.resource_id = 'mock-resource-id'
this.req.query.resource_type = 'institution'
const middleware = this.UserMembershipAuthorization.requireGraphAccess
return middleware(this.req, null, error => {
expect(error).to.not.exist
sinon.assert.calledWithMatch(
this.UserMembershipHandler.getEntity,
this.req.query.resource_id,
{ modelName: 'Institution' }
)
return done()
})
})
})
})
@@ -336,6 +336,7 @@ describe('UserMembershipController', function() {
describe('create', function() {
beforeEach(function() {
this.req.params.name = 'institution'
this.req.entityConfig = EntityConfigs['institution']
return (this.req.params.id = 123)
})
@@ -352,15 +353,5 @@ describe('UserMembershipController', function() {
}
})
})
it('checks canCreate', function(done) {
this.req.params.name = 'group'
return this.UserMembershipController.create(this.req, null, error => {
expect(error).to.extist
expect(error).to.be.an.instanceof(Errors.NotFoundError)
sinon.assert.notCalled(this.UserMembershipHandler.createEntity)
return done()
})
})
})
})
@@ -92,81 +92,6 @@ describe('UserMembershipHandler', function() {
}))
})
describe('getEntity', function() {
describe('group subscriptions', function() {
it('get subscription', function(done) {
return this.UserMembershipHandler.getEntity(
this.fakeEntityId,
EntityConfigs.group,
this.user,
null,
(error, subscription) => {
should.not.exist(error)
const expectedQuery = {
groupPlan: true,
_id: this.fakeEntityId,
manager_ids: ObjectId(this.user._id)
}
assertCalledWith(this.Subscription.findOne, expectedQuery)
expect(subscription).to.equal(this.subscription)
expect(subscription.membersLimit).to.equal(10)
return done()
}
)
})
it('get for admin', function(done) {
return this.UserMembershipHandler.getEntity(
this.fakeEntityId,
EntityConfigs.group,
{ isAdmin: true },
null,
(error, subscription) => {
should.not.exist(error)
const expectedQuery = {
groupPlan: true,
_id: this.fakeEntityId
}
assertCalledWith(this.Subscription.findOne, expectedQuery)
return done()
}
)
})
it('get with staffAccess field', function(done) {
return this.UserMembershipHandler.getEntity(
this.fakeEntityId,
EntityConfigs.group,
{ staffAccess: { institutionMetrics: true } },
'institutionMetrics',
(error, subscription) => {
should.not.exist(error)
const expectedQuery = {
groupPlan: true,
_id: this.fakeEntityId
}
assertCalledWith(this.Subscription.findOne, expectedQuery)
return done()
}
)
})
it('handle error', function(done) {
this.Subscription.findOne.yields(new Error('some error'))
return this.UserMembershipHandler.getEntity(
this.fakeEntityId,
EntityConfigs.group,
this.user._id,
null,
(error, subscription) => {
should.exist(error)
return done()
}
)
})
})
})
describe('getEntityWithoutAuthorizationCheck', function() {
it('get publisher', function(done) {
return this.UserMembershipHandler.getEntityWithoutAuthorizationCheck(
@@ -181,63 +106,6 @@ describe('UserMembershipHandler', function() {
}
)
})
describe('institutions', function() {
it('get institution', function(done) {
return this.UserMembershipHandler.getEntity(
this.institution.v1Id,
EntityConfigs.institution,
this.user,
null,
(error, institution) => {
should.not.exist(error)
const expectedQuery = {
v1Id: this.institution.v1Id,
managerIds: ObjectId(this.user._id)
}
assertCalledWith(this.Institution.findOne, expectedQuery)
expect(institution).to.equal(this.institution)
return done()
}
)
})
it('handle errors', function(done) {
this.Institution.findOne.yields(new Error('nope'))
return this.UserMembershipHandler.getEntity(
this.fakeEntityId,
EntityConfigs.institution,
this.user._id,
null,
(error, institution) => {
should.exist(error)
expect(error).to.not.be.an.instanceof(Errors.NotFoundError)
return done()
}
)
})
})
describe('publishers', function() {
it('get publisher', function(done) {
return this.UserMembershipHandler.getEntity(
this.publisher.slug,
EntityConfigs.publisher,
this.user,
null,
(error, institution) => {
should.not.exist(error)
const expectedQuery = {
slug: this.publisher.slug,
managerIds: ObjectId(this.user._id)
}
assertCalledWith(this.Publisher.findOne, expectedQuery)
expect(institution).to.equal(this.publisher)
return done()
}
)
})
})
})
describe('getUsers', function() {