diff --git a/services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee b/services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee index 867f5f894f..0cebb45fb1 100644 --- a/services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee +++ b/services/web/app/coffee/Features/Authorization/AuthorizationManager.coffee @@ -25,9 +25,10 @@ module.exports = AuthorizationManager = # * privilegeLevel: "owner", "readAndWrite", of "readOnly" if the user has # access. false if the user does not have access # * becausePublic: true if the access level is only because the project is public. + # * becauseSiteAdmin: true if access level is only because user is admin getPrivilegeLevelForProject: ( user_id, project_id, token, - callback = (error, privilegeLevel, becausePublic) -> + callback = (error, privilegeLevel, becausePublic, becauseSiteAdmin) -> ) -> if !user_id? # User is Anonymous, Try Token-based access @@ -41,48 +42,48 @@ module.exports = AuthorizationManager = return callback(err) if err? if isValidReadOnly # Grant anonymous user read-only access - callback null, PrivilegeLevels.READ_ONLY, false + callback null, PrivilegeLevels.READ_ONLY, false, false else if ( isValidReadAndWrite and TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED ) # Grant anonymous user read-and-write access - callback null, PrivilegeLevels.READ_AND_WRITE, false + callback null, PrivilegeLevels.READ_AND_WRITE, false, false else # Deny anonymous access - callback null, PrivilegeLevels.NONE, false + callback null, PrivilegeLevels.NONE, false, false else if publicAccessLevel == PublicAccessLevels.READ_ONLY # Legacy public read-only access for anonymous user - callback null, PrivilegeLevels.READ_ONLY, true + callback null, PrivilegeLevels.READ_ONLY, true, false else if publicAccessLevel == PublicAccessLevels.READ_AND_WRITE # Legacy public read-write access for anonymous user - callback null, PrivilegeLevels.READ_AND_WRITE, true + callback null, PrivilegeLevels.READ_AND_WRITE, true, false else # Deny anonymous user access - callback null, PrivilegeLevels.NONE, false + callback null, PrivilegeLevels.NONE, false, false else # User is present, get their privilege level from database CollaboratorsHandler.getMemberIdPrivilegeLevel user_id, project_id, (error, privilegeLevel) -> return callback(error) if error? if privilegeLevel? and privilegeLevel != PrivilegeLevels.NONE # The user has direct access - callback null, privilegeLevel, false + callback null, privilegeLevel, false, false else AuthorizationManager.isUserSiteAdmin user_id, (error, isAdmin) -> return callback(error) if error? if isAdmin - callback null, PrivilegeLevels.OWNER, false + callback null, PrivilegeLevels.OWNER, false, true else # Legacy public-access system # User is present (not anonymous), but does not have direct access AuthorizationManager.getPublicAccessLevel project_id, (err, publicAccessLevel) -> return callback(err) if err? if publicAccessLevel == PublicAccessLevels.READ_ONLY - callback null, PrivilegeLevels.READ_ONLY, true + callback null, PrivilegeLevels.READ_ONLY, true, false else if publicAccessLevel == PublicAccessLevels.READ_AND_WRITE - callback null, PrivilegeLevels.READ_AND_WRITE, true + callback null, PrivilegeLevels.READ_AND_WRITE, true, false else - callback null, PrivilegeLevels.NONE, false + callback null, PrivilegeLevels.NONE, false, false canUserReadProject: (user_id, project_id, token, callback = (error, canRead) ->) -> AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel) -> @@ -104,10 +105,10 @@ module.exports = AuthorizationManager = else return callback null, false - canUserAdminProject: (user_id, project_id, token, callback = (error, canAdmin) ->) -> - AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel) -> + canUserAdminProject: (user_id, project_id, token, callback = (error, canAdmin, becauseSiteAdmin) ->) -> + AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel, becausePublic, becauseSiteAdmin) -> return callback(error) if error? - return callback null, (privilegeLevel == PrivilegeLevels.OWNER) + return callback null, (privilegeLevel == PrivilegeLevels.OWNER), becauseSiteAdmin isUserSiteAdmin: (user_id, callback = (error, isAdmin) ->) -> if !user_id? diff --git a/services/web/app/coffee/Features/Project/ProjectCollabratecDetailsHandler.coffee b/services/web/app/coffee/Features/Project/ProjectCollabratecDetailsHandler.coffee index 85ee4d94a9..02d54cf1fe 100644 --- a/services/web/app/coffee/Features/Project/ProjectCollabratecDetailsHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectCollabratecDetailsHandler.coffee @@ -1,6 +1,54 @@ +ObjectId = require("mongojs").ObjectId Project = require("../../models/Project").Project module.exports = ProjectCollabratecDetailsHandler = - initializeCollabratecProject: (project_id, name, user_id, collabratec_document_id, collabratec_privategroup_id, callback=(err)->) -> - update = $set: { name, collabratecUsers: [ { user_id, collabratec_document_id, collabratec_privategroup_id } ] } + initializeCollabratecProject: (project_id, user_id, collabratec_document_id, collabratec_privategroup_id, callback=(err)->) -> + try + project_id = ObjectId(project_id) + user_id = ObjectId(user_id) + catch err + return callback err + update = $set: { collabratecUsers: [ { user_id, collabratec_document_id, collabratec_privategroup_id } ] } Project.update { _id: project_id }, update, callback + + isLinkedCollabratecUserProject: (project_id, user_id, callback=(err, isLinked)->) -> + try + project_id = ObjectId(project_id) + user_id = ObjectId(user_id) + catch err + return callback err + query = + _id: project_id + collabratecUsers: $elemMatch: + user_id: user_id + Project.findOne query, {_id: 1}, (err, project) -> + callback err if err? + callback null, project? + + linkCollabratecUserProject: (project_id, user_id, collabratec_document_id, callback=(err)->) -> + try + project_id = ObjectId(project_id) + user_id = ObjectId(user_id) + catch err + return callback err + query = + _id: project_id + collabratecUsers: $not: $elemMatch: + collabratec_document_id: collabratec_document_id + user_id: user_id + update = $push: collabratecUsers: + collabratec_document_id: collabratec_document_id + user_id: user_id + Project.update query, update, callback + + unlinkCollabratecUserProject: (project_id, user_id, callback=(err)->) -> + try + project_id = ObjectId(project_id) + user_id = ObjectId(user_id) + catch err + return callback err + query = + _id: project_id + update = $pull: collabratecUsers: + user_id: user_id + Project.update query, update, callback diff --git a/services/web/test/unit/coffee/Authorization/AuthorizationManagerTests.coffee b/services/web/test/unit/coffee/Authorization/AuthorizationManagerTests.coffee index 932fa375b7..063d5bdd26 100644 --- a/services/web/test/unit/coffee/Authorization/AuthorizationManagerTests.coffee +++ b/services/web/test/unit/coffee/Authorization/AuthorizationManagerTests.coffee @@ -42,7 +42,7 @@ describe "AuthorizationManager", -> @AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @token, @callback it "should return the user's privilege level", -> - @callback.calledWith(null, "readOnly", false).should.equal true + @callback.calledWith(null, "readOnly", false, false).should.equal true describe "with a user_id with no privilege level", -> beforeEach -> @@ -53,7 +53,7 @@ describe "AuthorizationManager", -> @AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @token, @callback it "should return false", -> - @callback.calledWith(null, false, false).should.equal true + @callback.calledWith(null, false, false, false).should.equal true describe "with a user_id who is an admin", -> beforeEach -> @@ -64,7 +64,7 @@ describe "AuthorizationManager", -> @AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @token, @callback it "should return the user as an owner", -> - @callback.calledWith(null, "owner", false).should.equal true + @callback.calledWith(null, "owner", false, true).should.equal true describe "with no user (anonymous)", -> @@ -86,7 +86,7 @@ describe "AuthorizationManager", -> @TokenAccessHandler.isValidToken.calledWith(@project_id, @token).should.equal true it "should return false", -> - @callback.calledWith(null, false, false).should.equal true + @callback.calledWith(null, false, false, false).should.equal true describe 'when the token is valid for read-and-write', -> @@ -108,7 +108,7 @@ describe "AuthorizationManager", -> @TokenAccessHandler.isValidToken.calledWith(@project_id, @token).should.equal true it "should deny access", -> - @callback.calledWith(null, false, false).should.equal true + @callback.calledWith(null, false, false, false).should.equal true describe 'when read-write-sharing is enabled', -> beforeEach -> @@ -165,7 +165,7 @@ describe "AuthorizationManager", -> @AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @token, @callback it "should return the user's privilege level", -> - @callback.calledWith(null, "readOnly", false).should.equal true + @callback.calledWith(null, "readOnly", false, false).should.equal true describe "with a user_id with no privilege level", -> beforeEach -> @@ -176,7 +176,7 @@ describe "AuthorizationManager", -> @AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @token, @callback it "should return false", -> - @callback.calledWith(null, false, false).should.equal true + @callback.calledWith(null, false, false, false).should.equal true describe "with a user_id who is an admin", -> beforeEach -> @@ -187,7 +187,7 @@ describe "AuthorizationManager", -> @AuthorizationManager.getPrivilegeLevelForProject @user_id, @project_id, @token, @callback it "should return the user as an owner", -> - @callback.calledWith(null, "owner", false).should.equal true + @callback.calledWith(null, "owner", false, true).should.equal true describe "with no user (anonymous)", -> beforeEach -> @@ -200,7 +200,7 @@ describe "AuthorizationManager", -> @AuthorizationManager.isUserSiteAdmin.called.should.equal false it "should return false", -> - @callback.calledWith(null, false, false).should.equal true + @callback.calledWith(null, false, false, false).should.equal true describe "with a public project", -> beforeEach -> diff --git a/services/web/test/unit/coffee/Project/ProjectCollabratecDetailsTest.coffee b/services/web/test/unit/coffee/Project/ProjectCollabratecDetailsTest.coffee index b23d75fe54..e3b4d2cc2d 100644 --- a/services/web/test/unit/coffee/Project/ProjectCollabratecDetailsTest.coffee +++ b/services/web/test/unit/coffee/Project/ProjectCollabratecDetailsTest.coffee @@ -13,6 +13,8 @@ modulePath = Path.join __dirname, "../../../../app/js/Features/Project/ProjectCo describe "ProjectCollabratecDetailsHandler", -> beforeEach -> + @projectId = ObjectId("5bea8747c7bba6012fcaceb3") + @userId = ObjectId("5be316a9c7f6aa03802ea8fb") @ProjectModel = {} @ProjectCollabratecDetailsHandler = SandboxedModule.require modulePath, requires: "../../models/Project": { Project: @ProjectModel } @@ -23,23 +25,150 @@ describe "ProjectCollabratecDetailsHandler", -> describe "when update succeeds", -> beforeEach -> @ProjectModel.update = sinon.stub().yields() - @ProjectCollabratecDetailsHandler.initializeCollabratecProject "project-id", "name", "user-id", "collabratec-document-id", "collabratec-private-group-id", @callback + @ProjectCollabratecDetailsHandler.initializeCollabratecProject @projectId, @userId, "collabratec-document-id", "collabratec-private-group-id", @callback it "should update project model", -> update = $set: { - name: "name", collabratecUsers: [ { - user_id: "user-id", + user_id: @userId, collabratec_document_id: "collabratec-document-id", collabratec_privategroup_id: "collabratec-private-group-id" } ] } - expect(@ProjectModel.update).to.have.been.calledWith { _id: "project-id" }, update, @callback + expect(@ProjectModel.update).to.have.been.calledWith { _id: @projectId }, update, @callback describe "when update has error", -> beforeEach -> @ProjectModel.update = sinon.stub().yields("error") - @ProjectCollabratecDetailsHandler.initializeCollabratecProject "project-id", "name", "user-id", "collabratec-document-id", "collabratec-private-group-id", @callback + @ProjectCollabratecDetailsHandler.initializeCollabratecProject @projectId, @userId, "collabratec-document-id", "collabratec-private-group-id", @callback it "should callback with error", -> expect(@callback).to.have.been.calledWith("error") + + describe "with invalid args", -> + beforeEach -> + @ProjectModel.update = sinon.stub() + @ProjectCollabratecDetailsHandler.initializeCollabratecProject "bad-project-id", "bad-user-id", "collabratec-document-id", "collabratec-private-group-id", @callback + + it "should not update", -> + expect(@ProjectModel.update).not.to.have.beenCalled + + it "should callback with error", -> + expect(@callback.firstCall.args[0]).to.be.instanceOf Error + + describe "isLinkedCollabratecUserProject", -> + beforeEach -> + @ProjectModel.findOne = sinon.stub().yields() + + describe "when find succeeds", -> + describe "when user project found", -> + beforeEach -> + @ProjectModel.findOne = sinon.stub().yields(null, "project") + @ProjectCollabratecDetailsHandler.isLinkedCollabratecUserProject @projectId, @userId, @callback + + it "should call find with project and user id", -> + expect(@ProjectModel.findOne).to.have.been.calledWithMatch { + _id: ObjectId(@projectId) + collabratecUsers: $elemMatch: + user_id: ObjectId(@userId) + } + + it "should callback with true", -> + expect(@callback).to.have.been.calledWith null, true + + describe "when user project found", -> + beforeEach -> + @ProjectModel.findOne = sinon.stub().yields(null, null) + @ProjectCollabratecDetailsHandler.isLinkedCollabratecUserProject @projectId, @userId, @callback + + it "should callback with false", -> + expect(@callback).to.have.been.calledWith null, false + + describe "when find has error", -> + beforeEach -> + @ProjectModel.findOne = sinon.stub().yields("error") + @ProjectCollabratecDetailsHandler.isLinkedCollabratecUserProject @projectId, @userId, @callback + + it "should callback with error", -> + expect(@callback).to.have.been.calledWith "error" + + describe "with invalid args", -> + beforeEach -> + @ProjectModel.findOne = sinon.stub() + @ProjectCollabratecDetailsHandler.isLinkedCollabratecUserProject "bad-project-id", "bad-user-id", @callback + + it "should not update", -> + expect(@ProjectModel.findOne).not.to.have.beenCalled + + it "should callback with error", -> + expect(@callback.firstCall.args[0]).to.be.instanceOf Error + + describe "linkCollabratecUserProject", -> + + describe "when update succeeds", -> + beforeEach -> + @ProjectModel.update = sinon.stub().yields() + @ProjectCollabratecDetailsHandler.linkCollabratecUserProject @projectId, @userId, "collabratec-document-id", @callback + + it "should update project model", -> + query = + _id: @projectId + collabratecUsers: $not: $elemMatch: + collabratec_document_id: "collabratec-document-id" + user_id: @userId + update = $push: collabratecUsers: + collabratec_document_id: "collabratec-document-id" + user_id: @userId + expect(@ProjectModel.update).to.have.been.calledWith query, update, @callback + + describe "when update has error", -> + beforeEach -> + @ProjectModel.update = sinon.stub().yields("error") + @ProjectCollabratecDetailsHandler.linkCollabratecUserProject @projectId, @userId, "collabratec-document-id", @callback + + it "should callback with error", -> + expect(@callback).to.have.been.calledWith("error") + + describe "with invalid args", -> + beforeEach -> + @ProjectModel.update = sinon.stub() + @ProjectCollabratecDetailsHandler.linkCollabratecUserProject "bad-project-id", "bad-user-id", "collabratec-document-id", @callback + + it "should not update", -> + expect(@ProjectModel.update).not.to.have.beenCalled + + it "should callback with error", -> + expect(@callback.firstCall.args[0]).to.be.instanceOf Error + + describe "unlinkCollabratecUserProject", -> + + describe "when update succeeds", -> + beforeEach -> + @ProjectModel.update = sinon.stub().yields() + @ProjectCollabratecDetailsHandler.unlinkCollabratecUserProject @projectId, @userId, @callback + + it "should update project model", -> + query = + _id: @projectId + update = $pull: collabratecUsers: + user_id: @userId + expect(@ProjectModel.update).to.have.been.calledWith query, update, @callback + + describe "when update has error", -> + beforeEach -> + @ProjectModel.update = sinon.stub().yields("error") + @ProjectCollabratecDetailsHandler.unlinkCollabratecUserProject @projectId, @userId, @callback + + it "should callback with error", -> + expect(@callback).to.have.been.calledWith("error") + + describe "with invalid args", -> + beforeEach -> + @ProjectModel.update = sinon.stub() + @ProjectCollabratecDetailsHandler.unlinkCollabratecUserProject "bad-project-id", "bad-user-id", @callback + + it "should not update", -> + expect(@ProjectModel.update).not.to.have.beenCalled + + it "should callback with error", -> + expect(@callback.firstCall.args[0]).to.be.instanceOf Error