mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-04 12:06:49 +00:00
Merge pull request #1275 from sharelatex/hb-authorization-flags
Authorization flags for metrics GitOrigin-RevId: 651587c11317bfc8bb7b1e8143e8c2c820683cb5
This commit is contained in:
parent
8717ddffad
commit
da6711dc99
7 changed files with 87 additions and 57 deletions
services/web
app/coffee
Features
Authentication
UserMembership
models
test/unit/coffee/UserMembership
|
@ -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()
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 : ''}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue