@@ -19,7 +19,10 @@ import {
1919
2020import type { Payload } from '@hapi/boom' ;
2121
22- import type { SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server' ;
22+ import type {
23+ SavedObjectAccessControl ,
24+ SavedObjectsBulkCreateObject ,
25+ } from '@kbn/core-saved-objects-api-server' ;
2326import {
2427 type SavedObjectsRawDoc ,
2528 type SavedObjectUnsanitizedDoc ,
@@ -58,6 +61,7 @@ import {
5861} from '../../test_helpers/repository.test.common' ;
5962import type { ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server' ;
6063import { savedObjectsExtensionsMock } from '../../mocks/saved_objects_extensions.mock' ;
64+ import type { AuthenticatedUser } from '@kbn/core-security-common' ;
6165
6266// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
6367
@@ -1086,6 +1090,266 @@ describe('#bulkCreate', () => {
10861090 } )
10871091 ) ;
10881092 } ) ;
1093+
1094+ describe ( 'access control' , ( ) => {
1095+ const CURRENT_USER_PROFILE_ID = 'current_user_profile_id' ;
1096+ const ACCESS_CONTROL_TYPE = 'access-control-type' ;
1097+
1098+ beforeEach ( ( ) => {
1099+ securityExtension . getCurrentUser . mockReturnValue ( {
1100+ authentication_realm : {
1101+ name : 'authentication_realm' ,
1102+ type : 'authentication_realm_type' ,
1103+ } ,
1104+ lookup_realm : {
1105+ name : 'lookup_realm' ,
1106+ type : 'lookup_realm_type' ,
1107+ } ,
1108+ authentication_provider : {
1109+ name : 'authentication_provider' ,
1110+ type : 'authentication_provider_type' ,
1111+ } ,
1112+ authentication_type : 'realm' ,
1113+ elastic_cloud_user : false ,
1114+ profile_uid : CURRENT_USER_PROFILE_ID ,
1115+ } as AuthenticatedUser ) ;
1116+ } ) ;
1117+
1118+ registry . registerType ( {
1119+ name : ACCESS_CONTROL_TYPE ,
1120+ hidden : false ,
1121+ namespaceType : 'multiple-isolated' ,
1122+ supportsAccessControl : true ,
1123+ mappings : {
1124+ dynamic : false ,
1125+ properties : {
1126+ description : { type : 'text' } ,
1127+ } ,
1128+ } ,
1129+ management : {
1130+ importableAndExportable : true ,
1131+ } ,
1132+ } ) ;
1133+
1134+ afterEach ( ( ) => {
1135+ securityExtension . getCurrentUser . mockClear ( ) ;
1136+ } ) ;
1137+
1138+ it ( 'applies access control options only to applicable types when using global access control options' , async ( ) => {
1139+ const obj1NoAccessControl = {
1140+ type : 'config' ,
1141+ id : '6.0.0-alpha1' ,
1142+ attributes : { title : 'Test One' } ,
1143+ references : [ { name : 'ref_0' , type : 'test' , id : '1' } ] ,
1144+ } ;
1145+ const obj2AccessControl = {
1146+ type : ACCESS_CONTROL_TYPE ,
1147+ id : 'has-read-only-metadata' ,
1148+ attributes : { title : 'Test Two' } ,
1149+ references : [ { name : 'ref_0' , type : 'test' , id : '2' } ] ,
1150+ } ;
1151+ await bulkCreateSuccess ( client , repository , [ obj1NoAccessControl , obj2AccessControl ] , {
1152+ accessControl : { accessMode : 'write_restricted' } ,
1153+ } ) ;
1154+
1155+ expect ( securityExtension . authorizeBulkCreate ) . toHaveBeenCalledWith (
1156+ expect . objectContaining ( {
1157+ objects : expect . arrayContaining ( [
1158+ {
1159+ type : ACCESS_CONTROL_TYPE ,
1160+ id : 'has-read-only-metadata' ,
1161+ name : 'Test Two' ,
1162+ existingNamespaces : [ ] ,
1163+ initialNamespace : undefined ,
1164+ accessControl : {
1165+ owner : CURRENT_USER_PROFILE_ID ,
1166+ accessMode : 'write_restricted' ,
1167+ } ,
1168+ } ,
1169+ ] ) ,
1170+ } )
1171+ ) ;
1172+ } ) ;
1173+
1174+ it ( 'applies access control options to supporting types when using per object access control options' , async ( ) => {
1175+ const obj1NoAccessControl = {
1176+ type : 'config' ,
1177+ id : '6.0.0-alpha1' ,
1178+ attributes : { title : 'Test One' } ,
1179+ references : [ { name : 'ref_0' , type : 'test' , id : '1' } ] ,
1180+ } ;
1181+ const obj2AccessControl = {
1182+ type : ACCESS_CONTROL_TYPE ,
1183+ id : 'has-read-only-metadata' ,
1184+ attributes : { title : 'Test Two' } ,
1185+ references : [ { name : 'ref_0' , type : 'test' , id : '2' } ] ,
1186+ accessControl : {
1187+ accessMode : 'write_restricted' ,
1188+ } as Pick < SavedObjectAccessControl , 'accessMode' > ,
1189+ } ;
1190+ await bulkCreateSuccess ( client , repository , [ obj1NoAccessControl , obj2AccessControl ] ) ;
1191+
1192+ expect ( securityExtension . authorizeBulkCreate ) . toHaveBeenCalledWith ( {
1193+ objects : [
1194+ {
1195+ type : 'config' ,
1196+ id : '6.0.0-alpha1' ,
1197+ name : 'Test One' ,
1198+ existingNamespaces : [ ] ,
1199+ initialNamespaces : undefined ,
1200+ } ,
1201+ {
1202+ type : ACCESS_CONTROL_TYPE ,
1203+ id : 'has-read-only-metadata' ,
1204+ name : 'Test Two' ,
1205+ existingNamespaces : [ ] ,
1206+ initialNamespaces : undefined ,
1207+ accessControl : {
1208+ owner : CURRENT_USER_PROFILE_ID ,
1209+ accessMode : 'write_restricted' ,
1210+ } ,
1211+ } ,
1212+ ] ,
1213+ } ) ;
1214+ } ) ;
1215+
1216+ it ( 'overrides access control options with incoming object property only for applicable types' , async ( ) => {
1217+ const obj1NoAccessControl = {
1218+ type : 'config' ,
1219+ id : '6.0.0-alpha1' ,
1220+ attributes : { title : 'Test One' } ,
1221+ references : [ { name : 'ref_0' , type : 'test' , id : '1' } ] ,
1222+ accessControl : {
1223+ accessMode : 'write_restricted' ,
1224+ } as Pick < SavedObjectAccessControl , 'accessMode' > ,
1225+ } ;
1226+ const obj2AccessControl = {
1227+ type : ACCESS_CONTROL_TYPE ,
1228+ id : 'has-read-only-metadata' ,
1229+ attributes : { title : 'Test Two' } ,
1230+ references : [ { name : 'ref_0' , type : 'test' , id : '2' } ] ,
1231+ accessControl : {
1232+ accessMode : 'default' , // default === RBAC-only
1233+ } as Pick < SavedObjectAccessControl , 'accessMode' > ,
1234+ } ;
1235+ const obj3AccessControl = {
1236+ type : ACCESS_CONTROL_TYPE ,
1237+ id : 'has-read-only-metadata' ,
1238+ attributes : { title : 'Test Three' } ,
1239+ references : [ { name : 'ref_0' , type : 'test' , id : '3' } ] ,
1240+ } ;
1241+ const obj4NoAccessControl = {
1242+ type : 'config' ,
1243+ id : '6.0.0-alpha4' ,
1244+ attributes : { title : 'Test One' } ,
1245+ references : [ { name : 'ref_0' , type : 'test' , id : '1' } ] ,
1246+ } ;
1247+
1248+ await bulkCreateSuccess (
1249+ client ,
1250+ repository ,
1251+ [ obj1NoAccessControl , obj2AccessControl , obj3AccessControl , obj4NoAccessControl ] ,
1252+ {
1253+ accessControl : { accessMode : 'write_restricted' } ,
1254+ }
1255+ ) ;
1256+
1257+ expect ( securityExtension . authorizeBulkCreate ) . toHaveBeenCalledWith ( {
1258+ objects : [
1259+ {
1260+ type : ACCESS_CONTROL_TYPE ,
1261+ id : 'has-read-only-metadata' ,
1262+ name : 'Test Two' ,
1263+ existingNamespaces : [ ] ,
1264+ initialNamespace : undefined ,
1265+ accessControl : {
1266+ owner : CURRENT_USER_PROFILE_ID ,
1267+ accessMode : 'default' , // explicitly confirm the mode is overriden
1268+ } ,
1269+ } ,
1270+ {
1271+ type : ACCESS_CONTROL_TYPE ,
1272+ id : 'has-read-only-metadata' ,
1273+ name : 'Test Three' ,
1274+ existingNamespaces : [ ] ,
1275+ initialNamespace : undefined ,
1276+ accessControl : {
1277+ owner : CURRENT_USER_PROFILE_ID ,
1278+ accessMode : 'write_restricted' , // explicitly confirm the mode is NOT overriden
1279+ } ,
1280+ } ,
1281+ ] ,
1282+ } ) ;
1283+ } ) ;
1284+
1285+ it ( 'does not create objects with access control when there is no active user profile' , async ( ) => {
1286+ securityExtension . getCurrentUser . mockReturnValueOnce ( null ) ;
1287+
1288+ const obj1NoAccessControl = {
1289+ type : 'config' ,
1290+ id : '6.0.0-alpha1' ,
1291+ attributes : { title : 'Test One' } ,
1292+ references : [ { name : 'ref_0' , type : 'test' , id : '1' } ] ,
1293+ accessControl : {
1294+ accessMode : 'write_restricted' ,
1295+ } as Pick < SavedObjectAccessControl , 'accessMode' > ,
1296+ } ;
1297+ const obj2AccessControl = {
1298+ type : ACCESS_CONTROL_TYPE ,
1299+ id : 'has-read-only-metadata' ,
1300+ attributes : { title : 'Test Two' } ,
1301+ references : [ { name : 'ref_0' , type : 'test' , id : '2' } ] ,
1302+ accessControl : {
1303+ accessMode : 'write_restricted' ,
1304+ } as Pick < SavedObjectAccessControl , 'accessMode' > ,
1305+ } ;
1306+ await bulkCreateSuccess ( client , repository , [ obj1NoAccessControl , obj2AccessControl ] ) ;
1307+
1308+ expect ( securityExtension . authorizeBulkCreate ) . not . toHaveBeenCalled ( ) ;
1309+ } ) ;
1310+
1311+ // regression test
1312+ it ( 'creates objects supporting access control with no access control metadata when there is no active user profile and no access mode is provided' , async ( ) => {
1313+ securityExtension . getCurrentUser . mockReturnValueOnce ( null ) ;
1314+
1315+ const obj1NoAccessControl = {
1316+ type : 'config' ,
1317+ id : '6.0.0-alpha1' ,
1318+ attributes : { title : 'Test One' } ,
1319+ references : [ { name : 'ref_0' , type : 'test' , id : '1' } ] ,
1320+ } ;
1321+ const obj2AccessControl = {
1322+ type : ACCESS_CONTROL_TYPE ,
1323+ id : 'could-have-read-only-metadata' ,
1324+ attributes : { title : 'Test Two' } ,
1325+ references : [ { name : 'ref_0' , type : 'test' , id : '2' } ] ,
1326+ } ;
1327+ await bulkCreateSuccess ( client , repository , [ obj1NoAccessControl , obj2AccessControl ] ) ; // no accessControl options
1328+
1329+ expect ( securityExtension . authorizeBulkCreate ) . toHaveBeenCalledWith (
1330+ expect . objectContaining ( {
1331+ objects : [
1332+ {
1333+ type : 'config' ,
1334+ id : '6.0.0-alpha1' ,
1335+ name : 'Test One' ,
1336+ existingNamespaces : [ ] ,
1337+ initialNamespace : undefined ,
1338+ // explicitly confirm there is no accessControl for non-supporting type
1339+ } ,
1340+ {
1341+ type : ACCESS_CONTROL_TYPE ,
1342+ id : 'could-have-read-only-metadata' ,
1343+ name : 'Test Two' ,
1344+ existingNamespaces : [ ] ,
1345+ initialNamespace : undefined ,
1346+ // explicitly confirm there is no accessControl for supporting type
1347+ } ,
1348+ ] ,
1349+ } )
1350+ ) ;
1351+ } ) ;
1352+ } ) ;
10891353 } ) ;
10901354 } ) ;
10911355} ) ;
0 commit comments