Просмотр исходного кода

Merge pull request #1192 from sharelatex/ta-publisher-create

Add Interface to Create Publishers

GitOrigin-RevId: 50af2d1c05821d20b1ec33e8f68a05d824e55c46
Simon Detheridge 7 лет назад
Родитель
Сommit
f3a10ac221

+ 21 - 8
services/web/app/coffee/Features/UserMembership/UserMembershipAuthorization.coffee

@@ -67,20 +67,33 @@ requireAccessToEntity = (entityName, entityId, req, res, next) ->
 	unless loggedInUser
 	unless loggedInUser
 		return AuthorizationMiddlewear.redirectToRestricted req, res, next
 		return AuthorizationMiddlewear.redirectToRestricted req, res, next
 
 
-	getEntity entityName, entityId, loggedInUser, (error, entity, entityConfig) ->
+	getEntity entityName, entityId, loggedInUser, (error, entity, entityConfig, entityExists) ->
 		return next(error) if error?
 		return next(error) if error?
-		unless entity?
+
+		if entity?
+			req.entity = entity
+			req.entityConfig = entityConfig
+			return next()
+
+		if entityExists # user doesn't have access to entity
 			return AuthorizationMiddlewear.redirectToRestricted(req, res, next)
 			return AuthorizationMiddlewear.redirectToRestricted(req, res, next)
 
 
-		req.entity = entity
-		req.entityConfig = entityConfig
-		next()
+		if loggedInUser.isAdmin and entityConfig.canCreate
+			# entity doesn't exists, admin can create it
+			return res.redirect "/entities/#{entityName}/create/#{entityId}"
 
 
-getEntity = (entityName, entityId, userId, callback = (error, entity, entityConfig)->) ->
+		next(new Errors.NotFoundError())
+
+getEntity = (entityName, entityId, user, callback = (error, entity, entityConfig, entityExists)->) ->
 	entityConfig = EntityConfigs[entityName]
 	entityConfig = EntityConfigs[entityName]
 	unless entityConfig
 	unless entityConfig
 		return callback(new Errors.NotFoundError("No such entity: #{entityName}"))
 		return callback(new Errors.NotFoundError("No such entity: #{entityName}"))
 
 
-	UserMembershipHandler.getEntity entityId, entityConfig, userId, (error, entity)->
+	UserMembershipHandler.getEntity entityId, entityConfig, user, (error, entity)->
 		return callback(error) if error?
 		return callback(error) if error?
-		callback(null, entity, entityConfig)
+		return callback(null, entity, entityConfig, true) if entity?
+
+		# no access to entity. Check if entity exists
+		UserMembershipHandler.getEntityWithoutAuthorizationCheck entityId, entityConfig, (error, entity)->
+			return callback(error) if error?
+			callback(null, null, entityConfig, entity?)

+ 18 - 0
services/web/app/coffee/Features/UserMembership/UserMembershipController.coffee

@@ -80,3 +80,21 @@ module.exports =
 			)
 			)
 			res.contentType('text/csv')
 			res.contentType('text/csv')
 			res.send(csvOutput)
 			res.send(csvOutput)
+
+	new: (req, res, next)->
+		res.render "user_membership/new",
+			entityName: req.params.name
+			entityId: req.params.id
+
+	create: (req, res, next)->
+		entityName = req.params.name
+		entityId = req.params.id
+		entityConfig = EntityConfigs[entityName]
+		unless entityConfig
+			return next(new Errors.NotFoundError("No such entity: #{entityName}"))
+		unless entityConfig.canCreate
+			return next(new Errors.NotFoundError("Cannot create new #{entityName}"))
+
+		UserMembershipHandler.createEntity entityId, entityConfig, (error, entity) ->
+			return next(error) if error?
+			res.redirect entityConfig.pathsFor(entityId).index

+ 4 - 0
services/web/app/coffee/Features/UserMembership/UserMembershipEntityConfigs.coffee

@@ -49,6 +49,7 @@ module.exports =
 
 
 	institution:
 	institution:
 		modelName: 'Institution'
 		modelName: 'Institution'
+		canCreate: true
 		fields:
 		fields:
 			primaryKey: 'v1Id'
 			primaryKey: 'v1Id'
 			read: ['managerIds']
 			read: ['managerIds']
@@ -60,11 +61,13 @@ module.exports =
 			subtitle: 'managers_management'
 			subtitle: 'managers_management'
 			remove: 'remove_manager'
 			remove: 'remove_manager'
 		pathsFor: (id) ->
 		pathsFor: (id) ->
+			index: "/manage/institutions/#{id}/managers"
 			addMember: "/manage/institutions/#{id}/managers"
 			addMember: "/manage/institutions/#{id}/managers"
 			removeMember: "/manage/institutions/#{id}/managers"
 			removeMember: "/manage/institutions/#{id}/managers"
 
 
 	publisher:
 	publisher:
 		modelName: 'Publisher'
 		modelName: 'Publisher'
+		canCreate: true
 		fields:
 		fields:
 			primaryKey: 'slug'
 			primaryKey: 'slug'
 			read: ['managerIds']
 			read: ['managerIds']
@@ -76,5 +79,6 @@ module.exports =
 			subtitle: 'managers_management'
 			subtitle: 'managers_management'
 			remove: 'remove_manager'
 			remove: 'remove_manager'
 		pathsFor: (id) ->
 		pathsFor: (id) ->
+			index: "/manage/publishers/#{id}/managers"
 			addMember: "/manage/publishers/#{id}/managers"
 			addMember: "/manage/publishers/#{id}/managers"
 			removeMember: "/manage/publishers/#{id}/managers"
 			removeMember: "/manage/publishers/#{id}/managers"

+ 15 - 3
services/web/app/coffee/Features/UserMembership/UserMembershipHandler.coffee

@@ -12,13 +12,19 @@ UserMembershipEntityConfigs = require "./UserMembershipEntityConfigs"
 
 
 module.exports =
 module.exports =
 	getEntity: (entityId, entityConfig, loggedInUser, callback = (error, entity) ->) ->
 	getEntity: (entityId, entityConfig, loggedInUser, callback = (error, entity) ->) ->
-		entityId = ObjectId(entityId) if ObjectId.isValid(entityId.toString())
-		query = Object.assign({}, entityConfig.baseQuery)
-		query[entityConfig.fields.primaryKey] = entityId
+		query = buildEntityQuery(entityId, entityConfig)
 		unless loggedInUser.isAdmin
 		unless loggedInUser.isAdmin
 			query[entityConfig.fields.access] = ObjectId(loggedInUser._id)
 			query[entityConfig.fields.access] = ObjectId(loggedInUser._id)
 		EntityModels[entityConfig.modelName].findOne query, callback
 		EntityModels[entityConfig.modelName].findOne query, callback
 
 
+	getEntityWithoutAuthorizationCheck: (entityId, entityConfig, callback = (error, entity) ->) ->
+		query = buildEntityQuery(entityId, entityConfig)
+		EntityModels[entityConfig.modelName].findOne query, callback
+
+	createEntity: (entityId, entityConfig, callback = (error, entity) ->) ->
+		data = buildEntityQuery(entityId, entityConfig)
+		EntityModels[entityConfig.modelName].create data, callback
+
 	getUsers: (entity, entityConfig, callback = (error, users) ->) ->
 	getUsers: (entity, entityConfig, callback = (error, users) ->) ->
 		attributes = entityConfig.fields.read
 		attributes = entityConfig.fields.read
 		getPopulatedListOfMembers(entity, attributes, callback)
 		getPopulatedListOfMembers(entity, attributes, callback)
@@ -72,3 +78,9 @@ removeUserFromEntity = (entity, attribute, userId, callback = (error)->) ->
 	fieldUpdate = {}
 	fieldUpdate = {}
 	fieldUpdate[attribute] = userId
 	fieldUpdate[attribute] = userId
 	entity.update { $pull: fieldUpdate }, callback
 	entity.update { $pull: fieldUpdate }, callback
+
+buildEntityQuery = (entityId, entityConfig, loggedInUser) ->
+	entityId = ObjectId(entityId) if ObjectId.isValid(entityId.toString())
+	query = Object.assign({}, entityConfig.baseQuery)
+	query[entityConfig.fields.primaryKey] = entityId
+	query

+ 9 - 0
services/web/app/coffee/Features/UserMembership/UserMembershipRouter.coffee

@@ -2,6 +2,7 @@ UserMembershipAuthorization = require './UserMembershipAuthorization'
 UserMembershipController = require './UserMembershipController'
 UserMembershipController = require './UserMembershipController'
 SubscriptionGroupController = require '../Subscription/SubscriptionGroupController'
 SubscriptionGroupController = require '../Subscription/SubscriptionGroupController'
 TeamInvitesController = require '../Subscription/TeamInvitesController'
 TeamInvitesController = require '../Subscription/TeamInvitesController'
+AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear')
 
 
 module.exports =
 module.exports =
 	apply: (webRouter) ->
 	apply: (webRouter) ->
@@ -54,3 +55,11 @@ module.exports =
 		webRouter.delete "/manage/publishers/:id/managers/:userId",
 		webRouter.delete "/manage/publishers/:id/managers/:userId",
 			UserMembershipAuthorization.requirePublisherAccess,
 			UserMembershipAuthorization.requirePublisherAccess,
 			UserMembershipController.remove
 			UserMembershipController.remove
+
+		# create new entitites
+		webRouter.get "/entities/:name/create/:id",
+			AuthorizationMiddlewear.ensureUserIsSiteAdmin,
+			UserMembershipController.new
+		webRouter.post "/entities/:name/create/:id",
+			AuthorizationMiddlewear.ensureUserIsSiteAdmin,
+			UserMembershipController.create

+ 15 - 0
services/web/app/views/user_membership/new.pug

@@ -0,0 +1,15 @@
+extends ../layout
+
+block content
+	.content.content-alt
+		.container
+			.row
+				.col-md-10.col-md-offset-1
+					h3 #{entityName} "#{entityId}" does not exists in v2
+					form(
+						enctype='multipart/form-data',
+						method='post',
+						action=''
+					)
+						input(name="_csrf", type="hidden", value=csrfToken)
+						button.btn.btn-primary.text-capitalize(type="submit") Create #{entityName} in v2

+ 30 - 2
services/web/test/unit/coffee/UserMembership/UserMembershipAuthorizationTests.coffee

@@ -18,6 +18,7 @@ describe "UserMembershipAuthorization", ->
 			getSessionUser: sinon.stub().returns(@user)
 			getSessionUser: sinon.stub().returns(@user)
 		@UserMembershipHandler =
 		@UserMembershipHandler =
 			getEntity: sinon.stub().yields(null, @subscription)
 			getEntity: sinon.stub().yields(null, @subscription)
+			getEntityWithoutAuthorizationCheck: sinon.stub().yields(null, @subscription)
 		@AuthorizationMiddlewear =
 		@AuthorizationMiddlewear =
 			redirectToRestricted: sinon.stub().yields()
 			redirectToRestricted: sinon.stub().yields()
 		@UserMembershipAuthorization = SandboxedModule.require modulePath, requires:
 		@UserMembershipAuthorization = SandboxedModule.require modulePath, requires:
@@ -45,15 +46,42 @@ describe "UserMembershipAuthorization", ->
 				expect(@req.entityConfig).to.exist
 				expect(@req.entityConfig).to.exist
 				done()
 				done()
 
 
-		it 'handle entity not found', (done) ->
+		it 'handle entity not found as non-admin', (done) ->
 			@UserMembershipHandler.getEntity.yields(null, null)
 			@UserMembershipHandler.getEntity.yields(null, null)
+			@UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(null, null)
 			@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
 			@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
 				expect(error).to.extist
 				expect(error).to.extist
-				sinon.assert.called(@AuthorizationMiddlewear.redirectToRestricted)
+				expect(error).to.be.instanceof(Error)
+				expect(error.constructor.name).to.equal('NotFoundError')
 				sinon.assert.called(@UserMembershipHandler.getEntity)
 				sinon.assert.called(@UserMembershipHandler.getEntity)
 				expect(@req.entity).to.not.exist
 				expect(@req.entity).to.not.exist
 				done()
 				done()
 
 
+		it 'handle entity not found an admin can create', (done) ->
+			@user.isAdmin = true
+			@UserMembershipHandler.getEntity.yields(null, null)
+			@UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(null, null)
+			@UserMembershipAuthorization.requirePublisherAccess @req, redirect: (path) =>
+				expect(path).to.extist
+				expect(path).to.match /create/
+				done()
+
+		it 'handle entity not found an admin cannot create', (done) ->
+			@user.isAdmin = true
+			@UserMembershipHandler.getEntity.yields(null, null)
+			@UserMembershipHandler.getEntityWithoutAuthorizationCheck.yields(null, null)
+			@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
+				expect(error).to.extist
+				expect(error).to.be.instanceof(Error)
+				expect(error.constructor.name).to.equal('NotFoundError')
+				done()
+
+		it 'handle entity no access', (done) ->
+			@UserMembershipHandler.getEntity.yields(null, null)
+			@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
+				sinon.assert.called(@AuthorizationMiddlewear.redirectToRestricted)
+				done()
+
 		it 'handle anonymous user', (done) ->
 		it 'handle anonymous user', (done) ->
 			@AuthenticationController.getSessionUser.returns(null)
 			@AuthenticationController.getSessionUser.returns(null)
 			@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
 			@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>

+ 35 - 0
services/web/test/unit/coffee/UserMembership/UserMembershipControllerTests.coffee

@@ -38,6 +38,7 @@ describe "UserMembershipController", ->
 			getLoggedInUserId: sinon.stub().returns(@user._id)
 			getLoggedInUserId: sinon.stub().returns(@user._id)
 		@UserMembershipHandler =
 		@UserMembershipHandler =
 			getEntity: sinon.stub().yields(null, @subscription)
 			getEntity: sinon.stub().yields(null, @subscription)
+			createEntity: sinon.stub().yields(null, @institution)
 			getUsers: sinon.stub().yields(null, @users)
 			getUsers: sinon.stub().yields(null, @users)
 			addUser: sinon.stub().yields(null, @newUser)
 			addUser: sinon.stub().yields(null, @newUser)
 			removeUser: sinon.stub().yields(null)
 			removeUser: sinon.stub().yields(null)
@@ -204,3 +205,37 @@ describe "UserMembershipController", ->
 
 
 		it "should export the correct csv", ->
 		it "should export the correct csv", ->
 			assertCalledWith(@res.send, "mock-email-1@foo.com\nmock-email-2@foo.com\n")
 			assertCalledWith(@res.send, "mock-email-1@foo.com\nmock-email-2@foo.com\n")
+
+	describe 'new', ->
+		beforeEach ->
+			@req.params.name = 'publisher'
+			@req.params.id = 'abc'
+
+		it 'renders view', (done) ->
+			@UserMembershipController.new @req, render: (viewPath, data) =>
+				expect(data.entityName).to.eq 'publisher'
+				expect(data.entityId).to.eq 'abc'
+				done()
+
+	describe 'create', ->
+		beforeEach ->
+			@req.params.name = 'institution'
+			@req.params.id = 123
+
+		it 'creates institution', (done) ->
+			@UserMembershipController.create @req, redirect: (path) =>
+				expect(path).to.eq EntityConfigs['institution'].pathsFor(123).index
+				sinon.assert.calledWithMatch(
+					@UserMembershipHandler.createEntity,
+					123,
+					modelName: 'Institution',
+				)
+				done()
+
+		it 'checks canCreate', (done) ->
+			@req.params.name = 'group'
+			@UserMembershipController.create @req, null, (error) =>
+				expect(error).to.extist
+				expect(error).to.be.an.instanceof(Errors.NotFoundError)
+				sinon.assert.notCalled(@UserMembershipHandler.createEntity)
+				done()

+ 18 - 1
services/web/test/unit/coffee/UserMembership/UserMembershipHandlerTests.coffee

@@ -46,6 +46,7 @@ describe 'UserMembershipHandler', ->
 			findOne: sinon.stub().yields(null, @subscription)
 			findOne: sinon.stub().yields(null, @subscription)
 		@Publisher =
 		@Publisher =
 			findOne: sinon.stub().yields(null, @publisher)
 			findOne: sinon.stub().yields(null, @publisher)
+			create: sinon.stub().yields(null, @publisher)
 		@UserMembershipHandler = SandboxedModule.require modulePath, requires:
 		@UserMembershipHandler = SandboxedModule.require modulePath, requires:
 			'./UserMembershipViewModel': @UserMembershipViewModel
 			'./UserMembershipViewModel': @UserMembershipViewModel
 			'../User/UserGetter': @UserGetter
 			'../User/UserGetter': @UserGetter
@@ -57,7 +58,7 @@ describe 'UserMembershipHandler', ->
 				log: -> 
 				log: -> 
 				err: ->
 				err: ->
 
 
-	describe 'getEntty', ->
+	describe 'getEntity', ->
 		describe 'group subscriptions', ->
 		describe 'group subscriptions', ->
 			it 'get subscription', (done) ->
 			it 'get subscription', (done) ->
 				@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, @user, (error, subscription) =>
 				@UserMembershipHandler.getEntity @fakeEntityId, EntityConfigs.group, @user, (error, subscription) =>
@@ -86,6 +87,15 @@ describe 'UserMembershipHandler', ->
 					should.exist(error)
 					should.exist(error)
 					done()
 					done()
 
 
+	describe 'getEntityWithoutAuthorizationCheck', ->
+		it 'get publisher', (done) ->
+			@UserMembershipHandler.getEntityWithoutAuthorizationCheck @fakeEntityId, EntityConfigs.publisher, (error, subscription) =>
+				should.not.exist(error)
+				expectedQuery = slug: @fakeEntityId
+				assertCalledWith(@Publisher.findOne, expectedQuery)
+				expect(subscription).to.equal @publisher
+				done()
+
 		describe 'institutions', ->
 		describe 'institutions', ->
 			it 'get institution', (done) ->
 			it 'get institution', (done) ->
 				@UserMembershipHandler.getEntity @institution.v1Id, EntityConfigs.institution, @user, (error, institution) =>
 				@UserMembershipHandler.getEntity @institution.v1Id, EntityConfigs.institution, @user, (error, institution) =>
@@ -136,6 +146,13 @@ describe 'UserMembershipHandler', ->
 					expect(@UserMembershipViewModel.buildAsync.callCount).to.equal expectedCallcount
 					expect(@UserMembershipViewModel.buildAsync.callCount).to.equal expectedCallcount
 					done()
 					done()
 
 
+	describe 'createEntity', ->
+		it 'creates publisher', (done) ->
+			@UserMembershipHandler.createEntity @fakeEntityId, EntityConfigs.publisher, (error, publisher) =>
+				should.not.exist(error)
+				assertCalledWith(@Publisher.create, slug: @fakeEntityId)
+				done()
+
 	describe 'addUser', ->
 	describe 'addUser', ->
 		beforeEach ->
 		beforeEach ->
 			@email = @newUser.email
 			@email = @newUser.email