Skip to content

Commit d3d6f16

Browse files
committed
Changes to permissions with recursive operations
Recursive delete requires DELETE perm on all sub-folders Recursive Sync copies current user's permissions to all sub-folders
1 parent 9a0aa7f commit d3d6f16

File tree

4 files changed

+92
-38
lines changed

4 files changed

+92
-38
lines changed

app/src/controllers/bucket.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ const controller = {
207207

208208
/**
209209
* @function deleteBucket
210-
* Deletes the bucket
210+
* Deletes a bucket
211+
* Recursive option will delete all sub-folders (where current user has DELETE permission)
211212
* @param {object} req Express request object
212213
* @param {object} res Express response object
213214
* @param {function} next The next callback function
@@ -218,11 +219,35 @@ const controller = {
218219
const bucketId = addDashesToUuid(req.params.bucketId);
219220
const parentBucket = await bucketService.read(bucketId);
220221
let buckets = [parentBucket];
222+
221223
// if doing recursive delete
222224
if (isTruthy(req.query.recursive)) {
223-
// get sub-folders
224-
const dbChildBuckets = await bucketService.searchChildBuckets(parentBucket);
225-
buckets = buckets.concat(dbChildBuckets);
225+
// if current user is OIDC
226+
const userId = await userService.getCurrentUserId(
227+
getCurrentIdentity(req.currentUser, SYSTEM_USER), SYSTEM_USER);
228+
if (userId !== SYSTEM_USER) {
229+
const dbChildBuckets = await bucketService.searchChildBuckets(parentBucket, true, userId);
230+
// if there are sub-folders
231+
if (dbChildBuckets.length > 0) {
232+
const checkForDelete = obj => obj.permCode === 'DELETE';
233+
const dbChildBucketsWithDelete = dbChildBuckets.filter(b =>
234+
b.bucketPermission.some(checkForDelete));
235+
// if user has DELETE on all subfolders
236+
if (dbChildBucketsWithDelete.length === dbChildBuckets.length) {
237+
buckets = buckets.concat(dbChildBucketsWithDelete);
238+
} else {
239+
throw new Problem(403, {
240+
detail: 'User lacks DELETE permission for some sub-folders',
241+
instance: req.originalUrl,
242+
});
243+
}
244+
}
245+
}
246+
// else basic auth
247+
else {
248+
const dbChildBuckets = await bucketService.searchChildBuckets(parentBucket);
249+
buckets = buckets.concat(dbChildBuckets);
250+
}
226251
}
227252
// do delete
228253
await utils.trxWrapper(async (trx) => {

app/src/controllers/sync.js

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,43 +33,44 @@ const controller = {
3333
*/
3434
async syncBucketRecursive(req, res, next) {
3535
try {
36-
// Wrap all sql operations in a single transaction
37-
const response = await utils.trxWrapper(async (trx) => {
36+
// current userId
37+
const userId = await userService.getCurrentUserId(
38+
getCurrentIdentity(req.currentUser, SYSTEM_USER),
39+
SYSTEM_USER
40+
);
41+
// parent bucket
42+
const bucketId = addDashesToUuid(req.params.bucketId);
43+
const parentBucket = await bucketService.read(bucketId);
3844

39-
// curren userId
40-
const userId = await userService.getCurrentUserId(
41-
getCurrentIdentity(req.currentUser, SYSTEM_USER),
42-
SYSTEM_USER
43-
);
44-
// parent bucket
45-
const bucketId = addDashesToUuid(req.params.bucketId);
46-
const parentBucket = await bucketService.read(bucketId);
45+
// current user's permissions on parent bucket (folder)
46+
const currentUserParentBucketPerms = userId !== SYSTEM_USER ? (await bucketPermissionService.searchPermissions({
47+
bucketId: parentBucket.bucketId,
48+
userId: userId
49+
})).map(p => p.permCode) : [];
4750

48-
// current user's permissions on parent bucket (folder)
49-
const currentUserParentBucketPerms = userId !== SYSTEM_USER ? (await bucketPermissionService.searchPermissions({
50-
bucketId: parentBucket.bucketId,
51-
userId: userId
52-
})).map(p => p.permCode) : [];
51+
/**
52+
* sync (ie create or delete) bucket records in COMS db to match 'folders' (S3 key prefixes) that exist in S3
53+
*/
54+
// parent + child bucket records already in COMS db
55+
const dbChildBuckets = await bucketService.searchChildBuckets(parentBucket, false, userId);
56+
let dbBuckets = [parentBucket].concat(dbChildBuckets);
57+
// 'folders' that exist below (and including) the parent 'folder' in S3
58+
const s3Response = await storageService.listAllObjectVersions({ bucketId: bucketId, precisePath: false });
59+
const s3Keys = [...new Set([
60+
...s3Response.DeleteMarkers.map(object => formatS3KeyForCompare(object.Key)),
61+
...s3Response.Versions.map(object => formatS3KeyForCompare(object.Key)),
62+
])];
5363

54-
/**
55-
* sync (ie create or delete) bucket records in COMS db to match 'folders' (S3 key prefixes) that exist in S3
56-
*/
57-
// parent + child bucket records already in COMS db
58-
const dbChildBuckets = await bucketService.searchChildBuckets(parentBucket);
59-
let dbBuckets = [parentBucket].concat(dbChildBuckets);
60-
// 'folders' that exist below (and including) the parent 'folder' in S3
61-
const s3Response = await storageService.listAllObjectVersions({ bucketId: bucketId, precisePath: false });
62-
const s3Keys = [...new Set([
63-
...s3Response.DeleteMarkers.map(object => formatS3KeyForCompare(object.Key)),
64-
...s3Response.Versions.map(object => formatS3KeyForCompare(object.Key)),
65-
])];
64+
// Wrap sync sql operations in a single transaction
65+
const response = await utils.trxWrapper(async (trx) => {
6666

6767
const syncedBuckets = await this.syncBucketRecords(
6868
dbBuckets,
6969
s3Keys,
7070
parentBucket,
7171
// assign current user's permissions on parent bucket to new sub-folders (buckets)
7272
currentUserParentBucketPerms,
73+
userId,
7374
trx
7475
);
7576

@@ -115,14 +116,16 @@ const controller = {
115116
/**
116117
* @function syncBucketRecords
117118
* Synchronizes (creates / prunes) COMS db bucket records for each 'directry' found in S3
119+
* Adds current user's permissions to all buckets
118120
* @param {object[]} Array of Bucket models - bucket records already in COMS db before syncing
119121
* @param {string[]} s3Keys Array of key prefixes from S3 representing 'directories'
120122
* @param {object} Bucket model for the COMS db bucket record of parent bucket
121123
* @param {string[]} currentUserParentBucketPerms Array of PermCodes to add to NEW buckets
122-
* @param {object} [trx] An Objection Transaction object
124+
* @param {string} userId the guid of current user
125+
* @param {object} [trx] An Objection Transaction object
123126
* @returns {string[]} And array of bucketId's for bucket records in COMS db
124127
*/
125-
async syncBucketRecords(dbBuckets, s3Keys, parentBucket, currentUserParentBucketPerms, trx) {
128+
async syncBucketRecords(dbBuckets, s3Keys, parentBucket, currentUserParentBucketPerms, userId, trx) {
126129
try {
127130
// delete buckets not found in S3 from COMS db
128131
const oldDbBuckets = dbBuckets.filter(b => !s3Keys.includes(b.key));
@@ -134,6 +137,17 @@ const controller = {
134137
})
135138
)
136139
);
140+
// add current user's permissions to all buckets
141+
await Promise.all(
142+
dbBuckets.map(bucket => {
143+
return bucketPermissionService.addPermissions(
144+
bucket.bucketId,
145+
currentUserParentBucketPerms.map(permCode => ({ userId, permCode })),
146+
undefined,
147+
trx
148+
);
149+
})
150+
);
137151

138152
// Create buckets only found in S3 in COMS db
139153
const newS3Keys = s3Keys.filter(k => !dbBuckets.map(b => b.key).includes(k));
@@ -149,8 +163,6 @@ const controller = {
149163
region: parentBucket.region ?? undefined,
150164
active: parentBucket.active,
151165
userId: parentBucket.createdBy ?? SYSTEM_USER,
152-
// current user has MANAGE perm on parent folder (see route.hasPermission)
153-
// ..so copy all their perms to NEW subfolders
154166
permCodes: currentUserParentBucketPerms
155167
};
156168
return bucketService.create(data, trx)
@@ -159,7 +171,6 @@ const controller = {
159171
});
160172
})
161173
);
162-
163174
return dbBuckets;
164175
}
165176
catch (err) {

app/src/docs/v1.api-spec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,11 @@ paths:
199199
summary: Deletes a bucket
200200
description: >-
201201
Deletes the bucket record (and child objects) from the COMS database, based on bucketId.
202+
When calling this endpoint using OIDC token authentication, the user requires the DELETE
203+
permission for the bucket.
202204
Providing the 'recursive' parameter will also delete all sub-folders.
205+
The recursive option also requires the OIDC user to have the DELETE permission
206+
on all of these sub-folders otherwise the entire operation will fail.
203207
This request does not dispatch an S3 operation to request the deletion of the associated
204208
bucket(s) or delete any objects in object storage, it simply 'de-registers' knowledge of the bucket/folder
205209
and objects from the COMS system.

app/src/services/bucket.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,18 +200,32 @@ const service = {
200200
* @function searchChildBuckets
201201
* Get db records for each bucket that acts as a sub-folder of the provided bucket
202202
* @param {object} parentBucket a bucket model (record) from the COMS db
203+
* @param {boolean} returnPermissions also return current user's permissions for each bucket
203204
* @param {object} [etrx=undefined] An optional Objection Transaction object
204205
* @returns {Promise<object[]>} An array of bucket records
205206
* @throws If there are no records found
206207
*/
207-
searchChildBuckets: async (parentBucket, etrx = undefined) => {
208+
searchChildBuckets: async (parentBucket, returnPermissions = false, userId, etrx = undefined) => {
208209
let trx;
209210
try {
210211
trx = etrx ? etrx : await Bucket.startTransaction();
211-
return Bucket.query()
212+
const response = Bucket.query()
213+
.modify(query => {
214+
if (returnPermissions) {
215+
query
216+
.withGraphJoined('bucketPermission')
217+
.whereIn('bucketPermission.bucketId', builder => {
218+
builder.distinct('bucketPermission.bucketId')
219+
.where('bucketPermission.userId', userId);
220+
});
221+
}
222+
})
212223
.modify('filterKeyIsChild', parentBucket.key)
213224
.modify('filterEndpoint', parentBucket.endpoint)
214225
.where('bucket', parentBucket.bucket);
226+
227+
if (!etrx) await trx.commit();
228+
return response;
215229
} catch (err) {
216230
if (!etrx && trx) await trx.rollback();
217231
throw err;

0 commit comments

Comments
 (0)