Merge pull request #1275 from sharelatex/hb-authorization-flags

Authorization flags for metrics

GitOrigin-RevId: 651587c11317bfc8bb7b1e8143e8c2c820683cb5
This commit is contained in:
Simon Detheridge 2019-01-11 14:17:27 +00:00 committed by sharelatex
parent 8717ddffad
commit da6711dc99
7 changed files with 87 additions and 57 deletions

View file

@ -24,6 +24,7 @@ module.exports = AuthenticationController =
first_name: user.first_name
last_name: user.last_name
isAdmin: user.isAdmin
staffAccess: user.staffAccess
email: user.email
referal_id: user.referal_id
session_created: (new Date()).toISOString()

View file

@ -8,22 +8,31 @@ settings = require 'settings-sharelatex'
request = require 'request'
module.exports = UserMembershipAuthorization =
requireTeamAccess: (req, res, next) ->
requireAccessToEntity('team', req.params.id, req, res, next)
requireTeamMetricsAccess: (req, res, next) ->
requireAccessToEntity('team', req.params.id, req, res, next, 'groupMetrics')
requireGroupAccess: (req, res, next) ->
requireAccessToEntity('group', req.params.id, req, res, next)
requireGroupManagementAccess: (req, res, next) ->
requireAccessToEntity('group', req.params.id, req, res, next, 'groupManagement')
requireGroupManagersAccess: (req, res, next) ->
requireAccessToEntity('groupManagers', req.params.id, req, res, next)
requireGroupMetricsAccess: (req, res, next) ->
requireAccessToEntity('group', req.params.id, req, res, next, 'groupMetrics')
requireInstitutionAccess: (req, res, next) ->
requireAccessToEntity('institution', req.params.id, req, res, next)
requireGroupManagersManagementAccess: (req, res, next) ->
requireAccessToEntity('groupManagers', req.params.id, req, res, next, 'groupManagement')
requirePublisherAccess: (req, res, next) ->
requireAccessToEntity('publisher', req.params.id, req, res, next)
requireInstitutionMetricsAccess: (req, res, next) ->
requireAccessToEntity('institution', req.params.id, req, res, next, 'institutionMetrics')
requireTemplateAccess: (req, res, next) ->
requireInstitutionManagementAccess: (req, res, next) ->
requireAccessToEntity('institution', req.params.id, req, res, next, 'institutionManagement')
requirePublisherMetricsAccess: (req, res, next) ->
requireAccessToEntity('publisher', req.params.id, req, res, next, 'publisherMetrics')
requirePublisherManagementAccess: (req, res, next) ->
requireAccessToEntity('publisher', req.params.id, req, res, next, 'publisherManagement')
requireTemplateMetricsAccess: (req, res, next) ->
templateId = req.params.id
request {
baseUrl: settings.apis.v1.url
@ -51,26 +60,29 @@ module.exports = UserMembershipAuthorization =
id: body.id
title: body.title
if body?.brand?.slug
requireAccessToEntity('publisher', body.brand.slug, req, res, next)
req.params.id = body.brand.slug
UserMembershipAuthorization.requirePublisherMetricsAccess(req, res, next)
else
AuthorizationMiddlewear.ensureUserIsSiteAdmin(req, res, next)
requireGraphAccess: (req, res, next) ->
req.params.id = req.query.resource_id
if req.query.resource_type == 'template'
# templates are a special case; can't use requireaccesstoentity directly
req.params.id = req.query.resource_id
return UserMembershipAuthorization.requireTemplateAccess(req, res, next)
return UserMembershipAuthorization.requireTemplateMetricsAccess(req, res, next)
else if req.query.resource_type == 'institution'
return UserMembershipAuthorization.requireInstitutionMetricsAccess(req, res, next)
else if req.query.resource_type == 'group'
return UserMembershipAuthorization.requireGroupMetricsAccess(req, res, next)
else if req.query.resource_type == 'team'
return UserMembershipAuthorization.requireTeamMetricsAccess(req, res, next)
requireAccessToEntity(req.query.resource_type, req.query.resource_id, req, res, next)
requireAccessToEntity(
req.query.resource_type, req.query.resource_id, req, res, next
)
requireAccessToEntity = (entityName, entityId, req, res, next) ->
requireAccessToEntity = (entityName, entityId, req, res, next, requiredStaffAccess=null) ->
loggedInUser = AuthenticationController.getSessionUser(req)
unless loggedInUser
return AuthorizationMiddlewear.redirectToRestricted req, res, next
getEntity entityName, entityId, loggedInUser, (error, entity, entityConfig, entityExists) ->
getEntity entityName, entityId, loggedInUser, requiredStaffAccess, (error, entity, entityConfig, entityExists) ->
return next(error) if error?
if entity?
@ -87,12 +99,12 @@ requireAccessToEntity = (entityName, entityId, req, res, next) ->
next(new Errors.NotFoundError())
getEntity = (entityName, entityId, user, callback = (error, entity, entityConfig, entityExists)->) ->
getEntity = (entityName, entityId, user, requiredStaffAccess, callback = (error, entity, entityConfig, entityExists)->) ->
entityConfig = EntityConfigs[entityName]
unless entityConfig
return callback(new Errors.NotFoundError("No such entity: #{entityName}"))
UserMembershipHandler.getEntity entityId, entityConfig, user, (error, entity)->
UserMembershipHandler.getEntity entityId, entityConfig, user, requiredStaffAccess, (error, entity)->
return callback(error) if error?
return callback(null, entity, entityConfig, true) if entity?

View file

@ -11,9 +11,9 @@ logger = require('logger-sharelatex')
UserMembershipEntityConfigs = require "./UserMembershipEntityConfigs"
module.exports =
getEntity: (entityId, entityConfig, loggedInUser, callback = (error, entity) ->) ->
getEntity: (entityId, entityConfig, loggedInUser, requiredStaffAccess, callback = (error, entity) ->) ->
query = buildEntityQuery(entityId, entityConfig)
unless loggedInUser.isAdmin
unless loggedInUser.isAdmin or loggedInUser.staffAccess?[requiredStaffAccess]
query[entityConfig.fields.access] = ObjectId(loggedInUser._id)
EntityModels[entityConfig.modelName].findOne query, callback

View file

@ -8,52 +8,52 @@ module.exports =
apply: (webRouter) ->
# group members routes
webRouter.get '/manage/groups/:id/members',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireGroupManagementAccess,
UserMembershipController.index
webRouter.post '/manage/groups/:id/invites',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireGroupManagementAccess,
TeamInvitesController.createInvite
webRouter.delete '/manage/groups/:id/user/:user_id',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireGroupManagementAccess,
SubscriptionGroupController.removeUserFromGroup
webRouter.delete '/manage/groups/:id/invites/:email',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireGroupManagementAccess,
TeamInvitesController.revokeInvite
webRouter.get '/manage/groups/:id/members/export',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireGroupManagementAccess,
UserMembershipController.exportCsv
# group managers routes
webRouter.get "/manage/groups/:id/managers",
UserMembershipAuthorization.requireGroupManagersAccess,
UserMembershipAuthorization.requireGroupManagersManagementAccess,
UserMembershipController.index
webRouter.post "/manage/groups/:id/managers",
UserMembershipAuthorization.requireGroupManagersAccess,
UserMembershipAuthorization.requireGroupManagersManagementAccess,
UserMembershipController.add
webRouter.delete "/manage/groups/:id/managers/:userId",
UserMembershipAuthorization.requireGroupManagersAccess,
UserMembershipAuthorization.requireGroupManagersManagementAccess,
UserMembershipController.remove
# institution members routes
webRouter.get "/manage/institutions/:id/managers",
UserMembershipAuthorization.requireInstitutionAccess,
UserMembershipAuthorization.requireInstitutionManagementAccess,
UserMembershipController.index
webRouter.post "/manage/institutions/:id/managers",
UserMembershipAuthorization.requireInstitutionAccess,
UserMembershipAuthorization.requireInstitutionManagementAccess,
UserMembershipController.add
webRouter.delete "/manage/institutions/:id/managers/:userId",
UserMembershipAuthorization.requireInstitutionAccess,
UserMembershipAuthorization.requireInstitutionManagementAccess,
UserMembershipController.remove
# publisher members routes
webRouter.get "/manage/publishers/:id/managers",
UserMembershipAuthorization.requirePublisherAccess,
UserMembershipAuthorization.requirePublisherManagementAccess,
UserMembershipController.index
webRouter.post "/manage/publishers/:id/managers",
UserMembershipAuthorization.requirePublisherAccess,
UserMembershipAuthorization.requirePublisherManagementAccess,
UserMembershipController.add
webRouter.delete "/manage/publishers/:id/managers/:userId",
UserMembershipAuthorization.requirePublisherAccess,
UserMembershipAuthorization.requirePublisherManagementAccess,
UserMembershipController.remove
# create new entitites

View file

@ -20,6 +20,14 @@ UserSchema = new Schema
institution : {type : String, default : ''}
hashedPassword : String
isAdmin : {type : Boolean, default : false}
staffAccess: {
publisherMetrics: {type : Boolean, default: false}
publisherManagement: {type : Boolean, default: false}
institutionMetrics: {type : Boolean, default: false}
institutionManagement: {type : Boolean, default: false}
groupMetrics: {type : Boolean, default: false}
groupManagement: {type : Boolean, default: false}
}
signUpDate : {type : Date, default: () -> new Date() }
lastLoggedIn : {type : Date}
lastLoginIp : {type : String, default : ''}

View file

@ -35,7 +35,7 @@ describe "UserMembershipAuthorization", ->
describe 'requireAccessToEntity', ->
it 'get entity', (done) ->
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
@UserMembershipAuthorization.requireGroupMetricsAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@ -50,7 +50,7 @@ describe "UserMembershipAuthorization", ->
it 'handle entity not found as non-admin', (done) ->
@UserMembershipHandler.getEntity.yields(null, null)
@UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(null, null)
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
@UserMembershipAuthorization.requireGroupMetricsAccess @req, null, (error) =>
expect(error).to.extist
expect(error).to.be.instanceof(Error)
expect(error.constructor.name).to.equal('NotFoundError')
@ -62,7 +62,7 @@ describe "UserMembershipAuthorization", ->
@user.isAdmin = true
@UserMembershipHandler.getEntity.yields(null, null)
@UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(null, null)
@UserMembershipAuthorization.requirePublisherAccess @req, redirect: (path) =>
@UserMembershipAuthorization.requirePublisherMetricsAccess @req, redirect: (path) =>
expect(path).to.extist
expect(path).to.match /create/
done()
@ -71,7 +71,7 @@ describe "UserMembershipAuthorization", ->
@user.isAdmin = true
@UserMembershipHandler.getEntity.yields(null, null)
@UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(null, null)
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
@UserMembershipAuthorization.requireGroupMetricsAccess @req, null, (error) =>
expect(error).to.extist
expect(error).to.be.instanceof(Error)
expect(error.constructor.name).to.equal('NotFoundError')
@ -79,13 +79,13 @@ describe "UserMembershipAuthorization", ->
it 'handle entity no access', (done) ->
@UserMembershipHandler.getEntity.yields(null, null)
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
@UserMembershipAuthorization.requireGroupMetricsAccess @req, null, (error) =>
sinon.assert.called(@AuthorizationMiddlewear.redirectToRestricted)
done()
it 'handle anonymous user', (done) ->
@AuthenticationController.getSessionUser.returns(null)
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
@UserMembershipAuthorization.requireGroupMetricsAccess @req, null, (error) =>
expect(error).to.extist
sinon.assert.called(@AuthorizationMiddlewear.redirectToRestricted)
sinon.assert.notCalled(@UserMembershipHandler.getEntity)
@ -94,7 +94,7 @@ describe "UserMembershipAuthorization", ->
describe 'requireEntityAccess', ->
it 'handle team access', (done) ->
@UserMembershipAuthorization.requireTeamAccess @req, null, (error) =>
@UserMembershipAuthorization.requireTeamMetricsAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@ -104,7 +104,7 @@ describe "UserMembershipAuthorization", ->
done()
it 'handle group access', (done) ->
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
@UserMembershipAuthorization.requireGroupMetricsAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@ -114,7 +114,7 @@ describe "UserMembershipAuthorization", ->
done()
it 'handle group managers access', (done) ->
@UserMembershipAuthorization.requireGroupManagersAccess @req, null, (error) =>
@UserMembershipAuthorization.requireGroupManagersManagementAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@ -124,7 +124,7 @@ describe "UserMembershipAuthorization", ->
done()
it 'handle institution access', (done) ->
@UserMembershipAuthorization.requireInstitutionAccess @req, null, (error) =>
@UserMembershipAuthorization.requireInstitutionMetricsAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@ -139,7 +139,7 @@ describe "UserMembershipAuthorization", ->
title: 'Template Title'
brand: { slug: 'brand-slug' }
@request.yields(null, { statusCode: 200 }, JSON.stringify(templateData))
@UserMembershipAuthorization.requireTemplateAccess @req, null, (error) =>
@UserMembershipAuthorization.requireTemplateMetricsAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@ -154,7 +154,7 @@ describe "UserMembershipAuthorization", ->
title: 'Template Title'
brand: null
@request.yields(null, { statusCode: 200 }, JSON.stringify(templateData))
@UserMembershipAuthorization.requireTemplateAccess @req, null, (error) =>
@UserMembershipAuthorization.requireTemplateMetricsAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.notCalled(@UserMembershipHandler.getEntity)
sinon.assert.calledOnce(@AuthorizationMiddlewear.ensureUserIsSiteAdmin)

View file

@ -61,7 +61,7 @@ describe 'UserMembershipHandler', ->
describe 'getEntity', ->
describe 'group subscriptions', ->
it 'get subscription', (done) ->
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, @user, (error, subscription) =>
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, @user, null, (error, subscription) =>
should.not.exist(error)
expectedQuery =
groupPlan: true
@ -73,7 +73,16 @@ describe 'UserMembershipHandler', ->
done()
it 'get for admin', (done) ->
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, { isAdmin: true }, (error, subscription) =>
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, { isAdmin: true }, null, (error, subscription) =>
should.not.exist(error)
expectedQuery =
groupPlan: true
_id: @fakeEntityId
assertCalledWith(@Subscription.findOne, expectedQuery)
done()
it 'get with staffAccess field', (done) ->
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, { staffAccess: {institutionMetrics: true}}, 'institutionMetrics', (error, subscription) =>
should.not.exist(error)
expectedQuery =
groupPlan: true
@ -83,7 +92,7 @@ describe 'UserMembershipHandler', ->
it 'handle error', (done) ->
@Subscription.findOne.yields(new Error('some error'))
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, @user._id, (error, subscription) =>
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, @user._id, null, (error, subscription) =>
should.exist(error)
done()
@ -98,7 +107,7 @@ describe 'UserMembershipHandler', ->
describe 'institutions', ->
it 'get institution', (done) ->
@UserMembershipHandler.getEntity @institution.v1Id, EntityConfigs.institution, @user, (error, institution) =>
@UserMembershipHandler.getEntity @institution.v1Id, EntityConfigs.institution, @user, null, (error, institution) =>
should.not.exist(error)
expectedQuery = v1Id: @institution.v1Id, managerIds: ObjectId(@user._id)
assertCalledWith(@Institution.findOne, expectedQuery)
@ -107,14 +116,14 @@ describe 'UserMembershipHandler', ->
it 'handle errors', (done) ->
@Institution.findOne.yields(new Error('nope'))
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.institution, @user._id, (error, institution) =>
@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.institution, @user._id, null, (error, institution) =>
should.exist(error)
expect(error).to.not.be.an.instanceof(Errors.NotFoundError)
done()
describe 'publishers', ->
it 'get publisher', (done) ->
@UserMembershipHandler.getEntity @publisher.slug, EntityConfigs.publisher, @user, (error, institution) =>
@UserMembershipHandler.getEntity @publisher.slug, EntityConfigs.publisher, @user, null, (error, institution) =>
should.not.exist(error)
expectedQuery = slug: @publisher.slug, managerIds: ObjectId(@user._id)
assertCalledWith(@Publisher.findOne, expectedQuery)