Generate tokens on old projects if they're not present

This commit is contained in:
Shane Kilkelly 2017-10-04 16:31:24 +01:00
parent b6c2a8f7f7
commit 6482cd7dd8
8 changed files with 242 additions and 31 deletions

View file

@ -9,6 +9,7 @@ DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
EditorRealTimeController = require("./EditorRealTimeController") EditorRealTimeController = require("./EditorRealTimeController")
async = require('async') async = require('async')
LockManager = require("../../infrastructure/LockManager") LockManager = require("../../infrastructure/LockManager")
PublicAccessLevels = require("../Authorization/PublicAccessLevels")
_ = require('underscore') _ = require('underscore')
module.exports = EditorController = module.exports = EditorController =
@ -200,8 +201,22 @@ module.exports = EditorController =
setPublicAccessLevel : (project_id, newAccessLevel, callback = (err) ->) -> setPublicAccessLevel : (project_id, newAccessLevel, callback = (err) ->) ->
ProjectDetailsHandler.setPublicAccessLevel project_id, newAccessLevel, (err) -> ProjectDetailsHandler.setPublicAccessLevel project_id, newAccessLevel, (err) ->
return callback(err) if err? return callback(err) if err?
EditorRealTimeController.emitToRoom project_id, 'publicAccessLevelUpdated', newAccessLevel EditorRealTimeController.emitToRoom(
callback() project_id,
'project:publicAccessLevel:changed',
{newAccessLevel}
)
if newAccessLevel == PublicAccessLevels.TOKEN_BASED
ProjectDetailsHandler.ensureTokensArePresent project_id, (err, tokens) ->
return callback(err) if err?
EditorRealTimeController.emitToRoom(
project_id,
'project:tokens:changed',
{tokens}
)
callback()
else
callback()
setRootDoc: (project_id, newRootDocID, callback = (err) ->) -> setRootDoc: (project_id, newRootDocID, callback = (err) ->) ->
ProjectEntityHandler.setRootDoc project_id, newRootDocID, (err) -> ProjectEntityHandler.setRootDoc project_id, newRootDocID, (err) ->

View file

@ -6,6 +6,7 @@ tpdsUpdateSender = require '../ThirdPartyDataStore/TpdsUpdateSender'
_ = require("underscore") _ = require("underscore")
PublicAccessLevels = require("../Authorization/PublicAccessLevels") PublicAccessLevels = require("../Authorization/PublicAccessLevels")
Errors = require("../Errors/Errors") Errors = require("../Errors/Errors")
ProjectTokenGenerator = require('./ProjectTokenGenerator')
module.exports = ProjectDetailsHandler = module.exports = ProjectDetailsHandler =
getDetails: (project_id, callback)-> getDetails: (project_id, callback)->
@ -65,6 +66,25 @@ module.exports = ProjectDetailsHandler =
setPublicAccessLevel : (project_id, newAccessLevel, callback = ->)-> setPublicAccessLevel : (project_id, newAccessLevel, callback = ->)->
logger.log project_id: project_id, level: newAccessLevel, "set public access level" logger.log project_id: project_id, level: newAccessLevel, "set public access level"
# TODO: remove the read-only and read-and-write items from here # TODO: remove the read-only and read-and-write items from here
if project_id? && newAccessLevel? and _.include [PublicAccessLevels.READ_ONLY, PublicAccessLevels.READ_AND_WRITE, PublicAccessLevels.PRIVATE, PublicAccessLevels.TOKEN_BASED], newAccessLevel if project_id? && newAccessLevel? and _.include [
PublicAccessLevels.READ_ONLY,
PublicAccessLevels.READ_AND_WRITE,
PublicAccessLevels.PRIVATE,
PublicAccessLevels.TOKEN_BASED
], newAccessLevel
Project.update {_id:project_id},{publicAccesLevel:newAccessLevel}, (err)-> Project.update {_id:project_id},{publicAccesLevel:newAccessLevel}, (err)->
callback() callback()
ensureTokensArePresent: (project_id, callback=(err, tokens)->) ->
ProjectGetter.getProject project_id, {tokens: 1}, (err, project) ->
return callback(err) if err?
if project.tokens? and project.tokens.readOnly? and project.tokens.readAndWrite?
return callback(null, project.tokens)
else
tokens =
readOnly: ProjectTokenGenerator.readOnlyToken()
readAndWrite: ProjectTokenGenerator.readAndWriteToken()
Project.update {_id: project_id}, {$set: {tokens: tokens}}, (err) ->
return callback(err) if err?
callback(null, tokens)

View file

@ -0,0 +1,16 @@
module.exports = ProjectTokenGenerator =
readOnlyToken: () ->
length = 12
tokenAlpha = 'bcdfghjkmnpqrstvwxyz'
result = ''
for _n in [1..length]
i = Math.floor(Math.floor(Math.random() * tokenAlpha.length))
result += tokenAlpha[i]
return result
readAndWriteToken: () ->
numerics = Math.random().toString().slice(2, 12)
token = ProjectTokenGenerator.readOnlyToken()
return "#{numerics}#{token}"

View file

@ -6,14 +6,7 @@ logger = require('logger-sharelatex')
sanitize = require('sanitizer') sanitize = require('sanitizer')
concreteObjectId = require('mongoose').Types.ObjectId concreteObjectId = require('mongoose').Types.ObjectId
Errors = require "../Features/Errors/Errors" Errors = require "../Features/Errors/Errors"
ProjectTokenGenerator = require '../Features/Project/ProjectTokenGenerator'
generateToken = (length) ->
tokenAlpha = 'bcdfghjkmnpqrstvwxyz'
result = ''
for _n in [1..length]
i = Math.floor(Math.floor(Math.random() * tokenAlpha.length))
result += tokenAlpha[i]
return result
Schema = mongoose.Schema Schema = mongoose.Schema
ObjectId = Schema.ObjectId ObjectId = Schema.ObjectId
@ -43,13 +36,11 @@ ProjectSchema = new Schema
tokens : tokens :
readOnly : { readOnly : {
type: String, type: String,
default: generateToken(14), default: ProjectTokenGenerator.readOnlyToken()
index: {unique: true}
} }
readAndWrite : { readAndWrite : {
type: String, type: String,
default: Math.random().toString().slice(2, 10) + generateToken(12) default: ProjectTokenGenerator.readAndWriteToken()
index: {unique: true}
} }
tokenAccessReadOnly_refs : [ type:ObjectId, ref:'User' ] tokenAccessReadOnly_refs : [ type:ObjectId, ref:'User' ]
tokenAccessReadAndWrite_refs : [ type:ObjectId, ref:'User' ] tokenAccessReadAndWrite_refs : [ type:ObjectId, ref:'User' ]

View file

@ -12,6 +12,16 @@ define [
scope: $scope scope: $scope
) )
ide.socket.on 'project:tokens:changed', (data) =>
if data.tokens?
ide.$scope.project.tokens = data.tokens
$scope.$digest()
ide.socket.on 'project:publicAccessLevel:changed', (data) =>
if data.newAccessLevel?
ide.$scope.project.publicAccesLevel = data.newAccessLevel
$scope.$digest()
ide.socket.on 'project:membership:changed', (data) => ide.socket.on 'project:membership:changed', (data) =>
if data.members if data.members
projectMembers.getMembers() projectMembers.getMembers()

View file

@ -1,7 +1,7 @@
define [ define [
"base" "base"
], (App) -> ], (App) ->
App.controller "ShareProjectModalController", ($scope, $modalInstance, $timeout, projectMembers, projectInvites, $modal, $http) -> App.controller "ShareProjectModalController", ($scope, $modalInstance, $timeout, projectMembers, projectInvites, $modal, $http, ide) ->
$scope.inputs = { $scope.inputs = {
privileges: "readAndWrite" privileges: "readAndWrite"
contacts: [] contacts: []
@ -200,10 +200,16 @@ define [
} }
$scope.getReadAndWriteTokenLink = () -> $scope.getReadAndWriteTokenLink = () ->
location.origin + "/" + $scope.project.tokens.readAndWrite if $scope?.project?.tokens?.readAndWrite?
location.origin + "/" + $scope.project.tokens.readAndWrite
else
''
$scope.getReadOnlyTokenLink = () -> $scope.getReadOnlyTokenLink = () ->
location.origin + "/read/" + $scope.project.tokens.readOnly if $scope?.project?.tokens?.readOnly?
location.origin + "/read/" + $scope.project.tokens.readOnly
else
''
$scope.done = () -> $scope.done = () ->
$modalInstance.close() $modalInstance.close()

View file

@ -564,20 +564,99 @@ describe "EditorController", ->
describe "setPublicAccessLevel", -> describe "setPublicAccessLevel", ->
beforeEach -> describe 'when setting to private', ->
@newAccessLevel = "public" beforeEach ->
@ProjectDetailsHandler.setPublicAccessLevel = sinon.stub().callsArgWith(2, null) @newAccessLevel = 'private'
@EditorRealTimeController.emitToRoom = sinon.stub() @ProjectDetailsHandler.setPublicAccessLevel = sinon.stub().callsArgWith(2, null)
@ProjectDetailsHandler.ensureTokensArePresent = sinon.stub()
.callsArgWith(1, null, @tokens)
@EditorRealTimeController.emitToRoom = sinon.stub()
it "should call the EditorController", (done)-> it 'should set the access level', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, => @EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
@ProjectDetailsHandler.setPublicAccessLevel.calledWith(@project_id, @newAccessLevel).should.equal true @ProjectDetailsHandler.setPublicAccessLevel
done() .calledWith(@project_id, @newAccessLevel).should.equal true
done()
it "should emit the update to the room", (done)-> it 'should broadcast the access level change', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, => @EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
@EditorRealTimeController.emitToRoom.calledWith(@project_id, 'publicAccessLevelUpdated', @newAccessLevel).should.equal true @EditorRealTimeController.emitToRoom
done() .calledWith(@project_id, 'project:publicAccessLevel:changed').should.equal true
done()
it 'should not ensure tokens are present for project', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
@ProjectDetailsHandler.ensureTokensArePresent
.calledWith(@project_id).should.equal false
done()
it 'should not broadcast a token change', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
@EditorRealTimeController.emitToRoom
.calledWith(@project_id, 'project:tokens:changed', {tokens: @tokens})
.should.equal false
done()
it 'should not produce an error', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, (err) =>
expect(err).to.not.exist
done()
describe 'when setting to tokenBased', ->
beforeEach ->
@newAccessLevel = 'tokenBased'
@tokens = {readOnly: 'aaa', readAndWrite: '42bbb'}
@ProjectDetailsHandler.setPublicAccessLevel = sinon.stub()
.callsArgWith(2, null)
@ProjectDetailsHandler.ensureTokensArePresent = sinon.stub()
.callsArgWith(1, null, @tokens)
@EditorRealTimeController.emitToRoom = sinon.stub()
it 'should set the access level', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
@ProjectDetailsHandler.setPublicAccessLevel
.calledWith(@project_id, @newAccessLevel).should.equal true
done()
it 'should broadcast the access level change', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
@EditorRealTimeController.emitToRoom
.calledWith(@project_id, 'project:publicAccessLevel:changed')
.should.equal true
done()
it 'should ensure tokens are present for project', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
@ProjectDetailsHandler.ensureTokensArePresent
.calledWith(@project_id).should.equal true
done()
it 'should broadcast the token change too', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
@EditorRealTimeController.emitToRoom
.calledWith(@project_id, 'project:tokens:changed', {tokens: @tokens})
.should.equal true
done()
it 'should not produce an error', (done) ->
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, (err) =>
expect(err).to.not.exist
done()
# beforeEach ->
# @newAccessLevel = "public"
# @ProjectDetailsHandler.setPublicAccessLevel = sinon.stub().callsArgWith(2, null)
# @EditorRealTimeController.emitToRoom = sinon.stub()
# it "should call the EditorController", (done)->
# @EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
# @ProjectDetailsHandler.setPublicAccessLevel.calledWith(@project_id, @newAccessLevel).should.equal true
# done()
# it "should emit the update to the room", (done)->
# @EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
# @EditorRealTimeController.emitToRoom.calledWith(@project_id, 'publicAccessLevelUpdated', @newAccessLevel).should.equal true
# done()
describe "setRootDoc", -> describe "setRootDoc", ->
@ -594,4 +673,4 @@ describe "EditorController", ->
it "should emit the update to the room", (done)-> it "should emit the update to the room", (done)->
@EditorController.setRootDoc @project_id, @newRootDocID, => @EditorController.setRootDoc @project_id, @newRootDocID, =>
@EditorRealTimeController.emitToRoom.calledWith(@project_id, 'rootDocUpdated', @newRootDocID).should.equal true @EditorRealTimeController.emitToRoom.calledWith(@project_id, 'rootDocUpdated', @newRootDocID).should.equal true
done() done()

View file

@ -38,6 +38,7 @@ describe 'ProjectDetailsHandler', ->
'logger-sharelatex': 'logger-sharelatex':
log:-> log:->
err:-> err:->
'./ProjectTokenGenerator': @ProjectTokenGenerator = {}
describe "getDetails", -> describe "getDetails", ->
@ -149,3 +150,76 @@ describe 'ProjectDetailsHandler', ->
@ProjectModel.update.calledWith({_id: @project_id}, {publicAccesLevel: @accessLevel}).should.equal true @ProjectModel.update.calledWith({_id: @project_id}, {publicAccesLevel: @accessLevel}).should.equal true
done() done()
describe "ensureTokensArePresent", ->
beforeEach ->
describe 'when the project has tokens', ->
beforeEach ->
@project =
_id: @project_id
tokens:
readOnly: 'aaa'
readAndWrite: '42bbb'
@ProjectGetter.getProject = sinon.stub()
.callsArgWith(2, null, @project)
@ProjectModel.update = sinon.stub()
it 'should get the project', (done) ->
@handler.ensureTokensArePresent @project_id, (err, tokens) =>
expect(@ProjectGetter.getProject.callCount).to.equal 1
expect(@ProjectGetter.getProject.calledWith(@project_id, {tokens: 1}))
.to.equal true
done()
it 'should not update the project with new tokens', (done) ->
@handler.ensureTokensArePresent @project_id, (err, tokens) =>
expect(@ProjectModel.update.callCount).to.equal 0
done()
it 'should produce the tokens without error', (done) ->
@handler.ensureTokensArePresent @project_id, (err, tokens) =>
expect(err).to.not.exist
expect(tokens).to.deep.equal @project.tokens
done()
describe 'when tokens are missing', ->
beforeEach ->
@project =
_id: @project_id
@ProjectGetter.getProject = sinon.stub()
.callsArgWith(2, null, @project)
@readOnlyToken = 'abc'
@readAndWriteToken = '42def'
@ProjectTokenGenerator.readOnlyToken = sinon.stub().returns(@readOnlyToken)
@ProjectTokenGenerator.readAndWriteToken = sinon.stub().returns(@readAndWriteToken)
@ProjectModel.update = sinon.stub()
.callsArgWith(2, null)
it 'should get the project', (done) ->
@handler.ensureTokensArePresent @project_id, (err, tokens) =>
expect(@ProjectGetter.getProject.callCount).to.equal 1
expect(@ProjectGetter.getProject.calledWith(@project_id, {tokens: 1}))
.to.equal true
done()
it 'should update the project with new tokens', (done) ->
@handler.ensureTokensArePresent @project_id, (err, tokens) =>
expect(@ProjectTokenGenerator.readOnlyToken.callCount)
.to.equal 1
expect(@ProjectTokenGenerator.readAndWriteToken.callCount)
.to.equal 1
expect(@ProjectModel.update.callCount).to.equal 1
expect(@ProjectModel.update.calledWith(
{_id: @project_id},
{$set: {tokens: {readOnly: @readOnlyToken, readAndWrite: @readAndWriteToken}}}
)).to.equal true
done()
it 'should produce the tokens without error', (done) ->
@handler.ensureTokensArePresent @project_id, (err, tokens) =>
expect(err).to.not.exist
expect(tokens).to.deep.equal {
readOnly: @readOnlyToken,
readAndWrite: @readAndWriteToken
}
done()