diff --git a/libraries/object-persistor/README.md b/libraries/object-persistor/README.md index 45d7a0df09..75d947fd96 100644 --- a/libraries/object-persistor/README.md +++ b/libraries/object-persistor/README.md @@ -293,6 +293,7 @@ GCS authentication is configured automatically via the local service account, or - `gcs.unlockBeforeDelete`: unlock an event-based hold before deleting. default false (see notes) - `gcs.deletedBucketSuffix`: if present, copy the object to a bucket with this suffix before deletion (see notes) - `gcs.deleteConcurrency`: when recursively deleting a directory, the maximum number of delete requests that will be used at once (default 50) +- `gcs.unsignedUrls`: For testing - do not sign GCS download URLs - `gcs.endpoint.apiEndpoint`: For testing - specify a different GCS endpoint to use - `gcs.endpoint.apiScheme`: For testing - specify a scheme to use for the GCS endpoint (`http` or `https`) - `gcs.endpoint.projectId`: For testing - the GCS project ID to supply to the overridden backend diff --git a/libraries/object-persistor/src/GcsPersistor.js b/libraries/object-persistor/src/GcsPersistor.js index b4dcdbdaed..3f7b7a5be3 100644 --- a/libraries/object-persistor/src/GcsPersistor.js +++ b/libraries/object-persistor/src/GcsPersistor.js @@ -125,6 +125,14 @@ module.exports = class GcsPersistor extends AbstractPersistor { } async getRedirectUrl(bucketName, key) { + if (this.settings.unsignedUrls) { + // Construct a direct URL to the object download endpoint + // (see https://cloud.google.com/storage/docs/request-endpoints#json-api) + const apiScheme = this.settings.endpoint.apiScheme || 'https://' + const apiEndpoint = + this.settings.endpoint.apiEndpoint || 'storage.googleapis.com' + return `${apiScheme}://${apiEndpoint}/download/storage/v1/b/${bucketName}/o/${key}?alt=media` + } try { const [url] = await this.storage .bucket(bucketName) diff --git a/libraries/object-persistor/test/unit/GcsPersistorTests.js b/libraries/object-persistor/test/unit/GcsPersistorTests.js index 70e1666c8c..5558d9709b 100644 --- a/libraries/object-persistor/test/unit/GcsPersistorTests.js +++ b/libraries/object-persistor/test/unit/GcsPersistorTests.js @@ -260,16 +260,35 @@ describe('GcsPersistorTests', function () { describe('getRedirectUrl', function () { let signedUrl - beforeEach(async function () { - signedUrl = await GcsPersistor.getRedirectUrl(bucket, key) + describe('with signed URLs', function () { + beforeEach(async function () { + signedUrl = await GcsPersistor.getRedirectUrl(bucket, key) + }) + + it('should request a signed URL', function () { + expect(GcsFile.getSignedUrl).to.have.been.called + }) + + it('should return the url', function () { + expect(signedUrl).to.equal(redirectUrl) + }) }) - it('should request a signed URL', function () { - expect(GcsFile.getSignedUrl).to.have.been.called - }) + describe('with unsigned URLs', function () { + beforeEach(async function () { + GcsPersistor.settings.unsignedUrls = true + GcsPersistor.settings.endpoint = { + apiScheme: 'http', + apiEndpoint: 'custom.endpoint' + } + signedUrl = await GcsPersistor.getRedirectUrl(bucket, key) + }) - it('should return the url', function () { - expect(signedUrl).to.equal(redirectUrl) + it('should return a plain URL', function () { + expect(signedUrl).to.equal( + `http://custom.endpoint/download/storage/v1/b/${bucket}/o/${key}?alt=media` + ) + }) }) })