rate limit pdf downloads

This commit is contained in:
Henry Oswald 2015-10-30 11:35:11 +00:00
parent 730088b6ab
commit cf48c94725
2 changed files with 60 additions and 10 deletions

View file

@ -7,6 +7,7 @@ request = require "request"
Settings = require "settings-sharelatex" Settings = require "settings-sharelatex"
AuthenticationController = require "../Authentication/AuthenticationController" AuthenticationController = require "../Authentication/AuthenticationController"
UserGetter = require "../User/UserGetter" UserGetter = require "../User/UserGetter"
RateLimiter = require("../../infrastructure/RateLimiter")
module.exports = CompileController = module.exports = CompileController =
compile: (req, res, next = (error) ->) -> compile: (req, res, next = (error) ->) ->
@ -35,8 +36,11 @@ module.exports = CompileController =
} }
downloadPdf: (req, res, next = (error) ->)-> downloadPdf: (req, res, next = (error) ->)->
Metrics.inc "pdf-downloads" Metrics.inc "pdf-downloads"
project_id = req.params.Project_id project_id = req.params.Project_id
isPdfjsPartialDownload = req.query?.pdfng
Project.findById project_id, {name: 1}, (err, project)-> Project.findById project_id, {name: 1}, (err, project)->
res.contentType("application/pdf") res.contentType("application/pdf")
if !!req.query.popupDownload if !!req.query.popupDownload
@ -45,6 +49,27 @@ module.exports = CompileController =
else else
logger.log project_id: project_id, "download pdf to embed in browser" logger.log project_id: project_id, "download pdf to embed in browser"
res.header('Content-Disposition', "filename=#{project.getSafeProjectName()}.pdf") res.header('Content-Disposition', "filename=#{project.getSafeProjectName()}.pdf")
if isPdfjsPartialDownload
rateLimitOpts =
endpointName: "partial-pdf-download"
throttle: 500
else
rateLimitOpts =
endpointName: "full-pdf-download"
throttle: 50
rateLimitOpts.subjectName = req.ip
rateLimitOpts.timeInterval = 60 * 5
RateLimiter.addCount rateLimitOpts, (err, canContinue)->
if err?
logger.err err:err, "error checking rate limit for pdf download"
return res.send 500
else if !canContinue
logger.log project_id:project_id, ip:req.ip, "rate limit hit downloading pdf"
res.send 500
else
CompileController.proxyToClsi(project_id, "/project/#{project_id}/output/output.pdf", req, res, next) CompileController.proxyToClsi(project_id, "/project/#{project_id}/output/output.pdf", req, res, next)
deleteAuxFiles: (req, res, next) -> deleteAuxFiles: (req, res, next) ->

View file

@ -15,13 +15,15 @@ describe "CompileController", ->
@ClsiManager = {} @ClsiManager = {}
@UserGetter = @UserGetter =
getUser:sinon.stub() getUser:sinon.stub()
@CompileController = SandboxedModule.require modulePath, requires: @RateLimiter = {addCount:sinon.stub()}
"settings-sharelatex": @settings = @settings =
apis: apis:
clsi: clsi:
url: "clsi.example.com" url: "clsi.example.com"
clsi_priority: clsi_priority:
url: "clsi-priority.example.com" url: "clsi-priority.example.com"
@CompileController = SandboxedModule.require modulePath, requires:
"settings-sharelatex": @settings
"request": @request = sinon.stub() "request": @request = sinon.stub()
"../../models/Project": Project: @Project = {} "../../models/Project": Project: @Project = {}
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
@ -30,6 +32,7 @@ describe "CompileController", ->
"../User/UserGetter":@UserGetter "../User/UserGetter":@UserGetter
"./ClsiManager": @ClsiManager "./ClsiManager": @ClsiManager
"../Authentication/AuthenticationController": @AuthenticationController = {} "../Authentication/AuthenticationController": @AuthenticationController = {}
"../../infrastructure/RateLimiter":@RateLimiter
@project_id = "project-id" @project_id = "project-id"
@user = @user =
features: features:
@ -94,11 +97,13 @@ describe "CompileController", ->
@project = @project =
getSafeProjectName: () => @safe_name = "safe-name" getSafeProjectName: () => @safe_name = "safe-name"
@req.query = {pdfng:true}
@Project.findById = sinon.stub().callsArgWith(2, null, @project) @Project.findById = sinon.stub().callsArgWith(2, null, @project)
describe "when downloading for embedding", -> describe "when downloading for embedding", ->
beforeEach -> beforeEach ->
@CompileController.proxyToClsi = sinon.stub() @CompileController.proxyToClsi = sinon.stub()
@RateLimiter.addCount.callsArgWith(1, null, true)
@CompileController.downloadPdf(@req, @res, @next) @CompileController.downloadPdf(@req, @res, @next)
it "should look up the project", -> it "should look up the project", ->
@ -122,9 +127,29 @@ describe "CompileController", ->
.should.equal true .should.equal true
it "should proxy the PDF from the CLSI", -> it "should proxy the PDF from the CLSI", ->
@CompileController.proxyToClsi @CompileController.proxyToClsi.calledWith(@project_id, "/project/#{@project_id}/output/output.pdf", @req, @res, @next).should.equal true
.calledWith(@project_id, "/project/#{@project_id}/output/output.pdf", @req, @res, @next)
.should.equal true it "should check the rate limiter", ->
@RateLimiter.addCount.args[0][0].throttle.should.equal 500
describe "when the pdf is not going to be used in pdfjs viewer", ->
it "should check the rate limiter when pdfng is not set", (done)->
@req.query = {}
@RateLimiter.addCount.callsArgWith(1, null, true)
@CompileController.proxyToClsi = (project_id, url)=>
@RateLimiter.addCount.args[0][0].throttle.should.equal 50
done()
@CompileController.downloadPdf @req, @res
it "should check the rate limiter when pdfng is false", (done)->
@req.query = {pdfng:false}
@RateLimiter.addCount.callsArgWith(1, null, true)
@CompileController.proxyToClsi = (project_id, url)=>
@RateLimiter.addCount.args[0][0].throttle.should.equal 50
done()
@CompileController.downloadPdf @req, @res
describe "proxyToClsi", -> describe "proxyToClsi", ->
beforeEach -> beforeEach ->