Add endpoint for arbitrary bucket fetch

Add `/bucket/:bucket/key/*`, which fetches the file from the given bucket at the given path. Uses auth stored at `settings.filestore.s3.{{bucketName}}` if present, and otherwise default auth.
This commit is contained in:
Michael Mazour 2018-07-04 11:18:55 +01:00
parent 980e1a5d6a
commit feca8933f1
5 changed files with 145 additions and 5 deletions

View file

@ -4,6 +4,7 @@ logger.initialize("filestore")
settings = require("settings-sharelatex") settings = require("settings-sharelatex")
request = require("request") request = require("request")
fileController = require("./app/js/FileController") fileController = require("./app/js/FileController")
bucketController = require("./app/js/BucketController")
keyBuilder = require("./app/js/KeyBuilder") keyBuilder = require("./app/js/KeyBuilder")
healthCheckController = require("./app/js/HealthCheckController") healthCheckController = require("./app/js/HealthCheckController")
domain = require("domain") domain = require("domain")
@ -86,6 +87,8 @@ app.del "/project/:project_id/public/:public_file_id", keyBuilder.publicFileKey,
app.get "/project/:project_id/size", keyBuilder.publicProjectKey, fileController.directorySize app.get "/project/:project_id/size", keyBuilder.publicProjectKey, fileController.directorySize
app.get "/bucket/:bucket/key/*", bucketController.getFile
app.get "/heapdump", (req, res)-> app.get "/heapdump", (req, res)->
require('heapdump').writeSnapshot '/tmp/' + Date.now() + '.filestore.heapsnapshot', (err, filename)-> require('heapdump').writeSnapshot '/tmp/' + Date.now() + '.filestore.heapsnapshot', (err, filename)->
res.send filename res.send filename
@ -105,8 +108,6 @@ app.get '/status', (req, res)->
app.get "/health_check", healthCheckController.check app.get "/health_check", healthCheckController.check
app.get '*', (req, res)-> app.get '*', (req, res)->
res.send 404 res.send 404

View file

@ -0,0 +1,36 @@
PersistorManager = require("./PersistorManager")
settings = require("settings-sharelatex")
logger = require("logger-sharelatex")
FileHandler = require("./FileHandler")
metrics = require("metrics-sharelatex")
parseRange = require('range-parser')
Errors = require('./Errors')
oneDayInSeconds = 60 * 60 * 24
maxSizeInBytes = 1024 * 1024 * 1024 # 1GB
module.exports = BucketController =
getFile: (req, res)->
{bucket} = req
key = req[0]
{format, style} = req.query
credentials = settings.filestore.s3&[bucket]
options = {
key: key,
bucket: bucket,
credentials: credentials
}
metrics.inc "getFile"
logger.log key:key, bucket:bucket, "receiving request to get file from bucket"
FileHandler.getFile bucket, key, options, (err, fileStream)->
if err?
logger.err err:err, key:key, bucket:bucket, format:format, style:style, "problem getting file from bucket"
if err instanceof Errors.NotFoundError
return res.send 404
else
return res.send 500
else
logger.log key:key, bucket:bucket, format:format, style:style, "sending bucket file to response"
fileStream.pipe res

View file

@ -68,8 +68,8 @@ module.exports =
callback = _.once callback callback = _.once callback
logger.log bucketName:bucketName, key:key, "getting file from s3" logger.log bucketName:bucketName, key:key, "getting file from s3"
s3Client = knox.createClient s3Client = knox.createClient
key: settings.filestore.s3.key key: opts.credentials?.auth_key || settings.filestore.s3.key
secret: settings.filestore.s3.secret secret: opts.credentials?.auth_secret || settings.filestore.s3.secret
bucket: bucketName bucket: bucketName
s3Stream = s3Client.get(key, headers) s3Stream = s3Client.get(key, headers)
s3Stream.end() s3Stream.end()

View file

@ -0,0 +1,68 @@
assert = require("chai").assert
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../app/js/BucketController.js"
SandboxedModule = require('sandboxed-module')
describe "BucketController", ->
beforeEach ->
@PersistorManager =
sendStream: sinon.stub()
copyFile: sinon.stub()
deleteFile:sinon.stub()
@settings =
s3:
buckets:
user_files:"user_files"
filestore:
backend: "s3"
s3:
secret: "secret"
key: "this_key"
@FileHandler =
getFile: sinon.stub()
deleteFile: sinon.stub()
insertFile: sinon.stub()
getDirectorySize: sinon.stub()
@LocalFileWriter = {}
@controller = SandboxedModule.require modulePath, requires:
"./LocalFileWriter":@LocalFileWriter
"./FileHandler": @FileHandler
"./PersistorManager":@PersistorManager
"settings-sharelatex": @settings
"logger-sharelatex":
log:->
err:->
@project_id = "project_id"
@file_id = "file_id"
@bucket = "user_files"
@key = "#{@project_id}/#{@file_id}"
@req =
bucket:@bucket
0:@key
query:{}
headers: {}
@res =
setHeader: ->
@fileStream = {}
describe "getFile", ->
it "should pipe the stream", (done)->
@FileHandler.getFile.callsArgWith(3, null, @fileStream)
@fileStream.pipe = (res)=>
res.should.equal @res
done()
@controller.getFile @req, @res
it "should send a 500 if there is a problem", (done)->
@FileHandler.getFile.callsArgWith(3, "error")
@res.send = (code)=>
code.should.equal 500
done()
@controller.getFile @req, @res

View file

@ -55,6 +55,41 @@ describe "S3PersistorManagerTests", ->
@stubbedKnoxClient.get.calledWith(@key).should.equal true @stubbedKnoxClient.get.calledWith(@key).should.equal true
done() done()
it "should use default auth", (done)->
@stubbedKnoxClient.get.returns(
on:->
end:->
)
@S3PersistorManager.getFileStream @bucketName, @key, @opts, (err)=> # empty callback
clientParams =
key: @settings.filestore.s3.key
secret: @settings.filestore.s3.secret
bucket: @bucketName
@knox.createClient.calledWith(clientParams).should.equal true
done()
describe "with supplied auth", ->
beforeEach ->
@S3PersistorManager = SandboxedModule.require modulePath, requires: @requires
@credentials =
auth_key: "that_key"
auth_secret: "that_secret"
@opts =
credentials: @credentials
it "should use supplied auth", (done)->
@stubbedKnoxClient.get.returns(
on:->
end:->
)
@S3PersistorManager.getFileStream @bucketName, @key, @opts, (err)=> # empty callback
clientParams =
key: @credentials.auth_key
secret: @credentials.auth_secret
bucket: @bucketName
@knox.createClient.calledWith(clientParams).should.equal true
done()
describe "with start and end options", -> describe "with start and end options", ->
beforeEach -> beforeEach ->
@opts = @opts =