From deb3ad380ec2fa5786c4cd84a8061db021d84802 Mon Sep 17 00:00:00 2001 From: marie flores Date: Mon, 23 Sep 2024 12:28:18 +0200 Subject: [PATCH] [backend] check administrated orga before admin add or remove organization to user (#8101) --- .../src/database/stix-converter.ts | 3 + .../opencti-graphql/src/domain/user.js | 14 +- .../src/python/testing/local_synchronizer.py | 3 + .../02-integration/02-resolvers/user-test.js | 173 +++++++++++++++--- .../tests/03-streams/00-Raw/raw-test.js | 8 +- .../opencti-graphql/tests/utils/testQuery.ts | 2 +- 6 files changed, 167 insertions(+), 36 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/database/stix-converter.ts b/opencti-platform/opencti-graphql/src/database/stix-converter.ts index bee2e18a107b..d1e96b469ee0 100644 --- a/opencti-platform/opencti-graphql/src/database/stix-converter.ts +++ b/opencti-platform/opencti-graphql/src/database/stix-converter.ts @@ -139,6 +139,9 @@ export const convertTypeToStixType = (type: string): string => { if (isStixCoreRelationship(type)) { return 'relationship'; } + if (isInternalRelationship(type)) { + return 'internal-relationship'; + } if (isStixSightingRelationship(type)) { return 'sighting'; } diff --git a/opencti-platform/opencti-graphql/src/domain/user.js b/opencti-platform/opencti-graphql/src/domain/user.js index c3c2ae0bcf9b..23872199c94a 100644 --- a/opencti-platform/opencti-graphql/src/domain/user.js +++ b/opencti-platform/opencti-graphql/src/domain/user.js @@ -418,9 +418,16 @@ export const roleEditContext = async (context, user, roleId, input) => { }); }; +const isUserAdministratingOrga = (user, organizationId) => { + return user.administrated_organizations.some(({ id }) => id === organizationId); +}; + export const assignOrganizationToUser = async (context, user, userId, organizationId) => { if (isOnlyOrgaAdmin(user)) { - throw ForbiddenAccess(); + // When user is organization admin, we make sure she is also admin of organization added + if (!isUserAdministratingOrga(user, organizationId)) { + throw ForbiddenAccess(); + } } const targetUser = await findById(context, user, userId); if (!targetUser) { @@ -1006,7 +1013,10 @@ export const userIdDeleteRelation = async (context, user, userId, toId, relation export const userDeleteOrganizationRelation = async (context, user, userId, toId) => { if (isOnlyOrgaAdmin(user)) { - throw ForbiddenAccess(); + // When user is organization admin, we make sure she is also admin of organization removed + if (!isUserAdministratingOrga(user, toId)) { + throw ForbiddenAccess(); + } } const targetUser = await findById(context, user, userId); if (!targetUser) { diff --git a/opencti-platform/opencti-graphql/src/python/testing/local_synchronizer.py b/opencti-platform/opencti-graphql/src/python/testing/local_synchronizer.py index 53305b015fac..9eca8c4859c4 100644 --- a/opencti-platform/opencti-graphql/src/python/testing/local_synchronizer.py +++ b/opencti-platform/opencti-graphql/src/python/testing/local_synchronizer.py @@ -62,6 +62,9 @@ def _process_message(self, msg): logging.info("%s", f"Processing event {msg.id}") self.count_number += 1 data = json.loads(msg.data) + type = data["data"]["type"] + if type == "internal-relationship": + return if msg.event == "create": bundle = { "type": "bundle", diff --git a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/user-test.js b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/user-test.js index a74156bf78c9..f404d8687703 100644 --- a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/user-test.js +++ b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/user-test.js @@ -11,6 +11,7 @@ import { getGroupIdByName, getOrganizationIdByName, getUserIdByEmail, + PLATFORM_ORGANIZATION, queryAsAdmin, TEST_ORGANIZATION, testContext, @@ -20,6 +21,7 @@ import { } from '../../utils/testQuery'; import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../../../src/modules/organization/organization-types'; import { VIRTUAL_ORGANIZATION_ADMIN } from '../../../src/utils/access'; +import { adminQueryWithSuccess, queryAsUserIsExpectedForbidden, queryAsUserWithSuccess } from '../../utils/testQueryHelper'; const LIST_QUERY = gql` query users( @@ -705,6 +707,44 @@ describe('User has no settings capability and is organization admin query behavi let userEditorId; let testOrganizationId; let amberGroupId; + let platformOrganizationId; + const organizationsIds = []; + + const ORGA_ADMIN_ADD_QUERY = gql` + mutation OrganizationAdminAdd($id: ID!, $memberId: String!) { + organizationAdminAdd(id: $id, memberId: $memberId) { + id + standard_id + } + } + `; + + const ORGANIZATION_ADD_QUERY = gql` + mutation UserOrganizationAddMutation( + $id: ID! + $organizationId: ID! + ) { + userEdit(id: $id) { + organizationAdd(organizationId: $organizationId) { + id + } + } + } + `; + + const ORGANIZATION_DELETE_QUERY = gql` + mutation UserOrganizationDeleteMutation( + $id: ID! + $organizationId: ID! + ) { + userEdit(id: $id) { + organizationDelete(organizationId: $organizationId) { + id + } + } + } + `; + afterAll(async () => { // remove the capability to administrate the Organization const ORGA_ADMIN_DELETE_QUERY = gql` @@ -714,15 +754,7 @@ describe('User has no settings capability and is organization admin query behavi } } `; - await adminQuery({ - query: ORGA_ADMIN_DELETE_QUERY, - variables: { - id: testOrganizationId, - memberId: userEditorId, - }, - }); - // remove granted_groups to TEST_ORGANIZATION const UPDATE_QUERY = gql` mutation OrganizationEdit($id: ID!, $input: [EditInput]!) { organizationFieldPatch(id: $id, input: $input) { @@ -734,31 +766,33 @@ describe('User has no settings capability and is organization admin query behavi } } `; + // Delete admin to ORGANIZATION await adminQuery({ - query: UPDATE_QUERY, - variables: { id: testOrganizationId, input: { key: 'grantable_groups', value: [] } }, + query: ORGA_ADMIN_DELETE_QUERY, // +1 update organization + variables: { + id: testOrganizationId, + memberId: userEditorId, + }, }); + for (let i = 0; i < organizationsIds.length; i += 1) { + // remove granted_groups to ORGANIZATION + await adminQuery({ + query: UPDATE_QUERY, // +1 update organization for each (+2 total) + variables: { id: organizationsIds[i], input: { key: 'grantable_groups', value: [] } }, + }); + } }); it('should has the capability to administrate the Organization', async () => { - const ORGA_ADMIN_ADD_QUERY = gql` - mutation OrganizationAdminAdd($id: ID!, $memberId: String!) { - organizationAdminAdd(id: $id, memberId: $memberId) { - id - standard_id - } - } - `; userEditorId = await getUserIdByEmail(USER_EDITOR.email); // USER_EDITOR is perfect because she has no settings capabilities and is part of TEST_ORGANIZATION - const queryResult = await adminQuery({ - query: ORGA_ADMIN_ADD_QUERY, + const organizationAdminAddQueryResult = await adminQueryWithSuccess({ + query: ORGA_ADMIN_ADD_QUERY, // +1 update event of organization variables: { id: TEST_ORGANIZATION.id, memberId: userEditorId, }, }); - expect(queryResult).not.toBeNull(); - expect(queryResult.data.organizationAdminAdd).not.toBeNull(); - expect(queryResult.data.organizationAdminAdd.standard_id).toEqual(TEST_ORGANIZATION.id); + expect(organizationAdminAddQueryResult.data.organizationAdminAdd).not.toBeNull(); + expect(organizationAdminAddQueryResult.data.organizationAdminAdd.standard_id).toEqual(TEST_ORGANIZATION.id); // Check that USER_EDITOR is Organization administrator const editorUserQueryResult = await adminQuery({ query: READ_QUERY, variables: { id: userEditorId } }); @@ -769,9 +803,10 @@ describe('User has no settings capability and is organization admin query behavi expect(capabilities.some((capa) => capa.name === VIRTUAL_ORGANIZATION_ADMIN)).toEqual(true); }); it('should user created', async () => { - // Create the user testOrganizationId = await getOrganizationIdByName(TEST_ORGANIZATION.name); + organizationsIds.push(testOrganizationId); amberGroupId = await getGroupIdByName(AMBER_GROUP.name); + const USER_TO_CREATE = { input: { name: 'User', @@ -811,9 +846,8 @@ describe('User has no settings capability and is organization admin query behavi }); expect(user).not.toBeNull(); expect(user.data.userAdd).not.toBeNull(); - userInternalId = user.data.userAdd.id; - expect(user.data.userAdd.name).toEqual('User'); + userInternalId = user.data.userAdd.id; }); it('should update user from its own organization', async () => { const UPDATE_QUERY = gql` @@ -825,12 +859,94 @@ describe('User has no settings capability and is organization admin query behavi } } `; - const queryResult = await editorQuery({ + const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { query: UPDATE_QUERY, variables: { id: userInternalId, input: { key: 'account_status', value: ['Inactive'] } }, }); expect(queryResult.data.userEdit.fieldPatch.account_status).toEqual('Inactive'); }); + it('should not add organization to user if not admin', async () => { + platformOrganizationId = await getOrganizationIdByName(PLATFORM_ORGANIZATION.name); + await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { + query: ORGANIZATION_ADD_QUERY, + variables: { + id: userInternalId, + organizationId: platformOrganizationId, + }, + }); + }); + it('should administrate more than 1 organization', async () => { + // Need to add granted_groups to PLATFORM_ORGANIZATION because of line 533 in domain/user.js + const UPDATE_QUERY = gql` + mutation OrganizationEdit($id: ID!, $input: [EditInput]!) { + organizationFieldPatch(id: $id, input: $input) { + id + name + grantable_groups { + id + } + } + } + `; + const grantableGroupQueryResult = await adminQuery({ + query: UPDATE_QUERY, + variables: { id: platformOrganizationId, input: { key: 'grantable_groups', value: [amberGroupId] } }, + }); + expect(grantableGroupQueryResult.data.organizationFieldPatch.grantable_groups.length).toEqual(1); + expect(grantableGroupQueryResult.data.organizationFieldPatch.grantable_groups[0]).toEqual({ id: amberGroupId }); + organizationsIds.push(platformOrganizationId); + + // Add Editor to PLATFORM_ORGANIZATION + const addEditorToOrgaQuery = await adminQueryWithSuccess({ + query: ORGANIZATION_ADD_QUERY, // +1 create of relation between orga & user + variables: { + id: userEditorId, + organizationId: platformOrganizationId, + }, + }); + expect(addEditorToOrgaQuery.data.userEdit.organizationAdd.id).toEqual(userEditorId); + + // Editor administrate PLATFORM_ORGANIZATION + const queryResult = await adminQueryWithSuccess({ + query: ORGA_ADMIN_ADD_QUERY, // +1 update event of organization + variables: { + id: PLATFORM_ORGANIZATION.id, + memberId: userEditorId, + }, + }); + expect(queryResult.data.organizationAdminAdd).not.toBeNull(); + expect(queryResult.data.organizationAdminAdd.standard_id).toEqual(PLATFORM_ORGANIZATION.id); + }); + it('should add 2nd organization to user if admin', async () => { + const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { + query: ORGANIZATION_ADD_QUERY, // +1 create of relation between orga & user + variables: { + id: userInternalId, + organizationId: platformOrganizationId, + }, + }); + expect(queryResult.data.userEdit.organizationAdd.id).toEqual(userInternalId); + }); + it('should delete 2nd organization to user if admin', async () => { + const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { + query: ORGANIZATION_DELETE_QUERY, // +1 delete of relation between orga & user + variables: { + id: userInternalId, + organizationId: platformOrganizationId, + }, + }); + expect(queryResult.data.userEdit.organizationDelete.id).toEqual(userInternalId); + }); + it('should remove Editor from PLATFORM_ORGANIZATION', async () => { + const queryResult = await adminQueryWithSuccess({ + query: ORGANIZATION_DELETE_QUERY, // +1 delete event (delete relation) +1 update event + variables: { + id: userEditorId, + organizationId: platformOrganizationId, + }, + }); + expect(queryResult.data.userEdit.organizationDelete.id).toEqual(userEditorId); + }); it('should user deleted', async () => { // Delete user await editorQuery({ @@ -838,8 +954,7 @@ describe('User has no settings capability and is organization admin query behavi variables: { id: userInternalId }, }); // Verify is no longer found - const queryResult = await adminQuery({ query: READ_QUERY, variables: { id: userInternalId } }); - expect(queryResult).not.toBeNull(); + const queryResult = await adminQueryWithSuccess({ query: READ_QUERY, variables: { id: userInternalId } }); expect(queryResult.data.user).toBeNull(); }); }); diff --git a/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js b/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js index f9f1b636901a..6587c68f33a5 100644 --- a/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js +++ b/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js @@ -46,7 +46,7 @@ describe('Raw streams tests', () => { expect(createEventsByTypes.tool.length).toBe(2); expect(createEventsByTypes.vocabulary.length).toBe(342); // 328 created at init + 2 created in tests + 5 vocabulary organizations types + 7 persona expect(createEventsByTypes.vulnerability.length).toBe(7); - expect(createEvents.length).toBe(793); + expect(createEvents.length).toBe(795); for (let createIndex = 0; createIndex < createEvents.length; createIndex += 1) { const { data: insideData, origin, type } = createEvents[createIndex]; expect(origin).toBeDefined(); @@ -58,7 +58,7 @@ describe('Raw streams tests', () => { expect(updateEventsByTypes['marking-definition'].length).toBe(2); expect(updateEventsByTypes['campaign'].length).toBe(7); expect(updateEventsByTypes['relationship'].length).toBe(8); - expect(updateEventsByTypes['identity'].length).toBe(18); + expect(updateEventsByTypes['identity'].length).toBe(22); expect(updateEventsByTypes['malware'].length).toBe(17); expect(updateEventsByTypes['intrusion-set'].length).toBe(4); expect(updateEventsByTypes['data-component'].length).toBe(4); @@ -82,7 +82,7 @@ describe('Raw streams tests', () => { expect(updateEventsByTypes['threat-actor'].length).toBe(17); expect(updateEventsByTypes['vocabulary'].length).toBe(3); expect(updateEventsByTypes['vulnerability'].length).toBe(3); - expect(updateEvents.length).toBe(169); + expect(updateEvents.length).toBe(173); for (let updateIndex = 0; updateIndex < updateEvents.length; updateIndex += 1) { const event = updateEvents[updateIndex]; const { data: insideData, origin, type } = event; @@ -95,7 +95,7 @@ describe('Raw streams tests', () => { } // 03 - CHECK DELETE EVENTS const deleteEvents = events.filter((e) => e.type === EVENT_TYPE_DELETE); - expect(deleteEvents.length).toBe(144); + expect(deleteEvents.length).toBe(146); // const deleteEventsByTypes = R.groupBy((e) => e.data.data.type, deleteEvents); for (let delIndex = 0; delIndex < deleteEvents.length; delIndex += 1) { const { data: insideData, origin, type } = deleteEvents[delIndex]; diff --git a/opencti-platform/opencti-graphql/tests/utils/testQuery.ts b/opencti-platform/opencti-graphql/tests/utils/testQuery.ts index 3bc5d92b72e3..9666c4cf6515 100644 --- a/opencti-platform/opencti-graphql/tests/utils/testQuery.ts +++ b/opencti-platform/opencti-graphql/tests/utils/testQuery.ts @@ -22,7 +22,7 @@ export const SYNC_LIVE_START_REMOTE_URI = conf.get('app:sync_live_start_remote_u export const SYNC_DIRECT_START_REMOTE_URI = conf.get('app:sync_direct_start_remote_uri'); export const SYNC_RESTORE_START_REMOTE_URI = conf.get('app:sync_restore_start_remote_uri'); export const SYNC_TEST_REMOTE_URI = `http://api-tests:${PORT}`; -export const RAW_EVENTS_SIZE = 1114; +export const RAW_EVENTS_SIZE = 1122; export const SYNC_LIVE_EVENTS_SIZE = 608; export const PYTHON_PATH = './src/python/testing';