diff --git a/opencti-platform/opencti-graphql/src/domain/user.js b/opencti-platform/opencti-graphql/src/domain/user.js index de391be31a06..86366e65c985 100644 --- a/opencti-platform/opencti-graphql/src/domain/user.js +++ b/opencti-platform/opencti-graphql/src/domain/user.js @@ -418,7 +418,11 @@ export const roleEditContext = async (context, user, roleId, input) => { 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 + const myAdministratedOrganizationsIds = user.administrated_organizations.map(({ id }) => id); + if (!myAdministratedOrganizationsIds.includes(organizationId)) { + throw ForbiddenAccess(); + } } const targetUser = await findById(context, user, userId); if (!targetUser) { @@ -1004,7 +1008,11 @@ 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 + const myAdministratedOrganizationsIds = user.administrated_organizations.map(({ id }) => id); + if (!myAdministratedOrganizationsIds.includes(toId)) { + throw ForbiddenAccess(); + } } const targetUser = await findById(context, user, userId); if (!targetUser) { 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 fd8c4737c713..2febe52d2e64 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 } } `; - await adminQuery({ - query: UPDATE_QUERY, - variables: { id: testOrganizationId, input: { key: 'grantable_groups', value: [] } }, - }); + for (let i = 0; i < organizationsIds.length; i += 1) { + // Delete admin to ORGANIZATION + await adminQuery({ + query: ORGA_ADMIN_DELETE_QUERY, + variables: { + id: organizationsIds[i], + memberId: userEditorId, + }, + }); + // remove granted_groups to ORGANIZATION + await adminQuery({ + query: UPDATE_QUERY, + 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({ + const organizationAdminAddQueryResult = await adminQueryWithSuccess({ query: ORGA_ADMIN_ADD_QUERY, 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,84 @@ 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); + organizationsIds.push(platformOrganizationId); + 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 }); + + // Add Editor to PLATFORM_ORGANIZATION + const addEditorToOrgaQuery = await adminQueryWithSuccess({ + query: ORGANIZATION_ADD_QUERY, + 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, + 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, + 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, + variables: { + id: userInternalId, + organizationId: platformOrganizationId, + }, + }); + expect(queryResult.data.userEdit.organizationDelete.id).toEqual(userInternalId); + }); it('should user deleted', async () => { // Delete user await editorQuery({ @@ -838,8 +944,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 4215933e2c39..f45d7c8ef3cd 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(784); + expect(createEvents.length).toBe(786); 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(16); + expect(updateEventsByTypes['identity'].length).toBe(20); expect(updateEventsByTypes['malware'].length).toBe(16); 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(161); + expect(updateEvents.length).toBe(165); 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(135); + expect(deleteEvents.length).toBe(136); // 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 c3c5b6b04b3f..07e7a3766b3a 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 = 1088; +export const RAW_EVENTS_SIZE = 1095; export const SYNC_LIVE_EVENTS_SIZE = 608; export const PYTHON_PATH = './src/python/testing';