diff --git a/addons/api/addon/adapters/storage-bucket.js b/addons/api/addon/adapters/storage-bucket.js new file mode 100644 index 000000000..4f72f57a2 --- /dev/null +++ b/addons/api/addon/adapters/storage-bucket.js @@ -0,0 +1,23 @@ +import ApplicationAdapter from './application'; + +export default class StorageBucketAdapter extends ApplicationAdapter { + /** + * Overrides the default `buildURL` such that on _create_, URLs for records of + * type `plugin` get an extra query parameter `?plugin_name`. All other + * request types behave as normal. + * + * @override + * @param {string} modelName + * @param {string} id + * @return {string} url + * @return {string} requestType + */ + buildURL(modelName, id, snapshot, requestType) { + let url = super.buildURL(...arguments); + const pluginName = snapshot?.attr('plugin')?.name; + if (requestType === 'createRecord' && pluginName) { + url = `${url}?plugin_name=${pluginName}`; + } + return url; + } +} diff --git a/addons/api/addon/generated/models/storage-bucket.js b/addons/api/addon/generated/models/storage-bucket.js index 6423d33ee..d81c1ade7 100644 --- a/addons/api/addon/generated/models/storage-bucket.js +++ b/addons/api/addon/generated/models/storage-bucket.js @@ -72,7 +72,7 @@ export default class GeneratedStorageBucketModel extends BaseModel { readOnly: true, isNestedAttribute: true, }) - client_secret_hmac; + secrets_hmac; @attr('string', { isNestedSecret: true, diff --git a/addons/api/addon/serializers/storage-bucket.js b/addons/api/addon/serializers/storage-bucket.js new file mode 100644 index 000000000..ae8e818d5 --- /dev/null +++ b/addons/api/addon/serializers/storage-bucket.js @@ -0,0 +1,13 @@ +import ApplicationSerializer from './application'; + +export default class StorageBucketSerializer extends ApplicationSerializer { + normalize(typeClass, hash, ...rest) { + const normalizedHash = structuredClone(hash); + const normalized = super.normalize(typeClass, normalizedHash, ...rest); + + // Remove secret fields + delete normalized.data.attributes.access_key_id; + delete normalized.data.attributes.secret_access_key; + return normalized; + } +} diff --git a/addons/api/app/adapters/storage-bucket.js b/addons/api/app/adapters/storage-bucket.js new file mode 100644 index 000000000..ceadb4e71 --- /dev/null +++ b/addons/api/app/adapters/storage-bucket.js @@ -0,0 +1 @@ +export { default } from 'api/adapters/storage-bucket'; diff --git a/addons/api/app/serializers/storage-bucket.js b/addons/api/app/serializers/storage-bucket.js new file mode 100644 index 000000000..b17b78a70 --- /dev/null +++ b/addons/api/app/serializers/storage-bucket.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/storage-bucket'; diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 62041703a..648229604 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -14,6 +14,7 @@ import { targetHandler } from './route-handlers/target'; import { pickRandomStatusString } from './factories/session'; import initializeMockIPC from './scenarios/ipc'; import makeBooleanFilter from './helpers/bexpr-filter'; +import { faker } from '@faker-js/faker'; const isTesting = environmentConfig.environment === 'test'; @@ -668,7 +669,7 @@ function routes() { this.patch('/storage-buckets/:id'); this.post( '/storage-buckets', - ({ storageBuckets }, { queryParams: { plugin_name } }) => { + function ({ storageBuckets }, { queryParams: { plugin_name } }) { const attrs = this.normalizedRequestAttrs(); if (plugin_name) { attrs.type = 'plugin'; @@ -676,6 +677,11 @@ function routes() { name: plugin_name, }; } + + // Remove the secrets and add the hmac in the response + delete attrs.secrets; + attrs.secrets_hmac = faker.git.commitSha(); + return storageBuckets.create(attrs); } ); diff --git a/addons/api/tests/unit/adapters/storage-bucket-test.js b/addons/api/tests/unit/adapters/storage-bucket-test.js new file mode 100644 index 000000000..dcfdfa83f --- /dev/null +++ b/addons/api/tests/unit/adapters/storage-bucket-test.js @@ -0,0 +1,30 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Adapter | storage bucket', function (hooks) { + setupTest(hooks); + + test('it generates correct createRecord URLs for storage buckets with extra query parameter ?plugin_name', function (assert) { + assert.expect(1); + const scopeID = 'o_1'; + const mockSnapshot = { + adapterOptions: { + scopeID, + }, + attr() { + return { name: 'aws_s3' }; + }, + }; + const adapter = this.owner.lookup('adapter:storage-bucket'); + const createRecordURL = adapter.buildURL( + 'storage-bucket', + null, + mockSnapshot, + 'createRecord' + ); + assert.strictEqual( + createRecordURL, + '/v1/storage-buckets?plugin_name=aws_s3' + ); + }); +}); diff --git a/addons/api/tests/unit/serializers/storage-bucket-test.js b/addons/api/tests/unit/serializers/storage-bucket-test.js new file mode 100644 index 000000000..d3fa6dab6 --- /dev/null +++ b/addons/api/tests/unit/serializers/storage-bucket-test.js @@ -0,0 +1,99 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { + TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + TYPE_STORAGE_BUCKET_PLUGIN, +} from 'api/models/storage-bucket'; + +module('Unit | Serializer | storage bucket', function (hooks) { + setupTest(hooks); + + test('it serializes records', function (assert) { + let store = this.owner.lookup('service:store'); + let record = store.createRecord('storage-bucket', {}); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + }); + + test('it serializes a new aws plugin as expected', async function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const record = store.createRecord('storage-bucket', { + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + name: 'AWS', + description: 'this has an aws plugin', + bucket_name: 'bucketname', + bucket_prefix: 'bucketprefix', + worker_filter: 'workerfilter', + region: 'eu-west-1', + access_key_id: 'foobars', + secret_access_key: 'testing', + secrets_hmac: 'hmac', + }); + const expectedResult = { + name: 'AWS', + description: 'this has an aws plugin', + type: TYPE_STORAGE_BUCKET_PLUGIN, + bucket_name: 'bucketname', + bucket_prefix: 'bucketprefix', + worker_filter: 'workerfilter', + attributes: { + region: 'eu-west-1', + }, + secrets: { + access_key_id: 'foobars', + secret_access_key: 'testing', + }, + }; + assert.deepEqual(record.serialize(), expectedResult); + }); + + test('it normalizes correctly', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('storage-bucket'); + const hostSetModelClass = store.createRecord('storage-bucket').constructor; + const payload = { + id: '1', + version: 1, + name: 'AWS', + description: 'this has an aws plugin', + type: TYPE_STORAGE_BUCKET_PLUGIN, + bucket_name: 'bucketname', + bucket_prefix: 'bucketprefix', + worker_filter: 'workerfilter', + attributes: { + region: 'eu-west-1', + }, + secrets_hmac: 'this is secret', + }; + const normalized = serializer.normalizeSingleResponse( + store, + hostSetModelClass, + payload + ); + + assert.deepEqual(normalized, { + included: [], + data: { + id: '1', + attributes: { + type: TYPE_STORAGE_BUCKET_PLUGIN, + version: 1, + authorized_actions: [], + name: 'AWS', + description: 'this has an aws plugin', + bucket_name: 'bucketname', + bucket_prefix: 'bucketprefix', + worker_filter: 'workerfilter', + secrets_hmac: 'this is secret', + region: 'eu-west-1', + }, + type: 'storage-bucket', + relationships: {}, + }, + }); + }); +});