From a8c38b7895a3e061ed798e96d5ae22dd8505e647 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 10 Aug 2018 07:53:40 -0400 Subject: [PATCH 01/24] Adding a namespace --- .../mappings/kibana_index_mappings_mixin.js | 3 + .../saved_objects/service/lib/repository.js | 64 +++++++----------- .../service/lib/search_dsl/query_params.js | 12 +++- .../service/lib/search_dsl/search_dsl.js | 3 +- .../service/lib/trim_id_prefix.js | 7 +- .../service/saved_objects_client.js | 14 ++-- .../saved_objects/basic/data.json.gz | Bin 1803 -> 2107 bytes .../saved_objects/basic/mappings.json | 5 +- 8 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/server/mappings/kibana_index_mappings_mixin.js b/src/server/mappings/kibana_index_mappings_mixin.js index 5b7d8fb683c58..be0202888d56e 100644 --- a/src/server/mappings/kibana_index_mappings_mixin.js +++ b/src/server/mappings/kibana_index_mappings_mixin.js @@ -29,6 +29,9 @@ const BASE_SAVED_OBJECT_MAPPINGS = { doc: { dynamic: 'strict', properties: { + namespace: { + type: 'keyword' + }, type: { type: 'keyword' }, diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index 6ede707c1819a..16597dc9e0ad4 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -21,7 +21,7 @@ import uuid from 'uuid'; import { getRootType } from '../../../mappings'; import { getSearchDsl } from './search_dsl'; -import { trimIdPrefix } from './trim_id_prefix'; +import { trimId } from './trim_id_prefix'; import { includedFields } from './included_fields'; import { decorateEsError } from './decorate_es_error'; import * as errors from './errors'; @@ -54,13 +54,11 @@ export class SavedObjectsRepository { * @param {object} [options={}] * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] - * @property {object} [options.extraDocumentProperties={}] - extra properties to append to the document body, outside of the object's type property * @returns {promise} - { id, type, version, attributes } */ - async create(type, attributes = {}, options = {}) { + async create(namespace, type, attributes = {}, options = {}) { const { id, - extraDocumentProperties = {}, overwrite = false } = options; @@ -69,12 +67,12 @@ export class SavedObjectsRepository { try { const response = await this._writeToCluster(method, { - id: this._generateEsId(type, id), + id: this._generateEsId(namespace, type, id), type: this._type, index: this._index, refresh: 'wait_for', body: { - ...extraDocumentProperties, + ...namespace && { namespace }, type, updated_at: time, [type]: attributes, @@ -82,7 +80,7 @@ export class SavedObjectsRepository { }); return { - id: trimIdPrefix(response._id, type), + id: trimId(response._id, namespace, type), type, updated_at: time, version: response._version, @@ -101,12 +99,12 @@ export class SavedObjectsRepository { /** * Creates multiple documents at once * - * @param {array} objects - [{ type, id, attributes, extraDocumentProperties }] + * @param {array} objects - [{ type, id, attributes }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] - overwrites existing documents * @returns {promise} - {saved_objects: [[{ id, type, version, attributes, error: { message } }]} */ - async bulkCreate(objects, options = {}) { + async bulkCreate(namespace, objects, options = {}) { const { overwrite = false } = options; @@ -117,12 +115,12 @@ export class SavedObjectsRepository { return [ { [method]: { - _id: this._generateEsId(object.type, object.id), + _id: this._generateEsId(namespace, object.type, object.id), _type: this._type, } }, { - ...object.extraDocumentProperties, + ... namespace && { namespace }, type: object.type, updated_at: time, [object.type]: object.attributes, @@ -188,9 +186,9 @@ export class SavedObjectsRepository { * @param {string} id * @returns {promise} */ - async delete(type, id) { + async delete(namespace, type, id) { const response = await this._writeToCluster('delete', { - id: this._generateEsId(type, id), + id: this._generateEsId(namespace, type, id), type: this._type, index: this._index, refresh: 'wait_for', @@ -228,7 +226,7 @@ export class SavedObjectsRepository { * @property {Array} [options.fields] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(options = {}) { + async find(namespace, options = {}) { const { type, search, @@ -262,6 +260,7 @@ export class SavedObjectsRepository { body: { version: true, ...getSearchDsl(this._mappings, { + namespace, search, searchFields, type, @@ -292,7 +291,7 @@ export class SavedObjectsRepository { saved_objects: response.hits.hits.map(hit => { const { type, updated_at: updatedAt } = hit._source; return { - id: trimIdPrefix(hit._id, type), + id: trimId(hit._id, namespace, type), type, ...updatedAt && { updated_at: updatedAt }, version: hit._version, @@ -306,8 +305,6 @@ export class SavedObjectsRepository { * Returns an array of objects by id * * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {object} [options = {}] - * @param {array} [options.extraDocumentProperties = []] - an array of extra properties to return from the underlying document * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -316,7 +313,7 @@ export class SavedObjectsRepository { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], options = {}) { + async bulkGet(namespace, objects = []) { if (objects.length === 0) { return { saved_objects: [] }; } @@ -325,7 +322,7 @@ export class SavedObjectsRepository { index: this._index, body: { docs: objects.map(object => ({ - _id: this._generateEsId(object.type, object.id), + _id: this._generateEsId(namespace, object.type, object.id), _type: this._type, })) } @@ -333,8 +330,6 @@ export class SavedObjectsRepository { const { docs } = response; - const { extraDocumentProperties = [] } = options; - return { saved_objects: docs.map((doc, i) => { const { id, type } = objects[i]; @@ -353,9 +348,7 @@ export class SavedObjectsRepository { type, ...time && { updated_at: time }, version: doc._version, - ...extraDocumentProperties - .map(s => ({ [s]: doc._source[s] })) - .reduce((acc, prop) => ({ ...acc, ...prop }), {}), + ...doc._source.namespace && { namespace: doc._source.namespace }, attributes: { ...doc._source[type], } @@ -371,13 +364,11 @@ export class SavedObjectsRepository { * * @param {string} type * @param {string} id - * @param {object} [options = {}] - * @param {array} [options.extraDocumentProperties = []] - an array of extra properties to return from the underlying document * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, options = {}) { + async get(namespace, type, id) { const response = await this._callCluster('get', { - id: this._generateEsId(type, id), + id: this._generateEsId(namespace, type, id), type: this._type, index: this._index, ignore: [404] @@ -390,8 +381,6 @@ export class SavedObjectsRepository { throw errors.createGenericNotFoundError(type, id); } - const { extraDocumentProperties = [] } = options; - const { updated_at: updatedAt } = response._source; return { @@ -399,9 +388,7 @@ export class SavedObjectsRepository { type, ...updatedAt && { updated_at: updatedAt }, version: response._version, - ...extraDocumentProperties - .map(s => ({ [s]: response._source[s] })) - .reduce((acc, prop) => ({ ...acc, ...prop }), {}), + ...response._source.namespace && { namespace: response._source.namespace }, attributes: { ...response._source[type], } @@ -418,10 +405,10 @@ export class SavedObjectsRepository { * @param {array} [options.extraDocumentProperties = {}] - an object of extra properties to write into the underlying document * @returns {promise} */ - async update(type, id, attributes, options = {}) { + async update(namespace, type, id, attributes, options = {}) { const time = this._getCurrentTime(); const response = await this._writeToCluster('update', { - id: this._generateEsId(type, id), + id: this._generateEsId(namespace, type, id), type: this._type, index: this._index, version: options.version, @@ -429,7 +416,7 @@ export class SavedObjectsRepository { ignore: [404], body: { doc: { - ...options.extraDocumentProperties, + ...namespace && { namespace }, updated_at: time, [type]: attributes, } @@ -467,8 +454,9 @@ export class SavedObjectsRepository { } } - _generateEsId(type, id) { - return `${type}:${id || uuid.v1()}`; + _generateEsId(namespace, type, id) { + const namespacePrefix = namespace ? `${namespace}` : ''; + return `${namespacePrefix}${type}:${id || uuid.v1()}`; } _getCurrentTime() { diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.js b/src/server/saved_objects/service/lib/search_dsl/query_params.js index c5ca55f985bdd..9a112e072d347 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.js @@ -68,12 +68,22 @@ function getFieldsForTypes(searchFields, types) { * @param {Array} filters additional query filters * @return {Object} */ -export function getQueryParams(mappings, type, search, searchFields, filters = []) { +export function getQueryParams(mappings, namespace, type, search, searchFields, filters = []) { const bool = { filter: [...filters], }; + if (namespace) { + bool.filter.push({ 'term': { namespace } }); + } else { + bool.must_not = [{ + exists: { + field: 'namespace', + } + }]; + } + if (type) { bool.filter.push({ [Array.isArray(type) ? 'terms' : 'term']: { type } }); } diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js index 94f938c921ed0..c550be9a057bf 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js @@ -24,6 +24,7 @@ import { getSortingParams } from './sorting_params'; export function getSearchDsl(mappings, options = {}) { const { + namespace, type, search, searchFields, @@ -45,7 +46,7 @@ export function getSearchDsl(mappings, options = {}) { } return { - ...getQueryParams(mappings, type, search, searchFields, filters), + ...getQueryParams(mappings, namespace, type, search, searchFields, filters), ...getSortingParams(mappings, type, sortField, sortOrder), }; } diff --git a/src/server/saved_objects/service/lib/trim_id_prefix.js b/src/server/saved_objects/service/lib/trim_id_prefix.js index e5a0bbf649748..f1c9d9ac2148f 100644 --- a/src/server/saved_objects/service/lib/trim_id_prefix.js +++ b/src/server/saved_objects/service/lib/trim_id_prefix.js @@ -30,14 +30,15 @@ function assertNonEmptyString(value, name) { * @param {string} type * @return {string} */ -export function trimIdPrefix(id, type) { +export function trimId(id, namespace, type) { assertNonEmptyString(id, 'document id'); assertNonEmptyString(type, 'saved object type'); - const prefix = `${type}:`; + const namespacePrefix = namespace ? `${namespace}:` : ''; + const prefix = `${namespacePrefix}${type}:`; if (!id.startsWith(prefix)) { - return id; + throw new Error('Unable to trim id'); } return id.slice(prefix.length); diff --git a/src/server/saved_objects/service/saved_objects_client.js b/src/server/saved_objects/service/saved_objects_client.js index 1d69d55f9f4ae..be2417a7d0d8d 100644 --- a/src/server/saved_objects/service/saved_objects_client.js +++ b/src/server/saved_objects/service/saved_objects_client.js @@ -106,7 +106,7 @@ export class SavedObjectsClient { * @returns {promise} - { id, type, version, attributes } */ async create(type, attributes = {}, options = {}) { - return this._repository.create(type, attributes, options); + return this._repository.create(null, type, attributes, options); } /** @@ -118,7 +118,7 @@ export class SavedObjectsClient { * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ async bulkCreate(objects, options = {}) { - return this._repository.bulkCreate(objects, options); + return this._repository.bulkCreate(null, objects, options); } /** @@ -129,7 +129,7 @@ export class SavedObjectsClient { * @returns {promise} */ async delete(type, id) { - return this._repository.delete(type, id); + return this._repository.delete(null, type, id); } /** @@ -147,7 +147,7 @@ export class SavedObjectsClient { * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ async find(options = {}) { - return this._repository.find(options); + return this._repository.find(null, options); } /** @@ -165,7 +165,7 @@ export class SavedObjectsClient { * ]) */ async bulkGet(objects = [], options = {}) { - return this._repository.bulkGet(objects, options); + return this._repository.bulkGet(null, objects, options); } /** @@ -178,7 +178,7 @@ export class SavedObjectsClient { * @returns {promise} - { id, type, version, attributes } */ async get(type, id, options = {}) { - return this._repository.get(type, id, options); + return this._repository.get(null, type, id, options); } /** @@ -192,6 +192,6 @@ export class SavedObjectsClient { * @returns {promise} */ async update(type, id, attributes, options = {}) { - return this._repository.update(type, id, attributes, options); + return this._repository.update(null, type, id, attributes, options); } } diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/basic/data.json.gz b/test/api_integration/fixtures/es_archiver/saved_objects/basic/data.json.gz index c07188439b0e07bf044e928e7f3a9346c2e46092..ac2a10f42f4dc92cbe2462e8e911fe5749a4e7f5 100644 GIT binary patch literal 2107 zcmV-B2*mdviwFpldu>|)17u-zVJ>QOZ*BnXTw8D3HWYr(uQ0r~AvPpCjT1d>>(HS? zZ)k!I*b*4DNJ(5NQ6s5b8u-7Dr0$NJI*St@1AIv=UO#^1Ifs_X@9tnQ7^>Tx41>WC z^Kf`*60Q&{Ov;^p(2q33jf1wMMAzNroJyR5$1Y2R0Bc+JQf>VFudC2odrzFB7 z-zT#X3K!GS6r;uI@$u<2LW{b-^t~;Id+N_G}>>|fV5j;oA6q4>OUn&RZU?Lo^oX`WaDGsp~+QtQAZO z#S#!j1`aHK>Lu4lWNcs;c;*!EhMF~#HMgb>2aXK6STXmkNEbvr8cv<~w8cCxeQO_4 zH*p7$N*q9DO#4F99~9?f;Xraei(5tHRu=RYz=VuydgzKDte#&O3%OAwlayz07Ec2S z(gKPJABUU^Ofz(EDe264&S_k#6d^D;kQhpIDcv=o&0>uRlEo$bS!e9A z3~4lw*je4Y?k<`=_ty216J_Cmnkf)*p3&?JActe&K$@i#g$d2bm;)lI#RQQlfl??g zC?zQuw{XxXG}a9YFf(QV3Br)=2F&UrSUQ?_R_=H-R zu=$i!kvXhg_yZteb7_xbK<3c`1kT~}{=WKj3NJbxCQ!oz%%8fZ=A2a!A_3^7 z$pawwfXjW@n6JUQ2h76WqW4hl0h?x*0iH|)au2wKy%p-A1k5JnP!2AR|64roVUI`C zWdXtBNaj3~pzN|l1c@b?qj2@8*Dg6K@jzl|cV}SlV*%wNf%(Kjg@Ardx{-j4N`u+) zOkEDE8#v2@`tR~!d2n#|Eccs2o<%ecW@B&cjS$Ndne}(XUYjL7;L)JknaJ)PFx|kJ(p{Q@+D@wNyS9mYwfZK5^!m0b zIT9!}i~DX}ZY<2nMUxlmPGT-@-_fkKO=D5&*7j(MaK?qL{{ta9ZMi&b>-~IVtC^c> zwn>e!$)R%O_u(Hj3`s)bVxJ55AdNsk{i{oHZ$Xw*2xM=%szkWPO@l!S2VZm3`uo^`uRN) zU)niXmLY0oxs{8FWF}~J`f3fR;YfxFNzp}hOiIvj#q5_hz*fypzSFGnZu5jn#pC)w zVW)jb;#1>DzAJr2Nq^rP*A&_jw)V{ln`1 z3D!P$p%Za6q~+zZ>m>_no#gLlA3s!%Qs%cMwrbV2dyK4LtTY7HOu@3(TIXG(X|3mu ziI8Qo-$^aw)ET-W_~YUq5~}w^p`N!lvBqvq&TPc#0IkkzYYsMvtCpq4hV7!#-d|K> ztv_B2Z8?UrxUerG&AKl_l3AxcY)~y44lLbct2MaQtO{d5Xl>F}q-L$u1be;uA z-uPs4+=L#!dD*)wY>cjSB0ieZ9@?z~mOZ>?}*Hebl)&{ALUPpuH`>ZzIMreAZ-g zVW_o!r=NMwO;c|K9h{MT;GYci{Jk3ZlVEZbOiu>Cy+0p$Gtawsk6!QLO~pS6E-N0F z6(^J6)g1XToDz9HnRQ<(gM}8sVe3Z#xn?eu9)7!T*ZTqd!I~5400^w93oC<_f zfp97iP6c9@&c~@hI28z|0`X#aoC<_ffp{`YP6fiLKs6^IvL)2TpgflQ|Y;Zz{NQt4D6oC*X?Hk}H@c6fw->(Qw| zY=J>g_oAE%#51tyR3M&>(*4+-I~5400s)&#*jpi|0`YVX!NqZ>0^w93w!xB9fp97i zP6cB7Xq*bf4pktkJ5Y}jgV^YjlT(UV$MZG?BQ}}_rzTNbrc;x!tvfXdrzUae)Fhmm lgj17vQY7D&sKiE5+$l~t#R;c4@q&sI{{gN*sXkML002`%2mAm4 literal 1803 zcmV+m2lV(KiwFP!000026V+Q=Z`(E$e$THkytg4XBs+~0J+{7_@jK zaiv6!q;hGX|2~qsC(dnY>f@I-IwTK2e&a04be(HdkH9QrfQBc3-ogosXj9~Ki5uI7_DC9A{z z{lj@gR?T=N3K@1&_a+(Wth<}$BBw+HT@r0O%{+fG@eU_5|JXkW_6`Goe>z)ue|P;lOMY;m{XgH1VY0kW?a_bOLU$_BC6%Hg8Gr7>thb8$q zIGB>p>QaOHArAcmz=WI`df2r*SU>+{EO4PAQ%q!doTr6^$$?@J(@+RWSw?QSl4)G# zg4GeY0c6w^R!mGO86ty&iJ`>wGE*bkEH;QRMO-2jqKanGU}ER`=5=$`?76qDk2I8n zgUV7U>O5!JH$(}?!NFvul!OV(U@8!i)M3KNlq4yZ6qG_L9PS#=DoOk6(j?3L8L?8i;v)6w9T4dV3gCJ6~kVB9P$|44I zY2QNPcma6}(!5x4rjl)dsvy*;ULD+x6H^tv1*s-6dNvJ4#7v4fFu(QPw3uCU2opD- zEJgM4?-qbtz+sR8ixZ8W${Oc3FnZ3YPVf`zVB+ReF%3&xyYM?e;^xvF$H2^^1Bjf% z=l#C=bP5eD+zb|yVj~`rFS{01b4@GLE2YdUA-Zdl&5h=KS+br!tm#}^& zhi6O`gtPC&{yGdOU4$f>J@Jm%KG-c)lc4Z^mCsfj;?*Nmzl3iIR1y@aq=1BBM1I?ALH zaoz=N3#4?RPa0IMPHvFfQ$DtxlNIoK)k8Jrzbtf_Ht;>q_H8)5D$7&z=)CdfYQ9wB zb}4PfdIynSw?wDs=K8ql{*=|3=2X!7l!=fj;rFdr3SF(&=FEt3ZBK`^_Pv7-^Cw*B z&fBcTPhFCg4AiH_vA%XQI(2Z1C$_MaCgHdG?2`#6QqAQS(4TC#<=(HBNKIjI?)e+& zbAarVJ>#!>92yX>nG8Afgr*C~T<~g%10K&BlioD2TPLCPSP1T}hP1_m(svL;MmrIA z&!756mfd$nd&|y})F?F1?}_}@*`TaS)HQdjoCz{pw0d)O0n~D&!UR&%9FvkPycYJQ zE3mcN**$7)c3UP)X%ROE3VZq$#A$P}sh?NeMb&k;3LW`pq%tchw5V!$Vf3eO`)#x8 zqhZRjrAhD0rpo)f_5BlSa_-6qX}zTKwCrceg1IF5=h4Rxb)r=HWreNwx^9n=6`WUr zz*`fP18u!`jaJ(r9fMSr*zZZJI(0uZ zy6RYJW7s*B$^NolYxC#H*!ANiiwpN6()xW7Lgte8xJ6a|%qmTZN>+kM8mwTxn9qq@ zDE#jV-RgT_bzTH}-t=I$->c4@iuING^O}O3Fm117dk^!MoMa{|H|BPkl6{AzHTeE+ z{a`INcM)a!zOCKCAC!{*E~1|pAF|h}x6Qm(DqW)X%{I3OQ#kOd=a$susACiO?H2yd zcq{LZ5t|r38#0Bo)VjPgY&;fL)f>r1N05*FgORb{i;+JIW_!W>VD!iP3~;5s9fs+}5O5F6q@-zcy6!!{?s7@E?V}G3DSv0085qc~bxY diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/basic/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/basic/mappings.json index 26c62bca335d9..4f3ead9f47c67 100644 --- a/test/api_integration/fixtures/es_archiver/saved_objects/basic/mappings.json +++ b/test/api_integration/fixtures/es_archiver/saved_objects/basic/mappings.json @@ -188,6 +188,9 @@ } } }, + "namespace": { + "type": "keyword" + }, "type": { "type": "keyword" }, @@ -249,4 +252,4 @@ } } } -} \ No newline at end of file +} From a3c92ad941514566afd482f83a251e5061b56b9f Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 23 Aug 2018 10:15:26 -0400 Subject: [PATCH 02/24] Allowing the saved objects client wrappers to specify the namespace --- .../saved_objects/service/lib/repository.js | 24 +- .../service/lib/search_dsl/query_params.js | 4 +- .../service/saved_objects_client.js | 38 +-- .../secure_saved_objects_client.js | 39 ++-- .../saved_objects_client_wrapper_factory.js | 3 +- .../spaces_saved_objects_client.js | 220 +++--------------- .../apis/saved_objects/delete.js | 11 +- .../saved_objects/spaces/data.json.gz | Bin 2448 -> 2464 bytes 8 files changed, 98 insertions(+), 241 deletions(-) diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index 16597dc9e0ad4..2e4d49819b7fc 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -54,9 +54,10 @@ export class SavedObjectsRepository { * @param {object} [options={}] * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] + * @param {string} [namespace] * @returns {promise} - { id, type, version, attributes } */ - async create(namespace, type, attributes = {}, options = {}) { + async create(type, attributes = {}, options = {}, namespace) { const { id, overwrite = false @@ -101,10 +102,11 @@ export class SavedObjectsRepository { * * @param {array} objects - [{ type, id, attributes }] * @param {object} [options={}] + * @param {string} [namespace] * @property {boolean} [options.overwrite=false] - overwrites existing documents * @returns {promise} - {saved_objects: [[{ id, type, version, attributes, error: { message } }]} */ - async bulkCreate(namespace, objects, options = {}) { + async bulkCreate(objects, options = {}, namespace) { const { overwrite = false } = options; @@ -184,9 +186,11 @@ export class SavedObjectsRepository { * * @param {string} type * @param {string} id + * @param {string} [namespace] * @returns {promise} */ - async delete(namespace, type, id) { + async delete(type, id, namespace) { + console.log('delete', this._generateEsId(namespace, type, id)); const response = await this._writeToCluster('delete', { id: this._generateEsId(namespace, type, id), type: this._type, @@ -224,9 +228,10 @@ export class SavedObjectsRepository { * @property {string} [options.sortField] * @property {string} [options.sortOrder] * @property {Array} [options.fields] + * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(namespace, options = {}) { + async find(options = {}, namespace) { const { type, search, @@ -305,6 +310,7 @@ export class SavedObjectsRepository { * Returns an array of objects by id * * @param {array} objects - an array ids, or an array of objects containing id and optionally type + * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -313,7 +319,7 @@ export class SavedObjectsRepository { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(namespace, objects = []) { + async bulkGet(objects = [], namespace) { if (objects.length === 0) { return { saved_objects: [] }; } @@ -364,9 +370,10 @@ export class SavedObjectsRepository { * * @param {string} type * @param {string} id + * @param {string} [namespace] * @returns {promise} - { id, type, version, attributes } */ - async get(namespace, type, id) { + async get(type, id, namespace) { const response = await this._callCluster('get', { id: this._generateEsId(namespace, type, id), type: this._type, @@ -403,9 +410,10 @@ export class SavedObjectsRepository { * @param {object} [options={}] * @property {integer} options.version - ensures version matches that of persisted object * @param {array} [options.extraDocumentProperties = {}] - an object of extra properties to write into the underlying document + * @param {string} [namespace] * @returns {promise} */ - async update(namespace, type, id, attributes, options = {}) { + async update(type, id, attributes, options = {}, namespace) { const time = this._getCurrentTime(); const response = await this._writeToCluster('update', { id: this._generateEsId(namespace, type, id), @@ -455,7 +463,7 @@ export class SavedObjectsRepository { } _generateEsId(namespace, type, id) { - const namespacePrefix = namespace ? `${namespace}` : ''; + const namespacePrefix = namespace ? `${namespace}:` : ''; return `${namespacePrefix}${type}:${id || uuid.v1()}`; } diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.js b/src/server/saved_objects/service/lib/search_dsl/query_params.js index 9a112e072d347..16767bca69cf2 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.js @@ -69,7 +69,7 @@ function getFieldsForTypes(searchFields, types) { * @return {Object} */ export function getQueryParams(mappings, namespace, type, search, searchFields, filters = []) { - + const types = getTypes(mappings, type); const bool = { filter: [...filters], }; @@ -95,7 +95,7 @@ export function getQueryParams(mappings, namespace, type, search, searchFields, query: search, ...getFieldsForTypes( searchFields, - getTypes(mappings, type) + types ) } } diff --git a/src/server/saved_objects/service/saved_objects_client.js b/src/server/saved_objects/service/saved_objects_client.js index be2417a7d0d8d..f0686be4602ab 100644 --- a/src/server/saved_objects/service/saved_objects_client.js +++ b/src/server/saved_objects/service/saved_objects_client.js @@ -102,11 +102,11 @@ export class SavedObjectsClient { * @param {object} [options={}] * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] - * @property {object} [options.extraDocumentProperties={}] - extra properties to append to the document body, outside of the object's type property + * @param {string} [namespace] * @returns {promise} - { id, type, version, attributes } */ - async create(type, attributes = {}, options = {}) { - return this._repository.create(null, type, attributes, options); + async create(type, attributes = {}, options = {}, namespace) { + return this._repository.create(type, attributes, options, namespace); } /** @@ -117,8 +117,8 @@ export class SavedObjectsClient { * @property {boolean} [options.overwrite=false] - overwrites existing documents * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ - async bulkCreate(objects, options = {}) { - return this._repository.bulkCreate(null, objects, options); + async bulkCreate(objects, options = {}, namespace) { + return this._repository.bulkCreate(objects, options, namespace); } /** @@ -126,10 +126,11 @@ export class SavedObjectsClient { * * @param {string} type * @param {string} id + * @param {string} [namespace] * @returns {promise} */ - async delete(type, id) { - return this._repository.delete(null, type, id); + async delete(type, id, namespace) { + return this._repository.delete(type, id, namespace); } /** @@ -144,10 +145,11 @@ export class SavedObjectsClient { * @property {string} [options.sortField] * @property {string} [options.sortOrder] * @property {Array} [options.fields] + * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(options = {}) { - return this._repository.find(null, options); + async find(options = {}, namespace) { + return this._repository.find(options, namespace); } /** @@ -155,7 +157,7 @@ export class SavedObjectsClient { * * @param {array} objects - an array ids, or an array of objects containing id and optionally type * @param {object} [options = {}] - * @param {array} [options.extraSourceProperties = []] - an array of extra properties to return from the underlying document + * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -164,8 +166,8 @@ export class SavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], options = {}) { - return this._repository.bulkGet(null, objects, options); + async bulkGet(objects = [], options = {}, namespace) { + return this._repository.bulkGet(objects, options, namespace); } /** @@ -174,11 +176,11 @@ export class SavedObjectsClient { * @param {string} type * @param {string} id * @param {object} [options = {}] - * @param {array} [options.extraSourceProperties = []] - an array of extra properties to return from the underlying document + * @param {string} [namespace] * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, options = {}) { - return this._repository.get(null, type, id, options); + async get(type, id, options = {}, namespace) { + return this._repository.get(type, id, options, namespace); } /** @@ -188,10 +190,10 @@ export class SavedObjectsClient { * @param {string} id * @param {object} [options={}] * @property {integer} options.version - ensures version matches that of persisted object - * @param {array} [options.extraDocumentProperties = {}] - an object of extra properties to write into the underlying document + * @param {string} [namespace] * @returns {promise} */ - async update(type, id, attributes, options = {}) { - return this._repository.update(null, type, id, attributes, options); + async update(type, id, attributes, options = {}, namespace) { + return this._repository.update(type, id, attributes, options, namespace); } } diff --git a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js index f246088f87cdc..f1f5bef1a2c6a 100644 --- a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js +++ b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js @@ -28,67 +28,67 @@ export class SecureSavedObjectsClient { this._actions = actions; } - async create(type, attributes = {}, options = {}) { + async create(type, attributes = {}, options = {}, namespace) { return await this._execute( type, 'create', { type, attributes, options }, - repository => repository.create(type, attributes, options), + repository => repository.create(type, attributes, options, namespace), ); } - async bulkCreate(objects, options = {}) { + async bulkCreate(objects, options = {}, namespace) { const types = uniq(objects.map(o => o.type)); return await this._execute( types, 'bulk_create', { objects, options }, - repository => repository.bulkCreate(objects, options), + repository => repository.bulkCreate(objects, options, namespace), ); } - async delete(type, id) { + async delete(type, id, namespace) { return await this._execute( type, 'delete', { type, id }, - repository => repository.delete(type, id), + repository => repository.delete(type, id, namespace), ); } - async find(options = {}) { + async find(options = {}, namespace) { if (options.type) { - return await this._findWithTypes(options); + return await this._findWithTypes(options, namespace); } - return await this._findAcrossAllTypes(options); + return await this._findAcrossAllTypes(options, namespace); } - async bulkGet(objects = [], options = {}) { + async bulkGet(objects = [], options = {}, namespace) { const types = uniq(objects.map(o => o.type)); return await this._execute( types, 'bulk_get', { objects, options }, - repository => repository.bulkGet(objects, options) + repository => repository.bulkGet(objects, options, namespace) ); } - async get(type, id, options = {}) { + async get(type, id, options = {}, namespace) { return await this._execute( type, 'get', { type, id, options }, - repository => repository.get(type, id, options) + repository => repository.get(type, id, options, namespace) ); } - async update(type, id, attributes, options = {}) { + async update(type, id, attributes, options = {}, namespace) { return await this._execute( type, 'update', { type, id, attributes, options }, - repository => repository.update(type, id, attributes, options) + repository => repository.update(type, id, attributes, options, namespace) ); } @@ -121,7 +121,7 @@ export class SecureSavedObjectsClient { } } - async _findAcrossAllTypes(options) { + async _findAcrossAllTypes(options, namespace) { const action = 'find'; // we have to filter for only their authorized types @@ -152,16 +152,17 @@ export class SecureSavedObjectsClient { return await this._internalRepository.find({ ...options, - type: authorizedTypes + type: authorizedTypes, + namespace }); } - async _findWithTypes(options) { + async _findWithTypes(options, namespace) { return await this._execute( options.type, 'find', { options }, - repository => repository.find(options) + repository => repository.find(options, namespace) ); } } diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.js index da7a448d86d2d..3f7c0cbe2124e 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/saved_objects_client_wrapper_factory.js @@ -6,11 +6,10 @@ import { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; -export function spacesSavedObjectsClientWrapperFactory(spacesService, types) { +export function spacesSavedObjectsClientWrapperFactory(spacesService) { return ({ client, request }) => new SpacesSavedObjectsClient({ baseClient: client, request, spacesService, - types, }); } diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js index c9fd34a60b6d8..98aa111503588 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js @@ -5,25 +5,17 @@ */ import { DEFAULT_SPACE_ID } from '../../../common/constants'; -import { isTypeSpaceAware } from './lib/is_type_space_aware'; -import { getSpacesQueryFilters } from './lib/query_filters'; -import uniq from 'lodash'; -import uuid from 'uuid'; export class SpacesSavedObjectsClient { constructor(options) { const { - request, baseClient, + request, spacesService, - types, } = options; this.errors = baseClient.errors; - this._client = baseClient; - this._types = types; - this._spaceId = spacesService.getSpaceId(request); } @@ -35,64 +27,30 @@ export class SpacesSavedObjectsClient { * @param {object} [options={}] * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] - * @property {object} [options.extraDocumentProperties={}] - extra properties to append to the document body, outside of the object's type property * @returns {promise} - { id, type, version, attributes } */ - async create(type, attributes = {}, options = {}) { - - const spaceId = this._spaceId; - - const createOptions = { - ...options, - extraDocumentProperties: { - ...options.extraDocumentProperties - }, - id: this._prependSpaceId(type, options.id) - }; - - if (this._shouldAssignSpaceId(type, spaceId)) { - createOptions.extraDocumentProperties.spaceId = spaceId; - } else { - delete createOptions.extraDocumentProperties.spaceId; + async create(type, attributes = {}, options = {}, namespace) { + if (namespace) { + throw new Error('Spaces currently determines the namespaces'); } - const result = await this._client.create(type, attributes, createOptions); - return this._trimSpaceId(result); + return await this._client.create(type, attributes, options, this._getNamespace(this._spaceId)); } /** * Creates multiple documents at once * - * @param {array} objects - [{ type, id, attributes, extraDocumentProperties }] + * @param {array} objects - [{ type, id, attributes, namespace }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] - overwrites existing documents * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ - async bulkCreate(objects, options = {}) { - const spaceId = this._spaceId; - const objectsToCreate = objects.map(object => { - - const objectToCreate = { - ...object, - extraDocumentProperties: { - ...object.extraDocumentProperties - }, - id: this._prependSpaceId(object.type, object.id) - }; - - if (this._shouldAssignSpaceId(object.type, spaceId)) { - objectToCreate.extraDocumentProperties.spaceId = spaceId; - } else { - delete objectToCreate.extraDocumentProperties.spaceId; - } - - return objectToCreate; - }); - - const result = await this._client.bulkCreate(objectsToCreate, options); - result.saved_objects.forEach(this._trimSpaceId.bind(this)); + async bulkCreate(objects, options = {}, namespace) { + if (namespace) { + throw new Error('Spaces currently determines the namespaces'); + } - return result; + return await this._client.bulkCreate(objects, options, this._getNamespace(this._spaceId)); } /** @@ -102,14 +60,12 @@ export class SpacesSavedObjectsClient { * @param {string} id * @returns {promise} */ - async delete(type, id) { - const objectId = this._prependSpaceId(type, id); - - // attempt to retrieve document before deleting. - // this ensures that the document belongs to the current space. - await this.get(type, id); + async delete(type, id, namespace) { + if (namespace) { + throw new Error('Spaces currently determines the namespaces'); + } - return await this._client.delete(type, objectId); + return await this._client.delete(type, id, this._getNamespace(this._spaceId)); } /** @@ -126,23 +82,12 @@ export class SpacesSavedObjectsClient { * @property {Array} [options.fields] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(options = {}) { - const spaceOptions = {}; - - let types = options.type || this._types; - if (!Array.isArray(types)) { - types = [types]; + async find(options = {}, namespace) { + if (namespace) { + throw new Error('Spaces currently determines the namespaces'); } - const filters = options.filters || []; - - const spaceId = this._spaceId; - - spaceOptions.filters = [...filters, ...getSpacesQueryFilters(spaceId, types)]; - - const result = await this._client.find({ ...options, ...spaceOptions }); - result.saved_objects.forEach(this._trimSpaceId.bind(this)); - return result; + return await this._client.find({ ...options }, this._getNamespace(this._spaceId)); } /** @@ -150,7 +95,6 @@ export class SpacesSavedObjectsClient { * * @param {array} objects - an array ids, or an array of objects containing id and optionally type * @param {object} [options = {}] - * @param {array} [options.extraSourceProperties = []] - an array of extra properties to return from the underlying document * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -159,39 +103,12 @@ export class SpacesSavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], options = {}) { - // ES 'mget' does not support queries, so we have to filter results after the fact. - const thisSpaceId = this._spaceId; - - const extraDocumentProperties = this._collectExtraDocumentProperties(['spaceId', 'type'], options.extraDocumentProperties); - - const objectsToRetrieve = objects.map(object => ({ - ...object, - id: this._prependSpaceId(object.type, object.id) - })); - - const result = await this._client.bulkGet(objectsToRetrieve, { - ...options, - extraDocumentProperties - }); - - result.saved_objects = result.saved_objects.map(savedObject => { - const { id, type, spaceId = DEFAULT_SPACE_ID } = this._trimSpaceId(savedObject); - - if (isTypeSpaceAware(type)) { - if (spaceId !== thisSpaceId) { - return { - id, - type, - error: { statusCode: 404, message: 'Not found' } - }; - } - } - - return savedObject; - }); + async bulkGet(objects = [], options = {}, namespace) { + if (namespace) { + throw new Error('Spaces currently determines the namespaces'); + } - return result; + return await this._client.bulkGet(objects, options, this._getNamespace(this._spaceId)); } /** @@ -200,31 +117,14 @@ export class SpacesSavedObjectsClient { * @param {string} type * @param {string} id * @param {object} [options = {}] - * @param {array} [options.extraSourceProperties = []] - an array of extra properties to return from the underlying document * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, options = {}) { - // ES 'get' does not support queries, so we have to filter results after the fact. - - const objectId = this._prependSpaceId(type, id); - - const extraDocumentProperties = this._collectExtraDocumentProperties(['spaceId'], options.extraDocumentProperties); - - const response = await this._client.get(type, objectId, { - ...options, - extraDocumentProperties - }); - - const { spaceId: objectSpaceId = DEFAULT_SPACE_ID } = response; - - if (isTypeSpaceAware(type)) { - const thisSpaceId = this._spaceId; - if (objectSpaceId !== thisSpaceId) { - throw this._client.errors.createGenericNotFoundError(); - } + async get(type, id, options = {}, namespace) { + if (namespace) { + throw new Error('Spaces currently determines the namespaces'); } - return this._trimSpaceId(response); + return await this._client.get(type, id, options, this._getNamespace(this._spaceId)); } /** @@ -234,67 +134,21 @@ export class SpacesSavedObjectsClient { * @param {string} id * @param {object} [options={}] * @property {integer} options.version - ensures version matches that of persisted object - * @param {array} [options.extraDocumentProperties = {}] - an object of extra properties to write into the underlying document * @returns {promise} */ - async update(type, id, attributes, options = {}) { - const updateOptions = { - ...options, - extraDocumentProperties: { - ...options.extraDocumentProperties - } - }; - - const objectId = this._prependSpaceId(type, id); - - // attempt to retrieve document before updating. - // this ensures that the document belongs to the current space. - if (isTypeSpaceAware(type)) { - await this.get(type, id); - - const spaceId = this._spaceId; - - if (this._shouldAssignSpaceId(type, spaceId)) { - updateOptions.extraDocumentProperties.spaceId = spaceId; - } else { - delete updateOptions.extraDocumentProperties.spaceId; - } + async update(type, id, attributes, options = {}, namespace) { + if (namespace) { + throw new Error('Spaces currently determines the namespaces'); } - const result = await this._client.update(type, objectId, attributes, updateOptions); - return this._trimSpaceId(result); - } - - _collectExtraDocumentProperties(thisClientProperties, optionalProperties = []) { - return uniq([...thisClientProperties, ...optionalProperties]).value(); - } - - _shouldAssignSpaceId(type, spaceId) { - return spaceId !== DEFAULT_SPACE_ID && isTypeSpaceAware(type); + return await this._client.update(type, id, attributes, options, this._getNamespace(this._spaceId)); } - _prependSpaceId(type, id = uuid.v1()) { - if (this._spaceId === DEFAULT_SPACE_ID || !isTypeSpaceAware(type)) { - return id; - } - return `${this._spaceId}:${id}`; - } - - _trimSpaceId(savedObject) { - const prefix = `${this._spaceId}:`; - - const idHasPrefix = savedObject.id.startsWith(prefix); - - if (this._shouldAssignSpaceId(savedObject.type, this._spaceId)) { - if (idHasPrefix) { - savedObject.id = savedObject.id.slice(prefix.length); - } else { - throw new Error(`Saved object [${savedObject.type}/${savedObject.id}] is missing its expected space identifier.`); - } - } else if (idHasPrefix) { - throw new Error(`Saved object [${savedObject.type}/${savedObject.id}] has an unexpected space identifier [${this._spaceId}].`); + _getNamespace(spaceId) { + if (spaceId === DEFAULT_SPACE_ID) { + return undefined; } - return savedObject; + return spaceId; } } diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/delete.js b/x-pack/test/spaces_api_integration/apis/saved_objects/delete.js index fe3cb7bb2be06..07bb72a30e982 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/delete.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/delete.js @@ -7,7 +7,6 @@ import expect from 'expect.js'; import { SPACES } from './lib/spaces'; import { getUrlPrefix, getIdPrefix } from './lib/space_test_utils'; -import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -47,16 +46,10 @@ export default function ({ getService }) { )); it(`should return ${tests.inOtherSpace.statusCode} when deleting a doc belonging to another space`, async () => { - const documentId = `${getIdPrefix('space_2')}be3733a0-9efe-11e7-acb3-3dab96693fab`; - - let expectedObjectId = documentId; - - if (spaceId !== DEFAULT_SPACE_ID) { - expectedObjectId = `${spaceId}:${expectedObjectId}`; - } + const expectedObjectId = `${getIdPrefix('space_2')}be3733a0-9efe-11e7-acb3-3dab96693fab`; await supertest - .delete(`${getUrlPrefix(spaceId)}/api/saved_objects/dashboard/${documentId}`) + .delete(`${getUrlPrefix(spaceId)}/api/saved_objects/dashboard/${expectedObjectId}`) .expect(tests.inOtherSpace.statusCode) .then(tests.inOtherSpace.response('dashboard', expectedObjectId)); }); diff --git a/x-pack/test/spaces_api_integration/fixtures/es_archiver/saved_objects/spaces/data.json.gz b/x-pack/test/spaces_api_integration/fixtures/es_archiver/saved_objects/spaces/data.json.gz index 0be0d4c12010a325540585af33a8a1f8cc877883..4d06609b58d781bcd2c8c1d1c3bd2313e94afb7b 100644 GIT binary patch delta 2443 zcmYLEc{tPy8?|LDDTZW?G#EsdW`>Y$O2(2sLiW9pC8FYI$hBSjV62U`8YNqn5JHx* zRGQ16WWN|$#-8EB*Yn(e&U@bHocBEEP3FdPgGAz4SZ+0irGVrCF^klu>`f`}yLDkayHD=(iMp_s?d$9F0O_vXf(?unBr8i||l?ms?Iu8R!w%t8V#9hz$YYg_lrLBP0FtVc3Ce!R9(uYM5+ zTyxB03(}R{;zyO@nw*2;?()7SWaWPYUPun5rM*yBTs$~K(@|W+QFa9VRpv|=65R-; zRO8%J(esx_l9f8eQigO==B_=IM0!h5S9{N@B?aHxjsp@rhquXi{X&zLu@^261O5C+ zB57Z!eYKzVsi*6rJr&+G7=3?Z$|YlH{+0G54c)J_l~|1pBUsKz+BgZgrMXpCq6_cS zR&Lk%Z2H`89~*jZV0ObPz%OWNZU&W}p_XK44T-bO(H?v%mzi!7pd#_gsWyg-U)%*d zn)r_&P7wfirFJ6klmwdfu!Kt9*ouz{t7d<-_gi|Xv-k_2F9~m{J z{phR}rrDQicx4d~D3!{u?o}F8Rq%*T0@Y9u4P4o2Tii>ptN``PA_DwgNfeY!A^t_@`+V+t_5#FeIyW^jB&^mWu=Y z$-BXcINLFIPNdg`lFNNev(5wme!d>{Y>jSR^_FBObXHo0`DNSQilb&^#Qm5;A^-du8(HBjD> z`l{Yd$Lhr@Ow=)8;a*pGUlAiqZaeMZ!=67I$K{dJyoyl=EB>jUEmQdQgk@eWNL2@ zYmox);<_gB^P5d_dXlVU(Y$g>jp3Z>1iL_%zQv(s@7I?=@0$5{K zmR_N;&o;A#e)w4=V-K|`b?rd%S1shHHuivyyUsqJHUu?J?=`oPLLwKoYd2d}H)NXRqqdgRQl-#jXz({~Ug&Ml_rhS(k=j9QH%&ZF&Ie8B9x zJDB-^u^QeIk?+E2?i99#y~6|rGk9Kp|8Ls<&-Y{l7@}B9xoncKR0m9wno%(ypN9CK zy=c&X%R{#JPL{dYuX$!wM#aF=+g|3?3TBbxA7-7G%oLqGVM|-`b1iEA`S1<(!P++} zq1i?)+4^;YneJ2@Yqgx-KIc#e=Vq@spWNBqlG0s$H+O=P7O~2<`|skxek(S+f#JPA zh1>5!Rb6U7c<;!IkRyTo6?o&6eO_96?yD=CpY}q-bP+p{@zqL(h6*ZxN+xgPY1SABJGQ ztFe|Kw2S3nF1!X%TB}pWP>9K5dCS%F)&szu8tb5p;>Vf+v=pHG0TqVJxaj5Bn4S|m z3-^Hif+6tyy3#1Cn?7a3#gHW4zOY4x`>^|~eHH&Sa&VW|#4Md>6P%Xebq{nh8+u!h zv>0}}SoS#u(Cu{#IZ4}=|qWPxv8t+mMh+ZXW+4Ai6vPajfSpGEwAV90!`|H%ciMaQBOEM zw?nxq$5obx_z4T%H$742ieThd-;TY=u;=x$RT{zt?$ZF8FAp@?Fx+@rPT)((^^V2v z{iV3@TwkPsd3U}mryQ}`RHF7c;IBI2!+sC)S|NpxX);H210aP3Fh0Ra=`0#vtn63h&0C4WIVtLL@Gfz$Ivu z+I zC?bCPB{Rm0v(bT-B>MjbNz5eCu&c0`Kx;!lp(N(o@as2xCiB@C(?qK}>6g+N0{B1B zs3#zWzsxU!1FwRL6f5OYix_U4;ywp8#!Q8XRNavTzsBNO!u0`TCa6~B{y3dD)Hq_Q zB-l8D;g~sT41kX3(Q@xBKLm3Mi`Qh0%d2<><~g#{vZ8D)%T>Xft6W@S5R@V!WjHBRJSL}3bPBlGA1OO!EFt2k7kEaLd| ksLp>NS9s+}3_lW6gv6oJxD#b?FZqxWP%-Z==%u6kAG6Mq_5c6? delta 2427 zcmYjLdpr{g8+K<{W1CxzC>wKEBZSOo<~o^>bipVTj?691wODnQnakWSO>8KaPAQk% zj!W(oz8yIg5f(xk;Va+yeZTY1^FGh>KJW8;iy#jmqKEH_iRs07ritp2!P=po;HRn? zuZ4CuRggWjpl3c#OV1m4`=xom@Q zlaq66M>0$YgZ%qi20|jT4~$YOI^Jo#Pa|0UxzqA&@~jqfS>B<|=?&$FukDR?RPc%1 z9K^`j-tLV+x8-kREoM8o6B()_a{kXqj(-p|T5G#Ag&Vu+IycsrXKP3B7aeg&Dv%{s zLi!BVveHo43-Udyv?6I|*Q-+mJh&NRx|1y0!bqlvPb~$>l*B7<##p&#R0|MUvU*4` z&H}1n^yGWRv-5IkHSw6g73x;>#wy(_YYeU#h4{YeOla`8HYxTFGzx$J+HmdpdX<+I zw@uJOBb3iWpDWS7Q)F~lLX@_~FxAkAs4b4*L)c`Ok?&}KZ(Z2Rs2LKqK3?H;ImB=f z27U;HI-83b$*H{V9yvRb@iDKiR{#`eUs$$zb;t8#+7UhxQ81?FIx`qJ|HS=hf^Vq) z^djZj>j$R=Q}a997vGmmHdCuTb?C{00ZfYyUJ`Md&ln~lI-dyJd(K~FEy!Q?i{Zn zqztsr@9{UDX903eGC`~}A!!QYX8Z1&yspB1&@ zi@s7TG9w)lB`LZl<4%q9n82+^mWuSa$s|~BYv<~o%=Qj> z!YA;Bma;pnC(j>D_sUFBuJKI=@il>TRxP~eL#AKp`0&pjiRX1#3dg-enAupIX12m2 zna#}sw_IM;A@BYeex+a(OPkT3RL-wns(1J_GPHV*VduK%*gNv@67GA2P59?oUT1mw zAwP)J^tcO09k6I%+~`xQ4A@T0ZaF~S(Sr&5E;Q+3E02Hjvu_EF4K|YZ%72#q9(je$Fbq$4Q-<)o{*>Gh+ zAT~YaZWl-^Flf}B9$g=Sa)@k8=I))a4WA4my`B|)Za*DehEPAbqA_%}ANT>y!DezZHt4EJ{7 zJf5&}<#?;GhiGJMFA<*nfheiF{{8bdDc`LbW$$75_Dk$}Y}96D?V%0+5qR0)X|1}Ry!iTe^LzAvYaCI6D~{|ElgAZOqQnF%X{6vYRrzij?Y+h0+! z?4Xi^9oHk13U*Qgq)uLvBHV(q&`a^w1ajiUA!b*o3D!W2kQmnwUn-Sip&RosA)&B0 z%6Oo+oL2F#?}`p~d>Q@;)H0D89rowFb4YLfaY_1b9WPwbt^h)Y#lIlc*40}-C()VV zF4yB6Uy_h24lTD>Z3IPG1H>Qi0?sG2VE~lbIT3YqJQzyW?Mwti_vv=1`TkT#uSRz( z1GI{Sgl+LlNpO0R1%Sep6NjsxEwKQI(~BmwUH%FFEZArGK3TZ>g?NXbJATH#bE(4W zS2V5%@w4MLb{?6fj;;|DLBol~LY+$PRsc$qb2RKxSm3XA@rm Date: Fri, 24 Aug 2018 08:27:41 -0400 Subject: [PATCH 03/24] Moving namespace agnosticism to OSS --- .../service/create_saved_objects_service.js | 6 +- src/server/saved_objects/service/lib/index.js | 1 + .../saved_objects/service/lib/repository.js | 18 +++-- .../service/lib/repository_provider.js | 3 + .../saved_objects/service/lib/schema.js | 30 ++++++++ .../service/lib/search_dsl/query_params.js | 69 +++++++++++++----- .../service/lib/search_dsl/search_dsl.js | 4 +- .../service/lib/trim_id_prefix.js | 5 +- .../service/saved_objects_client.js | 10 +-- .../secure_saved_objects_client.js | 33 +++++---- x-pack/plugins/spaces/index.js | 2 + .../spaces_saved_objects_client.js | 19 +++-- .../apis/saved_objects/bulk_get.js | 4 +- .../apis/saved_objects/create.js | 8 +- .../apis/saved_objects/find.js | 6 +- .../apis/saved_objects/get.js | 12 +-- .../saved_objects/lib/space_test_utils.js | 4 +- .../apis/saved_objects/update.js | 5 +- .../saved_objects/spaces/data.json.gz | Bin 2464 -> 2469 bytes 19 files changed, 154 insertions(+), 85 deletions(-) create mode 100644 src/server/saved_objects/service/lib/schema.js diff --git a/src/server/saved_objects/service/create_saved_objects_service.js b/src/server/saved_objects/service/create_saved_objects_service.js index fd471cad2dd23..c2d24296ade16 100644 --- a/src/server/saved_objects/service/create_saved_objects_service.js +++ b/src/server/saved_objects/service/create_saved_objects_service.js @@ -18,7 +18,7 @@ */ import { getRootPropertiesObjects } from '../../mappings'; -import { SavedObjectsRepository, ScopedSavedObjectsClientProvider, SavedObjectsRepositoryProvider } from './lib'; +import { SavedObjectsRepository, ScopedSavedObjectsClientProvider, SavedObjectsRepositoryProvider, SavedObjectsSchema } from './lib'; import { SavedObjectsClient } from './saved_objects_client'; export function createSavedObjectsService(server) { @@ -59,10 +59,13 @@ export function createSavedObjectsService(server) { } }; + const schema = new SavedObjectsSchema(); + const mappings = server.getKibanaIndexMappingsDsl(); const repositoryProvider = new SavedObjectsRepositoryProvider({ index: server.config().get('kibana.index'), mappings, + schema, onBeforeWrite, }); @@ -86,6 +89,7 @@ export function createSavedObjectsService(server) { types: Object.keys(getRootPropertiesObjects(mappings)), SavedObjectsClient, SavedObjectsRepository, + schema, getSavedObjectsRepository: (...args) => repositoryProvider.getRepository(...args), getScopedSavedObjectsClient: (...args) => diff --git a/src/server/saved_objects/service/lib/index.js b/src/server/saved_objects/service/lib/index.js index 30adefe7e47c1..e773275b9dfa1 100644 --- a/src/server/saved_objects/service/lib/index.js +++ b/src/server/saved_objects/service/lib/index.js @@ -20,6 +20,7 @@ export { SavedObjectsRepository } from './repository'; export { ScopedSavedObjectsClientProvider } from './scoped_client_provider'; export { SavedObjectsRepositoryProvider } from './repository_provider'; +export { SavedObjectsSchema } from './schema'; import * as errors from './errors'; export { errors }; diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index 2e4d49819b7fc..99cabf63808a9 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -35,12 +35,14 @@ export class SavedObjectsRepository { const { index, mappings, + schema, callCluster, onBeforeWrite = () => { }, } = options; this._index = index; this._mappings = mappings; + this._schema = schema; this._type = getRootType(this._mappings); this._onBeforeWrite = onBeforeWrite; this._unwrappedCallCluster = callCluster; @@ -73,7 +75,7 @@ export class SavedObjectsRepository { index: this._index, refresh: 'wait_for', body: { - ...namespace && { namespace }, + ...namespace && !this._schema.isNamespaceAgnostic(type) && { namespace }, type, updated_at: time, [type]: attributes, @@ -81,7 +83,7 @@ export class SavedObjectsRepository { }); return { - id: trimId(response._id, namespace, type), + id: trimId(this._schema, response._id, namespace, type), type, updated_at: time, version: response._version, @@ -122,7 +124,7 @@ export class SavedObjectsRepository { } }, { - ... namespace && { namespace }, + ... namespace && !this._schema.isNamespaceAgnostic(object.type) && { namespace }, type: object.type, updated_at: time, [object.type]: object.attributes, @@ -190,7 +192,6 @@ export class SavedObjectsRepository { * @returns {promise} */ async delete(type, id, namespace) { - console.log('delete', this._generateEsId(namespace, type, id)); const response = await this._writeToCluster('delete', { id: this._generateEsId(namespace, type, id), type: this._type, @@ -264,7 +265,7 @@ export class SavedObjectsRepository { ignore: [404], body: { version: true, - ...getSearchDsl(this._mappings, { + ...getSearchDsl(this._mappings, this._schema, { namespace, search, searchFields, @@ -296,7 +297,7 @@ export class SavedObjectsRepository { saved_objects: response.hits.hits.map(hit => { const { type, updated_at: updatedAt } = hit._source; return { - id: trimId(hit._id, namespace, type), + id: trimId(this._schema, hit._id, namespace, type), type, ...updatedAt && { updated_at: updatedAt }, version: hit._version, @@ -320,6 +321,7 @@ export class SavedObjectsRepository { * ]) */ async bulkGet(objects = [], namespace) { + console.log('repository.bulkGet', { objects, namespace }); if (objects.length === 0) { return { saved_objects: [] }; } @@ -424,7 +426,7 @@ export class SavedObjectsRepository { ignore: [404], body: { doc: { - ...namespace && { namespace }, + ...namespace && !this._schema.isNamespaceAgnostic(type) && { namespace }, updated_at: time, [type]: attributes, } @@ -463,7 +465,7 @@ export class SavedObjectsRepository { } _generateEsId(namespace, type, id) { - const namespacePrefix = namespace ? `${namespace}:` : ''; + const namespacePrefix = namespace && !this._schema.isNamespaceAgnostic(type) ? `${namespace}:` : ''; return `${namespacePrefix}${type}:${id || uuid.v1()}`; } diff --git a/src/server/saved_objects/service/lib/repository_provider.js b/src/server/saved_objects/service/lib/repository_provider.js index 131eefd619a25..eabe1be09fed6 100644 --- a/src/server/saved_objects/service/lib/repository_provider.js +++ b/src/server/saved_objects/service/lib/repository_provider.js @@ -27,10 +27,12 @@ export class SavedObjectsRepositoryProvider { constructor({ index, mappings, + schema, onBeforeWrite }) { this._index = index; this._mappings = mappings; + this._schema = schema; this._onBeforeWrite = onBeforeWrite; } @@ -43,6 +45,7 @@ export class SavedObjectsRepositoryProvider { return new SavedObjectsRepository({ index: this._index, mappings: this._mappings, + schema: this._schema, onBeforeWrite: this._onBeforeWrite, callCluster }); diff --git a/src/server/saved_objects/service/lib/schema.js b/src/server/saved_objects/service/lib/schema.js new file mode 100644 index 0000000000000..d8308e0e11e87 --- /dev/null +++ b/src/server/saved_objects/service/lib/schema.js @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export class SavedObjectsSchema { + _namespaceAgnosticTypes = []; + + addNamespaceAgnosticType(type) { + this._namespaceAgnosticTypes.push(type); + } + + isNamespaceAgnostic(type) { + return this._namespaceAgnosticTypes.includes(type); + } +} diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.js b/src/server/saved_objects/service/lib/search_dsl/query_params.js index 16767bca69cf2..0f61b5eecacc2 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.js @@ -59,34 +59,70 @@ function getFieldsForTypes(searchFields, types) { }; } +/** + * Gets the clause that will filter for the type in the namespace. + * Some types are namespace agnostic, so they must be treated differently. + * @param {SavedObjectsSchema} schema + * @param {string} namespace + * @param {string} type + * @return {Object} + */ +function getClauseForType(schema, namespace, type) { + const bool = { + must: [] + }; + + if (!type) { + throw new Error(`type is required to build filter clause`); + } + + bool.must.push({ + term: { + type + } + }); + + if (namespace && !schema.isNamespaceAgnostic(type)) { + bool.must.push({ + term: { + namespace + } + }); + } else { + bool.must_not = [{ + exists: { + field: 'namespace' + } + }]; + } + + return { + bool + }; +} + /** * Get the "query" related keys for the search body * @param {EsMapping} mapping mappings from Ui + * *@param {SavedObjectsSchema} schema * @param {(string|Array)} type * @param {String} search * @param {Array} searchFields * @param {Array} filters additional query filters * @return {Object} */ -export function getQueryParams(mappings, namespace, type, search, searchFields, filters = []) { +export function getQueryParams(mappings, schema, namespace, type, search, searchFields, filters = []) { const types = getTypes(mappings, type); const bool = { filter: [...filters], }; - if (namespace) { - bool.filter.push({ 'term': { namespace } }); - } else { - bool.must_not = [{ - exists: { - field: 'namespace', - } - }]; - } - - if (type) { - bool.filter.push({ [Array.isArray(type) ? 'terms' : 'term']: { type } }); - } + bool.filter.push({ + bool: { + should: types.map(type => getClauseForType(schema, namespace, type)), + minimum_should_match: 1 + } + }); if (search) { bool.must = [ @@ -102,11 +138,6 @@ export function getQueryParams(mappings, namespace, type, search, searchFields, ]; } - // Don't construct a query if there is nothing to search on. - if (bool.filter.length === 0 && !search) { - return {}; - } - return { query: { bool } }; } diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js index c550be9a057bf..1a0bc8e1c87c6 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js @@ -22,7 +22,7 @@ import Boom from 'boom'; import { getQueryParams } from './query_params'; import { getSortingParams } from './sorting_params'; -export function getSearchDsl(mappings, options = {}) { +export function getSearchDsl(mappings, schema, options = {}) { const { namespace, type, @@ -46,7 +46,7 @@ export function getSearchDsl(mappings, options = {}) { } return { - ...getQueryParams(mappings, namespace, type, search, searchFields, filters), + ...getQueryParams(mappings, schema, namespace, type, search, searchFields, filters), ...getSortingParams(mappings, type, sortField, sortOrder), }; } diff --git a/src/server/saved_objects/service/lib/trim_id_prefix.js b/src/server/saved_objects/service/lib/trim_id_prefix.js index f1c9d9ac2148f..7b392ffa5ca41 100644 --- a/src/server/saved_objects/service/lib/trim_id_prefix.js +++ b/src/server/saved_objects/service/lib/trim_id_prefix.js @@ -30,14 +30,15 @@ function assertNonEmptyString(value, name) { * @param {string} type * @return {string} */ -export function trimId(id, namespace, type) { +export function trimId(schema, id, namespace, type) { assertNonEmptyString(id, 'document id'); assertNonEmptyString(type, 'saved object type'); - const namespacePrefix = namespace ? `${namespace}:` : ''; + const namespacePrefix = namespace && !schema.isNamespaceAgnostic(type) ? `${namespace}:` : ''; const prefix = `${namespacePrefix}${type}:`; if (!id.startsWith(prefix)) { + console.log({ id, namespace, type }); throw new Error('Unable to trim id'); } diff --git a/src/server/saved_objects/service/saved_objects_client.js b/src/server/saved_objects/service/saved_objects_client.js index f0686be4602ab..a2fb118432835 100644 --- a/src/server/saved_objects/service/saved_objects_client.js +++ b/src/server/saved_objects/service/saved_objects_client.js @@ -156,7 +156,6 @@ export class SavedObjectsClient { * Returns an array of objects by id * * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {object} [options = {}] * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example @@ -166,8 +165,8 @@ export class SavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], options = {}, namespace) { - return this._repository.bulkGet(objects, options, namespace); + async bulkGet(objects = [], namespace) { + return this._repository.bulkGet(objects, namespace); } /** @@ -175,12 +174,11 @@ export class SavedObjectsClient { * * @param {string} type * @param {string} id - * @param {object} [options = {}] * @param {string} [namespace] * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, options = {}, namespace) { - return this._repository.get(type, id, options, namespace); + async get(type, id, namespace) { + return this._repository.get(type, id, namespace); } /** diff --git a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js index f1f5bef1a2c6a..55a8473be11d7 100644 --- a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js +++ b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js @@ -32,7 +32,7 @@ export class SecureSavedObjectsClient { return await this._execute( type, 'create', - { type, attributes, options }, + { type, attributes, options, namespace }, repository => repository.create(type, attributes, options, namespace), ); } @@ -42,7 +42,7 @@ export class SecureSavedObjectsClient { return await this._execute( types, 'bulk_create', - { objects, options }, + { objects, options, namespace }, repository => repository.bulkCreate(objects, options, namespace), ); } @@ -51,7 +51,7 @@ export class SecureSavedObjectsClient { return await this._execute( type, 'delete', - { type, id }, + { type, id, namespace }, repository => repository.delete(type, id, namespace), ); } @@ -64,22 +64,22 @@ export class SecureSavedObjectsClient { return await this._findAcrossAllTypes(options, namespace); } - async bulkGet(objects = [], options = {}, namespace) { + async bulkGet(objects = [], namespace) { const types = uniq(objects.map(o => o.type)); return await this._execute( types, 'bulk_get', - { objects, options }, - repository => repository.bulkGet(objects, options, namespace) + { objects, namespace }, + repository => repository.bulkGet(objects, namespace) ); } - async get(type, id, options = {}, namespace) { + async get(type, id, namespace) { return await this._execute( type, 'get', - { type, id, options }, - repository => repository.get(type, id, options, namespace) + { type, id, namespace }, + repository => repository.get(type, id, namespace) ); } @@ -87,7 +87,7 @@ export class SecureSavedObjectsClient { return await this._execute( type, 'update', - { type, id, attributes, options }, + { type, id, attributes, options, namespace }, repository => repository.update(type, id, attributes, options, namespace) ); } @@ -130,7 +130,7 @@ export class SecureSavedObjectsClient { const { result, username, missing } = await this._checkSavedObjectPrivileges(Array.from(typesToPrivilegesMap.values())); if (result === CHECK_PRIVILEGES_RESULT.LEGACY) { - return await this._callWithRequestRepository.find(options); + return await this._callWithRequestRepository.find(options, namespace); } const authorizedTypes = Array.from(typesToPrivilegesMap.entries()) @@ -150,11 +150,12 @@ export class SecureSavedObjectsClient { this._auditLogger.savedObjectsAuthorizationSuccess(username, action, authorizedTypes, { options }); - return await this._internalRepository.find({ - ...options, - type: authorizedTypes, - namespace - }); + return await this._internalRepository.find( + { + ...options, + type: authorizedTypes, + }, + namespace); } async _findWithTypes(options, namespace) { diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js index 480641cc792f2..47ba71d2949f6 100644 --- a/x-pack/plugins/spaces/index.js +++ b/x-pack/plugins/spaces/index.js @@ -68,6 +68,8 @@ export const spaces = (kibana) => new kibana.Plugin({ const thisPlugin = this; const xpackMainPlugin = server.plugins.xpack_main; + server.savedObjects.schema.addNamespaceAgnosticType('space'); + watchStatusAndLicenseToInitialize(xpackMainPlugin, thisPlugin, async () => { await createDefaultSpace(server); }); diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js index 98aa111503588..d1dc2e034ddb3 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js @@ -34,7 +34,12 @@ export class SpacesSavedObjectsClient { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.create(type, attributes, options, this._getNamespace(this._spaceId)); + try { + return await this._client.create(type, attributes, options, this._getNamespace(this._spaceId)); + } catch (err) { + console.log("WE KNOW THE ERROR", err); + throw err; + } } /** @@ -87,14 +92,13 @@ export class SpacesSavedObjectsClient { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.find({ ...options }, this._getNamespace(this._spaceId)); + return await this._client.find(options, this._getNamespace(this._spaceId)); } /** * Returns an array of objects by id * * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {object} [options = {}] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -103,12 +107,12 @@ export class SpacesSavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], options = {}, namespace) { + async bulkGet(objects = [], namespace) { if (namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.bulkGet(objects, options, this._getNamespace(this._spaceId)); + return await this._client.bulkGet(objects, this._getNamespace(this._spaceId)); } /** @@ -116,15 +120,14 @@ export class SpacesSavedObjectsClient { * * @param {string} type * @param {string} id - * @param {object} [options = {}] * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, options = {}, namespace) { + async get(type, id, namespace) { if (namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.get(type, id, options, this._getNamespace(this._spaceId)); + return await this._client.get(type, id, this._getNamespace(this._spaceId)); } /** diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/bulk_get.js b/x-pack/test/spaces_api_integration/apis/saved_objects/bulk_get.js index 37bd80f56a5c8..d797c53f60a45 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/bulk_get.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/bulk_get.js @@ -6,7 +6,7 @@ import expect from 'expect.js'; import { SPACES } from './lib/spaces'; -import { getIdPrefix, getUrlPrefix, getExpectedSpaceIdProperty } from './lib/space_test_utils'; +import { getIdPrefix, getUrlPrefix, getExpectedNamespaceProperty } from './lib/space_test_utils'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -73,7 +73,7 @@ export default function ({ getService }) { type: 'visualization', updated_at: '2017-09-21T18:51:23.794Z', version: resp.body.saved_objects[0].version, - ...getExpectedSpaceIdProperty(spaceId), + ...getExpectedNamespaceProperty(spaceId), attributes: { title: 'Count of requests', description: '', diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/create.js b/x-pack/test/spaces_api_integration/apis/saved_objects/create.js index 14d6ba54de2e8..4a7bfa30d40dc 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/create.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/create.js @@ -31,17 +31,17 @@ export default function ({ getService }) { } }); - const expectedDocumentId = spaceId === DEFAULT_SPACE_ID ? resp.body.id : `${spaceId}:${resp.body.id}`; + const expectedSpacePrefix = spaceId === DEFAULT_SPACE_ID ? '' : `${spaceId}:`; // query ES directory to assert on space id const { _source } = await es.get({ - id: `visualization:${expectedDocumentId}`, + id: `${expectedSpacePrefix}visualization:${resp.body.id}`, type: 'doc', index: '.kibana' }); const { - spaceId: actualSpaceId = '**not defined**' + namespace: actualSpaceId = '**not defined**' } = _source; if (spaceId === DEFAULT_SPACE_ID) { @@ -75,7 +75,7 @@ export default function ({ getService }) { }); const { - spaceId: actualSpaceId = '**not defined**' + namespace: actualSpaceId = '**not defined**' } = _source; expect(actualSpaceId).to.eql('**not defined**'); diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/find.js b/x-pack/test/spaces_api_integration/apis/saved_objects/find.js index 01383433774ef..ea59e01eff96d 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/find.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/find.js @@ -44,7 +44,7 @@ export default function ({ getService }) { id: '7.0.0-alpha1', type: 'config', updated_at: '2017-09-21T18:49:16.302Z', - version: 3, + version: 1, }, { id: `default`, type: 'space', @@ -85,7 +85,9 @@ export default function ({ getService }) { .sort(sortById); expectedSavedObjects.forEach((object, index) => { - object.attributes = resp.body.saved_objects[index].attributes; + if (resp.body.saved_objects[index]) { + object.attributes = resp.body.saved_objects[index].attributes; + } }); diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/get.js b/x-pack/test/spaces_api_integration/apis/saved_objects/get.js index 6a8e21e9b5c99..bef423c0cfdc5 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/get.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/get.js @@ -7,7 +7,6 @@ import expect from 'expect.js'; import { getIdPrefix, getUrlPrefix } from './lib/space_test_utils'; import { SPACES } from './lib/spaces'; -import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -38,7 +37,7 @@ export default function ({ getService }) { }; if (expectedSpaceId) { - expectedBody.spaceId = expectedSpaceId; + expectedBody.namespace = expectedSpaceId; } expect(resp.body).to.eql(expectedBody); @@ -60,17 +59,10 @@ export default function ({ getService }) { it(`should return ${tests.exists.statusCode}`, async () => { const objectId = `${getIdPrefix(otherSpaceId)}dd7caf20-9efd-11e7-acb3-3dab96693fab`; - let expectedObjectId = objectId; - const testingMismatchedSpaces = spaceId !== otherSpaceId; - - if (testingMismatchedSpaces && spaceId !== DEFAULT_SPACE_ID) { - expectedObjectId = `${spaceId}:${expectedObjectId}`; - } - return supertest .get(`${getUrlPrefix(spaceId)}/api/saved_objects/visualization/${objectId}`) .expect(tests.exists.statusCode) - .then(tests.exists.response('visualization', expectedObjectId)); + .then(tests.exists.response('visualization', objectId)); }); }); }; diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/lib/space_test_utils.js b/x-pack/test/spaces_api_integration/apis/saved_objects/lib/space_test_utils.js index fab201a5b3c00..53a0da25791b5 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/lib/space_test_utils.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/lib/space_test_utils.js @@ -14,11 +14,11 @@ export function getIdPrefix(spaceId) { return spaceId === DEFAULT_SPACE_ID ? '' : `${spaceId}-`; } -export function getExpectedSpaceIdProperty(spaceId) { +export function getExpectedNamespaceProperty(spaceId) { if (spaceId === DEFAULT_SPACE_ID) { return {}; } return { - spaceId + namespace: spaceId }; } diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/update.js b/x-pack/test/spaces_api_integration/apis/saved_objects/update.js index 3aea0aadde82e..4bc938ba7a499 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/update.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/update.js @@ -7,7 +7,6 @@ import expect from 'expect.js'; import { SPACES } from './lib/spaces'; import { getUrlPrefix, getIdPrefix } from './lib/space_test_utils'; -import { DEFAULT_SPACE_ID } from '../../../../plugins/spaces/common/constants'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -92,7 +91,7 @@ export default function ({ getService }) { } }) .expect(tests.inOtherSpace.statusCode) - .then(tests.inOtherSpace.response(`visualization`, `${spaceId === DEFAULT_SPACE_ID ? '' : (spaceId + ':')}${id}`)); + .then(tests.inOtherSpace.response(`visualization`, `${id}`)); }); describe('unknown id', () => { @@ -105,7 +104,7 @@ export default function ({ getService }) { } }) .expect(tests.doesntExist.statusCode) - .then(tests.doesntExist.response(`visualization`, `${spaceId === DEFAULT_SPACE_ID ? '' : (spaceId + ':')}not an id`)); + .then(tests.doesntExist.response(`visualization`, `not an id`)); }); }); }); diff --git a/x-pack/test/spaces_api_integration/fixtures/es_archiver/saved_objects/spaces/data.json.gz b/x-pack/test/spaces_api_integration/fixtures/es_archiver/saved_objects/spaces/data.json.gz index 4d06609b58d781bcd2c8c1d1c3bd2313e94afb7b..a795be4f88ea3c0783f7268adf701f995cce6529 100644 GIT binary patch delta 2107 zcmYL_dpy(s7suyLwW#@#`*j#5tO>bCY$!%lNUr_Lt(r@7`+Qxhga_xRfdm5mBK`C( zVk5#qtKK0I^F~rzrM`Z|IVp-tf1&Yc4b^SXUy5x5iA)L;7KV@Qk5M0W#NZBn0uG{3 z)WA4-I%R(4qmXbR{-A>5m)QY_wj~yE++;Ef5-l5jybgby5!$JM+U@lvP;U^*fWX*HBX;oJ9+OTd-j|K%vikeS0Y#Z8%Ntx3nJA~|O)yO!TNi1MI602f z8cy!QN`>|n`sehqDjcv1ZIDW2F7E1rNU6P9W8Bl8mX(dr5$I^`+^U;lTie5j@3Z=s zalu{z+^Fl*T`dJn@0yPub@+S@Z5FqxpSma$*Lxamc~a*~72E*ajB0){W#mfbey>sW zYK?8Jd9%vOs;*O|(2VI(*efKoV`(p0cfTFkx#E1{!R9i)YkWXV`nmqluGAq-hSj6Z zjlGZ6Uh6v@$bwj%Kr-jegpaM6Op3+ZC^${UYm;9{Fk$gvhbiwihJiAB8N|PIsbkI4 z_jpB;P;sYCb+*`{Pat;98f|M>I|DDs83_dUslEPkVDF+%lOkVpQ`Ry(&;D_>dC*^) z73KoYqnR!ym2WCWs2$(W<>_ogc;sh zKReRrUBBLnxqVFN1gl0BLAtXfo#GK*;Aj=^dm^k#{T}fA-KWfkWa6f{|1Z-$Fa_H{ z`F4H#d&tFLr;g8E<#Tf0C5H>b#hXp-Kir=gQ!x1Gt_hhO!UNHM*Hfr$-l%i3LmgI zp|fi>BF}-L;Ch-0_{nLor%wmC@#9Gw$;B+q?-EZYGE$k9n|Ubc+r;oDgtFWDb>cCk z90J+5>S;l>fU4d0Xbu+-;0~xQT{t?&4xKIY`}fG@o4Z0{kk|?CvJp26YRO!fPFwf zd?n4E>R_xbUk{jjaT76x&RzEUx|(aCiJ2X6F2^-D%Y08XvAf8hGF@tkNMM}eK24l& zl$Dp2h@fR)m|BON3-01u?)sjs;YNRvLUJ7RMd zfXzphLl2tOCk2XDvTPHJ8#`O}xbB|5|H?_X)woR?|asDJ%3~3P)xa$Iv!~&jQ7z&j(`G-TQqG7H^d4M)PP4s1l&4OqHEbql<{ab=+Y>Dn62Vrzv$~J_%1c1n6Mf~_0>>6e3o`LdWeuY_=IdlP88=3j?aPKQ&r#FuqdZ@%6` zWeRVezntda{3!#E@SQ=Y69G*B`NdSRc51V{2g8(sDNP zQPL88VYC?0zWO8L*g&RqP_MG+oM46JsTZyfD1q8_J|mTN7_tTo*V2`!m?ft$lP2|S zxICk5sOY6tJ1t?g&ptHdt)qpi!Dp?%4Op5>9PLJ5xck-(M$)DYRCPZ!A5g3x)6;q? zB4>Tk=his&huC}Taz(-aOShryM2eNU=tRnt+KF0`q*w`ha%!wZt;lCgw2L_ZS7oX- zuU0EU@v7c={*m$m_cwlT+> zH{oL?;;J~t$Cq~YG|^HMBzg5aSW&&qs_jT0lA-@*G3TW+E?2ZI0Y^#r;J^NK1`w0@ zA{I5)wj@RvZRfw$R^B6c(dmzDmW4P?7$lI9uDw|OBFIXBi8jVv-!+FH_z$e~yJ}pN zS|=Kn25MKg`->nsz}V+P;D^G_rinmdJGXE1@jIsr;qBLK>;2wVs9kx)DKSSaR1!gQ z`k*8lbI+D{(?V@?*#7!QcpT6e9tWPsaBt^|J`2lSW?Dvig1 zAo-ia&h+0LP-(w8s0*PnJPvup-wDiR^N7X|)$KsX2$eZ=oYp*|s0SJ&_!iG(W^elGs}4h2QaBJUe~JCJF~ zemrpc8aQ(JmH?S>!{6Ypy2-jqz2?6+6ljkO6}c!CE+56O1lfUGC9;WZ`&*K-eL4OF zH&aRz%t8BNz-m>8uGN_<)k7nOPlNHI(i9a+p*c@cF*!yfgTL`tpFI{?Y_aJqwEk|D zrw>T*YbYxTKm=g&)8gZ@k`2=r{zfFgZ&J;K=nhnTo~*o7miNuadgJ}=RCb4DywEmu z-y=Ng4DD>I^AEb$w|A85qN98>62TV_%;Epq6Mj1jqN>FEr1O&|8k-H97O;T2OA%+7 z{*}#hh$?K0TUeqk{|kIx@fYB!^hjprQ$SOBVgDpuS9t+T*%rF1Hfuhg;)$=Kq6&{a zp1VAjcC|}9eMC2XRy{;o&rgcF(tiq;8t%B2nCvsUMaCh^%-Y7Ex`%`Y1(8HEKT!u7 zyZ5Lk2#+#zn>W}cy%+SJ}U}u^-J2$RnRljv%W5%%%8q&DozO8 z2sG^&A1N`ixZxTc6t+0~3z3}zOLef3NVG424?mEvFJX>j2EOyZ&mcbn6_^%dXsgQ_ zOsUZwiY_n{CQXucDpf$Bbq+1P0t!`hp+0Z&)U|%P0ugfwo_Fkt9w=w!DD0r`zoXsd zT^!#{x%xfZ;Mbp(k`}1pn3gEA(uDgL@#DYiAoX7d+m;Unx2%62P0zIZr!CRv?w-0}G-%*`rT~68@y5?}>OSQ;+leT;$K`7UYYG(s0=pS&4bard?O$;oY z*{Q7BL3(=Ouj&wA+xI@H7#_6dbQl_?4JZMoZz46^8{heDD~gh1iYT8uJ+5khq ztp=O0oQl7+gX!tLx2X_p&IMnW=Inxm8C7q{ZwM53PX7|Z`etDDXoW;7|3K6Ni$BDF zWuW$-W^Ue^hWN#^oI=xb{9d8179+2k^ejhRFI0a_2lV?rB;)u!PACj)lXpEGQ2XB4 z)d$`ayR|86`!?ACJb73o4{mGh1JFXhu4f#(XR_urTB2o{Vs3mM=})RDC{bDyH%96j zcp(*wY78F*zIBtbw4p!|#A~6(zXj+pnnKdLew86{eB63EMQT)jN+sHA*-z-CYC?5N zWgd64u}4eWi&{%N1BNS?Y0wWX1B_<6sZJ_xijSnu;4ppzCGRnnC3 zT1%wVYdS!ni2di=(dHBKf}bL;cP{kqEha`6-U+aH-8#5QeE6$dZj`tLlRSv!UW#!l zv^*QuMH?zJ8s8#?t=N@TL?n8vcN-6>lj_u79?u>!+nvRZN6QL_lg4tsY(Eon+pfR3 zXM4Zk`t6JVDDC<7@?}{5bq3(dhJHA?VX`&^DG1pkrr5MWMYK3#skH!kXRfE92D;Kx z3HvMf5kcf&(3`wkMiF59Ss=E~E~kM|%`uXE!)2>t*-w&?Obr_wo(?K6xBCPqpUYaU zx3Lh4hZ0_i?8FBcKj_n!kUC$fDn+LhI?ilV$Kl(yg?kmy;^HoLzGk!n}sA6_5qC7!3lP5a>e_hSS`Bj(Aw_K%gE@sxK07|(XnI9vADHz6u0ZC z%VmO{-()mXM$`_|+5F{QMB`B0Q!~ObNz>1cV|-6HJA+7K|8J0boFo>d0*Mc`F$R<> zs!*`7g;!tXe5eFMyGCXf=N;a6*E;Cj*4M9RIXY>?1!6iIG#EufjO3i(*`9L)jkCC zG3<1BDO(qUp)g>+W)IFk#y_ttiR3c2F1gwSFrS7P?-9G4ePmT6>wyv{R{##p8L*^G zSi9J>ipyEJG1eqTBovW49g=dG&Rl>T)4z~~3s-ymW9k|Lg=6cIw}yi_j~Yyt>wpqP z9b?MUN$a_c{#FPSTf3x;V0FylE2GYTL%vLb0izi(U071)684A;)6xz=tbiK2x3r&$ F>3^P?@4^58 From c90476b34529961f68edc22e7964765dd44ca131 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 08:39:02 -0400 Subject: [PATCH 04/24] Fixing rbac tests, spaces can be managed with the SOC temporarily --- .../apis/privileges/index.js | 3 ++ .../apis/saved_objects/find.js | 48 +------------------ 2 files changed, 5 insertions(+), 46 deletions(-) diff --git a/x-pack/test/rbac_api_integration/apis/privileges/index.js b/x-pack/test/rbac_api_integration/apis/privileges/index.js index 9563a1b65540f..aa66f8f7d2d96 100644 --- a/x-pack/test/rbac_api_integration/apis/privileges/index.js +++ b/x-pack/test/rbac_api_integration/apis/privileges/index.js @@ -38,6 +38,9 @@ export default function ({ getService }) { 'action:saved_objects/graph-workspace/get', 'action:saved_objects/graph-workspace/bulk_get', 'action:saved_objects/graph-workspace/find', + 'action:saved_objects/space/get', + 'action:saved_objects/space/bulk_get', + 'action:saved_objects/space/find', 'action:saved_objects/index-pattern/get', 'action:saved_objects/index-pattern/bulk_get', 'action:saved_objects/index-pattern/find', diff --git a/x-pack/test/rbac_api_integration/apis/saved_objects/find.js b/x-pack/test/rbac_api_integration/apis/saved_objects/find.js index 5bb42acacd392..ca9b9bb2ec1c2 100644 --- a/x-pack/test/rbac_api_integration/apis/saved_objects/find.js +++ b/x-pack/test/rbac_api_integration/apis/saved_objects/find.js @@ -69,50 +69,6 @@ export default function ({ getService }) { }); }; - const expectAllResultsIncludingInvalidTypes = (resp) => { - expect(resp.body).to.eql({ - page: 1, - per_page: 20, - total: 5, - saved_objects: [ - { - id: '91200a00-9efd-11e7-acb3-3dab96693fab', - type: 'index-pattern', - updated_at: '2017-09-21T18:49:16.270Z', - version: 1, - attributes: resp.body.saved_objects[0].attributes - }, - { - id: '7.0.0-alpha1', - type: 'config', - updated_at: '2017-09-21T18:49:16.302Z', - version: 1, - attributes: resp.body.saved_objects[1].attributes - }, - { - id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - type: 'visualization', - updated_at: '2017-09-21T18:51:23.794Z', - version: 1, - attributes: resp.body.saved_objects[2].attributes - }, - { - id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', - type: 'dashboard', - updated_at: '2017-09-21T18:57:40.826Z', - version: 1, - attributes: resp.body.saved_objects[3].attributes - }, - { - id: 'visualization:dd7caf20-9efd-11e7-acb3-3dab96693faa', - type: 'not-a-visualization', - updated_at: '2017-09-21T18:51:23.794Z', - version: 1 - }, - ] - }); - }; - const createExpectEmpty = (page, perPage, total) => (resp) => { expect(resp.body).to.eql({ page: page, @@ -290,7 +246,7 @@ export default function ({ getService }) { noType: { description: 'all objects', statusCode: 200, - response: expectAllResultsIncludingInvalidTypes, + response: expectResultsWithValidTypes, }, }, }); @@ -324,7 +280,7 @@ export default function ({ getService }) { noType: { description: 'all objects', statusCode: 200, - response: expectAllResultsIncludingInvalidTypes, + response: expectResultsWithValidTypes, }, } }); From 6ec276fc09421440596d4f2b2e99589aa9085bbd Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 08:40:55 -0400 Subject: [PATCH 05/24] Putting trimIdPrefix back to it's original name --- src/server/saved_objects/service/lib/repository.js | 6 +++--- src/server/saved_objects/service/lib/trim_id_prefix.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index 99cabf63808a9..3b86ff19007f7 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -21,7 +21,7 @@ import uuid from 'uuid'; import { getRootType } from '../../../mappings'; import { getSearchDsl } from './search_dsl'; -import { trimId } from './trim_id_prefix'; +import { trimIdPrefix } from './trim_id_prefix'; import { includedFields } from './included_fields'; import { decorateEsError } from './decorate_es_error'; import * as errors from './errors'; @@ -83,7 +83,7 @@ export class SavedObjectsRepository { }); return { - id: trimId(this._schema, response._id, namespace, type), + id: trimIdPrefix(this._schema, response._id, namespace, type), type, updated_at: time, version: response._version, @@ -297,7 +297,7 @@ export class SavedObjectsRepository { saved_objects: response.hits.hits.map(hit => { const { type, updated_at: updatedAt } = hit._source; return { - id: trimId(this._schema, hit._id, namespace, type), + id: trimIdPrefix(this._schema, hit._id, namespace, type), type, ...updatedAt && { updated_at: updatedAt }, version: hit._version, diff --git a/src/server/saved_objects/service/lib/trim_id_prefix.js b/src/server/saved_objects/service/lib/trim_id_prefix.js index 7b392ffa5ca41..b6bd29c42df49 100644 --- a/src/server/saved_objects/service/lib/trim_id_prefix.js +++ b/src/server/saved_objects/service/lib/trim_id_prefix.js @@ -30,7 +30,7 @@ function assertNonEmptyString(value, name) { * @param {string} type * @return {string} */ -export function trimId(schema, id, namespace, type) { +export function trimIdPrefix(schema, id, namespace, type) { assertNonEmptyString(id, 'document id'); assertNonEmptyString(type, 'saved object type'); From 7cfc737557cc89011dd9502b5beca7f35cdac79d Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 08:46:16 -0400 Subject: [PATCH 06/24] Removing unused code and debug statements --- .../service/lib/trim_id_prefix.js | 1 - .../__snapshots__/query_filters.test.js.snap | 5 - .../lib/is_type_space_aware.js | 14 -- .../lib/is_type_space_aware.test.js | 29 ---- .../saved_objects_client/lib/query_filters.js | 70 --------- .../lib/query_filters.test.js | 139 ------------------ .../spaces_saved_objects_client.js | 7 +- 7 files changed, 1 insertion(+), 264 deletions(-) delete mode 100644 x-pack/plugins/spaces/server/lib/saved_objects_client/lib/__snapshots__/query_filters.test.js.snap delete mode 100644 x-pack/plugins/spaces/server/lib/saved_objects_client/lib/is_type_space_aware.js delete mode 100644 x-pack/plugins/spaces/server/lib/saved_objects_client/lib/is_type_space_aware.test.js delete mode 100644 x-pack/plugins/spaces/server/lib/saved_objects_client/lib/query_filters.js delete mode 100644 x-pack/plugins/spaces/server/lib/saved_objects_client/lib/query_filters.test.js diff --git a/src/server/saved_objects/service/lib/trim_id_prefix.js b/src/server/saved_objects/service/lib/trim_id_prefix.js index b6bd29c42df49..6691686225e5d 100644 --- a/src/server/saved_objects/service/lib/trim_id_prefix.js +++ b/src/server/saved_objects/service/lib/trim_id_prefix.js @@ -38,7 +38,6 @@ export function trimIdPrefix(schema, id, namespace, type) { const prefix = `${namespacePrefix}${type}:`; if (!id.startsWith(prefix)) { - console.log({ id, namespace, type }); throw new Error('Unable to trim id'); } diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/__snapshots__/query_filters.test.js.snap b/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/__snapshots__/query_filters.test.js.snap deleted file mode 100644 index d3b95886e8353..0000000000000 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/__snapshots__/query_filters.test.js.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`throws if types contains an empty entry 1`] = `"type is required to build filter clause"`; - -exports[`throws when no types are provided 1`] = `"At least one type must be provided to \\"getSpacesQueryFilters\\""`; diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/is_type_space_aware.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/is_type_space_aware.js deleted file mode 100644 index 720fb7e28e117..0000000000000 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/is_type_space_aware.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * Returns if the provided Saved Object type is "space aware". - * Most types should be space-aware, and those that aren't should typically strive to become space-aware. - * Types that are not space-aware will appear in every space, and are not bound by any space-specific access controls. - */ -export function isTypeSpaceAware(type) { - return type !== 'space' && type !== 'config'; -} diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/is_type_space_aware.test.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/is_type_space_aware.test.js deleted file mode 100644 index a88d4c35125f7..0000000000000 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/is_type_space_aware.test.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isTypeSpaceAware } from "./is_type_space_aware"; - -const knownSpaceAwareTypes = [ - 'dashboard', - 'visualization', - 'saved_search', - 'timelion_sheet', - 'index_pattern' -]; - -const unawareTypes = ['space']; - -knownSpaceAwareTypes.forEach(type => test(`${type} should be space-aware`, () => { - expect(isTypeSpaceAware(type)).toBe(true); -})); - -unawareTypes.forEach(type => test(`${type} should not be space-aware`, () => { - expect(isTypeSpaceAware(type)).toBe(false); -})); - -test(`unknown types should default to space-aware`, () => { - expect(isTypeSpaceAware('an-unknown-type')).toBe(true); -}); diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/query_filters.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/query_filters.js deleted file mode 100644 index cf1754c056c3f..0000000000000 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/query_filters.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { DEFAULT_SPACE_ID } from "../../../../common/constants"; -import { isTypeSpaceAware } from "./is_type_space_aware"; - -function getClauseForType(spaceId, type) { - const shouldFilterOnSpace = isTypeSpaceAware(type) && spaceId; - const isDefaultSpace = spaceId === DEFAULT_SPACE_ID; - - const bool = { - must: [] - }; - - if (!type) { - throw new Error(`type is required to build filter clause`); - } - - bool.must.push({ - term: { - type - } - }); - - if (shouldFilterOnSpace) { - if (isDefaultSpace) { - // The default space does not add its spaceId to the objects that belong to it, in order - // to be compatible with installations that are not always space-aware. - bool.must_not = [{ - exists: { - field: "spaceId" - } - }]; - } else { - bool.must.push({ - term: { - spaceId - } - }); - } - } - - return { - bool - }; -} - -export function getSpacesQueryFilters(spaceId, types = []) { - if (types.length === 0) { - throw new Error(`At least one type must be provided to "getSpacesQueryFilters"`); - } - - const filters = []; - - const typeClauses = types.map((type) => getClauseForType(spaceId, type)); - - if (typeClauses.length > 0) { - filters.push({ - bool: { - should: typeClauses, - minimum_should_match: 1 - } - }); - } - - return filters; -} diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/query_filters.test.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/query_filters.test.js deleted file mode 100644 index a7bcacc524caa..0000000000000 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/lib/query_filters.test.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getSpacesQueryFilters } from './query_filters'; - -test('throws when no types are provided', () => { - expect(() => getSpacesQueryFilters('space_1', [])).toThrowErrorMatchingSnapshot(); -}); - -test('throws if types contains an empty entry', () => { - expect(() => getSpacesQueryFilters('space_1', ['dashboard', ''])).toThrowErrorMatchingSnapshot(); -}); - -test('creates a query that filters on type, but not on space, for types that are not space-aware', () => { - const spaceId = 'space_1'; - const type = 'space'; - - const expectedTypeClause = { - bool: { - must: [{ - term: { - type - } - }] - } - }; - expect(getSpacesQueryFilters(spaceId, [type])).toEqual([{ - bool: { - should: [expectedTypeClause], - minimum_should_match: 1 - } - }]); -}); - -test('creates a query that restricts a space-aware type to the provided space (space_1)', () => { - const spaceId = 'space_1'; - const type = 'dashboard'; - - const expectedTypeClause = { - bool: { - must: [{ - term: { - type - } - }, { - term: { - spaceId - } - }] - } - }; - - expect(getSpacesQueryFilters(spaceId, [type])).toEqual([{ - bool: { - should: [expectedTypeClause], - minimum_should_match: 1 - } - }]); -}); - -test('creates a query that restricts a space-aware type to the provided space (default)', () => { - const spaceId = 'default'; - const type = 'dashboard'; - - const expectedTypeClause = { - bool: { - must: [{ - term: { - type - } - }], - // The default space does not add its spaceId to the objects that belong to it, in order - // to be compatible with installations that are not always space-aware. - must_not: [{ - exists: { - field: 'spaceId' - } - }] - } - }; - - expect(getSpacesQueryFilters(spaceId, [type])).toEqual([{ - bool: { - should: [expectedTypeClause], - minimum_should_match: 1 - } - }]); -}); - -test('creates a query supporting a find operation on multiple types', () => { - const spaceId = 'space_1'; - const types = [ - 'dashboard', - 'space', - 'visualization', - ]; - - const expectedSpaceClause = { - term: { - spaceId - } - }; - - const expectedTypeClauses = [{ - bool: { - must: [{ - term: { - type: 'dashboard' - } - }, expectedSpaceClause] - } - }, { - bool: { - must: [{ - term: { - type: 'space' - } - }] - } - }, { - bool: { - must: [{ - term: { - type: 'visualization' - } - }, expectedSpaceClause] - } - }]; - - expect(getSpacesQueryFilters(spaceId, types)).toEqual([{ - bool: { - should: expectedTypeClauses, - minimum_should_match: 1 - } - }]); -}); diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js index d1dc2e034ddb3..17537d87509c0 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js @@ -34,12 +34,7 @@ export class SpacesSavedObjectsClient { throw new Error('Spaces currently determines the namespaces'); } - try { - return await this._client.create(type, attributes, options, this._getNamespace(this._spaceId)); - } catch (err) { - console.log("WE KNOW THE ERROR", err); - throw err; - } + return await this._client.create(type, attributes, options, this._getNamespace(this._spaceId)); } /** From 27c60c0388e4de8e80bb7dcac2b37bf249ebcbe7 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 08:49:45 -0400 Subject: [PATCH 07/24] Fixing some jsdocs --- src/server/saved_objects/service/saved_objects_client.js | 1 + .../saved_objects_client/spaces_saved_objects_client.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/server/saved_objects/service/saved_objects_client.js b/src/server/saved_objects/service/saved_objects_client.js index a2fb118432835..a88ebe9439017 100644 --- a/src/server/saved_objects/service/saved_objects_client.js +++ b/src/server/saved_objects/service/saved_objects_client.js @@ -115,6 +115,7 @@ export class SavedObjectsClient { * @param {array} objects - [{ type, id, attributes, extraDocumentProperties }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] - overwrites existing documents + * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ async bulkCreate(objects, options = {}, namespace) { diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js index 17537d87509c0..54790095f23d9 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js @@ -27,6 +27,7 @@ export class SpacesSavedObjectsClient { * @param {object} [options={}] * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] + * @param {string} [namespace] * @returns {promise} - { id, type, version, attributes } */ async create(type, attributes = {}, options = {}, namespace) { @@ -40,9 +41,10 @@ export class SpacesSavedObjectsClient { /** * Creates multiple documents at once * - * @param {array} objects - [{ type, id, attributes, namespace }] + * @param {array} objects - [{ type, id, attributes, extraDocumentProperties }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] - overwrites existing documents + * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ async bulkCreate(objects, options = {}, namespace) { @@ -58,6 +60,7 @@ export class SpacesSavedObjectsClient { * * @param {string} type * @param {string} id + * @param {string} [namespace] * @returns {promise} */ async delete(type, id, namespace) { @@ -80,6 +83,7 @@ export class SpacesSavedObjectsClient { * @property {string} [options.sortField] * @property {string} [options.sortOrder] * @property {Array} [options.fields] + * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ async find(options = {}, namespace) { @@ -94,6 +98,7 @@ export class SpacesSavedObjectsClient { * Returns an array of objects by id * * @param {array} objects - an array ids, or an array of objects containing id and optionally type + * @param {string} [namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -115,6 +120,7 @@ export class SpacesSavedObjectsClient { * * @param {string} type * @param {string} id + * @param {string} [namespace] * @returns {promise} - { id, type, version, attributes } */ async get(type, id, namespace) { @@ -132,6 +138,7 @@ export class SpacesSavedObjectsClient { * @param {string} id * @param {object} [options={}] * @property {integer} options.version - ensures version matches that of persisted object + * @param {string} [namespace] * @returns {promise} */ async update(type, id, attributes, options = {}, namespace) { From 5b937bb8d6016c10878678bba9fb2aea45fcdd8b Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 08:50:32 -0400 Subject: [PATCH 08/24] Removing unused type parameter --- x-pack/plugins/spaces/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js index 47ba71d2949f6..1c7627411462f 100644 --- a/x-pack/plugins/spaces/index.js +++ b/x-pack/plugins/spaces/index.js @@ -84,9 +84,9 @@ export const spaces = (kibana) => new kibana.Plugin({ const spacesService = createSpacesService(server); server.decorate('server', 'spaces', spacesService); - const { addScopedSavedObjectsClientWrapperFactory, types } = server.savedObjects; + const { addScopedSavedObjectsClientWrapperFactory } = server.savedObjects; addScopedSavedObjectsClientWrapperFactory( - spacesSavedObjectsClientWrapperFactory(spacesService, types) + spacesSavedObjectsClientWrapperFactory(spacesService) ); initSpacesApi(server); From 709dffd193f4f2a98a87a3ab8a6a509871d1a635 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 08:51:37 -0400 Subject: [PATCH 09/24] Another stray console.log... --- src/server/saved_objects/service/lib/repository.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index 3b86ff19007f7..26dccab947108 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -321,7 +321,6 @@ export class SavedObjectsRepository { * ]) */ async bulkGet(objects = [], namespace) { - console.log('repository.bulkGet', { objects, namespace }); if (objects.length === 0) { return { saved_objects: [] }; } From ff087005fc5c20acdb6628ca8b9f8eed335a2faa Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 09:42:59 -0400 Subject: [PATCH 10/24] Fixing repository provider test --- .../service/lib/repository_provider.test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/server/saved_objects/service/lib/repository_provider.test.js b/src/server/saved_objects/service/lib/repository_provider.test.js index bc39fcecf9384..58a5313dab24f 100644 --- a/src/server/saved_objects/service/lib/repository_provider.test.js +++ b/src/server/saved_objects/service/lib/repository_provider.test.js @@ -41,22 +41,26 @@ test('creates a valid Repository', async () => { } } }, + schema: { + isNamespaceAgnostic: jest.fn(), + }, onBeforeWrite: jest.fn() }; const provider = new SavedObjectsRepositoryProvider(properties); const callCluster = jest.fn().mockReturnValue({ - _id: 'new' + _id: 'ns:foo:new' }); const repository = provider.getRepository(callCluster); - await repository.create('foo', {}); + await repository.create('foo', {}, {}, 'ns'); expect(callCluster).toHaveBeenCalledTimes(1); + expect(properties.schema.isNamespaceAgnostic).toHaveBeenCalled(); expect(properties.onBeforeWrite).toHaveBeenCalledTimes(1); expect(callCluster).toHaveBeenCalledWith('index', expect.objectContaining({ index: properties.index })); -}); \ No newline at end of file +}); From 57487afca8f52614a7cfe2b79340594c2b3d382c Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 13:05:29 -0400 Subject: [PATCH 11/24] Fixing repository tests --- .../service/lib/repository.test.js | 522 ++++++++++++------ .../service/lib/trim_id_prefix.js | 2 +- 2 files changed, 357 insertions(+), 167 deletions(-) diff --git a/src/server/saved_objects/service/lib/repository.test.js b/src/server/saved_objects/service/lib/repository.test.js index 2b84e773e0276..e7ac574d1aadd 100644 --- a/src/server/saved_objects/service/lib/repository.test.js +++ b/src/server/saved_objects/service/lib/repository.test.js @@ -36,9 +36,9 @@ describe('SavedObjectsRepository', () => { let savedObjectsRepository; const mockTimestamp = '2017-08-14T15:49:14.886Z'; const mockTimestampFields = { updated_at: mockTimestamp }; - const searchResults = { + const noNamespaceSearchResults = { hits: { - total: 3, + total: 4, hits: [{ _index: '.kibana', _type: 'doc', @@ -80,6 +80,81 @@ describe('SavedObjectsRepository', () => { notExpandable: true } } + }, { + _index: '.kibana', + _type: 'doc', + _id: 'no-ns-type:something', + _score: 1, + _source: { + type: 'no-ns-type', + ...mockTimestampFields, + 'no-ns-type': { + name: 'bar', + } + } + }] + } + }; + + const namespacedSearchResults = { + hits: { + total: 4, + hits: [{ + _index: '.kibana', + _type: 'doc', + _id: 'foo-namespace:index-pattern:logstash-*', + _score: 1, + _source: { + namespace: 'foo-namespace', + type: 'index-pattern', + ...mockTimestampFields, + 'index-pattern': { + title: 'logstash-*', + timeFieldName: '@timestamp', + notExpandable: true + } + } + }, { + _index: '.kibana', + _type: 'doc', + _id: 'foo-namespace:config:6.0.0-alpha1', + _score: 1, + _source: { + namespace: 'foo-namespace', + type: 'config', + ...mockTimestampFields, + config: { + buildNum: 8467, + defaultIndex: 'logstash-*' + } + } + }, { + _index: '.kibana', + _type: 'doc', + _id: 'foo-namespace:index-pattern:stocks-*', + _score: 1, + _source: { + namespace: 'foo-namespace', + type: 'index-pattern', + ...mockTimestampFields, + 'index-pattern': { + title: 'stocks-*', + timeFieldName: '@timestamp', + notExpandable: true + } + } + }, { + _index: '.kibana', + _type: 'doc', + _id: 'no-ns-type:something', + _score: 1, + _source: { + type: 'no-ns-type', + ...mockTimestampFields, + 'no-ns-type': { + name: 'bar', + } + } }] } }; @@ -98,6 +173,10 @@ describe('SavedObjectsRepository', () => { } }; + const schema = { + isNamespaceAgnostic: type => type === 'no-ns-type', + }; + beforeEach(() => { callAdminCluster = sandbox.stub(); onBeforeWrite = sandbox.stub(); @@ -105,6 +184,7 @@ describe('SavedObjectsRepository', () => { savedObjectsRepository = new SavedObjectsRepository({ index: '.kibana-test', mappings, + schema, callCluster: callAdminCluster, onBeforeWrite }); @@ -120,17 +200,20 @@ describe('SavedObjectsRepository', () => { describe('#create', () => { beforeEach(() => { - callAdminCluster.returns(Promise.resolve({ + callAdminCluster.callsFake((method, params) => ({ _type: 'doc', - _id: 'index-pattern:logstash-*', + _id: params.id, _version: 2 })); }); it('formats Elasticsearch response', async () => { - const response = await savedObjectsRepository.create('index-pattern', { - title: 'Logstash' - }); + const response = await savedObjectsRepository.create('index-pattern', + { + title: 'Logstash' + }, { + id: 'logstash-*' + }); expect(response).toEqual({ type: 'index-pattern', @@ -193,25 +276,23 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledOnce(onBeforeWrite); }); - it('appends extraDocumentProperties to the document', async () => { + it('prepends namespace to the id and adds namespace to body when providing namespace for namespaced type', async () => { await savedObjectsRepository.create('index-pattern', { title: 'Logstash' }, { - extraDocumentProperties: { - myExtraProp: 'myExtraValue', - myOtherExtraProp: true, - } - } + id: 'foo-id' + }, + 'foo-namespace', ); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ + id: `foo-namespace:index-pattern:foo-id`, body: { [`index-pattern`]: { title: 'Logstash' }, - myExtraProp: 'myExtraValue', - myOtherExtraProp: true, + namespace: 'foo-namespace', type: 'index-pattern', updated_at: '2017-08-14T15:49:14.886Z' } @@ -220,29 +301,21 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledOnce(onBeforeWrite); }); - it('does not allow extraDocumentProperties to overwrite existing properties', async () => { + it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { await savedObjectsRepository.create('index-pattern', { title: 'Logstash' }, { - extraDocumentProperties: { - myExtraProp: 'myExtraValue', - myOtherExtraProp: true, - updated_at: 'should_not_be_used', - 'index-pattern': { - title: 'should_not_be_used' - } - } + id: 'foo-id' } ); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ + id: `index-pattern:foo-id`, body: { [`index-pattern`]: { title: 'Logstash' }, - myExtraProp: 'myExtraValue', - myOtherExtraProp: true, type: 'index-pattern', updated_at: '2017-08-14T15:49:14.886Z' } @@ -250,6 +323,29 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledOnce(onBeforeWrite); }); + + it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { + await savedObjectsRepository.create('no-ns-type', + { + title: 'Logstash' + }, + { + id: 'foo-id' + } + ); + + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ + id: `no-ns-type:foo-id`, + body: { + [`no-ns-type`]: { title: 'Logstash' }, + type: 'no-ns-type', + updated_at: '2017-08-14T15:49:14.886Z' + } + })); + + sinon.assert.calledOnce(onBeforeWrite); + }); }); describe('#bulkCreate', () => { @@ -389,67 +485,71 @@ describe('SavedObjectsRepository', () => { }); }); - it('appends extraDocumentProperties to each created object', async () => { + it('prepends namespace to the id and adds namespace to body when providing namespace for namespaced type', async () => { callAdminCluster.returns({ items: [] }); await savedObjectsRepository.bulkCreate( [ - { type: 'config', id: 'one', attributes: { title: 'Test One' }, extraDocumentProperties: { extraConfigValue: true } }, - { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' }, extraDocumentProperties: { extraIndexValue: true } } - ]); + { type: 'config', id: 'one', attributes: { title: 'Test One' } }, + { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } + ], + {}, + 'foo-namespace' + ); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ body: [ - { create: { _type: 'doc', _id: 'config:one' } }, - { type: 'config', ...mockTimestampFields, config: { title: 'Test One' }, extraConfigValue: true }, - { create: { _type: 'doc', _id: 'index-pattern:two' } }, - { type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two' }, extraIndexValue: true } + { create: { _type: 'doc', _id: 'foo-namespace:config:one' } }, + { namespace: 'foo-namespace', type: 'config', ...mockTimestampFields, config: { title: 'Test One' } }, + { create: { _type: 'doc', _id: 'foo-namespace:index-pattern:two' } }, + { namespace: 'foo-namespace', type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two' } } ] })); sinon.assert.calledOnce(onBeforeWrite); }); - it('does not allow extraDocumentProperties to overwrite existing properties', async () => { - + it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { callAdminCluster.returns({ items: [] }); - const extraDocumentProperties = { - extraProp: 'extraVal', - updated_at: 'should_not_be_used', - }; - const configExtraDocumentProperties = { - ...extraDocumentProperties, - 'config': { newIgnoredProp: 'should_not_be_used' } - }; - const indexPatternExtraDocumentProperties = { - ...extraDocumentProperties, - 'index-pattern': { title: 'should_not_be_used', newIgnoredProp: 'should_not_be_used' } - }; - await savedObjectsRepository.bulkCreate( - [{ - type: 'config', - id: 'one', - attributes: { title: 'Test One' }, - extraDocumentProperties: configExtraDocumentProperties - }, - { - type: 'index-pattern', - id: 'two', - attributes: { title: 'Test Two' }, - extraDocumentProperties: indexPatternExtraDocumentProperties - }] + [ + { type: 'config', id: 'one', attributes: { title: 'Test One' } }, + { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } + ], + {} ); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ body: [ { create: { _type: 'doc', _id: 'config:one' } }, - { type: 'config', ...mockTimestampFields, config: { title: 'Test One' }, extraProp: 'extraVal' }, + { type: 'config', ...mockTimestampFields, config: { title: 'Test One' } }, { create: { _type: 'doc', _id: 'index-pattern:two' } }, - { type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two' }, extraProp: 'extraVal' } + { type: 'index-pattern', ...mockTimestampFields, 'index-pattern': { title: 'Test Two' } } + ] + })); + + sinon.assert.calledOnce(onBeforeWrite); + }); + + it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { + callAdminCluster.returns({ items: [] }); + + await savedObjectsRepository.bulkCreate( + [ + { type: 'no-ns-type', id: 'one', attributes: { title: 'Test One' } }, + ], + {}, + 'foo-namespace' + ); + + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ + body: [ + { create: { _type: 'doc', _id: 'no-ns-type:one' } }, + { type: 'no-ns-type', ...mockTimestampFields, 'no-ns-type': { title: 'Test One' } }, ] })); @@ -472,7 +572,25 @@ describe('SavedObjectsRepository', () => { } }); - it('passes the parameters to callAdminCluster', async () => { + it(`prepends namespace to the id when providing namespace for namespaced type`, async () => { + callAdminCluster.returns({ + result: 'deleted' + }); + await savedObjectsRepository.delete('index-pattern', 'logstash-*', 'foo-namespace'); + + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, 'delete', { + type: 'doc', + id: 'foo-namespace:index-pattern:logstash-*', + refresh: 'wait_for', + index: '.kibana-test', + ignore: [404], + }); + + sinon.assert.calledOnce(onBeforeWrite); + }); + + it(`doesn't prepend namespace to the id when providing no namespace for namespaced type`, async () => { callAdminCluster.returns({ result: 'deleted' }); @@ -489,14 +607,29 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledOnce(onBeforeWrite); }); - }); - describe('#find', () => { - beforeEach(() => { - callAdminCluster.returns(searchResults); + it(`doesn't prepend namespace to the id when providing namespace for namespace agnostic type`, async () => { + callAdminCluster.returns({ + result: 'deleted' + }); + await savedObjectsRepository.delete('no-ns-type', 'logstash-*', 'foo-namespace'); + + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, 'delete', { + type: 'doc', + id: 'no-ns-type:logstash-*', + refresh: 'wait_for', + index: '.kibana-test', + ignore: [404], + }); + + sinon.assert.calledOnce(onBeforeWrite); }); + }); + describe('#find', () => { it('requires searchFields be an array if defined', async () => { + callAdminCluster.returns(noNamespaceSearchResults); try { await savedObjectsRepository.find({ searchFields: 'string' }); throw new Error('expected find() to reject'); @@ -508,6 +641,7 @@ describe('SavedObjectsRepository', () => { }); it('requires fields be an array if defined', async () => { + callAdminCluster.returns(noNamespaceSearchResults); try { await savedObjectsRepository.find({ fields: 'string' }); throw new Error('expected find() to reject'); @@ -519,6 +653,7 @@ describe('SavedObjectsRepository', () => { }); it('requires filters to be an array if defined', async () => { + callAdminCluster.returns(noNamespaceSearchResults); try { await savedObjectsRepository.find({ filters: 'string' }); throw new Error('expected find() to reject'); @@ -529,8 +664,10 @@ describe('SavedObjectsRepository', () => { } }); - it('passes mappings, search, searchFields, type, sortField, filters, and sortOrder to getSearchDsl', async () => { + it('passes mappings, schema, namespace, search, searchFields, type, sortField, filters, and sortOrder to getSearchDsl', async () => { + callAdminCluster.returns(namespacedSearchResults); const relevantOpts = { + namespace: 'foo-namespace', search: 'foo*', searchFields: ['foo'], type: 'bar', @@ -539,12 +676,13 @@ describe('SavedObjectsRepository', () => { filters: [{ bool: {} }], }; - await savedObjectsRepository.find(relevantOpts); + await savedObjectsRepository.find(relevantOpts, 'foo-namespace'); sinon.assert.calledOnce(getSearchDsl); - sinon.assert.calledWithExactly(getSearchDsl, mappings, relevantOpts); + sinon.assert.calledWithExactly(getSearchDsl, mappings, schema, relevantOpts); }); it('merges output of getSearchDsl into es request body', async () => { + callAdminCluster.returns(noNamespaceSearchResults); getSearchDsl.returns({ query: 1, aggregations: 2 }); await savedObjectsRepository.find(); sinon.assert.calledOnce(callAdminCluster); @@ -557,17 +695,38 @@ describe('SavedObjectsRepository', () => { })); }); - it('formats Elasticsearch response', async () => { - const count = searchResults.hits.hits.length; + it('formats Elasticsearch response when there is no namespace', async () => { + callAdminCluster.returns(noNamespaceSearchResults); + const count = noNamespaceSearchResults.hits.hits.length; const response = await savedObjectsRepository.find(); expect(response.total).toBe(count); expect(response.saved_objects).toHaveLength(count); - searchResults.hits.hits.forEach((doc, i) => { + noNamespaceSearchResults.hits.hits.forEach((doc, i) => { expect(response.saved_objects[i]).toEqual({ - id: doc._id.replace(/(index-pattern|config)\:/, ''), + id: doc._id.replace(/(index-pattern|config|no-ns-type)\:/, ''), + type: doc._source.type, + ...mockTimestampFields, + version: doc._version, + attributes: doc._source[doc._source.type] + }); + }); + }); + + it('formats Elasticsearch response when there is a namespace', async () => { + callAdminCluster.returns(namespacedSearchResults); + const count = namespacedSearchResults.hits.hits.length; + + const response = await savedObjectsRepository.find({}, 'foo-namespace'); + + expect(response.total).toBe(count); + expect(response.saved_objects).toHaveLength(count); + + namespacedSearchResults.hits.hits.forEach((doc, i) => { + expect(response.saved_objects[i]).toEqual({ + id: doc._id.replace(/(foo-namespace\:)?(index-pattern|config|no-ns-type)\:/, ''), type: doc._source.type, ...mockTimestampFields, version: doc._version, @@ -577,6 +736,7 @@ describe('SavedObjectsRepository', () => { }); it('accepts per_page/page', async () => { + callAdminCluster.returns(noNamespaceSearchResults); await savedObjectsRepository.find({ perPage: 10, page: 6 }); sinon.assert.calledOnce(callAdminCluster); @@ -589,6 +749,7 @@ describe('SavedObjectsRepository', () => { }); it('can filter by fields', async () => { + callAdminCluster.returns(noNamespaceSearchResults); await savedObjectsRepository.find({ fields: ['title'] }); sinon.assert.calledOnce(callAdminCluster); @@ -603,23 +764,36 @@ describe('SavedObjectsRepository', () => { }); describe('#get', () => { - beforeEach(() => { - callAdminCluster.returns(Promise.resolve({ - _id: 'index-pattern:logstash-*', - _type: 'doc', - _version: 2, - _source: { - type: 'index-pattern', - specialProperty: 'specialValue', - ...mockTimestampFields, - 'index-pattern': { - title: 'Testing' - } + const noNamespaceResult = { + _id: 'index-pattern:logstash-*', + _type: 'doc', + _version: 2, + _source: { + type: 'index-pattern', + specialProperty: 'specialValue', + ...mockTimestampFields, + 'index-pattern': { + title: 'Testing' } - })); - }); + } + }; + const namespacedResult = { + _id: 'foo-namespace:index-pattern:logstash-*', + _type: 'doc', + _version: 2, + _source: { + namespace: 'foo-namespace', + type: 'index-pattern', + specialProperty: 'specialValue', + ...mockTimestampFields, + 'index-pattern': { + title: 'Testing' + } + } + }; - it('formats Elasticsearch response', async () => { + it('formats Elasticsearch response when there is no namespace', async () => { + callAdminCluster.returns(Promise.resolve(noNamespaceResult)); const response = await savedObjectsRepository.get('index-pattern', 'logstash-*'); sinon.assert.notCalled(onBeforeWrite); expect(response).toEqual({ @@ -633,7 +807,36 @@ describe('SavedObjectsRepository', () => { }); }); - it('prepends type to the id', async () => { + it('formats Elasticsearch response when there is a namespace', async () => { + callAdminCluster.returns(Promise.resolve(namespacedResult)); + const response = await savedObjectsRepository.get('index-pattern', 'logstash-*', 'foo-namespace'); + sinon.assert.notCalled(onBeforeWrite); + expect(response).toEqual({ + id: 'logstash-*', + namespace: 'foo-namespace', + type: 'index-pattern', + updated_at: mockTimestamp, + version: 2, + attributes: { + title: 'Testing' + } + }); + }); + + it('prepends namespace and type to the id when providing namespace for namespaced type', async () => { + callAdminCluster.returns(Promise.resolve(namespacedResult)); + await savedObjectsRepository.get('index-pattern', 'logstash-*', 'foo-namespace'); + + sinon.assert.notCalled(onBeforeWrite); + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ + id: 'foo-namespace:index-pattern:logstash-*', + type: 'doc' + })); + }); + + it(`only prepends type to the id when providing no namespace for namespaced type`, async () => { + callAdminCluster.returns(Promise.resolve(noNamespaceResult)); await savedObjectsRepository.get('index-pattern', 'logstash-*'); sinon.assert.notCalled(onBeforeWrite); @@ -644,32 +847,27 @@ describe('SavedObjectsRepository', () => { })); }); - it('includes the requested extraDocumentProperties in the response for the requested object', async () => { - const response = await savedObjectsRepository.get('index-pattern', 'logstash-*', { - extraDocumentProperties: ['specialProperty', 'undefinedProperty'] - }); + it(`doesn't prepend namespace to the id when providing namespace for namespace agnostic type`, async () => { + callAdminCluster.returns(Promise.resolve(namespacedResult)); + await savedObjectsRepository.get('no-ns-type', 'logstash-*', 'foo-namespace'); - expect(response).toEqual({ - id: 'logstash-*', - type: 'index-pattern', - updated_at: mockTimestamp, - version: 2, - specialProperty: 'specialValue', - undefinedProperty: undefined, - attributes: { - title: 'Testing' - } - }); + sinon.assert.notCalled(onBeforeWrite); + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ + id: 'no-ns-type:logstash-*', + type: 'doc' + })); }); }); describe('#bulkGet', () => { - it('accepts a array of mixed type and ids', async () => { + it('prepends type to id when getting objects when there is no namespace', async () => { callAdminCluster.returns({ docs: [] }); await savedObjectsRepository.bulkGet([ { id: 'one', type: 'config' }, - { id: 'two', type: 'index-pattern' } + { id: 'two', type: 'index-pattern' }, + { id: 'three', type: 'no-ns-type' }, ]); sinon.assert.calledOnce(callAdminCluster); @@ -677,7 +875,31 @@ describe('SavedObjectsRepository', () => { body: { docs: [ { _type: 'doc', _id: 'config:one' }, - { _type: 'doc', _id: 'index-pattern:two' } + { _type: 'doc', _id: 'index-pattern:two' }, + { _type: 'doc', _id: 'no-ns-type:three' }, + ] + } + })); + + sinon.assert.notCalled(onBeforeWrite); + }); + + it('prepends namespace and type appropriately to id when getting objects when there is a namespace', async () => { + callAdminCluster.returns({ docs: [] }); + + await savedObjectsRepository.bulkGet([ + { id: 'one', type: 'config' }, + { id: 'two', type: 'index-pattern' }, + { id: 'three', type: 'no-ns-type' }, + ], 'foo-namespace'); + + sinon.assert.calledOnce(callAdminCluster); + sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ + body: { + docs: [ + { _type: 'doc', _id: 'foo-namespace:config:one' }, + { _type: 'doc', _id: 'foo-namespace:index-pattern:two' }, + { _type: 'doc', _id: 'no-ns-type:three' }, ] } })); @@ -732,34 +954,30 @@ describe('SavedObjectsRepository', () => { }); }); - it('includes the requested extraDocumentProperties in the response for each requested object', async () => { + it(`includes namespace if it's specified on the document source`, async () => { callAdminCluster.returns(Promise.resolve({ docs: [{ _type: 'doc', - _id: 'config:good', + _id: 'foo-namespace:config:one', found: true, _version: 2, _source: { + namespace: 'foo-namespace', ...mockTimestampFields, type: 'config', specialProperty: 'specialValue', config: { title: 'Test' } } }, { - _type: 'doc', - _id: 'config:bad', - found: false - }, { - _id: 'index-pattern:logstash-*', + _id: 'no-ns-type:two', _type: 'doc', found: true, _version: 2, _source: { - type: 'index-pattern', - specialProperty: 'anotherSpecialValue', + type: 'no-ns-type', ...mockTimestampFields, - 'index-pattern': { - title: 'Testing' + 'no-ns-type': { + name: 'bar' } } }] @@ -767,41 +985,29 @@ describe('SavedObjectsRepository', () => { const { saved_objects: savedObjects } = await savedObjectsRepository.bulkGet( [ - { id: 'good', type: 'config' }, - { id: 'bad', type: 'config' }, - { id: 'logstash-*', type: 'index-pattern' } - ], { - extraDocumentProperties: ['specialProperty', 'undefinedProperty'] - } + { id: 'one', type: 'config' }, + { id: 'two', type: 'no-ns-type' } + ] ); - expect(savedObjects).toHaveLength(3); + expect(savedObjects).toHaveLength(2); expect(savedObjects[0]).toEqual({ - id: 'good', + id: 'one', + namespace: 'foo-namespace', type: 'config', ...mockTimestampFields, version: 2, - specialProperty: 'specialValue', - undefinedProperty: undefined, attributes: { title: 'Test' } }); expect(savedObjects[1]).toEqual({ - id: 'bad', - type: 'config', - error: { statusCode: 404, message: 'Not found' } - }); - - expect(savedObjects[2]).toEqual({ - id: 'logstash-*', - type: 'index-pattern', + id: 'two', + type: 'no-ns-type', ...mockTimestampFields, version: 2, - specialProperty: 'anotherSpecialValue', - undefinedProperty: undefined, attributes: { - title: 'Testing' + name: 'bar' } }); }); @@ -847,8 +1053,8 @@ describe('SavedObjectsRepository', () => { })); }); - it('passes the parameters to callAdminCluster', async () => { - await savedObjectsRepository.update('index-pattern', 'logstash-*', { title: 'Testing' }); + it('prepends namespace to the id and adds namespace to body when providing namespace for namespaced type', async () => { + await savedObjectsRepository.update('index-pattern', 'logstash-*', { title: 'Testing' }, 'foo-namespace'); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'update', { @@ -866,13 +1072,8 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledOnce(onBeforeWrite); }); - it('updates the document including all provided extraDocumentProperties', async () => { - await savedObjectsRepository.update( - 'index-pattern', - 'logstash-*', - { title: 'Testing' }, - { extraDocumentProperties: { extraProp: 'extraVal' } } - ); + it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { + await savedObjectsRepository.update('index-pattern', 'logstash-*', { title: 'Testing' }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'update', { @@ -880,7 +1081,7 @@ describe('SavedObjectsRepository', () => { id: 'index-pattern:logstash-*', version: undefined, body: { - doc: { updated_at: mockTimestamp, extraProp: 'extraVal', 'index-pattern': { title: 'Testing' } } + doc: { updated_at: mockTimestamp, 'index-pattern': { title: 'Testing' } } }, ignore: [404], refresh: 'wait_for', @@ -890,27 +1091,16 @@ describe('SavedObjectsRepository', () => { sinon.assert.calledOnce(onBeforeWrite); }); - it('does not allow extraDocumentProperties to overwrite existing properties', async () => { - await savedObjectsRepository.update( - 'index-pattern', - 'logstash-*', - { title: 'Testing' }, - { - extraDocumentProperties: { - extraProp: 'extraVal', - updated_at: 'should_not_be_used', - 'index-pattern': { title: 'should_not_be_used', newIgnoredProp: 'should_not_be_used' } - } - } - ); + it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { + await savedObjectsRepository.update('no-ns-type', 'foo', { name: 'bar' }, 'foo-namespace'); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'update', { type: 'doc', - id: 'index-pattern:logstash-*', + id: 'no-ns-type:foo', version: undefined, body: { - doc: { updated_at: mockTimestamp, extraProp: 'extraVal', 'index-pattern': { title: 'Testing' } } + doc: { updated_at: mockTimestamp, 'no-ns-type': { name: 'bar' } } }, ignore: [404], refresh: 'wait_for', diff --git a/src/server/saved_objects/service/lib/trim_id_prefix.js b/src/server/saved_objects/service/lib/trim_id_prefix.js index 6691686225e5d..039ff0848159c 100644 --- a/src/server/saved_objects/service/lib/trim_id_prefix.js +++ b/src/server/saved_objects/service/lib/trim_id_prefix.js @@ -38,7 +38,7 @@ export function trimIdPrefix(schema, id, namespace, type) { const prefix = `${namespacePrefix}${type}:`; if (!id.startsWith(prefix)) { - throw new Error('Unable to trim id'); + throw new Error(`Unable to trim id ${id}`); } return id.slice(prefix.length); From f877510a5828bca8ae49f771a9d585bad6814612 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 13:23:28 -0400 Subject: [PATCH 12/24] No longer exposing the namespace in get and bulkGet --- .../saved_objects/service/lib/repository.js | 2 - .../service/lib/repository.test.js | 76 +------------------ .../apis/saved_objects/bulk_get.js | 3 +- .../apis/saved_objects/get.js | 8 -- .../saved_objects/lib/space_test_utils.js | 9 --- 5 files changed, 2 insertions(+), 96 deletions(-) diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index 26dccab947108..b16ef728b4b27 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -355,7 +355,6 @@ export class SavedObjectsRepository { type, ...time && { updated_at: time }, version: doc._version, - ...doc._source.namespace && { namespace: doc._source.namespace }, attributes: { ...doc._source[type], } @@ -396,7 +395,6 @@ export class SavedObjectsRepository { type, ...updatedAt && { updated_at: updatedAt }, version: response._version, - ...response._source.namespace && { namespace: response._source.namespace }, attributes: { ...response._source[type], } diff --git a/src/server/saved_objects/service/lib/repository.test.js b/src/server/saved_objects/service/lib/repository.test.js index e7ac574d1aadd..7794720ab2e3c 100644 --- a/src/server/saved_objects/service/lib/repository.test.js +++ b/src/server/saved_objects/service/lib/repository.test.js @@ -792,28 +792,12 @@ describe('SavedObjectsRepository', () => { } }; - it('formats Elasticsearch response when there is no namespace', async () => { - callAdminCluster.returns(Promise.resolve(noNamespaceResult)); - const response = await savedObjectsRepository.get('index-pattern', 'logstash-*'); - sinon.assert.notCalled(onBeforeWrite); - expect(response).toEqual({ - id: 'logstash-*', - type: 'index-pattern', - updated_at: mockTimestamp, - version: 2, - attributes: { - title: 'Testing' - } - }); - }); - - it('formats Elasticsearch response when there is a namespace', async () => { + it('formats Elasticsearch response', async () => { callAdminCluster.returns(Promise.resolve(namespacedResult)); const response = await savedObjectsRepository.get('index-pattern', 'logstash-*', 'foo-namespace'); sinon.assert.notCalled(onBeforeWrite); expect(response).toEqual({ id: 'logstash-*', - namespace: 'foo-namespace', type: 'index-pattern', updated_at: mockTimestamp, version: 2, @@ -953,64 +937,6 @@ describe('SavedObjectsRepository', () => { error: { statusCode: 404, message: 'Not found' } }); }); - - it(`includes namespace if it's specified on the document source`, async () => { - callAdminCluster.returns(Promise.resolve({ - docs: [{ - _type: 'doc', - _id: 'foo-namespace:config:one', - found: true, - _version: 2, - _source: { - namespace: 'foo-namespace', - ...mockTimestampFields, - type: 'config', - specialProperty: 'specialValue', - config: { title: 'Test' } - } - }, { - _id: 'no-ns-type:two', - _type: 'doc', - found: true, - _version: 2, - _source: { - type: 'no-ns-type', - ...mockTimestampFields, - 'no-ns-type': { - name: 'bar' - } - } - }] - })); - - const { saved_objects: savedObjects } = await savedObjectsRepository.bulkGet( - [ - { id: 'one', type: 'config' }, - { id: 'two', type: 'no-ns-type' } - ] - ); - - expect(savedObjects).toHaveLength(2); - - expect(savedObjects[0]).toEqual({ - id: 'one', - namespace: 'foo-namespace', - type: 'config', - ...mockTimestampFields, - version: 2, - attributes: { title: 'Test' } - }); - - expect(savedObjects[1]).toEqual({ - id: 'two', - type: 'no-ns-type', - ...mockTimestampFields, - version: 2, - attributes: { - name: 'bar' - } - }); - }); }); describe('#update', () => { diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/bulk_get.js b/x-pack/test/spaces_api_integration/apis/saved_objects/bulk_get.js index d797c53f60a45..517aab85312fa 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/bulk_get.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/bulk_get.js @@ -6,7 +6,7 @@ import expect from 'expect.js'; import { SPACES } from './lib/spaces'; -import { getIdPrefix, getUrlPrefix, getExpectedNamespaceProperty } from './lib/space_test_utils'; +import { getIdPrefix, getUrlPrefix } from './lib/space_test_utils'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -73,7 +73,6 @@ export default function ({ getService }) { type: 'visualization', updated_at: '2017-09-21T18:51:23.794Z', version: resp.body.saved_objects[0].version, - ...getExpectedNamespaceProperty(spaceId), attributes: { title: 'Count of requests', description: '', diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/get.js b/x-pack/test/spaces_api_integration/apis/saved_objects/get.js index bef423c0cfdc5..0369bffba26e5 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/get.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/get.js @@ -15,10 +15,6 @@ export default function ({ getService }) { describe('get', () => { const expectResults = (spaceId) => () => (resp) => { - - // The default space does not assign a space id. - const expectedSpaceId = spaceId === 'default' ? undefined : spaceId; - const expectedBody = { id: `${getIdPrefix(spaceId)}dd7caf20-9efd-11e7-acb3-3dab96693fab`, type: 'visualization', @@ -36,10 +32,6 @@ export default function ({ getService }) { } }; - if (expectedSpaceId) { - expectedBody.namespace = expectedSpaceId; - } - expect(resp.body).to.eql(expectedBody); }; diff --git a/x-pack/test/spaces_api_integration/apis/saved_objects/lib/space_test_utils.js b/x-pack/test/spaces_api_integration/apis/saved_objects/lib/space_test_utils.js index 53a0da25791b5..5d7f40f9c54b1 100644 --- a/x-pack/test/spaces_api_integration/apis/saved_objects/lib/space_test_utils.js +++ b/x-pack/test/spaces_api_integration/apis/saved_objects/lib/space_test_utils.js @@ -13,12 +13,3 @@ export function getUrlPrefix(spaceId) { export function getIdPrefix(spaceId) { return spaceId === DEFAULT_SPACE_ID ? '' : `${spaceId}-`; } - -export function getExpectedNamespaceProperty(spaceId) { - if (spaceId === DEFAULT_SPACE_ID) { - return {}; - } - return { - namespace: spaceId - }; -} From c9059e7eb87b76f2c2e63ff52a7aa5b9eb8c6a69 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 13:29:14 -0400 Subject: [PATCH 13/24] Fixing SavedObjectClient tests, using more Symbols... It ends up that two different instances of {} are considered to be equal by jest's .toHaveBeenCalledWith, so for these white-box tests we're just using Symbols... --- .../service/saved_objects_client.test.js | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/server/saved_objects/service/saved_objects_client.test.js b/src/server/saved_objects/service/saved_objects_client.test.js index 56127023dd1fe..3fe1f46a994fd 100644 --- a/src/server/saved_objects/service/saved_objects_client.test.js +++ b/src/server/saved_objects/service/saved_objects_client.test.js @@ -26,12 +26,13 @@ test(`#create`, async () => { }; const client = new SavedObjectsClient(mockRepository); - const type = 'foo'; - const attributes = {}; - const options = {}; - const result = await client.create(type, attributes, options); + const type = Symbol(); + const attributes = Symbol(); + const options = Symbol(); + const namespace = Symbol(); + const result = await client.create(type, attributes, options, namespace); - expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options); + expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options, namespace); expect(result).toBe(returnValue); }); @@ -42,11 +43,12 @@ test(`#bulkCreate`, async () => { }; const client = new SavedObjectsClient(mockRepository); - const objects = []; - const options = {}; - const result = await client.bulkCreate(objects, options); + const objects = Symbol(); + const options = Symbol(); + const namespace = Symbol(); + const result = await client.bulkCreate(objects, options, namespace); - expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options); + expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options, namespace); expect(result).toBe(returnValue); }); @@ -57,11 +59,12 @@ test(`#delete`, async () => { }; const client = new SavedObjectsClient(mockRepository); - const type = 'foo'; - const id = 1; - const result = await client.delete(type, id); + const type = Symbol(); + const id = Symbol(); + const namespace = Symbol(); + const result = await client.delete(type, id, namespace); - expect(mockRepository.delete).toHaveBeenCalledWith(type, id); + expect(mockRepository.delete).toHaveBeenCalledWith(type, id, namespace); expect(result).toBe(returnValue); }); @@ -72,10 +75,11 @@ test(`#find`, async () => { }; const client = new SavedObjectsClient(mockRepository); - const options = {}; - const result = await client.find(options); + const options = Symbol(); + const namespace = Symbol(); + const result = await client.find(options, namespace); - expect(mockRepository.find).toHaveBeenCalledWith(options); + expect(mockRepository.find).toHaveBeenCalledWith(options, namespace); expect(result).toBe(returnValue); }); @@ -86,11 +90,11 @@ test(`#bulkGet`, async () => { }; const client = new SavedObjectsClient(mockRepository); - const objects = {}; - const options = Symbol(); - const result = await client.bulkGet(objects, options); + const objects = Symbol(); + const namespace = Symbol(); + const result = await client.bulkGet(objects, namespace); - expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, options); + expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, namespace); expect(result).toBe(returnValue); }); @@ -101,12 +105,12 @@ test(`#get`, async () => { }; const client = new SavedObjectsClient(mockRepository); - const type = 'foo'; - const id = 1; - const options = Symbol(); - const result = await client.get(type, id, options); + const type = Symbol(); + const id = Symbol(); + const namespace = Symbol(); + const result = await client.get(type, id, namespace); - expect(mockRepository.get).toHaveBeenCalledWith(type, id, options); + expect(mockRepository.get).toHaveBeenCalledWith(type, id, namespace); expect(result).toBe(returnValue); }); @@ -117,12 +121,13 @@ test(`#update`, async () => { }; const client = new SavedObjectsClient(mockRepository); - const type = 'foo'; - const id = 1; - const attributes = {}; - const options = {}; - const result = await client.update(type, id, attributes, options); + const type = Symbol(); + const id = Symbol(); + const attributes = Symbol(); + const options = Symbol(); + const namespace = Symbol(); + const result = await client.update(type, id, attributes, options, namespace); - expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options); + expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options, namespace); expect(result).toBe(returnValue); }); From 2c4f13565e64c4ac700a64e9b84f0e13e3afe5f9 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 13:36:07 -0400 Subject: [PATCH 14/24] Fixing getSearchDsl tests --- .../service/lib/search_dsl/search_dsl.test.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js index 6ed40a7929dcf..5cb88f6bc92a4 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js @@ -29,7 +29,7 @@ describe('getSearchDsl', () => { describe('validation', () => { it('throws when sortField is passed without type', () => { expect(() => { - getSearchDsl({}, { + getSearchDsl({}, {}, { type: undefined, sortField: 'title' }); @@ -37,7 +37,7 @@ describe('getSearchDsl', () => { }); it('throws when sortOrder without sortField', () => { expect(() => { - getSearchDsl({}, { + getSearchDsl({}, {}, { type: 'foo', sortOrder: 'desc' }); @@ -46,21 +46,25 @@ describe('getSearchDsl', () => { }); describe('passes control', () => { - it('passes (mappings, type, search, searchFields, filters) to getQueryParams', () => { + it('passes (mappings, schema, namespace, type, search, searchFields, filters) to getQueryParams', () => { const spy = sandbox.spy(queryParamsNS, 'getQueryParams'); const mappings = { type: { properties: {} } }; + const schema = { isNamespaceAgnostic: () => {} }; const opts = { + namespace: 'foo-namespace', type: 'foo', search: 'bar', searchFields: ['baz'], filters: [{ bool: {} }], }; - getSearchDsl(mappings, opts); + getSearchDsl(mappings, schema, opts); sinon.assert.calledOnce(spy); sinon.assert.calledWithExactly( spy, mappings, + schema, + opts.namespace, opts.type, opts.search, opts.searchFields, @@ -71,13 +75,14 @@ describe('getSearchDsl', () => { it('passes (mappings, type, sortField, sortOrder) to getSortingParams', () => { const spy = sandbox.stub(sortParamsNS, 'getSortingParams').returns({}); const mappings = { type: { properties: {} } }; + const schema = { isNamespaceAgnostic: () => {} }; const opts = { type: 'foo', sortField: 'bar', sortOrder: 'baz' }; - getSearchDsl(mappings, opts); + getSearchDsl(mappings, schema, opts); sinon.assert.calledOnce(spy); sinon.assert.calledWithExactly( spy, From 636d58399bee1d03ebb7bfe8ad55a25d05b10259 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 24 Aug 2018 13:47:56 -0400 Subject: [PATCH 15/24] Removing filters, we don't use them anymore --- .../saved_objects/service/lib/repository.js | 7 ------- .../service/lib/repository.test.js | 15 +-------------- .../service/lib/search_dsl/query_params.js | 19 +++++++------------ .../service/lib/search_dsl/search_dsl.js | 7 +------ .../service/lib/search_dsl/search_dsl.test.js | 4 +--- .../service/saved_objects_client.js | 1 - .../spaces_saved_objects_client.js | 1 - 7 files changed, 10 insertions(+), 44 deletions(-) diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index b16ef728b4b27..c2a24539931e5 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -223,7 +223,6 @@ export class SavedObjectsRepository { * @property {string} [options.search] * @property {Array} [options.searchFields] - see Elasticsearch Simple Query String * Query field argument for more information - * @property {object} [options.filters] - ES Query filters to append * @property {integer} [options.page=1] * @property {integer} [options.perPage=20] * @property {string} [options.sortField] @@ -242,7 +241,6 @@ export class SavedObjectsRepository { sortField, sortOrder, fields, - filters, } = options; if (searchFields && !Array.isArray(searchFields)) { @@ -253,10 +251,6 @@ export class SavedObjectsRepository { throw new TypeError('options.searchFields must be an array'); } - if (filters && !Array.isArray(filters)) { - throw new TypeError('options.filters must be an array'); - } - const esOptions = { index: this._index, size: perPage, @@ -272,7 +266,6 @@ export class SavedObjectsRepository { type, sortField, sortOrder, - filters }) } }; diff --git a/src/server/saved_objects/service/lib/repository.test.js b/src/server/saved_objects/service/lib/repository.test.js index 7794720ab2e3c..d959262dac96f 100644 --- a/src/server/saved_objects/service/lib/repository.test.js +++ b/src/server/saved_objects/service/lib/repository.test.js @@ -652,19 +652,7 @@ describe('SavedObjectsRepository', () => { } }); - it('requires filters to be an array if defined', async () => { - callAdminCluster.returns(noNamespaceSearchResults); - try { - await savedObjectsRepository.find({ filters: 'string' }); - throw new Error('expected find() to reject'); - } catch (error) { - sinon.assert.notCalled(callAdminCluster); - sinon.assert.notCalled(onBeforeWrite); - expect(error.message).toMatch('must be an array'); - } - }); - - it('passes mappings, schema, namespace, search, searchFields, type, sortField, filters, and sortOrder to getSearchDsl', async () => { + it('passes mappings, schema, namespace, search, searchFields, type, sortField, and sortOrder to getSearchDsl', async () => { callAdminCluster.returns(namespacedSearchResults); const relevantOpts = { namespace: 'foo-namespace', @@ -673,7 +661,6 @@ describe('SavedObjectsRepository', () => { type: 'bar', sortField: 'name', sortOrder: 'desc', - filters: [{ bool: {} }], }; await savedObjectsRepository.find(relevantOpts, 'foo-namespace'); diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.js b/src/server/saved_objects/service/lib/search_dsl/query_params.js index 0f61b5eecacc2..55ad680ee7c26 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.js @@ -108,22 +108,19 @@ function getClauseForType(schema, namespace, type) { * @param {(string|Array)} type * @param {String} search * @param {Array} searchFields - * @param {Array} filters additional query filters * @return {Object} */ -export function getQueryParams(mappings, schema, namespace, type, search, searchFields, filters = []) { +export function getQueryParams(mappings, schema, namespace, type, search, searchFields) { const types = getTypes(mappings, type); const bool = { - filter: [...filters], + filter: [{ + bool: { + should: types.map(type => getClauseForType(schema, namespace, type)), + minimum_should_match: 1 + } + }], }; - bool.filter.push({ - bool: { - should: types.map(type => getClauseForType(schema, namespace, type)), - minimum_should_match: 1 - } - }); - if (search) { bool.must = [ { @@ -140,5 +137,3 @@ export function getQueryParams(mappings, schema, namespace, type, search, search return { query: { bool } }; } - - diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js index 1a0bc8e1c87c6..5bbe199a0e7e8 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.js @@ -30,7 +30,6 @@ export function getSearchDsl(mappings, schema, options = {}) { searchFields, sortField, sortOrder, - filters, } = options; if (!type && sortField) { @@ -41,12 +40,8 @@ export function getSearchDsl(mappings, schema, options = {}) { throw Boom.notAcceptable('sortOrder requires a sortField'); } - if (filters && !Array.isArray(filters)) { - throw Boom.notAcceptable('filters must be an array'); - } - return { - ...getQueryParams(mappings, schema, namespace, type, search, searchFields, filters), + ...getQueryParams(mappings, schema, namespace, type, search, searchFields), ...getSortingParams(mappings, type, sortField, sortOrder), }; } diff --git a/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js b/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js index 5cb88f6bc92a4..7acdb6d7bd6ed 100644 --- a/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js +++ b/src/server/saved_objects/service/lib/search_dsl/search_dsl.test.js @@ -46,7 +46,7 @@ describe('getSearchDsl', () => { }); describe('passes control', () => { - it('passes (mappings, schema, namespace, type, search, searchFields, filters) to getQueryParams', () => { + it('passes (mappings, schema, namespace, type, search, searchFields) to getQueryParams', () => { const spy = sandbox.spy(queryParamsNS, 'getQueryParams'); const mappings = { type: { properties: {} } }; const schema = { isNamespaceAgnostic: () => {} }; @@ -55,7 +55,6 @@ describe('getSearchDsl', () => { type: 'foo', search: 'bar', searchFields: ['baz'], - filters: [{ bool: {} }], }; getSearchDsl(mappings, schema, opts); @@ -68,7 +67,6 @@ describe('getSearchDsl', () => { opts.type, opts.search, opts.searchFields, - opts.filters, ); }); diff --git a/src/server/saved_objects/service/saved_objects_client.js b/src/server/saved_objects/service/saved_objects_client.js index a88ebe9439017..c7b4a81309434 100644 --- a/src/server/saved_objects/service/saved_objects_client.js +++ b/src/server/saved_objects/service/saved_objects_client.js @@ -140,7 +140,6 @@ export class SavedObjectsClient { * @property {string} [options.search] * @property {Array} [options.searchFields] - see Elasticsearch Simple Query String * Query field argument for more information - * @property {object} [options.filters] - ES Query filters to append * @property {integer} [options.page=1] * @property {integer} [options.perPage=20] * @property {string} [options.sortField] diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js index 54790095f23d9..4ef77e933d610 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js @@ -77,7 +77,6 @@ export class SpacesSavedObjectsClient { * @property {string} [options.search] * @property {Array} [options.searchFields] - see Elasticsearch Simple Query String * Query field argument for more information - * @property {object} [options.filters] - ES Query filters to append * @property {integer} [options.page=1] * @property {integer} [options.perPage=20] * @property {string} [options.sortField] From c05b369869febaf34897f677af9a8673ebad73f6 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 27 Aug 2018 10:31:56 -0400 Subject: [PATCH 16/24] Fixing query param tests --- .../service/lib/search_dsl/query_params.js | 32 +- .../lib/search_dsl/query_params.test.js | 467 +++++++++++++----- 2 files changed, 366 insertions(+), 133 deletions(-) diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.js b/src/server/saved_objects/service/lib/search_dsl/query_params.js index 55ad680ee7c26..5bcff743c1082 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.js @@ -68,36 +68,26 @@ function getFieldsForTypes(searchFields, types) { * @return {Object} */ function getClauseForType(schema, namespace, type) { - const bool = { - must: [] - }; - if (!type) { throw new Error(`type is required to build filter clause`); } - bool.must.push({ - term: { - type - } - }); - if (namespace && !schema.isNamespaceAgnostic(type)) { - bool.must.push({ - term: { - namespace - } - }); - } else { - bool.must_not = [{ - exists: { - field: 'namespace' + return { + bool: { + must: [ + { term: { type } }, + { term: { namespace } }, + ] } - }]; + }; } return { - bool + bool: { + must: [{ term: { type } }], + must_not: [{ exists: { field: 'namespace' } }] + } }; } diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.test.js b/src/server/saved_objects/service/lib/search_dsl/query_params.test.js index e3e261db1e77e..ac7d301c73ecf 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.test.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.test.js @@ -50,61 +50,189 @@ const MAPPINGS = { } } } + }, + global: { + properties: { + name: { + type: 'keyword', + } + } } } } }; +const SCHEMA = { + isNamespaceAgnostic: type => type === 'global', +}; + + +// create a type clause to be used within the "should", if a namespace is specified +// the clause will ensure the namespace matches; otherwise, the clause will ensure +// that there isn't a namespace field. +const createTypeClause = (type, namespace) => { + if (namespace) { + return { + bool: { + must: [ + { term: { type } }, + { term: { namespace } }, + ] + } + }; + } + + return { + bool: { + must: [{ term: { type } }], + must_not: [{ exists: { field: 'namespace' } }] + } + }; +}; + describe('searchDsl/queryParams', () => { - describe('{}', () => { - it('searches for everything', () => { - expect(getQueryParams(MAPPINGS)) - .toEqual({}); + describe('no parameters', () => { + it('searches for all known types without a namespace specified', () => { + expect(getQueryParams(MAPPINGS, SCHEMA)) + .toEqual({ + query: { + bool: { + filter: [{ + bool: { + should: [ + createTypeClause('pending'), + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }] + } + } + }); }); }); - describe('{type}', () => { - it('includes just a terms filter', () => { - expect(getQueryParams(MAPPINGS, 'saved')) + describe('namespace', () => { + it('filters namespaced types for namespace, and ensures namespace agnostic types have no namespace', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace')) .toEqual({ query: { bool: { - filter: [ - { - term: { type: 'saved' } + filter: [{ + bool: { + should: [ + createTypeClause('pending', 'foo-namespace'), + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 } - ] + }] } } }); }); }); - describe('{type,filters}', () => { - it('includes filters and a term filter for type', () => { - expect(getQueryParams(MAPPINGS, 'saved', null, null, [{ terms: { foo: ['bar', 'baz'] } }])) + describe('type (singular, namespaced)', () => { + it('includes a terms filter for type and namespace not being specified', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, null, 'saved')) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } }, - { - term: { type: 'saved' } + filter: [{ + bool: { + should: [ + createTypeClause('saved'), + ], + minimum_should_match: 1 } - ] + }] + } + } + }); + }); + }); + + describe('type (singular, global)', () => { + it('includes a terms filter for type and namespace not being specified', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, null, 'saved')) + .toEqual({ + query: { + bool: { + filter: [{ + bool: { + should: [ + createTypeClause('saved'), + ], + minimum_should_match: 1 + } + }] } } }); }); }); - describe('{search}', () => { - it('includes just a sqs query', () => { - expect(getQueryParams(MAPPINGS, null, 'us*')) + describe('type (plural, namespaced and global)', () => { + it('includes term filters for types and namespace not being specified', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, null, ['saved', 'global'])) .toEqual({ query: { bool: { - filter: [], + filter: [{ + bool: { + should: [ + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }] + } + } + }); + }); + }); + + describe('namespace, type (plural, namespaced and global)', () => { + it('includes a terms filter for type and namespace not being specified', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', ['saved', 'global'])) + .toEqual({ + query: { + bool: { + filter: [{ + bool: { + should: [ + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }] + } + } + }); + }); + }); + + describe('search', () => { + it('includes a sqs query and all known types without a namespace specified', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, null, null, 'us*')) + .toEqual({ + query: { + bool: { + filter: [{ + bool: { + should: [ + createTypeClause('pending'), + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { @@ -119,15 +247,22 @@ describe('searchDsl/queryParams', () => { }); }); - describe('{search,filters}', () => { - it('includes filters and a sqs query', () => { - expect(getQueryParams(MAPPINGS, null, 'us*', null, [{ terms: { foo: ['bar', 'baz'] } }])) + describe('namespace, search', () => { + it('includes a sqs query and namespaced types with the namespace and global types without a namespace', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', null, 'us*')) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('pending', 'foo-namespace'), + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { @@ -142,19 +277,25 @@ describe('searchDsl/queryParams', () => { }); }); - describe('{type,search}', () => { - it('includes bool with sqs query and term filter for type', () => { - expect(getQueryParams(MAPPINGS, 'saved', 'y*')) + describe('type (plural, namespaced and global), search', () => { + it('includes a sqs query and types without a namespace', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, null, ['saved', 'global'], 'us*')) .toEqual({ query: { bool: { - filter: [ - { term: { type: 'saved' } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { - query: 'y*', + query: 'us*', all_fields: true } } @@ -165,20 +306,25 @@ describe('searchDsl/queryParams', () => { }); }); - describe('{type,search,filters}', () => { - it('includes bool with sqs query, filters and term filter for type', () => { - expect(getQueryParams(MAPPINGS, 'saved', 'y*', null, [{ terms: { foo: ['bar', 'baz'] } }])) + describe('namespace, type (plural, namespaced and global), search', () => { + it('includes a sqs query and namespace type with a namespace and global type without a namespace', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', ['saved', 'global'], 'us*')) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } }, - { term: { type: 'saved' } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { - query: 'y*', + query: 'us*', all_fields: true } } @@ -189,20 +335,30 @@ describe('searchDsl/queryParams', () => { }); }); - describe('{search,searchFields}', () => { + describe('search, searchFields', () => { it('includes all types for field', () => { - expect(getQueryParams(MAPPINGS, null, 'y*', ['title'])) + expect(getQueryParams(MAPPINGS, SCHEMA, null, null, 'y*', ['title'])) .toEqual({ query: { bool: { - filter: [], + filter: [{ + bool: { + should: [ + createTypeClause('pending'), + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ 'pending.title', - 'saved.title' + 'saved.title', + 'global.title', ] } } @@ -212,18 +368,28 @@ describe('searchDsl/queryParams', () => { }); }); it('supports field boosting', () => { - expect(getQueryParams(MAPPINGS, null, 'y*', ['title^3'])) + expect(getQueryParams(MAPPINGS, SCHEMA, null, null, 'y*', ['title^3'])) .toEqual({ query: { bool: { - filter: [], + filter: [{ + bool: { + should: [ + createTypeClause('pending'), + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ 'pending.title^3', - 'saved.title^3' + 'saved.title^3', + 'global.title^3', ] } } @@ -233,11 +399,20 @@ describe('searchDsl/queryParams', () => { }); }); it('supports field and multi-field', () => { - expect(getQueryParams(MAPPINGS, null, 'y*', ['title', 'title.raw'])) + expect(getQueryParams(MAPPINGS, SCHEMA, null, null, 'y*', ['title', 'title.raw'])) .toEqual({ query: { bool: { - filter: [], + filter: [{ + bool: { + should: [ + createTypeClause('pending'), + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { @@ -245,8 +420,10 @@ describe('searchDsl/queryParams', () => { fields: [ 'pending.title', 'saved.title', + 'global.title', 'pending.title.raw', 'saved.title.raw', + 'global.title.raw', ] } } @@ -257,22 +434,30 @@ describe('searchDsl/queryParams', () => { }); }); - describe('{search,searchFields,filters}', () => { - it('specifies filters and includes all types for field', () => { - expect(getQueryParams(MAPPINGS, null, 'y*', ['title'], [{ terms: { foo: ['bar', 'baz'] } }])) + describe('namespace, search, searchFields', () => { + it('includes all types for field', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', null, 'y*', ['title'])) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } }, - ], + filter: [{ + bool: { + should: [ + createTypeClause('pending', 'foo-namespace'), + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ 'pending.title', - 'saved.title' + 'saved.title', + 'global.title', ] } } @@ -281,21 +466,29 @@ describe('searchDsl/queryParams', () => { } }); }); - it('specifies filters and supports field boosting', () => { - expect(getQueryParams(MAPPINGS, null, 'y*', ['title^3'], [{ terms: { foo: ['bar', 'baz'] } }])) + it('supports field boosting', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', null, 'y*', ['title^3'])) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } }, - ], + filter: [{ + bool: { + should: [ + createTypeClause('pending', 'foo-namespace'), + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ 'pending.title^3', - 'saved.title^3' + 'saved.title^3', + 'global.title^3', ] } } @@ -304,14 +497,21 @@ describe('searchDsl/queryParams', () => { } }); }); - it('specifies filters and supports field and multi-field', () => { - expect(getQueryParams(MAPPINGS, null, 'y*', ['title', 'title.raw'], [{ terms: { foo: ['bar', 'baz'] } }])) + it('supports field and multi-field', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', null, 'y*', ['title', 'title.raw'])) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } }, - ], + filter: [{ + bool: { + should: [ + createTypeClause('pending', 'foo-namespace'), + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { @@ -319,8 +519,10 @@ describe('searchDsl/queryParams', () => { fields: [ 'pending.title', 'saved.title', + 'global.title', 'pending.title.raw', 'saved.title.raw', + 'global.title.raw', ] } } @@ -331,21 +533,28 @@ describe('searchDsl/queryParams', () => { }); }); - describe('{type,search,searchFields}', () => { - it('includes bool, and sqs with field list', () => { - expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title'])) + describe('type (plural, namespaced and global), search, searchFields', () => { + it('includes all types for field', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, null, ['saved', 'global'], 'y*', ['title'])) .toEqual({ query: { bool: { - filter: [ - { term: { type: 'saved' } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ - 'saved.title' + 'saved.title', + 'global.title', ] } } @@ -354,20 +563,27 @@ describe('searchDsl/queryParams', () => { } }); }); - it('supports fields pointing to multi-fields', () => { - expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title.raw'])) + it('supports field boosting', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, null, ['saved', 'global'], 'y*', ['title^3'])) .toEqual({ query: { bool: { - filter: [ - { term: { type: 'saved' } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ - 'saved.title.raw' + 'saved.title^3', + 'global.title^3', ] } } @@ -376,21 +592,29 @@ describe('searchDsl/queryParams', () => { } }); }); - it('supports multiple search fields', () => { - expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title', 'title.raw'])) + it('supports field and multi-field', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, null, ['saved', 'global'], 'y*', ['title', 'title.raw'])) .toEqual({ query: { bool: { - filter: [ - { term: { type: 'saved' } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('saved'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ 'saved.title', - 'saved.title.raw' + 'global.title', + 'saved.title.raw', + 'global.title.raw', ] } } @@ -401,22 +625,28 @@ describe('searchDsl/queryParams', () => { }); }); - describe('{type,search,searchFields,filters}', () => { - it('includes specified filters, type filter and sqs with field list', () => { - expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title'], [{ terms: { foo: ['bar', 'baz'] } }])) + describe('namespace, type (plural, namespaced and global), search, searchFields', () => { + it('includes all types for field', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', ['saved', 'global'], 'y*', ['title'])) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } }, - { term: { type: 'saved' } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ - 'saved.title' + 'saved.title', + 'global.title', ] } } @@ -425,21 +655,27 @@ describe('searchDsl/queryParams', () => { } }); }); - it('supports fields pointing to multi-fields', () => { - expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title.raw'], [{ terms: { foo: ['bar', 'baz'] } }])) + it('supports field boosting', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', ['saved', 'global'], 'y*', ['title^3'])) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } }, - { term: { type: 'saved' } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ - 'saved.title.raw' + 'saved.title^3', + 'global.title^3', ] } } @@ -448,22 +684,29 @@ describe('searchDsl/queryParams', () => { } }); }); - it('supports multiple search fields', () => { - expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title', 'title.raw'], [{ terms: { foo: ['bar', 'baz'] } }])) + it('supports field and multi-field', () => { + expect(getQueryParams(MAPPINGS, SCHEMA, 'foo-namespace', ['saved', 'global'], 'y*', ['title', 'title.raw'])) .toEqual({ query: { bool: { - filter: [ - { terms: { foo: ['bar', 'baz'] } }, - { term: { type: 'saved' } } - ], + filter: [{ + bool: { + should: [ + createTypeClause('saved', 'foo-namespace'), + createTypeClause('global'), + ], + minimum_should_match: 1 + } + }], must: [ { simple_query_string: { query: 'y*', fields: [ 'saved.title', - 'saved.title.raw' + 'global.title', + 'saved.title.raw', + 'global.title.raw', ] } } From ad190b98c663d0f70252047c59f982d44250c026 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 27 Aug 2018 10:58:18 -0400 Subject: [PATCH 17/24] Adding Schema tests --- .../lib/__snapshots__/schema.test.js.snap | 11 +++ .../saved_objects/service/lib/schema.js | 4 + .../saved_objects/service/lib/schema.test.js | 76 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 src/server/saved_objects/service/lib/__snapshots__/schema.test.js.snap create mode 100644 src/server/saved_objects/service/lib/schema.test.js diff --git a/src/server/saved_objects/service/lib/__snapshots__/schema.test.js.snap b/src/server/saved_objects/service/lib/__snapshots__/schema.test.js.snap new file mode 100644 index 0000000000000..01df0c487d63a --- /dev/null +++ b/src/server/saved_objects/service/lib/__snapshots__/schema.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`#addNamespaceAgnosticType can't add Symbol type 1`] = `"type must be a string"`; + +exports[`#addNamespaceAgnosticType can't add a bool type 1`] = `"type must be a string"`; + +exports[`#addNamespaceAgnosticType can't add function type 1`] = `"type must be a string"`; + +exports[`#addNamespaceAgnosticType can't add null type 1`] = `"type must be a string"`; + +exports[`#addNamespaceAgnosticType can't add number type 1`] = `"type must be a string"`; diff --git a/src/server/saved_objects/service/lib/schema.js b/src/server/saved_objects/service/lib/schema.js index d8308e0e11e87..7a75b986bf373 100644 --- a/src/server/saved_objects/service/lib/schema.js +++ b/src/server/saved_objects/service/lib/schema.js @@ -21,6 +21,10 @@ export class SavedObjectsSchema { _namespaceAgnosticTypes = []; addNamespaceAgnosticType(type) { + if (typeof type !== 'string') { + throw new Error('type must be a string'); + } + this._namespaceAgnosticTypes.push(type); } diff --git a/src/server/saved_objects/service/lib/schema.test.js b/src/server/saved_objects/service/lib/schema.test.js new file mode 100644 index 0000000000000..5c92a3d40414f --- /dev/null +++ b/src/server/saved_objects/service/lib/schema.test.js @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsSchema } from './schema'; + +describe('#addNamespaceAgnosticType', () => { + it(`can add a string type`, () => { + const schema = new SavedObjectsSchema(); + schema.addNamespaceAgnosticType('foo'); + }); + + it(`can't add a bool type`, () => { + const schema = new SavedObjectsSchema(); + expect(() => schema.addNamespaceAgnosticType(true)).toThrowErrorMatchingSnapshot(); + }); + + it(`can't add null type`, () => { + const schema = new SavedObjectsSchema(); + expect(() => schema.addNamespaceAgnosticType(null)).toThrowErrorMatchingSnapshot(); + }); + + it(`can't add number type`, () => { + const schema = new SavedObjectsSchema(); + expect(() => schema.addNamespaceAgnosticType(1)).toThrowErrorMatchingSnapshot(); + }); + + it(`can't add function type`, () => { + const schema = new SavedObjectsSchema(); + expect(() => schema.addNamespaceAgnosticType(() => {})).toThrowErrorMatchingSnapshot(); + }); + + it(`can't add Symbol type`, () => { + const schema = new SavedObjectsSchema(); + expect(() => schema.addNamespaceAgnosticType(Symbol())).toThrowErrorMatchingSnapshot(); + }); + + it(`returns true for namespace agnostic registered types`, () => { + const schema = new SavedObjectsSchema(); + schema.addNamespaceAgnosticType('foo'); + const result = schema.isNamespaceAgnostic('foo'); + expect(result).toBe(true); + }); +}); + +describe('#isNamespaceAgnostic', () => { + it(`returns false for unknown types`, () => { + const schema = new SavedObjectsSchema(); + schema.addNamespaceAgnosticType('foo'); + const result = schema.isNamespaceAgnostic('bar'); + expect(result).toBe(false); + }); + + it(`returns true for namespace agnostic registered types`, () => { + const schema = new SavedObjectsSchema(); + schema.addNamespaceAgnosticType('foo'); + const result = schema.isNamespaceAgnostic('foo'); + expect(result).toBe(true); + }); +}); + From 3bb65ddc5d91040f209128b1fdaaa342518af727 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 27 Aug 2018 13:50:02 -0400 Subject: [PATCH 18/24] Fixing secure saved objects client test --- .../secure_saved_objects_client.js | 6 +- .../secure_saved_objects_client.test.js | 147 +++++++++++------- 2 files changed, 93 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js index 55a8473be11d7..592f278ca9f12 100644 --- a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js +++ b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js @@ -143,12 +143,12 @@ export class SecureSavedObjectsClient { action, types, missing, - { options } + { options, namespace } ); throw this.errors.decorateForbiddenError(new Error(`Not authorized to find saved_object`)); } - this._auditLogger.savedObjectsAuthorizationSuccess(username, action, authorizedTypes, { options }); + this._auditLogger.savedObjectsAuthorizationSuccess(username, action, authorizedTypes, { options, namespace }); return await this._internalRepository.find( { @@ -162,7 +162,7 @@ export class SecureSavedObjectsClient { return await this._execute( options.type, 'find', - { options }, + { options, namespace }, repository => repository.find(options, namespace) ); } diff --git a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js index e9ac730f5831b..9c15310f96569 100644 --- a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js +++ b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js @@ -87,8 +87,9 @@ describe('#create', () => { }); const attributes = Symbol(); const options = Symbol(); + const namespace = Symbol(); - await expect(client.create(type, attributes, options)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.create(type, attributes, options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'create')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -101,6 +102,7 @@ describe('#create', () => { type, attributes, options, + namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -126,16 +128,18 @@ describe('#create', () => { }); const attributes = Symbol(); const options = Symbol(); + const namespace = Symbol(); - const result = await client.create(type, attributes, options); + const result = await client.create(type, attributes, options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options); + expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'create', [type], { type, attributes, options, + namespace, }); }); @@ -160,11 +164,12 @@ describe('#create', () => { }); const attributes = Symbol(); const options = Symbol(); + const namespace = Symbol(); - const result = await client.create(type, attributes, options); + const result = await client.create(type, attributes, options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options); + expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options, namespace); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); }); @@ -220,8 +225,9 @@ describe('#bulkCreate', () => { { type: type2 }, ]; const options = Symbol(); + const namespace = Symbol(); - await expect(client.bulkCreate(objects, options)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.bulkCreate(objects, options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([ mockActions.getSavedObjectAction(type1, 'bulk_create'), @@ -236,6 +242,7 @@ describe('#bulkCreate', () => { { objects, options, + namespace, } ); }); @@ -264,15 +271,17 @@ describe('#bulkCreate', () => { { type: type2, otherThing: 'everyone' }, ]; const options = Symbol(); + const namespace = Symbol(); - const result = await client.bulkCreate(objects, options); + const result = await client.bulkCreate(objects, options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options); + expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'bulk_create', [type1, type2], { objects, options, + namespace, }); }); @@ -301,11 +310,12 @@ describe('#bulkCreate', () => { { type: type2, otherThing: 'everyone' }, ]; const options = Symbol(); + const namespace = Symbol(); - const result = await client.bulkCreate(objects, options); + const result = await client.bulkCreate(objects, options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options); + expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -353,8 +363,9 @@ describe('#delete', () => { actions: mockActions, }); const id = Symbol(); + const namespace = Symbol(); - await expect(client.delete(type, id)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.delete(type, id, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'delete')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -366,6 +377,7 @@ describe('#delete', () => { { type, id, + namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -390,15 +402,17 @@ describe('#delete', () => { actions: createMockActions(), }); const id = Symbol(); + const namespace = Symbol(); - const result = await client.delete(type, id); + const result = await client.delete(type, id, namespace); expect(result).toBe(returnValue); - expect(mockRepository.delete).toHaveBeenCalledWith(type, id); + expect(mockRepository.delete).toHaveBeenCalledWith(type, id, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'delete', [type], { type, id, + namespace, }); }); @@ -422,11 +436,12 @@ describe('#delete', () => { actions: createMockActions(), }); const id = Symbol(); + const namespace = Symbol(); - const result = await client.delete(type, id); + const result = await client.delete(type, id, namespace); expect(result).toBe(returnValue); - expect(mockRepository.delete).toHaveBeenCalledWith(type, id); + expect(mockRepository.delete).toHaveBeenCalledWith(type, id, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -477,8 +492,9 @@ describe('#find', () => { actions: mockActions, }); const options = { type }; + const namespace = Symbol(); - await expect(client.find(options)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.find(options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'find')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -488,7 +504,8 @@ describe('#find', () => { [type], [mockActions.getSavedObjectAction(type, 'find')], { - options + options, + namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -518,8 +535,9 @@ describe('#find', () => { actions: mockActions, }); const options = { type: [type1, type2] }; + const namespace = Symbol(); - await expect(client.find(options)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.find(options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([ mockActions.getSavedObjectAction(type1, 'find'), @@ -532,7 +550,8 @@ describe('#find', () => { [type1, type2], [mockActions.getSavedObjectAction(type1, 'find')], { - options + options, + namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -557,14 +576,16 @@ describe('#find', () => { actions: createMockActions(), }); const options = { type }; + const namespace = Symbol(); - const result = await client.find(options); + const result = await client.find(options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.find).toHaveBeenCalledWith({ type }); + expect(mockRepository.find).toHaveBeenCalledWith({ type }, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'find', [type], { options, + namespace, }); }); @@ -588,11 +609,12 @@ describe('#find', () => { actions: createMockActions(), }); const options = { type }; + const namespace = Symbol(); - const result = await client.find(options); + const result = await client.find(options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.find).toHaveBeenCalledWith({ type }); + expect(mockRepository.find).toHaveBeenCalledWith({ type }, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -652,8 +674,9 @@ describe('#find', () => { actions: mockActions, }); const options = Symbol(); + const namespace = Symbol(); - await expect(client.find(options)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.find(options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'find')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -663,7 +686,8 @@ describe('#find', () => { [type], [mockActions.getSavedObjectAction(type, 'find')], { - options + options, + namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -693,12 +717,13 @@ describe('#find', () => { actions: mockActions, }); const options = Symbol(); + const namespace = Symbol(); - const result = await client.find(options); + const result = await client.find(options, namespace); expect(result).toBe(returnValue); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'find')]); - expect(mockRepository.find).toHaveBeenCalledWith(options); + expect(mockRepository.find).toHaveBeenCalledWith(options, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -726,16 +751,17 @@ describe('#find', () => { savedObjectTypes: [type1, type2], actions: mockActions, }); + const namespace = Symbol(); - await client.find(); + await client.find({}, namespace); expect(mockCheckPrivileges).toHaveBeenCalledWith([ mockActions.getSavedObjectAction(type1, 'find'), mockActions.getSavedObjectAction(type2, 'find'), ]); expect(mockRepository.find).toHaveBeenCalledWith(expect.objectContaining({ - type: [type2] - })); + type: [type2], + }), namespace); }); test(`returns result of repository.find`, async () => { @@ -759,14 +785,16 @@ describe('#find', () => { actions: createMockActions(), }); const options = Symbol(); + const namespace = Symbol(); - const result = await client.find(options); + const result = await client.find(options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.find).toHaveBeenCalledWith({ type: [type] }); + expect(mockRepository.find).toHaveBeenCalledWith({ type: [type] }, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'find', [type], { options, + namespace, }); }); }); @@ -821,9 +849,9 @@ describe('#bulkGet', () => { { type: type1 }, { type: type2 }, ]; - const options = Symbol(); + const namespace = Symbol(); - await expect(client.bulkGet(objects, options)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.bulkGet(objects, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([ mockActions.getSavedObjectAction(type1, 'bulk_get'), @@ -837,7 +865,7 @@ describe('#bulkGet', () => { [mockActions.getSavedObjectAction(type1, 'bulk_get')], { objects, - options, + namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -866,16 +894,16 @@ describe('#bulkGet', () => { { type: type1, id: 'foo-id' }, { type: type2, id: 'bar-id' }, ]; - const options = Symbol(); + const namespace = Symbol(); - const result = await client.bulkGet(objects, options); + const result = await client.bulkGet(objects, namespace); expect(result).toBe(returnValue); - expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, options); + expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'bulk_get', [type1, type2], { objects, - options, + namespace, }); }); @@ -903,12 +931,12 @@ describe('#bulkGet', () => { { type: type1, id: 'foo-id' }, { type: type2, id: 'bar-id' }, ]; - const options = Symbol(); + const namespace = Symbol(); - const result = await client.bulkGet(objects, options); + const result = await client.bulkGet(objects, namespace); expect(result).toBe(returnValue); - expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, options); + expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -956,9 +984,9 @@ describe('#get', () => { actions: mockActions, }); const id = Symbol(); - const options = Symbol(); + const namespace = Symbol(); - await expect(client.get(type, id, options)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.get(type, id, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'get')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -970,7 +998,7 @@ describe('#get', () => { { type, id, - options, + namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -995,17 +1023,17 @@ describe('#get', () => { actions: createMockActions(), }); const id = Symbol(); - const options = Symbol(); + const namespace = Symbol(); - const result = await client.get(type, id, options); + const result = await client.get(type, id, namespace); expect(result).toBe(returnValue); - expect(mockRepository.get).toHaveBeenCalledWith(type, id, options); + expect(mockRepository.get).toHaveBeenCalledWith(type, id, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'get', [type], { type, id, - options + namespace, }); }); @@ -1029,12 +1057,12 @@ describe('#get', () => { actions: createMockActions(), }); const id = Symbol(); - const options = Symbol(); + const namespace = Symbol(); - const result = await client.get(type, id, options); + const result = await client.get(type, id, namespace); expect(result).toBe(returnValue); - expect(mockRepository.get).toHaveBeenCalledWith(type, id, options); + expect(mockRepository.get).toHaveBeenCalledWith(type, id, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -1084,8 +1112,9 @@ describe('#update', () => { const id = Symbol(); const attributes = Symbol(); const options = Symbol(); + const namespace = Symbol(); - await expect(client.update(type, id, attributes, options)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.update(type, id, attributes, options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'update')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -1099,6 +1128,7 @@ describe('#update', () => { id, attributes, options, + namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -1125,17 +1155,19 @@ describe('#update', () => { const id = Symbol(); const attributes = Symbol(); const options = Symbol(); + const namespace = Symbol(); - const result = await client.update(type, id, attributes, options); + const result = await client.update(type, id, attributes, options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options); + expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'update', [type], { type, id, attributes, options, + namespace, }); }); @@ -1161,11 +1193,12 @@ describe('#update', () => { const id = Symbol(); const attributes = Symbol(); const options = Symbol(); + const namespace = Symbol(); - const result = await client.update(type, id, attributes, options); + const result = await client.update(type, id, attributes, options, namespace); expect(result).toBe(returnValue); - expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options); + expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options, namespace); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); From a2c19ecf1fca2ae87dd7de3bbb4f7ce5001a0f0f Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 27 Aug 2018 16:20:22 -0400 Subject: [PATCH 19/24] Namespaces via options --- .../saved_objects/service/lib/repository.js | 58 +- .../service/lib/repository.test.js | 80 +- .../service/lib/repository_provider.test.js | 2 +- .../service/saved_objects_client.js | 45 +- .../service/saved_objects_client.test.js | 38 +- .../secure_saved_objects_client.js | 59 +- .../secure_saved_objects_client.test.js | 144 +- .../spaces_saved_objects_client.test.js.snap | 36 +- .../spaces_saved_objects_client.js | 80 +- .../spaces_saved_objects_client.test.js | 1868 ++--------------- 10 files changed, 492 insertions(+), 1918 deletions(-) diff --git a/src/server/saved_objects/service/lib/repository.js b/src/server/saved_objects/service/lib/repository.js index c2a24539931e5..d7fa6ac9e5188 100644 --- a/src/server/saved_objects/service/lib/repository.js +++ b/src/server/saved_objects/service/lib/repository.js @@ -56,13 +56,14 @@ export class SavedObjectsRepository { * @param {object} [options={}] * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async create(type, attributes = {}, options = {}, namespace) { + async create(type, attributes = {}, options = {}) { const { id, - overwrite = false + overwrite = false, + namespace, } = options; const method = id && !overwrite ? 'create' : 'index'; @@ -104,13 +105,14 @@ export class SavedObjectsRepository { * * @param {array} objects - [{ type, id, attributes }] * @param {object} [options={}] - * @param {string} [namespace] * @property {boolean} [options.overwrite=false] - overwrites existing documents + * @property {string} [options.namespace] * @returns {promise} - {saved_objects: [[{ id, type, version, attributes, error: { message } }]} */ - async bulkCreate(objects, options = {}, namespace) { + async bulkCreate(objects, options = {}) { const { - overwrite = false + overwrite = false, + namespace } = options; const time = this._getCurrentTime(); const objectToBulkRequest = (object) => { @@ -188,10 +190,15 @@ export class SavedObjectsRepository { * * @param {string} type * @param {string} id - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} */ - async delete(type, id, namespace) { + async delete(type, id, options = {}) { + const { + namespace + } = options; + const response = await this._writeToCluster('delete', { id: this._generateEsId(namespace, type, id), type: this._type, @@ -228,10 +235,10 @@ export class SavedObjectsRepository { * @property {string} [options.sortField] * @property {string} [options.sortOrder] * @property {Array} [options.fields] - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(options = {}, namespace) { + async find(options = {}) { const { type, search, @@ -241,6 +248,7 @@ export class SavedObjectsRepository { sortField, sortOrder, fields, + namespace, } = options; if (searchFields && !Array.isArray(searchFields)) { @@ -304,7 +312,8 @@ export class SavedObjectsRepository { * Returns an array of objects by id * * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -313,7 +322,11 @@ export class SavedObjectsRepository { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], namespace) { + async bulkGet(objects = [], options = {}) { + const { + namespace + } = options; + if (objects.length === 0) { return { saved_objects: [] }; } @@ -363,10 +376,15 @@ export class SavedObjectsRepository { * * @param {string} type * @param {string} id - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, namespace) { + async get(type, id, options = {}) { + const { + namespace + } = options; + const response = await this._callCluster('get', { id: this._generateEsId(namespace, type, id), type: this._type, @@ -401,17 +419,21 @@ export class SavedObjectsRepository { * @param {string} id * @param {object} [options={}] * @property {integer} options.version - ensures version matches that of persisted object - * @param {array} [options.extraDocumentProperties = {}] - an object of extra properties to write into the underlying document - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} */ - async update(type, id, attributes, options = {}, namespace) { + async update(type, id, attributes, options = {}) { + const { + version, + namespace + } = options; + const time = this._getCurrentTime(); const response = await this._writeToCluster('update', { id: this._generateEsId(namespace, type, id), type: this._type, index: this._index, - version: options.version, + version, refresh: 'wait_for', ignore: [404], body: { diff --git a/src/server/saved_objects/service/lib/repository.test.js b/src/server/saved_objects/service/lib/repository.test.js index d959262dac96f..8ed28ffa1b6b8 100644 --- a/src/server/saved_objects/service/lib/repository.test.js +++ b/src/server/saved_objects/service/lib/repository.test.js @@ -282,9 +282,9 @@ describe('SavedObjectsRepository', () => { title: 'Logstash' }, { - id: 'foo-id' + id: 'foo-id', + namespace: 'foo-namespace', }, - 'foo-namespace', ); sinon.assert.calledOnce(callAdminCluster); @@ -330,7 +330,8 @@ describe('SavedObjectsRepository', () => { title: 'Logstash' }, { - id: 'foo-id' + id: 'foo-id', + namespace: 'foo-namespace', } ); @@ -493,8 +494,9 @@ describe('SavedObjectsRepository', () => { { type: 'config', id: 'one', attributes: { title: 'Test One' } }, { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } ], - {}, - 'foo-namespace' + { + namespace: 'foo-namespace', + }, ); sinon.assert.calledOnce(callAdminCluster); @@ -513,13 +515,10 @@ describe('SavedObjectsRepository', () => { it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { callAdminCluster.returns({ items: [] }); - await savedObjectsRepository.bulkCreate( - [ - { type: 'config', id: 'one', attributes: { title: 'Test One' } }, - { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } - ], - {} - ); + await savedObjectsRepository.bulkCreate([ + { type: 'config', id: 'one', attributes: { title: 'Test One' } }, + { type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } } + ]); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({ @@ -541,8 +540,9 @@ describe('SavedObjectsRepository', () => { [ { type: 'no-ns-type', id: 'one', attributes: { title: 'Test One' } }, ], - {}, - 'foo-namespace' + { + namespace: 'foo-namespace', + }, ); sinon.assert.calledOnce(callAdminCluster); @@ -576,7 +576,9 @@ describe('SavedObjectsRepository', () => { callAdminCluster.returns({ result: 'deleted' }); - await savedObjectsRepository.delete('index-pattern', 'logstash-*', 'foo-namespace'); + await savedObjectsRepository.delete('index-pattern', 'logstash-*', { + namespace: 'foo-namespace', + }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'delete', { @@ -612,7 +614,9 @@ describe('SavedObjectsRepository', () => { callAdminCluster.returns({ result: 'deleted' }); - await savedObjectsRepository.delete('no-ns-type', 'logstash-*', 'foo-namespace'); + await savedObjectsRepository.delete('no-ns-type', 'logstash-*', { + namespace: 'foo-namespace', + }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'delete', { @@ -663,7 +667,7 @@ describe('SavedObjectsRepository', () => { sortOrder: 'desc', }; - await savedObjectsRepository.find(relevantOpts, 'foo-namespace'); + await savedObjectsRepository.find(relevantOpts); sinon.assert.calledOnce(getSearchDsl); sinon.assert.calledWithExactly(getSearchDsl, mappings, schema, relevantOpts); }); @@ -706,7 +710,9 @@ describe('SavedObjectsRepository', () => { callAdminCluster.returns(namespacedSearchResults); const count = namespacedSearchResults.hits.hits.length; - const response = await savedObjectsRepository.find({}, 'foo-namespace'); + const response = await savedObjectsRepository.find({ + namespace: 'foo-namespace', + }); expect(response.total).toBe(count); expect(response.saved_objects).toHaveLength(count); @@ -796,7 +802,9 @@ describe('SavedObjectsRepository', () => { it('prepends namespace and type to the id when providing namespace for namespaced type', async () => { callAdminCluster.returns(Promise.resolve(namespacedResult)); - await savedObjectsRepository.get('index-pattern', 'logstash-*', 'foo-namespace'); + await savedObjectsRepository.get('index-pattern', 'logstash-*', { + namespace: 'foo-namespace', + }); sinon.assert.notCalled(onBeforeWrite); sinon.assert.calledOnce(callAdminCluster); @@ -820,7 +828,9 @@ describe('SavedObjectsRepository', () => { it(`doesn't prepend namespace to the id when providing namespace for namespace agnostic type`, async () => { callAdminCluster.returns(Promise.resolve(namespacedResult)); - await savedObjectsRepository.get('no-ns-type', 'logstash-*', 'foo-namespace'); + await savedObjectsRepository.get('no-ns-type', 'logstash-*', { + namespace: 'foo-namespace', + }); sinon.assert.notCalled(onBeforeWrite); sinon.assert.calledOnce(callAdminCluster); @@ -858,11 +868,15 @@ describe('SavedObjectsRepository', () => { it('prepends namespace and type appropriately to id when getting objects when there is a namespace', async () => { callAdminCluster.returns({ docs: [] }); - await savedObjectsRepository.bulkGet([ - { id: 'one', type: 'config' }, - { id: 'two', type: 'index-pattern' }, - { id: 'three', type: 'no-ns-type' }, - ], 'foo-namespace'); + await savedObjectsRepository.bulkGet( + [ + { id: 'one', type: 'config' }, + { id: 'two', type: 'index-pattern' }, + { id: 'three', type: 'no-ns-type' }, + ], { + namespace: 'foo-namespace', + } + ); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({ @@ -967,15 +981,19 @@ describe('SavedObjectsRepository', () => { }); it('prepends namespace to the id and adds namespace to body when providing namespace for namespaced type', async () => { - await savedObjectsRepository.update('index-pattern', 'logstash-*', { title: 'Testing' }, 'foo-namespace'); + await savedObjectsRepository.update('index-pattern', 'logstash-*', { + title: 'Testing', + }, { + namespace: 'foo-namespace', + }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'update', { type: 'doc', - id: 'index-pattern:logstash-*', + id: 'foo-namespace:index-pattern:logstash-*', version: undefined, body: { - doc: { updated_at: mockTimestamp, 'index-pattern': { title: 'Testing' } } + doc: { namespace: 'foo-namespace', updated_at: mockTimestamp, 'index-pattern': { title: 'Testing' } } }, ignore: [404], refresh: 'wait_for', @@ -1005,7 +1023,11 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { - await savedObjectsRepository.update('no-ns-type', 'foo', { name: 'bar' }, 'foo-namespace'); + await savedObjectsRepository.update('no-ns-type', 'foo', { + name: 'bar', + }, { + namespace: 'foo-namespace', + }); sinon.assert.calledOnce(callAdminCluster); sinon.assert.calledWithExactly(callAdminCluster, 'update', { diff --git a/src/server/saved_objects/service/lib/repository_provider.test.js b/src/server/saved_objects/service/lib/repository_provider.test.js index 58a5313dab24f..41a8f72f2e643 100644 --- a/src/server/saved_objects/service/lib/repository_provider.test.js +++ b/src/server/saved_objects/service/lib/repository_provider.test.js @@ -55,7 +55,7 @@ test('creates a valid Repository', async () => { const repository = provider.getRepository(callCluster); - await repository.create('foo', {}, {}, 'ns'); + await repository.create('foo', {}, { namespace: 'ns' }); expect(callCluster).toHaveBeenCalledTimes(1); expect(properties.schema.isNamespaceAgnostic).toHaveBeenCalled(); diff --git a/src/server/saved_objects/service/saved_objects_client.js b/src/server/saved_objects/service/saved_objects_client.js index c7b4a81309434..25c252d195bf1 100644 --- a/src/server/saved_objects/service/saved_objects_client.js +++ b/src/server/saved_objects/service/saved_objects_client.js @@ -102,11 +102,11 @@ export class SavedObjectsClient { * @param {object} [options={}] * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async create(type, attributes = {}, options = {}, namespace) { - return this._repository.create(type, attributes, options, namespace); + async create(type, attributes = {}, options = {}) { + return this._repository.create(type, attributes, options); } /** @@ -115,11 +115,11 @@ export class SavedObjectsClient { * @param {array} objects - [{ type, id, attributes, extraDocumentProperties }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] - overwrites existing documents - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ - async bulkCreate(objects, options = {}, namespace) { - return this._repository.bulkCreate(objects, options, namespace); + async bulkCreate(objects, options = {}) { + return this._repository.bulkCreate(objects, options); } /** @@ -127,11 +127,12 @@ export class SavedObjectsClient { * * @param {string} type * @param {string} id - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} */ - async delete(type, id, namespace) { - return this._repository.delete(type, id, namespace); + async delete(type, id, options = {}) { + return this._repository.delete(type, id, options); } /** @@ -145,18 +146,19 @@ export class SavedObjectsClient { * @property {string} [options.sortField] * @property {string} [options.sortOrder] * @property {Array} [options.fields] - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(options = {}, namespace) { - return this._repository.find(options, namespace); + async find(options = {}) { + return this._repository.find(options); } /** * Returns an array of objects by id * * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -165,8 +167,8 @@ export class SavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], namespace) { - return this._repository.bulkGet(objects, namespace); + async bulkGet(objects = [], options = {}) { + return this._repository.bulkGet(objects, options); } /** @@ -174,11 +176,12 @@ export class SavedObjectsClient { * * @param {string} type * @param {string} id - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, namespace) { - return this._repository.get(type, id, namespace); + async get(type, id, options = {}) { + return this._repository.get(type, id, options); } /** @@ -188,10 +191,10 @@ export class SavedObjectsClient { * @param {string} id * @param {object} [options={}] * @property {integer} options.version - ensures version matches that of persisted object - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} */ - async update(type, id, attributes, options = {}, namespace) { - return this._repository.update(type, id, attributes, options, namespace); + async update(type, id, attributes, options = {}) { + return this._repository.update(type, id, attributes, options); } } diff --git a/src/server/saved_objects/service/saved_objects_client.test.js b/src/server/saved_objects/service/saved_objects_client.test.js index 3fe1f46a994fd..e69b12d1218cc 100644 --- a/src/server/saved_objects/service/saved_objects_client.test.js +++ b/src/server/saved_objects/service/saved_objects_client.test.js @@ -29,10 +29,9 @@ test(`#create`, async () => { const type = Symbol(); const attributes = Symbol(); const options = Symbol(); - const namespace = Symbol(); - const result = await client.create(type, attributes, options, namespace); + const result = await client.create(type, attributes, options); - expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options, namespace); + expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options); expect(result).toBe(returnValue); }); @@ -45,10 +44,9 @@ test(`#bulkCreate`, async () => { const objects = Symbol(); const options = Symbol(); - const namespace = Symbol(); - const result = await client.bulkCreate(objects, options, namespace); + const result = await client.bulkCreate(objects, options); - expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options, namespace); + expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options); expect(result).toBe(returnValue); }); @@ -61,10 +59,10 @@ test(`#delete`, async () => { const type = Symbol(); const id = Symbol(); - const namespace = Symbol(); - const result = await client.delete(type, id, namespace); + const options = Symbol(); + const result = await client.delete(type, id, options); - expect(mockRepository.delete).toHaveBeenCalledWith(type, id, namespace); + expect(mockRepository.delete).toHaveBeenCalledWith(type, id, options); expect(result).toBe(returnValue); }); @@ -76,10 +74,9 @@ test(`#find`, async () => { const client = new SavedObjectsClient(mockRepository); const options = Symbol(); - const namespace = Symbol(); - const result = await client.find(options, namespace); + const result = await client.find(options); - expect(mockRepository.find).toHaveBeenCalledWith(options, namespace); + expect(mockRepository.find).toHaveBeenCalledWith(options); expect(result).toBe(returnValue); }); @@ -91,10 +88,10 @@ test(`#bulkGet`, async () => { const client = new SavedObjectsClient(mockRepository); const objects = Symbol(); - const namespace = Symbol(); - const result = await client.bulkGet(objects, namespace); + const options = Symbol(); + const result = await client.bulkGet(objects, options); - expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, namespace); + expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, options); expect(result).toBe(returnValue); }); @@ -107,10 +104,10 @@ test(`#get`, async () => { const type = Symbol(); const id = Symbol(); - const namespace = Symbol(); - const result = await client.get(type, id, namespace); + const options = Symbol(); + const result = await client.get(type, id, options); - expect(mockRepository.get).toHaveBeenCalledWith(type, id, namespace); + expect(mockRepository.get).toHaveBeenCalledWith(type, id, options); expect(result).toBe(returnValue); }); @@ -125,9 +122,8 @@ test(`#update`, async () => { const id = Symbol(); const attributes = Symbol(); const options = Symbol(); - const namespace = Symbol(); - const result = await client.update(type, id, attributes, options, namespace); + const result = await client.update(type, id, attributes, options); - expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options, namespace); + expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options); expect(result).toBe(returnValue); }); diff --git a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js index 592f278ca9f12..bd32c89614993 100644 --- a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js +++ b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.js @@ -28,67 +28,67 @@ export class SecureSavedObjectsClient { this._actions = actions; } - async create(type, attributes = {}, options = {}, namespace) { + async create(type, attributes = {}, options = {}) { return await this._execute( type, 'create', - { type, attributes, options, namespace }, - repository => repository.create(type, attributes, options, namespace), + { type, attributes, options }, + repository => repository.create(type, attributes, options), ); } - async bulkCreate(objects, options = {}, namespace) { + async bulkCreate(objects, options = {}) { const types = uniq(objects.map(o => o.type)); return await this._execute( types, 'bulk_create', - { objects, options, namespace }, - repository => repository.bulkCreate(objects, options, namespace), + { objects, options }, + repository => repository.bulkCreate(objects, options), ); } - async delete(type, id, namespace) { + async delete(type, id, options = {}) { return await this._execute( type, 'delete', - { type, id, namespace }, - repository => repository.delete(type, id, namespace), + { type, id, options }, + repository => repository.delete(type, id, options), ); } - async find(options = {}, namespace) { + async find(options = {}) { if (options.type) { - return await this._findWithTypes(options, namespace); + return await this._findWithTypes(options); } - return await this._findAcrossAllTypes(options, namespace); + return await this._findAcrossAllTypes(options); } - async bulkGet(objects = [], namespace) { + async bulkGet(objects = [], options = {}) { const types = uniq(objects.map(o => o.type)); return await this._execute( types, 'bulk_get', - { objects, namespace }, - repository => repository.bulkGet(objects, namespace) + { objects, options }, + repository => repository.bulkGet(objects, options) ); } - async get(type, id, namespace) { + async get(type, id, options = {}) { return await this._execute( type, 'get', - { type, id, namespace }, - repository => repository.get(type, id, namespace) + { type, id, options }, + repository => repository.get(type, id, options) ); } - async update(type, id, attributes, options = {}, namespace) { + async update(type, id, attributes, options = {}) { return await this._execute( type, 'update', - { type, id, attributes, options, namespace }, - repository => repository.update(type, id, attributes, options, namespace) + { type, id, attributes, options }, + repository => repository.update(type, id, attributes, options) ); } @@ -121,7 +121,7 @@ export class SecureSavedObjectsClient { } } - async _findAcrossAllTypes(options, namespace) { + async _findAcrossAllTypes(options) { const action = 'find'; // we have to filter for only their authorized types @@ -130,7 +130,7 @@ export class SecureSavedObjectsClient { const { result, username, missing } = await this._checkSavedObjectPrivileges(Array.from(typesToPrivilegesMap.values())); if (result === CHECK_PRIVILEGES_RESULT.LEGACY) { - return await this._callWithRequestRepository.find(options, namespace); + return await this._callWithRequestRepository.find(options); } const authorizedTypes = Array.from(typesToPrivilegesMap.entries()) @@ -143,27 +143,26 @@ export class SecureSavedObjectsClient { action, types, missing, - { options, namespace } + { options } ); throw this.errors.decorateForbiddenError(new Error(`Not authorized to find saved_object`)); } - this._auditLogger.savedObjectsAuthorizationSuccess(username, action, authorizedTypes, { options, namespace }); + this._auditLogger.savedObjectsAuthorizationSuccess(username, action, authorizedTypes, { options }); return await this._internalRepository.find( { ...options, type: authorizedTypes, - }, - namespace); + }); } - async _findWithTypes(options, namespace) { + async _findWithTypes(options) { return await this._execute( options.type, 'find', - { options, namespace }, - repository => repository.find(options, namespace) + { options }, + repository => repository.find(options) ); } } diff --git a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js index 9c15310f96569..95a4e9aa0eaac 100644 --- a/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js +++ b/x-pack/plugins/security/server/lib/saved_objects_client/secure_saved_objects_client.test.js @@ -87,9 +87,8 @@ describe('#create', () => { }); const attributes = Symbol(); const options = Symbol(); - const namespace = Symbol(); - await expect(client.create(type, attributes, options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.create(type, attributes, options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'create')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -102,7 +101,6 @@ describe('#create', () => { type, attributes, options, - namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -128,18 +126,16 @@ describe('#create', () => { }); const attributes = Symbol(); const options = Symbol(); - const namespace = Symbol(); - const result = await client.create(type, attributes, options, namespace); + const result = await client.create(type, attributes, options); expect(result).toBe(returnValue); - expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options, namespace); + expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'create', [type], { type, attributes, options, - namespace, }); }); @@ -164,12 +160,11 @@ describe('#create', () => { }); const attributes = Symbol(); const options = Symbol(); - const namespace = Symbol(); - const result = await client.create(type, attributes, options, namespace); + const result = await client.create(type, attributes, options); expect(result).toBe(returnValue); - expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options, namespace); + expect(mockRepository.create).toHaveBeenCalledWith(type, attributes, options); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); }); @@ -225,9 +220,8 @@ describe('#bulkCreate', () => { { type: type2 }, ]; const options = Symbol(); - const namespace = Symbol(); - await expect(client.bulkCreate(objects, options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.bulkCreate(objects, options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([ mockActions.getSavedObjectAction(type1, 'bulk_create'), @@ -242,7 +236,6 @@ describe('#bulkCreate', () => { { objects, options, - namespace, } ); }); @@ -271,17 +264,15 @@ describe('#bulkCreate', () => { { type: type2, otherThing: 'everyone' }, ]; const options = Symbol(); - const namespace = Symbol(); - const result = await client.bulkCreate(objects, options, namespace); + const result = await client.bulkCreate(objects, options); expect(result).toBe(returnValue); - expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options, namespace); + expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'bulk_create', [type1, type2], { objects, options, - namespace, }); }); @@ -310,12 +301,11 @@ describe('#bulkCreate', () => { { type: type2, otherThing: 'everyone' }, ]; const options = Symbol(); - const namespace = Symbol(); - const result = await client.bulkCreate(objects, options, namespace); + const result = await client.bulkCreate(objects, options); expect(result).toBe(returnValue); - expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options, namespace); + expect(mockRepository.bulkCreate).toHaveBeenCalledWith(objects, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -363,9 +353,9 @@ describe('#delete', () => { actions: mockActions, }); const id = Symbol(); - const namespace = Symbol(); + const options = Symbol(); - await expect(client.delete(type, id, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.delete(type, id, options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'delete')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -377,7 +367,7 @@ describe('#delete', () => { { type, id, - namespace, + options, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -402,17 +392,17 @@ describe('#delete', () => { actions: createMockActions(), }); const id = Symbol(); - const namespace = Symbol(); + const options = Symbol(); - const result = await client.delete(type, id, namespace); + const result = await client.delete(type, id, options); expect(result).toBe(returnValue); - expect(mockRepository.delete).toHaveBeenCalledWith(type, id, namespace); + expect(mockRepository.delete).toHaveBeenCalledWith(type, id, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'delete', [type], { type, id, - namespace, + options, }); }); @@ -436,12 +426,12 @@ describe('#delete', () => { actions: createMockActions(), }); const id = Symbol(); - const namespace = Symbol(); + const options = Symbol(); - const result = await client.delete(type, id, namespace); + const result = await client.delete(type, id, options); expect(result).toBe(returnValue); - expect(mockRepository.delete).toHaveBeenCalledWith(type, id, namespace); + expect(mockRepository.delete).toHaveBeenCalledWith(type, id, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -492,9 +482,8 @@ describe('#find', () => { actions: mockActions, }); const options = { type }; - const namespace = Symbol(); - await expect(client.find(options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.find(options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'find')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -505,7 +494,6 @@ describe('#find', () => { [mockActions.getSavedObjectAction(type, 'find')], { options, - namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -535,9 +523,8 @@ describe('#find', () => { actions: mockActions, }); const options = { type: [type1, type2] }; - const namespace = Symbol(); - await expect(client.find(options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.find(options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([ mockActions.getSavedObjectAction(type1, 'find'), @@ -551,7 +538,6 @@ describe('#find', () => { [mockActions.getSavedObjectAction(type1, 'find')], { options, - namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -576,16 +562,14 @@ describe('#find', () => { actions: createMockActions(), }); const options = { type }; - const namespace = Symbol(); - const result = await client.find(options, namespace); + const result = await client.find(options); expect(result).toBe(returnValue); - expect(mockRepository.find).toHaveBeenCalledWith({ type }, namespace); + expect(mockRepository.find).toHaveBeenCalledWith({ type }); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'find', [type], { options, - namespace, }); }); @@ -609,12 +593,11 @@ describe('#find', () => { actions: createMockActions(), }); const options = { type }; - const namespace = Symbol(); - const result = await client.find(options, namespace); + const result = await client.find(options); expect(result).toBe(returnValue); - expect(mockRepository.find).toHaveBeenCalledWith({ type }, namespace); + expect(mockRepository.find).toHaveBeenCalledWith({ type }); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -674,9 +657,8 @@ describe('#find', () => { actions: mockActions, }); const options = Symbol(); - const namespace = Symbol(); - await expect(client.find(options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.find(options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'find')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -687,7 +669,6 @@ describe('#find', () => { [mockActions.getSavedObjectAction(type, 'find')], { options, - namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -717,13 +698,12 @@ describe('#find', () => { actions: mockActions, }); const options = Symbol(); - const namespace = Symbol(); - const result = await client.find(options, namespace); + const result = await client.find(options); expect(result).toBe(returnValue); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'find')]); - expect(mockRepository.find).toHaveBeenCalledWith(options, namespace); + expect(mockRepository.find).toHaveBeenCalledWith(options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -751,9 +731,8 @@ describe('#find', () => { savedObjectTypes: [type1, type2], actions: mockActions, }); - const namespace = Symbol(); - await client.find({}, namespace); + await client.find({}); expect(mockCheckPrivileges).toHaveBeenCalledWith([ mockActions.getSavedObjectAction(type1, 'find'), @@ -761,7 +740,7 @@ describe('#find', () => { ]); expect(mockRepository.find).toHaveBeenCalledWith(expect.objectContaining({ type: [type2], - }), namespace); + })); }); test(`returns result of repository.find`, async () => { @@ -785,16 +764,14 @@ describe('#find', () => { actions: createMockActions(), }); const options = Symbol(); - const namespace = Symbol(); - const result = await client.find(options, namespace); + const result = await client.find(options); expect(result).toBe(returnValue); - expect(mockRepository.find).toHaveBeenCalledWith({ type: [type] }, namespace); + expect(mockRepository.find).toHaveBeenCalledWith({ type: [type] }); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'find', [type], { options, - namespace, }); }); }); @@ -849,9 +826,9 @@ describe('#bulkGet', () => { { type: type1 }, { type: type2 }, ]; - const namespace = Symbol(); + const options = Symbol(); - await expect(client.bulkGet(objects, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.bulkGet(objects, options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([ mockActions.getSavedObjectAction(type1, 'bulk_get'), @@ -865,7 +842,7 @@ describe('#bulkGet', () => { [mockActions.getSavedObjectAction(type1, 'bulk_get')], { objects, - namespace, + options, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -894,16 +871,16 @@ describe('#bulkGet', () => { { type: type1, id: 'foo-id' }, { type: type2, id: 'bar-id' }, ]; - const namespace = Symbol(); + const options = Symbol(); - const result = await client.bulkGet(objects, namespace); + const result = await client.bulkGet(objects, options); expect(result).toBe(returnValue); - expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, namespace); + expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'bulk_get', [type1, type2], { objects, - namespace, + options, }); }); @@ -931,12 +908,12 @@ describe('#bulkGet', () => { { type: type1, id: 'foo-id' }, { type: type2, id: 'bar-id' }, ]; - const namespace = Symbol(); + const options = Symbol(); - const result = await client.bulkGet(objects, namespace); + const result = await client.bulkGet(objects, options); expect(result).toBe(returnValue); - expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, namespace); + expect(mockRepository.bulkGet).toHaveBeenCalledWith(objects, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -984,9 +961,9 @@ describe('#get', () => { actions: mockActions, }); const id = Symbol(); - const namespace = Symbol(); + const options = Symbol(); - await expect(client.get(type, id, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.get(type, id, options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'get')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -998,7 +975,7 @@ describe('#get', () => { { type, id, - namespace, + options, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -1023,17 +1000,17 @@ describe('#get', () => { actions: createMockActions(), }); const id = Symbol(); - const namespace = Symbol(); + const options = Symbol(); - const result = await client.get(type, id, namespace); + const result = await client.get(type, id, options); expect(result).toBe(returnValue); - expect(mockRepository.get).toHaveBeenCalledWith(type, id, namespace); + expect(mockRepository.get).toHaveBeenCalledWith(type, id, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'get', [type], { type, id, - namespace, + options, }); }); @@ -1057,12 +1034,12 @@ describe('#get', () => { actions: createMockActions(), }); const id = Symbol(); - const namespace = Symbol(); + const options = Symbol(); - const result = await client.get(type, id, namespace); + const result = await client.get(type, id, options); expect(result).toBe(returnValue); - expect(mockRepository.get).toHaveBeenCalledWith(type, id, namespace); + expect(mockRepository.get).toHaveBeenCalledWith(type, id, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); @@ -1112,9 +1089,8 @@ describe('#update', () => { const id = Symbol(); const attributes = Symbol(); const options = Symbol(); - const namespace = Symbol(); - await expect(client.update(type, id, attributes, options, namespace)).rejects.toThrowError(mockErrors.forbiddenError); + await expect(client.update(type, id, attributes, options)).rejects.toThrowError(mockErrors.forbiddenError); expect(mockCheckPrivileges).toHaveBeenCalledWith([mockActions.getSavedObjectAction(type, 'update')]); expect(mockErrors.decorateForbiddenError).toHaveBeenCalledTimes(1); @@ -1128,7 +1104,6 @@ describe('#update', () => { id, attributes, options, - namespace, } ); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); @@ -1155,19 +1130,17 @@ describe('#update', () => { const id = Symbol(); const attributes = Symbol(); const options = Symbol(); - const namespace = Symbol(); - const result = await client.update(type, id, attributes, options, namespace); + const result = await client.update(type, id, attributes, options); expect(result).toBe(returnValue); - expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options, namespace); + expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).toHaveBeenCalledWith(username, 'update', [type], { type, id, attributes, options, - namespace, }); }); @@ -1193,12 +1166,11 @@ describe('#update', () => { const id = Symbol(); const attributes = Symbol(); const options = Symbol(); - const namespace = Symbol(); - const result = await client.update(type, id, attributes, options, namespace); + const result = await client.update(type, id, attributes, options); expect(result).toBe(returnValue); - expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options, namespace); + expect(mockRepository.update).toHaveBeenCalledWith(type, id, attributes, options); expect(mockAuditLogger.savedObjectsAuthorizationFailure).not.toHaveBeenCalled(); expect(mockAuditLogger.savedObjectsAuthorizationSuccess).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/__snapshots__/spaces_saved_objects_client.test.js.snap b/x-pack/plugins/spaces/server/lib/saved_objects_client/__snapshots__/spaces_saved_objects_client.test.js.snap index 9bd6165f8d057..8b1a258138355 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/__snapshots__/spaces_saved_objects_client.test.js.snap +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/__snapshots__/spaces_saved_objects_client.test.js.snap @@ -1,37 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`current space (space_1) #bulk_create throws when the base client returns a malformed document id 1`] = `"Saved object [foo/mock-id] is missing its expected space identifier."`; +exports[`default space #bulkCreate throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`current space (space_1) #bulk_get throws when base client returns documents with malformed ids 1`] = `"Saved object [foo/object_1] is missing its expected space identifier."`; +exports[`default space #bulkGet throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`current space (space_1) #create throws when the base client returns a malformed document id 1`] = `"Saved object [foo/mock-id] is missing its expected space identifier."`; +exports[`default space #create throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`current space (space_1) #delete does not allow an object to be deleted via a different space 1`] = `"not found: foo space_1:object_2"`; +exports[`default space #delete throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`current space (space_1) #find throws when base client returns documents with malformed ids 1`] = `"Saved object [foo/object_1] is missing its expected space identifier."`; +exports[`default space #find throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`current space (space_1) #get returns error when the object belongs to a different space 1`] = `"not found: foo space_1:object_2"`; +exports[`default space #get throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`current space (space_1) #get returns error when the object has a malformed identifier 1`] = `"Saved object [foo/object_1] is missing its expected space identifier."`; +exports[`default space #update throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`current space (space_1) #update does not allow an object to be updated via a different space 1`] = `"not found: foo space_1:object_2"`; +exports[`space_1 space #bulkCreate throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`current space (space_1) #update throws when the base client returns a malformed document id 1`] = `"Saved object [foo/object_1] is missing its expected space identifier."`; +exports[`space_1 space #bulkGet throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`default space #bulk_create throws when the base client returns a malformed document id 1`] = `"Saved object [foo/default:default] has an unexpected space identifier [default]."`; +exports[`space_1 space #create throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`default space #bulk_get throws when the base client returns a malformed document id 1`] = `"Saved object [foo/default:default] has an unexpected space identifier [default]."`; +exports[`space_1 space #delete throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`default space #create throws when the base client returns a malformed document id 1`] = `"Saved object [foo/default:default] has an unexpected space identifier [default]."`; +exports[`space_1 space #find throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`default space #delete does not allow an object to be deleted via a different space 1`] = `"not found: foo object_2"`; +exports[`space_1 space #get throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; -exports[`default space #find throws when the base client returns a malformed document id 1`] = `"Saved object [foo/default:default] has an unexpected space identifier [default]."`; - -exports[`default space #get returns error when the object belongs to a different space 1`] = `"not found: foo object_2"`; - -exports[`default space #get throws when the base client returns a malformed document id 1`] = `"Saved object [foo/default:default] has an unexpected space identifier [default]."`; - -exports[`default space #update does not allow an object to be updated via a different space 1`] = `"not found: foo object_2"`; - -exports[`default space #update throws when the base client returns a malformed document id 1`] = `"Saved object [space/default:default] has an unexpected space identifier [default]."`; +exports[`space_1 space #update throws error if options.namespace is specified 1`] = `"Spaces currently determines the namespaces"`; diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js index 4ef77e933d610..7f66b145e593e 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.js @@ -27,15 +27,18 @@ export class SpacesSavedObjectsClient { * @param {object} [options={}] * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async create(type, attributes = {}, options = {}, namespace) { - if (namespace) { + async create(type, attributes = {}, options = {}) { + if (options.namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.create(type, attributes, options, this._getNamespace(this._spaceId)); + return await this._client.create(type, attributes, { + ...options, + namespace: this._getNamespace(this._spaceId) + }); } /** @@ -44,15 +47,18 @@ export class SpacesSavedObjectsClient { * @param {array} objects - [{ type, id, attributes, extraDocumentProperties }] * @param {object} [options={}] * @property {boolean} [options.overwrite=false] - overwrites existing documents - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ - async bulkCreate(objects, options = {}, namespace) { - if (namespace) { + async bulkCreate(objects, options = {}) { + if (options.namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.bulkCreate(objects, options, this._getNamespace(this._spaceId)); + return await this._client.bulkCreate(objects, { + ...options, + namespace: this._getNamespace(this._spaceId) + }); } /** @@ -60,15 +66,19 @@ export class SpacesSavedObjectsClient { * * @param {string} type * @param {string} id - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} */ - async delete(type, id, namespace) { - if (namespace) { + async delete(type, id, options = {}) { + if (options.namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.delete(type, id, this._getNamespace(this._spaceId)); + return await this._client.delete(type, id, { + ...options, + namespace: this._getNamespace(this._spaceId) + }); } /** @@ -82,22 +92,26 @@ export class SpacesSavedObjectsClient { * @property {string} [options.sortField] * @property {string} [options.sortOrder] * @property {Array} [options.fields] - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find(options = {}, namespace) { - if (namespace) { + async find(options = {}) { + if (options.namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.find(options, this._getNamespace(this._spaceId)); + return await this._client.find({ + ...options, + namespace: this._getNamespace(this._spaceId) + }); } /** * Returns an array of objects by id * * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example * @@ -106,12 +120,15 @@ export class SpacesSavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet(objects = [], namespace) { - if (namespace) { + async bulkGet(objects = [], options = {}) { + if (options.namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.bulkGet(objects, this._getNamespace(this._spaceId)); + return await this._client.bulkGet(objects, { + ...options, + namespace: this._getNamespace(this._spaceId) + }); } /** @@ -119,15 +136,19 @@ export class SpacesSavedObjectsClient { * * @param {string} type * @param {string} id - * @param {string} [namespace] + * @param {object} [options={}] + * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async get(type, id, namespace) { - if (namespace) { + async get(type, id, options = {}) { + if (options.namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.get(type, id, this._getNamespace(this._spaceId)); + return await this._client.get(type, id, { + ...options, + namespace: this._getNamespace(this._spaceId) + }); } /** @@ -137,15 +158,18 @@ export class SpacesSavedObjectsClient { * @param {string} id * @param {object} [options={}] * @property {integer} options.version - ensures version matches that of persisted object - * @param {string} [namespace] + * @property {string} [options.namespace] * @returns {promise} */ - async update(type, id, attributes, options = {}, namespace) { - if (namespace) { + async update(type, id, attributes, options = {}) { + if (options.namespace) { throw new Error('Spaces currently determines the namespaces'); } - return await this._client.update(type, id, attributes, options, this._getNamespace(this._spaceId)); + return await this._client.update(type, id, attributes, { + ...options, + namespace: this._getNamespace(this._spaceId) + }); } _getNamespace(spaceId) { diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.js b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.js index e7dbffa413c9e..a25ccc2136de9 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.js +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.js @@ -6,29 +6,7 @@ import { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; import { createSpacesService } from '../create_spaces_service'; - -jest.mock('uuid', () => ({ - v1: jest.fn(() => `mock-id`) -})); import { DEFAULT_SPACE_ID } from '../../../common/constants'; -import { cloneDeep } from 'lodash'; - -const createObjectEntry = (type, id, spaceId) => ({ - [id]: { - id, - type, - spaceId - } -}); - -const SAVED_OBJECTS = { - ...createObjectEntry('foo', 'object_0'), - ...createObjectEntry('foo', 'space_1:object_1', 'space_1'), - ...createObjectEntry('foo', 'space_2:object_2', 'space_2'), - ...createObjectEntry('space', 'space_1'), -}; - -const createSavedObjects = () => cloneDeep(SAVED_OBJECTS); const config = { 'server.basePath': '/' @@ -46,1723 +24,289 @@ const createMockRequest = (space) => ({ getBasePath: () => space.id !== DEFAULT_SPACE_ID ? `/s/${space.id}` : '', }); -const createMockClient = (space, { mangleSpaceIdentifier = false } = {}) => { - const errors = { - createGenericNotFoundError: jest.fn((type, id) => { - return new Error(`not found: ${type} ${id}`); - }) - }; - - const maybeTransformSavedObject = (savedObject) => { - if (!mangleSpaceIdentifier) { - return savedObject; - } - if (space.id === DEFAULT_SPACE_ID) { - savedObject.id = `default:${space.id}`; - } else { - savedObject.id = savedObject.id.split(':')[1]; - } - - return savedObject; - }; +const createMockClient = () => { + const errors = Symbol(); return { - get: jest.fn((type, id) => { - const result = createSavedObjects()[id]; - if (!result) { - throw errors.createGenericNotFoundError(type, id); - } - - return maybeTransformSavedObject(result); - }), - bulkGet: jest.fn((objects) => { - return { - saved_objects: objects.map(object => { - const result = createSavedObjects()[object.id]; - if (!result) { - return { - id: object.id, - type: object.type, - error: { statusCode: 404, message: 'Not found' } - }; - } - return maybeTransformSavedObject(result); - }) - }; - }), - find: jest.fn(({ type }) => { - // used to locate spaces when type is `space` within these tests - if (type === 'space') { - return { - saved_objects: [space] - }; - } - const objects = createSavedObjects(); - const result = Object.keys(objects) - .filter(key => objects[key].spaceId === space.id || (space.id === DEFAULT_SPACE_ID && !objects[key].spaceId)) - .map(key => maybeTransformSavedObject(objects[key])); - - return { - saved_objects: result - }; - }), - create: jest.fn((type, attributes, options) => { - return maybeTransformSavedObject({ - id: options.id || 'foo-id', - type, - attributes - }); - }), - bulkCreate: jest.fn((objects) => { - return { - saved_objects: cloneDeep(objects).map(maybeTransformSavedObject) - }; - }), - update: jest.fn((type, id, attributes) => { - return maybeTransformSavedObject({ - id, - type, - attributes - }); - }), + get: jest.fn(), + bulkGet: jest.fn(), + find: jest.fn(), + create: jest.fn(), + bulkCreate: jest.fn(), + update: jest.fn(), delete: jest.fn(), errors, }; }; -describe('default space', () => { - const currentSpace = { - id: 'default', - }; - - describe('#get', () => { - test(`returns the object when it belongs to the current space`, async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_0'; - const options = {}; - - const result = await client.get(type, id, options); - - expect(result).toEqual(SAVED_OBJECTS[id]); - }); - - test(`does not append the space id to the document id`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_0'; - const options = {}; - - await client.get(type, id, options); - - expect(baseClient.get).toHaveBeenCalledWith(type, id, { extraDocumentProperties: ['spaceId'] }); - }); - - test(`returns global objects that don't belong to a specific space`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'space'; - const id = 'space_1'; - const options = {}; - - const result = await client.get(type, id, options); - - expect(result).toEqual(SAVED_OBJECTS[id]); - }); - - test(`merges options.extraDocumentProperties`, async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_0'; - const options = { - extraDocumentProperties: ['otherSourceProp'] - }; - - await client.get(type, id, options); - - expect(baseClient.get).toHaveBeenCalledWith(type, id, { - extraDocumentProperties: ['spaceId', 'otherSourceProp'] - }); - }); - - test(`returns error when the object belongs to a different space`, async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_2'; - const options = {}; - - await expect(client.get(type, id, options)).rejects.toThrowErrorMatchingSnapshot(); - }); - - test(`throws when the base client returns a malformed document id`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_0'; - const options = {}; - - await expect(client.get(type, id, options)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('#bulk_get', () => { - test(`only returns objects belonging to the current space`, async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const options = {}; - - const result = await client.bulkGet([{ - type, - id: 'object_0' - }, { - type, - id: 'object_2' - }], options); - - expect(result).toEqual({ - saved_objects: [{ - id: 'object_0', - type: 'foo', - }, { - id: 'object_2', - type: 'foo', - error: { - message: 'Not found', - statusCode: 404 - } - }] - }); - }); - - test(`does not append the space id to the document id`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const options = {}; - - const objects = [{ - type, - id: 'object_0' - }, { - type, - id: 'object_2' - }]; - - await client.bulkGet(objects, options); - - expect(baseClient.bulkGet).toHaveBeenCalledWith(objects, { ...options, extraDocumentProperties: ["spaceId", "type"] }); - }); - - test(`returns global objects that don't belong to a specific space`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const options = {}; - - const result = await client.bulkGet([{ - type, - id: 'object_0' - }, { - type, - id: 'space_1' - }], options); - - expect(result).toEqual({ - saved_objects: [{ - id: 'object_0', - type: 'foo', - }, { - id: 'space_1', - type: 'space', - }] - }); - }); - - test(`merges options.extraDocumentProperties`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - - const objects = [{ - type, - id: 'object_1' - }, { - type, - id: 'object_2' - }]; - - const options = { - extraDocumentProperties: ['otherSourceProp'] - }; - - await client.bulkGet(objects, options); - - expect(baseClient.bulkGet).toHaveBeenCalledWith(objects, { - extraDocumentProperties: ['spaceId', 'type', 'otherSourceProp'] - }); - }); - - test(`throws when the base client returns a malformed document id`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_0'; - const options = {}; - - await expect(client.bulkGet([{ type, id }], options)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('#find', () => { - test(`creates ES query filters restricting objects to the current space`, async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = ['foo', 'space']; - const options = { - type - }; - - await client.find(options); - - expect(baseClient.find).toHaveBeenCalledWith({ - type, - filters: [{ - bool: { - minimum_should_match: 1, - should: [{ - bool: { - must: [{ - term: { - type: 'foo' - }, - }], - must_not: [{ - exists: { - field: "spaceId" - } - }] - } - }, { - bool: { - must: [{ - term: { - type: 'space' - }, - }], - } - }] - } - }] - }); - }); - - test(`merges incoming filters with filters generated by Spaces Saved Objects Client`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const otherFilters = [{ - bool: { - must: [{ - term: { - foo: 'bar' - } - }] - } - }]; - - const options = { - type, - filters: otherFilters - }; - - await client.find(options); - - expect(baseClient.find).toHaveBeenCalledWith({ - type, - filters: [...otherFilters, { - bool: { - minimum_should_match: 1, - should: [{ - bool: { - must: [{ - term: { - type - }, - }], - must_not: [{ - exists: { - field: "spaceId" - } - }] - } - }] - } - }] - }); - }); - - test(`throws when the base client returns a malformed document id`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const options = { type }; - - await expect(client.find(options)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('#create', () => { - - test('does not assign a space-unaware (global) object to a space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'space'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await client.create(type, attributes); - - expect(baseClient.create).toHaveBeenCalledWith(type, attributes, { extraDocumentProperties: {}, id: 'mock-id' }); - }); - - test('does not assign a spaceId to space-aware objects belonging to the default space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await client.create(type, attributes); - - // called without extraDocumentProperties - expect(baseClient.create).toHaveBeenCalledWith(type, attributes, { extraDocumentProperties: {}, id: 'mock-id' }); - }); - - test(`throws when the base client returns a malformed document id`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await expect(client.create(type, attributes)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('#bulk_create', () => { - test('allows for bulk creation when all types are space-aware', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - const objects = [{ - type: 'foo', - attributes - }, { - type: 'bar', - attributes - }]; - - await client.bulkCreate(objects, {}); - - const expectedCalledWithObjects = objects.map(object => ({ - ...object, - extraDocumentProperties: {}, - id: 'mock-id' - })); - - expect(baseClient.bulkCreate).toHaveBeenCalledWith(expectedCalledWithObjects, {}); - }); - - test('allows for bulk creation when all types are not space-aware (global)', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - const objects = [{ - type: 'space', - attributes - }, { - type: 'space', - attributes - }]; - - await client.bulkCreate(objects, {}); - - expect(baseClient.bulkCreate).toHaveBeenCalledWith(objects.map(o => { - return { ...o, extraDocumentProperties: {}, id: 'mock-id' }; - }), {}); - }); - - test('allows space-aware and non-space-aware (global) objects to be created at the same time', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - const objects = [{ - type: 'space', - attributes - }, { - type: 'foo', - attributes - }]; - - await client.bulkCreate(objects, {}); - - const expectedCalledWithObjects = [...objects]; - expectedCalledWithObjects[0] = { - ...expectedCalledWithObjects[0], - extraDocumentProperties: {}, - id: 'mock-id' - }; - expectedCalledWithObjects[1] = { - ...expectedCalledWithObjects[1], - extraDocumentProperties: {}, - id: 'mock-id' - }; - - expect(baseClient.bulkCreate).toHaveBeenCalledWith(expectedCalledWithObjects, {}); - }); - - test('does not assign a spaceId to space-aware objects that belong to the default space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - const objects = [{ - type: 'foo', - attributes - }, { - type: 'bar', - attributes - }]; - - await client.bulkCreate(objects, {}); - - // called with empty extraDocumentProperties - expect(baseClient.bulkCreate).toHaveBeenCalledWith(objects.map(o => ({ - ...o, - extraDocumentProperties: {}, - id: 'mock-id' - })), {}); - }); - - test(`throws when the base client returns a malformed document id`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - const objects = [{ - type: 'foo', - attributes - }, { - type: 'bar', - attributes - }]; - - await expect(client.bulkCreate(objects, {})).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('#update', () => { - test('allows an object to be updated if it exists in the same space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'object_0'; - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; +[ + { id: DEFAULT_SPACE_ID, expectedNamespace: undefined }, + { id: 'space_1', expectedNamespace: 'space_1' } +].forEach(currentSpace => { + describe(`${currentSpace.id} space`, () => { - await client.update(type, id, attributes); + describe('#get', () => { + test(`throws error if options.namespace is specified`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const spacesService = createSpacesService(server); - expect(baseClient.update).toHaveBeenCalledWith(type, id, attributes, { extraDocumentProperties: {} }); - }); - - test('allows a global object to be updated', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + await expect(client.get(null, null, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); }); - const id = 'space_1'; - const type = 'space'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; + test(`supplements options with undefined namespace`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const expectedReturnValue = Symbol(); + baseClient.get.mockReturnValue(expectedReturnValue); + const spacesService = createSpacesService(server); - await client.update(type, id, attributes); - - expect(baseClient.update).toHaveBeenCalledWith(type, id, attributes, { extraDocumentProperties: {} }); - }); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); + const type = Symbol(); + const id = Symbol(); + const options = Object.freeze({ foo: 'bar' }); + const actualReturnValue = await client.get(type, id, options); - test('does not allow an object to be updated via a different space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.get).toHaveBeenCalledWith(type, id, { foo: 'bar', namespace: currentSpace.expectedNamespace }); }); - - const id = 'object_2'; - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await expect(client.update(type, id, attributes)).rejects.toThrowErrorMatchingSnapshot(); - }); - - test(`throws when the base client returns a malformed document id`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'space_1'; - const type = 'space'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await expect(client.update(type, id, attributes)).rejects.toThrowErrorMatchingSnapshot(); }); - }); - describe('#delete', () => { - test('allows an object to be deleted if it exists in the same space', async () => { + describe('#bulkGet', () => { + test(`throws error if options.namespace is specified`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const spacesService = createSpacesService(server); - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + await expect(client.bulkGet(null, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); }); - const id = 'object_0'; - const type = 'foo'; + test(`supplements options with undefined namespace`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const expectedReturnValue = Symbol(); + baseClient.bulkGet.mockReturnValue(expectedReturnValue); + const spacesService = createSpacesService(server); - await client.delete(type, id); - - expect(baseClient.delete).toHaveBeenCalledWith(type, id); - }); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - test(`allows a global object to be deleted`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); + const objects = Symbol(); + const options = Object.freeze({ foo: 'bar' }); + const actualReturnValue = await client.bulkGet(objects, options); - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.bulkGet).toHaveBeenCalledWith(objects, { foo: 'bar', namespace: currentSpace.expectedNamespace }); }); - - const id = 'space_1'; - const type = 'space'; - - await client.delete(type, id); - - expect(baseClient.delete).toHaveBeenCalledWith(type, id); }); - test('does not allow an object to be deleted via a different space', async () => { + describe('#find', () => { + test(`throws error if options.namespace is specified`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const spacesService = createSpacesService(server); - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + await expect(client.find({ namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); }); - const id = 'object_2'; - const type = 'foo'; - - await expect(client.delete(type, id)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); -}); - -describe('current space (space_1)', () => { - const currentSpace = { - id: 'space_1', - }; + test(`supplements options with undefined namespace`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const expectedReturnValue = Symbol(); + baseClient.find.mockReturnValue(expectedReturnValue); + const spacesService = createSpacesService(server); - describe('#get', () => { - test(`returns the object when it belongs to the current space`, async () => { + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); + const options = Object.freeze({ foo: 'bar' }); + const actualReturnValue = await client.find(options); - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_1'; - const options = {}; - - const result = await client.get(type, id, options); - - expect(result).toEqual({ - id, - type, - spaceId: currentSpace.id + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.find).toHaveBeenCalledWith({ foo: 'bar', namespace: currentSpace.expectedNamespace }); }); }); - test('appends the space id to the document id', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_1'; - const options = {}; - - await client.get(type, id, options); - - expect(baseClient.get).toHaveBeenCalledWith(type, `${currentSpace.id}:${id}`, { ...options, extraDocumentProperties: ['spaceId'] }); - }); - - test(`returns global objects that don't belong to a specific space`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'space'; - const id = 'space_1'; - const options = {}; - - const result = await client.get(type, id, options); - - expect(result).toEqual(SAVED_OBJECTS[id]); - }); - - test(`merges options.extraDocumentProperties`, async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const id = 'object_1'; - const options = { - extraDocumentProperties: ['otherSourceProp'] - }; - - await client.get(type, id, options); - - expect(baseClient.get).toHaveBeenCalledWith(type, `${currentSpace.id}:${id}`, { - extraDocumentProperties: ['spaceId', 'otherSourceProp'] - }); - }); + describe('#create', () => { + test(`throws error if options.namespace is specified`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const spacesService = createSpacesService(server); - test(`returns error when the object belongs to a different space`, async () => { + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); + }); - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + await expect(client.create('foo', {}, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); }); - const type = 'foo'; - const id = 'object_2'; - const options = {}; + test(`supplements options with undefined namespace`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const expectedReturnValue = Symbol(); + baseClient.create.mockReturnValue(expectedReturnValue); + const spacesService = createSpacesService(server); - await expect(client.get(type, id, options)).rejects.toThrowErrorMatchingSnapshot(); - }); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - test(`returns error when the object has a malformed identifier`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); + const type = Symbol(); + const attributes = Symbol(); + const options = Object.freeze({ foo: 'bar' }); + const actualReturnValue = await client.create(type, attributes, options); - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.create).toHaveBeenCalledWith(type, attributes, { foo: 'bar', namespace: currentSpace.expectedNamespace }); }); - - const type = 'foo'; - const id = 'object_1'; - const options = {}; - - await expect(client.get(type, id, options)).rejects.toThrowErrorMatchingSnapshot(); }); - }); - describe('#bulk_get', () => { - test(`only returns objects belonging to the current space`, async () => { + describe('#bulkCreate', () => { + test(`throws error if options.namespace is specified`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const spacesService = createSpacesService(server); - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + await expect(client.bulkCreate(null, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); }); - const type = 'foo'; - const options = {}; + test(`supplements options with undefined namespace`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const expectedReturnValue = Symbol(); + baseClient.bulkCreate.mockReturnValue(expectedReturnValue); + const spacesService = createSpacesService(server); - const result = await client.bulkGet([{ - type, - id: 'object_1' - }, { - type, - id: 'object_2' - }], options); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - expect(result).toEqual({ - saved_objects: [{ - id: 'object_1', - spaceId: 'space_1', - type: 'foo', - }, { - id: 'object_2', - type: 'foo', - error: { - message: 'Not found', - statusCode: 404 - } - }] - }); - }); + const objects = Symbol(); + const options = Object.freeze({ foo: 'bar' }); + const actualReturnValue = await client.bulkCreate(objects, options); - test('appends the space id to the document id', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.bulkCreate).toHaveBeenCalledWith(objects, { foo: 'bar', namespace: currentSpace.expectedNamespace }); }); - - const type = 'foo'; - const options = {}; - const objects = [{ - type, - id: 'object_1' - }, { - type, - id: 'object_2' - }]; - - await client.bulkGet(objects, options); - - const expectedObjects = objects.map(o => ({ ...o, id: `${currentSpace.id}:${o.id}` })); - expect(baseClient.bulkGet) - .toHaveBeenCalledWith(expectedObjects, { ...options, extraDocumentProperties: ["spaceId", "type"] }); }); - test(`returns global objects that don't belong to a specific space`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const options = {}; + describe('#update', () => { + test(`throws error if options.namespace is specified`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const spacesService = createSpacesService(server); - const result = await client.bulkGet([{ - type, - id: 'object_1' - }, { - type: 'space', - id: 'space_1' - }], options); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - expect(result).toEqual({ - saved_objects: [{ - id: 'object_1', - spaceId: 'space_1', - type: 'foo', - }, { - id: 'space_1', - type: 'space', - }] + await expect(client.update(null, null, null, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); }); - }); - test(`merges options.extraDocumentProperties`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); + test(`supplements options with undefined namespace`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const expectedReturnValue = Symbol(); + baseClient.update.mockReturnValue(expectedReturnValue); + const spacesService = createSpacesService(server); - const type = 'foo'; + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - const objects = [{ - type, - id: 'object_1' - }, { - type, - id: 'object_2' - }]; + const type = Symbol(); + const id = Symbol(); + const attributes = Symbol(); + const options = Object.freeze({ foo: 'bar' }); + const actualReturnValue = await client.update(type, id, attributes, options); - const options = { - extraDocumentProperties: ['otherSourceProp'] - }; - - await client.bulkGet(objects, options); - - const expectedCalledWithObjects = objects.map(object => { - const id = `${currentSpace.id}:${object.id}`; - return { - ...object, - id - }; - }); - - expect(baseClient.bulkGet).toHaveBeenCalledWith(expectedCalledWithObjects, { - extraDocumentProperties: ['spaceId', 'type', 'otherSourceProp'] + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.update).toHaveBeenCalledWith(type, id, attributes, { foo: 'bar', namespace: currentSpace.expectedNamespace }); }); }); - test(`throws when base client returns documents with malformed ids`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const objects = [{ - type, - id: 'object_1' - }]; - const options = {}; + describe('#delete', () => { + test(`throws error if options.namespace is specified`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const spacesService = createSpacesService(server); - await expect(client.bulkGet(objects, options)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - describe('#find', () => { - test(`creates ES query filters restricting objects to the current space`, async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], + await expect(client.delete(null, null, { namespace: 'bar' })).rejects.toThrowErrorMatchingSnapshot(); }); - const type = ['foo', 'space']; - const options = { - type - }; + test(`supplements options with undefined namespace`, async () => { + const request = createMockRequest({ id: currentSpace.id }); + const baseClient = createMockClient(); + const expectedReturnValue = Symbol(); + baseClient.delete.mockReturnValue(expectedReturnValue); + const spacesService = createSpacesService(server); - await client.find(options); + const client = new SpacesSavedObjectsClient({ + request, + baseClient, + spacesService, + }); - expect(baseClient.find).toHaveBeenCalledWith({ - type, - filters: [{ - bool: { - minimum_should_match: 1, - should: [{ - bool: { - must: [{ - term: { - type: 'foo' - }, - }, { - term: { - spaceId: 'space_1' - } - }], - } - }, { - bool: { - must: [{ - term: { - type: 'space' - }, - }], - } - }] - } - }] - }); - }); + const type = Symbol(); + const id = Symbol(); + const options = Object.freeze({ foo: 'bar' }); + const actualReturnValue = await client.delete(type, id, options); - test(`merges incoming filters with filters generated by Spaces Saved Objects Client`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const otherFilters = [{ - bool: { - must: [{ - term: { - foo: 'bar' - } - }] - } - }]; - - const options = { - type, - filters: otherFilters - }; - - await client.find(options); - - expect(baseClient.find).toHaveBeenCalledWith({ - type, - filters: [...otherFilters, { - bool: { - minimum_should_match: 1, - should: [{ - bool: { - must: [{ - term: { - type - }, - }, { - term: { - spaceId: 'space_1' - } - }] - } - }] - } - }] + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.delete).toHaveBeenCalledWith(type, id, { foo: 'bar', namespace: currentSpace.expectedNamespace }); }); }); - - test(`throws when base client returns documents with malformed ids`, async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const options = { - type, - }; - - await expect(client.find(options)).rejects.toThrowErrorMatchingSnapshot(); - }); }); - describe('#create', () => { - test('automatically assigns the object to the current space via extraDocumentProperties', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await client.create(type, attributes); - - expect(baseClient.create).toHaveBeenCalledWith(type, attributes, { - id: `${currentSpace.id}:mock-id`, - extraDocumentProperties: { - spaceId: 'space_1' - } - }); - }); - - test('does not assign a space-unaware (global) object to a space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'space'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await client.create(type, attributes); - - expect(baseClient.create).toHaveBeenCalledWith(type, attributes, { extraDocumentProperties: {}, id: 'mock-id' }); - }); - - test('throws when the base client returns a malformed document id', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await expect(client.create(type, attributes)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('#bulk_create', () => { - test('allows for bulk creation when all types are space-aware', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - const objects = [{ - type: 'foo', - attributes - }, { - type: 'bar', - attributes - }]; - - await client.bulkCreate(objects, {}); - - const expectedCalledWithObjects = objects.map(object => ({ - ...object, - extraDocumentProperties: { - spaceId: 'space_1' - }, - id: `${currentSpace.id}:mock-id` - })); - - expect(baseClient.bulkCreate).toHaveBeenCalledWith(expectedCalledWithObjects, {}); - }); - - test('allows for bulk creation when all types are not space-aware', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - const objects = [{ - type: 'space', - attributes - }, { - type: 'space', - attributes - }]; - - await client.bulkCreate(objects, {}); - - expect(baseClient.bulkCreate).toHaveBeenCalledWith(objects.map(o => { - return { ...o, extraDocumentProperties: {}, id: 'mock-id' }; - }), {}); - }); - - test('allows space-aware and non-space-aware (global) objects to be created at the same time', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - const objects = [{ - type: 'space', - attributes - }, { - type: 'foo', - attributes - }]; - - await client.bulkCreate(objects, {}); - - const expectedCalledWithObjects = [...objects]; - expectedCalledWithObjects[0] = { - ...expectedCalledWithObjects[0], - extraDocumentProperties: {}, - id: 'mock-id' - }; - expectedCalledWithObjects[1] = { - ...expectedCalledWithObjects[1], - extraDocumentProperties: { - spaceId: 'space_1' - }, - id: `${currentSpace.id}:mock-id` - }; - - expect(baseClient.bulkCreate).toHaveBeenCalledWith(expectedCalledWithObjects, {}); - }); - - test('throws when the base client returns a malformed document id', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await expect(client.bulkCreate([{ type, attributes }])).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('#update', () => { - test('allows an object to be updated if it exists in the same space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'object_1'; - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await client.update(type, id, attributes); - - expect(baseClient.update) - .toHaveBeenCalledWith(type, `${currentSpace.id}:${id}`, attributes, { extraDocumentProperties: { spaceId: 'space_1' } }); - }); - - test('allows a global object to be updated', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'space_1'; - const type = 'space'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await client.update(type, id, attributes); - - expect(baseClient.update).toHaveBeenCalledWith(type, id, attributes, { extraDocumentProperties: {} }); - }); - - test('does not allow an object to be updated via a different space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'object_2'; - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await expect(client.update(type, id, attributes)).rejects.toThrowErrorMatchingSnapshot(); - }); - - test('throws when the base client returns a malformed document id', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace, { mangleSpaceIdentifier: true }); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'object_1'; - const type = 'foo'; - const attributes = { - prop1: 'value 1', - prop2: 'value 2' - }; - - await expect(client.update(type, id, attributes)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('#delete', () => { - test('allows an object to be deleted if it exists in the same space', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'object_1'; - const type = 'foo'; - - await client.delete(type, id); - - expect(baseClient.delete).toHaveBeenCalledWith(type, `${currentSpace.id}:${id}`); - }); - - test('allows a global object to be deleted', async () => { - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'space_1'; - const type = 'space'; - - await client.delete(type, id); - - expect(baseClient.delete).toHaveBeenCalledWith(type, id); - }); - - test('does not allow an object to be deleted via a different space', async () => { - - const request = createMockRequest(currentSpace); - const baseClient = createMockClient(currentSpace); - const spacesService = createSpacesService(server); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - spacesService, - types: [], - }); - - const id = 'object_2'; - const type = 'foo'; - - await expect(client.delete(type, id)).rejects.toThrowErrorMatchingSnapshot(); - }); - }); }); From c424230527f7e6bc86a4a45572294bfe64c2faf0 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 28 Aug 2018 10:44:50 -0400 Subject: [PATCH 20/24] Removing duplicate test --- src/server/saved_objects/service/lib/schema.test.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/server/saved_objects/service/lib/schema.test.js b/src/server/saved_objects/service/lib/schema.test.js index 5c92a3d40414f..2cd95fdede14f 100644 --- a/src/server/saved_objects/service/lib/schema.test.js +++ b/src/server/saved_objects/service/lib/schema.test.js @@ -49,13 +49,6 @@ describe('#addNamespaceAgnosticType', () => { const schema = new SavedObjectsSchema(); expect(() => schema.addNamespaceAgnosticType(Symbol())).toThrowErrorMatchingSnapshot(); }); - - it(`returns true for namespace agnostic registered types`, () => { - const schema = new SavedObjectsSchema(); - schema.addNamespaceAgnosticType('foo'); - const result = schema.isNamespaceAgnostic('foo'); - expect(result).toBe(true); - }); }); describe('#isNamespaceAgnostic', () => { From 607b807377cc24b31bfbea3731211c6208683418 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 28 Aug 2018 10:54:18 -0400 Subject: [PATCH 21/24] Removing spaceId from mappings --- x-pack/plugins/spaces/mappings.json | 3 --- .../fixtures/es_archiver/saved_objects/spaces/mappings.json | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/x-pack/plugins/spaces/mappings.json b/x-pack/plugins/spaces/mappings.json index 91c6e324e197e..6c91d9b020ff6 100644 --- a/x-pack/plugins/spaces/mappings.json +++ b/x-pack/plugins/spaces/mappings.json @@ -1,7 +1,4 @@ { - "spaceId": { - "type": "keyword" - }, "space": { "properties": { "name": { diff --git a/x-pack/test/spaces_api_integration/fixtures/es_archiver/saved_objects/spaces/mappings.json b/x-pack/test/spaces_api_integration/fixtures/es_archiver/saved_objects/spaces/mappings.json index bb9b0c33b7394..d8d4c52985bf5 100644 --- a/x-pack/test/spaces_api_integration/fixtures/es_archiver/saved_objects/spaces/mappings.json +++ b/x-pack/test/spaces_api_integration/fixtures/es_archiver/saved_objects/spaces/mappings.json @@ -30,9 +30,6 @@ } } }, - "spaceId": { - "type": "keyword" - }, "space": { "properties": { "name": { @@ -308,4 +305,4 @@ }, "aliases": {} } -} \ No newline at end of file +} From 002e3b007f57780531417e8d1f60afc1de90b145 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 28 Aug 2018 12:57:23 -0400 Subject: [PATCH 22/24] Fixing test --- .../saved_objects/service/lib/search_dsl/query_params.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/saved_objects/service/lib/search_dsl/query_params.test.js b/src/server/saved_objects/service/lib/search_dsl/query_params.test.js index ac7d301c73ecf..45f241ba41883 100644 --- a/src/server/saved_objects/service/lib/search_dsl/query_params.test.js +++ b/src/server/saved_objects/service/lib/search_dsl/query_params.test.js @@ -157,14 +157,14 @@ describe('searchDsl/queryParams', () => { describe('type (singular, global)', () => { it('includes a terms filter for type and namespace not being specified', () => { - expect(getQueryParams(MAPPINGS, SCHEMA, null, 'saved')) + expect(getQueryParams(MAPPINGS, SCHEMA, null, 'global')) .toEqual({ query: { bool: { filter: [{ bool: { should: [ - createTypeClause('saved'), + createTypeClause('global'), ], minimum_should_match: 1 } From d213cd8709b634bd76cad7a76f909de24e825ab6 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 4 Sep 2018 10:18:38 -0400 Subject: [PATCH 23/24] Registering the namespace agnostic types using uiExports --- src/server/saved_objects/saved_objects_mixin.js | 2 +- .../service/create_saved_objects_service.js | 4 ++-- src/server/saved_objects/service/lib/schema.js | 12 +++--------- src/ui/ui_exports/ui_export_types/index.js | 3 ++- .../{saved_object_mappings.js => saved_objects.js} | 4 +++- x-pack/plugins/spaces/index.js | 5 +++-- 6 files changed, 14 insertions(+), 16 deletions(-) rename src/ui/ui_exports/ui_export_types/{saved_object_mappings.js => saved_objects.js} (89%) diff --git a/src/server/saved_objects/saved_objects_mixin.js b/src/server/saved_objects/saved_objects_mixin.js index 086107953cf37..61ee6d2a5e762 100644 --- a/src/server/saved_objects/saved_objects_mixin.js +++ b/src/server/saved_objects/saved_objects_mixin.js @@ -54,7 +54,7 @@ export function savedObjectsMixin(kbnServer, server) { server.route(createGetRoute(prereqs)); server.route(createUpdateRoute(prereqs)); - server.decorate('server', 'savedObjects', createSavedObjectsService(server)); + server.decorate('server', 'savedObjects', createSavedObjectsService(server, kbnServer.uiExports.savedObjectsSchema)); const savedObjectsClientCache = new WeakMap(); server.decorate('request', 'getSavedObjectsClient', function () { diff --git a/src/server/saved_objects/service/create_saved_objects_service.js b/src/server/saved_objects/service/create_saved_objects_service.js index c2d24296ade16..35e2d385d435a 100644 --- a/src/server/saved_objects/service/create_saved_objects_service.js +++ b/src/server/saved_objects/service/create_saved_objects_service.js @@ -21,7 +21,7 @@ import { getRootPropertiesObjects } from '../../mappings'; import { SavedObjectsRepository, ScopedSavedObjectsClientProvider, SavedObjectsRepositoryProvider, SavedObjectsSchema } from './lib'; import { SavedObjectsClient } from './saved_objects_client'; -export function createSavedObjectsService(server) { +export function createSavedObjectsService(server, uiExportsSchema) { const onBeforeWrite = async () => { const adminCluster = server.plugins.elasticsearch.getCluster('admin'); @@ -59,7 +59,7 @@ export function createSavedObjectsService(server) { } }; - const schema = new SavedObjectsSchema(); + const schema = new SavedObjectsSchema(uiExportsSchema); const mappings = server.getKibanaIndexMappingsDsl(); const repositoryProvider = new SavedObjectsRepositoryProvider({ diff --git a/src/server/saved_objects/service/lib/schema.js b/src/server/saved_objects/service/lib/schema.js index 7a75b986bf373..12630945d83c0 100644 --- a/src/server/saved_objects/service/lib/schema.js +++ b/src/server/saved_objects/service/lib/schema.js @@ -18,17 +18,11 @@ */ export class SavedObjectsSchema { - _namespaceAgnosticTypes = []; - - addNamespaceAgnosticType(type) { - if (typeof type !== 'string') { - throw new Error('type must be a string'); - } - - this._namespaceAgnosticTypes.push(type); + constructor(uiExportsSchema) { + this._schema = uiExportsSchema; } isNamespaceAgnostic(type) { - return this._namespaceAgnosticTypes.includes(type); + return this._schema.namespaceAgnosticTypes.includes(type); } } diff --git a/src/ui/ui_exports/ui_export_types/index.js b/src/ui/ui_exports/ui_export_types/index.js index 2247b685bd063..9f72ff9767f91 100644 --- a/src/ui/ui_exports/ui_export_types/index.js +++ b/src/ui/ui_exports/ui_export_types/index.js @@ -24,7 +24,8 @@ export { export { mappings, -} from './saved_object_mappings'; + savedObjectsSchema, +} from './saved_objects'; export { app, diff --git a/src/ui/ui_exports/ui_export_types/saved_object_mappings.js b/src/ui/ui_exports/ui_export_types/saved_objects.js similarity index 89% rename from src/ui/ui_exports/ui_export_types/saved_object_mappings.js rename to src/ui/ui_exports/ui_export_types/saved_objects.js index de3570ce2e864..eb845252a17bc 100644 --- a/src/ui/ui_exports/ui_export_types/saved_object_mappings.js +++ b/src/ui/ui_exports/ui_export_types/saved_objects.js @@ -17,7 +17,7 @@ * under the License. */ -import { flatConcatAtType } from './reduce'; +import { flatConcatAtType, flatConcatValuesAtType } from './reduce'; import { alias, mapSpec, wrap } from './modify_reduce'; // mapping types @@ -29,3 +29,5 @@ export const mappings = wrap( })), flatConcatAtType ); + +export const savedObjectsSchema = flatConcatValuesAtType; diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js index 1c7627411462f..cd5f19c9b267c 100644 --- a/x-pack/plugins/spaces/index.js +++ b/x-pack/plugins/spaces/index.js @@ -41,6 +41,9 @@ export const spaces = (kibana) => new kibana.Plugin({ }], hacks: [], mappings, + savedObjectsSchema: { + namespaceAgnosticTypes: ['space'] + }, home: ['plugins/spaces/register_feature'], injectDefaultVars: function () { return { @@ -68,8 +71,6 @@ export const spaces = (kibana) => new kibana.Plugin({ const thisPlugin = this; const xpackMainPlugin = server.plugins.xpack_main; - server.savedObjects.schema.addNamespaceAgnosticType('space'); - watchStatusAndLicenseToInitialize(xpackMainPlugin, thisPlugin, async () => { await createDefaultSpace(server); }); From 95ef4b02bd525251354846b259df46e4173e8d3c Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 4 Sep 2018 12:51:41 -0400 Subject: [PATCH 24/24] Even better schema --- .../lib/__snapshots__/schema.test.js.snap | 11 ---- .../saved_objects/service/lib/schema.js | 6 ++- .../saved_objects/service/lib/schema.test.js | 54 ++++++------------- .../ui_export_types/saved_objects.js | 6 +-- x-pack/plugins/spaces/index.js | 4 +- 5 files changed, 28 insertions(+), 53 deletions(-) delete mode 100644 src/server/saved_objects/service/lib/__snapshots__/schema.test.js.snap diff --git a/src/server/saved_objects/service/lib/__snapshots__/schema.test.js.snap b/src/server/saved_objects/service/lib/__snapshots__/schema.test.js.snap deleted file mode 100644 index 01df0c487d63a..0000000000000 --- a/src/server/saved_objects/service/lib/__snapshots__/schema.test.js.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`#addNamespaceAgnosticType can't add Symbol type 1`] = `"type must be a string"`; - -exports[`#addNamespaceAgnosticType can't add a bool type 1`] = `"type must be a string"`; - -exports[`#addNamespaceAgnosticType can't add function type 1`] = `"type must be a string"`; - -exports[`#addNamespaceAgnosticType can't add null type 1`] = `"type must be a string"`; - -exports[`#addNamespaceAgnosticType can't add number type 1`] = `"type must be a string"`; diff --git a/src/server/saved_objects/service/lib/schema.js b/src/server/saved_objects/service/lib/schema.js index 12630945d83c0..02cbbc4271c6c 100644 --- a/src/server/saved_objects/service/lib/schema.js +++ b/src/server/saved_objects/service/lib/schema.js @@ -23,6 +23,10 @@ export class SavedObjectsSchema { } isNamespaceAgnostic(type) { - return this._schema.namespaceAgnosticTypes.includes(type); + const typeSchema = this._schema[type]; + if (!typeSchema) { + return false; + } + return Boolean(typeSchema.isNamespaceAgnostic); } } diff --git a/src/server/saved_objects/service/lib/schema.test.js b/src/server/saved_objects/service/lib/schema.test.js index 2cd95fdede14f..a524c4bb6bd84 100644 --- a/src/server/saved_objects/service/lib/schema.test.js +++ b/src/server/saved_objects/service/lib/schema.test.js @@ -19,51 +19,31 @@ import { SavedObjectsSchema } from './schema'; -describe('#addNamespaceAgnosticType', () => { - it(`can add a string type`, () => { - const schema = new SavedObjectsSchema(); - schema.addNamespaceAgnosticType('foo'); - }); - - it(`can't add a bool type`, () => { - const schema = new SavedObjectsSchema(); - expect(() => schema.addNamespaceAgnosticType(true)).toThrowErrorMatchingSnapshot(); - }); - - it(`can't add null type`, () => { - const schema = new SavedObjectsSchema(); - expect(() => schema.addNamespaceAgnosticType(null)).toThrowErrorMatchingSnapshot(); - }); - - it(`can't add number type`, () => { - const schema = new SavedObjectsSchema(); - expect(() => schema.addNamespaceAgnosticType(1)).toThrowErrorMatchingSnapshot(); - }); - - it(`can't add function type`, () => { - const schema = new SavedObjectsSchema(); - expect(() => schema.addNamespaceAgnosticType(() => {})).toThrowErrorMatchingSnapshot(); - }); - - it(`can't add Symbol type`, () => { - const schema = new SavedObjectsSchema(); - expect(() => schema.addNamespaceAgnosticType(Symbol())).toThrowErrorMatchingSnapshot(); - }); -}); - describe('#isNamespaceAgnostic', () => { it(`returns false for unknown types`, () => { - const schema = new SavedObjectsSchema(); - schema.addNamespaceAgnosticType('foo'); + const schema = new SavedObjectsSchema({}); const result = schema.isNamespaceAgnostic('bar'); expect(result).toBe(false); }); - it(`returns true for namespace agnostic registered types`, () => { - const schema = new SavedObjectsSchema(); - schema.addNamespaceAgnosticType('foo'); + it(`returns true for explicitly namespace agnostic type`, () => { + const schema = new SavedObjectsSchema({ + foo: { + isNamespaceAgnostic: true, + } + }); const result = schema.isNamespaceAgnostic('foo'); expect(result).toBe(true); }); + + it(`returns false for explicitly namespaced type`, () => { + const schema = new SavedObjectsSchema({ + foo: { + isNamespaceAgnostic: false, + } + }); + const result = schema.isNamespaceAgnostic('foo'); + expect(result).toBe(false); + }); }); diff --git a/src/ui/ui_exports/ui_export_types/saved_objects.js b/src/ui/ui_exports/ui_export_types/saved_objects.js index eb845252a17bc..025d369091207 100644 --- a/src/ui/ui_exports/ui_export_types/saved_objects.js +++ b/src/ui/ui_exports/ui_export_types/saved_objects.js @@ -17,8 +17,8 @@ * under the License. */ -import { flatConcatAtType, flatConcatValuesAtType } from './reduce'; -import { alias, mapSpec, wrap } from './modify_reduce'; +import { flatConcatAtType, mergeAtType } from './reduce'; +import { alias, mapSpec, uniqueKeys, wrap } from './modify_reduce'; // mapping types export const mappings = wrap( @@ -30,4 +30,4 @@ export const mappings = wrap( flatConcatAtType ); -export const savedObjectsSchema = flatConcatValuesAtType; +export const savedObjectsSchema = wrap(uniqueKeys(), mergeAtType); diff --git a/x-pack/plugins/spaces/index.js b/x-pack/plugins/spaces/index.js index cd5f19c9b267c..476af92d326a9 100644 --- a/x-pack/plugins/spaces/index.js +++ b/x-pack/plugins/spaces/index.js @@ -42,7 +42,9 @@ export const spaces = (kibana) => new kibana.Plugin({ hacks: [], mappings, savedObjectsSchema: { - namespaceAgnosticTypes: ['space'] + space: { + isNamespaceAgnostic: true, + }, }, home: ['plugins/spaces/register_feature'], injectDefaultVars: function () {