Skip to content

Commit f6805ab

Browse files
authored
Merge pull request #8661 from shirady/nsfs-nc-iam-past-tech-debts
NC | NSFS | IAM | Tech Debts (IAM Integration Tests, Username Validation Move module and Allow IAM User to Create Bucket)
2 parents e3bbfa1 + d759a0e commit f6805ab

12 files changed

+684
-330
lines changed

src/endpoint/iam/iam_rest.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const RPC_ERRORS_TO_IAM = Object.freeze({
1919
INVALID_ACCESS_KEY_ID: IamError.InvalidClientTokenId,
2020
DEACTIVATED_ACCESS_KEY_ID: IamError.InvalidClientTokenIdInactiveAccessKey,
2121
NO_SUCH_ACCOUNT: IamError.AccessDeniedException,
22-
NO_SUCH_ROLE: IamError.AccessDeniedException
22+
NO_SUCH_ROLE: IamError.AccessDeniedException,
23+
VALIDATION_ERROR: IamError.ValidationError,
2324
});
2425

2526
const ACTIONS = Object.freeze({

src/endpoint/iam/iam_utils.js

+139-178
Large diffs are not rendered by default.

src/manage_nsfs/manage_nsfs_validations.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const { throw_cli_error, get_options_from_file, get_boolean_or_string_value, get
1313
is_name_update, is_access_key_update } = require('../manage_nsfs/manage_nsfs_cli_utils');
1414
const { TYPES, ACTIONS, VALID_OPTIONS, OPTION_TYPE, FROM_FILE, BOOLEAN_STRING_VALUES, BOOLEAN_STRING_OPTIONS,
1515
GLACIER_ACTIONS, LIST_UNSETABLE_OPTIONS, ANONYMOUS, DIAGNOSE_ACTIONS, UPGRADE_ACTIONS } = require('../manage_nsfs/manage_nsfs_constants');
16-
const iam_utils = require('../endpoint/iam/iam_utils');
1716
const { check_root_account_owns_user } = require('../nc/nc_utils');
17+
const { validate_username } = require('../util/validation_utils');
1818

1919
/////////////////////////////
2020
//// GENERAL VALIDATIONS ////
@@ -317,15 +317,14 @@ function validate_account_name(type, action, input_options_with_data) {
317317
try {
318318
if (action === ACTIONS.ADD) {
319319
account_name = String(input_options_with_data.name);
320-
iam_utils.validate_username(account_name, 'name');
320+
validate_username(account_name, 'name');
321321
} else if (action === ACTIONS.UPDATE && input_options_with_data.new_name !== undefined) {
322322
account_name = String(input_options_with_data.new_name);
323-
iam_utils.validate_username(account_name, 'new_name');
323+
validate_username(account_name, 'new_name');
324324
}
325325
} catch (err) {
326326
if (err instanceof ManageCLIError) throw err;
327-
// we receive IAMError and replace it to ManageCLIError
328-
// we do not use the mapping errors because it is a general error ValidationError
327+
// we replace it to ManageCLIError
329328
const detail = err.message;
330329
throw_cli_error(ManageCLIError.InvalidAccountName, detail);
331330
}

src/sdk/bucketspace_fs.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,6 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
294294
if (!sdk.requesting_account.allow_bucket_creation) {
295295
throw new RpcError('UNAUTHORIZED', 'Not allowed to create new buckets');
296296
}
297-
// currently we do not allow IAM account to create a bucket (temporary)
298-
if (sdk.requesting_account.owner !== undefined) {
299-
dbg.warn('create_bucket: account is IAM account (currently not allowed to create buckets)');
300-
throw new RpcError('UNAUTHORIZED', 'Not allowed to create new buckets');
301-
}
302297
if (!sdk.requesting_account.nsfs_account_config || !sdk.requesting_account.nsfs_account_config.new_buckets_path) {
303298
throw new RpcError('MISSING_NSFS_ACCOUNT_CONFIGURATION');
304299
}
@@ -345,7 +340,7 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
345340
_id: mongo_utils.mongoObjectId(),
346341
name,
347342
tag: js_utils.default_value(tag, undefined),
348-
owner_account: account._id,
343+
owner_account: account.owner ? account.owner : account._id, // The account is the owner of the buckets that were created by it or by its users.
349344
creator: account._id,
350345
versioning: config.NSFS_VERSIONING_ENABLED && lock_enabled ? 'ENABLED' : 'DISABLED',
351346
object_lock_configuration: config.WORM_ENABLED ? {

src/test/system_tests/test_utils.js

+17
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ const fs = require('fs');
55
const _ = require('lodash');
66
const path = require('path');
77
const http = require('http');
8+
const https = require('https');
89
const P = require('../../util/promise');
910
const config = require('../../../config');
1011
const { S3 } = require('@aws-sdk/client-s3');
12+
const { IAMClient } = require('@aws-sdk/client-iam');
1113
const os_utils = require('../../util/os_utils');
1214
const fs_utils = require('../../util/fs_utils');
1315
const nb_native = require('../../util/nb_native');
@@ -416,6 +418,20 @@ function generate_s3_client(access_key, secret_key, endpoint) {
416418
endpoint
417419
});
418420
}
421+
422+
function generate_iam_client(access_key, secret_key, endpoint) {
423+
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // disable SSL certificate validation
424+
return new IAMClient({
425+
region: config.DEFAULT_REGION,
426+
credentials: {
427+
accessKeyId: access_key,
428+
secretAccessKey: secret_key,
429+
},
430+
endpoint,
431+
requestHandler: new NodeHttpHandler({ httpsAgent }),
432+
});
433+
}
434+
419435
/**
420436
* generate_nsfs_account generate an nsfs account and returns its credentials
421437
* if the admin flag is received (in the options object) the function will not create
@@ -720,6 +736,7 @@ exports.empty_and_delete_buckets = empty_and_delete_buckets;
720736
exports.disable_accounts_s3_access = disable_accounts_s3_access;
721737
exports.generate_s3_policy = generate_s3_policy;
722738
exports.generate_s3_client = generate_s3_client;
739+
exports.generate_iam_client = generate_iam_client;
723740
exports.invalid_nsfs_root_permissions = invalid_nsfs_root_permissions;
724741
exports.get_coretest_path = get_coretest_path;
725742
exports.exec_manage_cli = exec_manage_cli;

src/test/unit_tests/jest_tests/test_iam_utils.test.js

-126
Original file line numberDiff line numberDiff line change
@@ -312,132 +312,6 @@ describe('validate_user_input_iam', () => {
312312
});
313313
});
314314

315-
describe('validate_username', () => {
316-
const min_length = 1;
317-
const max_length = 64;
318-
it('should return true when username is undefined', () => {
319-
let dummy_username;
320-
const res = iam_utils.validate_username(dummy_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
321-
expect(res).toBeUndefined();
322-
});
323-
324-
it('should return true when username is at the min or max length', () => {
325-
expect(iam_utils.validate_username('a', iam_constants.IAM_PARAMETER_NAME.USERNAME)).toBe(true);
326-
expect(iam_utils.validate_username('a'.repeat(max_length), iam_constants.IAM_PARAMETER_NAME.USERNAME)).toBe(true);
327-
});
328-
329-
it('should return true when username is within the length constraint', () => {
330-
expect(iam_utils.validate_username('a'.repeat(min_length + 1), iam_constants.IAM_PARAMETER_NAME.USERNAME)).toBe(true);
331-
expect(iam_utils.validate_username('a'.repeat(max_length - 1), iam_constants.IAM_PARAMETER_NAME.USERNAME)).toBe(true);
332-
});
333-
334-
it('should return true when username is valid', () => {
335-
const dummy_username = 'Robert';
336-
const res = iam_utils.validate_username(dummy_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
337-
expect(res).toBe(true);
338-
});
339-
340-
it('should throw error when username is invalid - contains invalid character', () => {
341-
try {
342-
iam_utils.validate_username('{}', iam_constants.IAM_PARAMETER_NAME.USERNAME);
343-
throw new NoErrorThrownError();
344-
} catch (err) {
345-
expect(err).toBeInstanceOf(IamError);
346-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
347-
}
348-
});
349-
350-
it('should throw error when username is invalid - internal limitation (anonymous)', () => {
351-
try {
352-
iam_utils.validate_username('anonymous', iam_constants.IAM_PARAMETER_NAME.USERNAME);
353-
throw new NoErrorThrownError();
354-
} catch (err) {
355-
expect(err).toBeInstanceOf(IamError);
356-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
357-
}
358-
});
359-
360-
it('should throw error when username is invalid - internal limitation (with leading or trailing spaces)', () => {
361-
try {
362-
iam_utils.validate_username(' name-with-spaces ', iam_constants.IAM_PARAMETER_NAME.USERNAME);
363-
throw new NoErrorThrownError();
364-
} catch (err) {
365-
expect(err).toBeInstanceOf(IamError);
366-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
367-
}
368-
});
369-
370-
it('should throw error when username is too short', () => {
371-
try {
372-
const dummy_username = '';
373-
iam_utils.validate_username(dummy_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
374-
throw new NoErrorThrownError();
375-
} catch (err) {
376-
expect(err).toBeInstanceOf(IamError);
377-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
378-
}
379-
});
380-
381-
it('should throw error when username is too long', () => {
382-
try {
383-
const dummy_username = 'A'.repeat(max_length + 1);
384-
iam_utils.validate_username(dummy_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
385-
throw new NoErrorThrownError();
386-
} catch (err) {
387-
expect(err).toBeInstanceOf(IamError);
388-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
389-
}
390-
});
391-
392-
it('should throw error for invalid input types (null)', () => {
393-
try {
394-
// @ts-ignore
395-
const invalid_username = null;
396-
iam_utils.validate_username(invalid_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
397-
throw new NoErrorThrownError();
398-
} catch (err) {
399-
expect(err).toBeInstanceOf(IamError);
400-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
401-
}
402-
});
403-
404-
it('should throw error for invalid input types (number)', () => {
405-
try {
406-
const invalid_username = 1;
407-
// @ts-ignore
408-
iam_utils.validate_username(invalid_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
409-
throw new NoErrorThrownError();
410-
} catch (err) {
411-
expect(err).toBeInstanceOf(IamError);
412-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
413-
}
414-
});
415-
416-
it('should throw error for invalid input types (object)', () => {
417-
try {
418-
const invalid_username = {};
419-
// @ts-ignore
420-
iam_utils.validate_username(invalid_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
421-
throw new NoErrorThrownError();
422-
} catch (err) {
423-
expect(err).toBeInstanceOf(IamError);
424-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
425-
}
426-
});
427-
428-
it('should throw error for invalid input types (boolean)', () => {
429-
try {
430-
const invalid_username = false;
431-
// @ts-ignore
432-
iam_utils.validate_username(invalid_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
433-
throw new NoErrorThrownError();
434-
} catch (err) {
435-
expect(err).toBeInstanceOf(IamError);
436-
expect(err).toHaveProperty('code', IamError.ValidationError.code);
437-
}
438-
});
439-
});
440-
441315
describe('validate_marker', () => {
442316
const min_length = 1;
443317
it('should return true when marker is undefined', () => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/* Copyright (C) 2024 NooBaa */
2+
/* eslint-disable max-lines-per-function */
3+
'use strict';
4+
const validation_utils = require('../../../util/validation_utils');
5+
const iam_constants = require('../../../endpoint/iam/iam_constants');
6+
const RpcError = require('../../../rpc/rpc_error');
7+
8+
class NoErrorThrownError extends Error {}
9+
10+
describe('validate_user_input', () => {
11+
const RPC_CODE_VALIDATION_ERROR = 'VALIDATION_ERROR';
12+
describe('validate_username', () => {
13+
const min_length = 1;
14+
const max_length = 64;
15+
it('should return true when username is undefined', () => {
16+
let dummy_username;
17+
const res = validation_utils.validate_username(dummy_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
18+
expect(res).toBeUndefined();
19+
});
20+
21+
it('should return true when username is at the min or max length', () => {
22+
expect(validation_utils.validate_username('a', iam_constants.IAM_PARAMETER_NAME.USERNAME)).toBe(true);
23+
expect(validation_utils.validate_username('a'.repeat(max_length), iam_constants.IAM_PARAMETER_NAME.USERNAME)).toBe(true);
24+
});
25+
26+
it('should return true when username is within the length constraint', () => {
27+
expect(validation_utils.validate_username('a'.repeat(min_length + 1), iam_constants.IAM_PARAMETER_NAME.USERNAME)).toBe(true);
28+
expect(validation_utils.validate_username('a'.repeat(max_length - 1), iam_constants.IAM_PARAMETER_NAME.USERNAME)).toBe(true);
29+
});
30+
31+
it('should return true when username is valid', () => {
32+
const dummy_username = 'Robert';
33+
const res = validation_utils.validate_username(dummy_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
34+
expect(res).toBe(true);
35+
});
36+
37+
it('should throw error when username is invalid - contains invalid character', () => {
38+
try {
39+
validation_utils.validate_username('{}', iam_constants.IAM_PARAMETER_NAME.USERNAME);
40+
throw new NoErrorThrownError();
41+
} catch (err) {
42+
expect(err).toBeInstanceOf(RpcError);
43+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
44+
}
45+
});
46+
47+
48+
it('should throw error when username is invalid - internal limitation (anonymous)', () => {
49+
try {
50+
validation_utils.validate_username('anonymous', iam_constants.IAM_PARAMETER_NAME.USERNAME);
51+
throw new NoErrorThrownError();
52+
} catch (err) {
53+
expect(err).toBeInstanceOf(RpcError);
54+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
55+
}
56+
});
57+
58+
it('should throw error when username is invalid - internal limitation (with leading or trailing spaces)', () => {
59+
try {
60+
validation_utils.validate_username(' name-with-spaces ', iam_constants.IAM_PARAMETER_NAME.USERNAME);
61+
throw new NoErrorThrownError();
62+
} catch (err) {
63+
expect(err).toBeInstanceOf(RpcError);
64+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
65+
}
66+
});
67+
68+
it('should throw error when username is too short', () => {
69+
try {
70+
const dummy_username = '';
71+
validation_utils.validate_username(dummy_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
72+
throw new NoErrorThrownError();
73+
} catch (err) {
74+
expect(err).toBeInstanceOf(RpcError);
75+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
76+
}
77+
});
78+
79+
it('should throw error when username is too long', () => {
80+
try {
81+
const dummy_username = 'A'.repeat(max_length + 1);
82+
validation_utils.validate_username(dummy_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
83+
throw new NoErrorThrownError();
84+
} catch (err) {
85+
expect(err).toBeInstanceOf(RpcError);
86+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
87+
}
88+
});
89+
90+
it('should throw error for invalid input types (null)', () => {
91+
try {
92+
// @ts-ignore
93+
const invalid_username = null;
94+
validation_utils.validate_username(invalid_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
95+
throw new NoErrorThrownError();
96+
} catch (err) {
97+
expect(err).toBeInstanceOf(RpcError);
98+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
99+
}
100+
});
101+
102+
it('should throw error for invalid input types (number)', () => {
103+
try {
104+
const invalid_username = 1;
105+
// @ts-ignore
106+
validation_utils.validate_username(invalid_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
107+
throw new NoErrorThrownError();
108+
} catch (err) {
109+
expect(err).toBeInstanceOf(RpcError);
110+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
111+
}
112+
});
113+
114+
it('should throw error for invalid input types (object)', () => {
115+
try {
116+
const invalid_username = {};
117+
// @ts-ignore
118+
validation_utils.validate_username(invalid_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
119+
throw new NoErrorThrownError();
120+
} catch (err) {
121+
expect(err).toBeInstanceOf(RpcError);
122+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
123+
}
124+
});
125+
126+
it('should throw error for invalid input types (boolean)', () => {
127+
try {
128+
const invalid_username = false;
129+
// @ts-ignore
130+
validation_utils.validate_username(invalid_username, iam_constants.IAM_PARAMETER_NAME.USERNAME);
131+
throw new NoErrorThrownError();
132+
} catch (err) {
133+
expect(err).toBeInstanceOf(RpcError);
134+
expect(err.rpc_code).toBe(RPC_CODE_VALIDATION_ERROR);
135+
}
136+
});
137+
});
138+
});

0 commit comments

Comments
 (0)