Skip to content

Commit 882402e

Browse files
[backend] WIP Update model: authorized members activation via entity(#4538)
1 parent 552d470 commit 882402e

File tree

6 files changed

+119
-6
lines changed

6 files changed

+119
-6
lines changed

opencti-platform/opencti-front/src/schema/relay.schema.graphql

+1
Original file line numberDiff line numberDiff line change
@@ -8381,6 +8381,7 @@ type Mutation {
83818381
caseTemplateRelationDelete(id: ID!, toId: StixRef!, relationship_type: String!): CaseTemplate
83828382
caseIncidentAdd(input: CaseIncidentAddInput!): CaseIncident
83838383
caseIncidentDelete(id: ID!): ID
8384+
caseIncidentEditAuthorizedMembers(id: ID!, input: [MemberAccessInput!]): CaseIncident
83848385
caseRfiAdd(input: CaseRfiAddInput!): CaseRfi
83858386
caseRfiDelete(id: ID!): ID
83868387
caseRftAdd(input: CaseRftAddInput!): CaseRft

opencti-platform/opencti-graphql/src/generated/graphql.ts

+8
Original file line numberDiff line numberDiff line change
@@ -12550,6 +12550,7 @@ export type Mutation = {
1255012550
caseDelete?: Maybe<Scalars['ID']['output']>;
1255112551
caseIncidentAdd?: Maybe<CaseIncident>;
1255212552
caseIncidentDelete?: Maybe<Scalars['ID']['output']>;
12553+
caseIncidentEditAuthorizedMembers?: Maybe<CaseIncident>;
1255312554
caseRfiAdd?: Maybe<CaseRfi>;
1255412555
caseRfiDelete?: Maybe<Scalars['ID']['output']>;
1255512556
caseRftAdd?: Maybe<CaseRft>;
@@ -13059,6 +13060,12 @@ export type MutationCaseIncidentDeleteArgs = {
1305913060
};
1306013061

1306113062

13063+
export type MutationCaseIncidentEditAuthorizedMembersArgs = {
13064+
id: Scalars['ID']['input'];
13065+
input?: InputMaybe<Array<MemberAccessInput>>;
13066+
};
13067+
13068+
1306213069
export type MutationCaseRfiAddArgs = {
1306313070
input: CaseRfiAddInput;
1306413071
};
@@ -34583,6 +34590,7 @@ export type MutationResolvers<ContextType = any, ParentType extends ResolversPar
3458334590
caseDelete?: Resolver<Maybe<ResolversTypes['ID']>, ParentType, ContextType, RequireFields<MutationCaseDeleteArgs, 'id'>>;
3458434591
caseIncidentAdd?: Resolver<Maybe<ResolversTypes['CaseIncident']>, ParentType, ContextType, RequireFields<MutationCaseIncidentAddArgs, 'input'>>;
3458534592
caseIncidentDelete?: Resolver<Maybe<ResolversTypes['ID']>, ParentType, ContextType, RequireFields<MutationCaseIncidentDeleteArgs, 'id'>>;
34593+
caseIncidentEditAuthorizedMembers?: Resolver<Maybe<ResolversTypes['CaseIncident']>, ParentType, ContextType, RequireFields<MutationCaseIncidentEditAuthorizedMembersArgs, 'id'>>;
3458634594
caseRfiAdd?: Resolver<Maybe<ResolversTypes['CaseRfi']>, ParentType, ContextType, RequireFields<MutationCaseRfiAddArgs, 'input'>>;
3458734595
caseRfiDelete?: Resolver<Maybe<ResolversTypes['ID']>, ParentType, ContextType, RequireFields<MutationCaseRfiDeleteArgs, 'id'>>;
3458834596
caseRftAdd?: Resolver<Maybe<ResolversTypes['CaseRft']>, ParentType, ContextType, RequireFields<MutationCaseRftAddArgs, 'input'>>;

opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident-domain.ts

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type { AuthContext, AuthUser } from '../../../types/user';
2-
import { createEntity } from '../../../database/middleware';
2+
import { createEntity, patchAttribute } from '../../../database/middleware';
33
import type { EntityOptions } from '../../../database/middleware-loader';
44
import { internalLoadById, listEntitiesPaginated, storeLoadById } from '../../../database/middleware-loader';
55
import { BUS_TOPICS } from '../../../config/conf';
6-
import { ABSTRACT_STIX_DOMAIN_OBJECT, buildRefRelationKey } from '../../../schema/general';
6+
import { ABSTRACT_STIX_CORE_OBJECT, ABSTRACT_STIX_DOMAIN_OBJECT, buildRefRelationKey } from '../../../schema/general';
77
import { notify } from '../../../database/redis';
88
import { now } from '../../../utils/format';
99
import { userAddIndividual } from '../../../domain/user';
@@ -12,10 +12,13 @@ import { upsertTemplateForCase } from '../case-domain';
1212
import type { BasicStoreEntityCaseIncident } from './case-incident-types';
1313
import { ENTITY_TYPE_CONTAINER_CASE_INCIDENT } from './case-incident-types';
1414
import type { DomainFindById } from '../../../domain/domainTypes';
15-
import type { CaseIncidentAddInput } from '../../../generated/graphql';
15+
import type { CaseIncidentAddInput, MemberAccessInput } from '../../../generated/graphql';
1616
import { isStixId } from '../../../schema/schemaUtils';
1717
import { RELATION_OBJECT } from '../../../schema/stixRefRelationship';
1818
import { FilterMode } from '../../../generated/graphql';
19+
import { isValidMemberAccessRight } from '../../../utils/access';
20+
import { containsValidAdmin } from '../../../utils/authorizedMembers';
21+
import { FunctionalError } from '../../../config/errors';
1922

2023
export const findById: DomainFindById<BasicStoreEntityCaseIncident> = (context: AuthContext, user: AuthUser, caseIncidentId: string) => {
2124
return storeLoadById(context, user, caseIncidentId, ENTITY_TYPE_CONTAINER_CASE_INCIDENT);
@@ -59,3 +62,34 @@ export const caseIncidentContainsStixObjectOrStixRelationship = async (context:
5962
const caseIncidentFound = await findAll(context, user, args);
6063
return caseIncidentFound.edges.length > 0;
6164
};
65+
66+
export const caseIncidentEditAuthorizedMembers = async (
67+
context: AuthContext,
68+
user: AuthUser,
69+
entityId: string,
70+
input: MemberAccessInput[] | undefined | null
71+
) => {
72+
let authorized_members: { id: string, access_right: string }[] | null = null;
73+
74+
if (input) {
75+
// validate input (validate access right) and remove duplicates
76+
const filteredInput = input.filter((value, index, array) => {
77+
return isValidMemberAccessRight(value.access_right) && array.findIndex((e) => e.id === value.id) === index;
78+
});
79+
80+
const hasValidAdmin = await containsValidAdmin(
81+
context,
82+
filteredInput,
83+
['KNOWLEDGE_KNUPDATE_KNMANAGEAUTHMEMBERS']
84+
);
85+
if (!hasValidAdmin) {
86+
throw FunctionalError('It should have at least one valid member with admin access');
87+
}
88+
89+
authorized_members = filteredInput.map(({ id, access_right }) => ({ id, access_right }));
90+
}
91+
92+
const patch = { authorized_members };
93+
const { element } = await patchAttribute(context, user, entityId, ENTITY_TYPE_CONTAINER_CASE_INCIDENT, patch);
94+
return notify(BUS_TOPICS[ABSTRACT_STIX_CORE_OBJECT].EDIT_TOPIC, element, user);
95+
};

opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident-resolvers.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Resolvers } from '../../../generated/graphql';
22
import { buildRefRelationKey } from '../../../schema/general';
33
import { RELATION_OBJECT_ASSIGNEE } from '../../../schema/stixRefRelationship';
44
import { stixDomainObjectDelete } from '../../../domain/stixDomainObject';
5-
import { addCaseIncident, caseIncidentContainsStixObjectOrStixRelationship, findAll, findById } from './case-incident-domain';
5+
import { addCaseIncident, caseIncidentContainsStixObjectOrStixRelationship, findAll, findById, caseIncidentEditAuthorizedMembers } from './case-incident-domain';
66
import { getAuthorizedMembers } from '../../../utils/authorizedMembers';
77

88
const caseIncidentResolvers: Resolvers = {
@@ -27,6 +27,9 @@ const caseIncidentResolvers: Resolvers = {
2727
caseIncidentDelete: (_, { id }, context) => {
2828
return stixDomainObjectDelete(context, context.user, id);
2929
},
30+
caseIncidentEditAuthorizedMembers: (_, { id, input }, context) => {
31+
return caseIncidentEditAuthorizedMembers(context, context.user, id, input);
32+
},
3033
}
3134
};
3235

opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql

+1
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,5 @@ input CaseIncidentAddInput {
218218
type Mutation {
219219
caseIncidentAdd(input: CaseIncidentAddInput!): CaseIncident @auth
220220
caseIncidentDelete(id: ID!): ID @auth(for: [KNOWLEDGE_KNUPDATE_KNDELETE])
221+
caseIncidentEditAuthorizedMembers(id: ID!, input:[MemberAccessInput!]): CaseIncident @auth(for: [KNOWLEDGE_KNUPDATE_KNMANAGEAUTHMEMBERS])
221222
}

opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/case-incident-response-test.ts

+68-2
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,75 @@ describe('Case Incident Response resolver standard behavior', () => {
134134
});
135135
});
136136

137-
describe('Case Incident Response authorized_members standard behavior', () => {
137+
describe('Case Incident Response standard behavior with authorized_members activation from entity', () => {
138+
let caseIncidentResponse: CaseIncident;
139+
it('should Case Incident Response created', async () => {
140+
// Create Case Incident Response
141+
const caseIncidentResponseQueryResult = await queryAsAdmin({
142+
query: CREATE_QUERY,
143+
variables: {
144+
input: {
145+
name: 'Case Incident Response With Authorized Members'
146+
}
147+
}
148+
});
149+
caseIncidentResponse = caseIncidentResponseQueryResult?.data?.caseIncidentAdd;
150+
151+
// Activate Authorized members
152+
const EDIT_AUTHORIZED_MEMBERS_QUERY = gql`
153+
mutation CaseIncidentEditAuthorizedMembers(
154+
$id: ID!
155+
$input: [MemberAccessInput!]!
156+
) {
157+
caseIncidentEditAuthorizedMembers(id: $id, input: $input){
158+
authorized_members {
159+
id
160+
name
161+
entity_type
162+
access_right
163+
}
164+
id
165+
}
166+
}
167+
`;
168+
169+
await queryAsAdmin({
170+
query: EDIT_AUTHORIZED_MEMBERS_QUERY,
171+
variables: {
172+
id: caseIncidentResponse.id,
173+
input: [
174+
{
175+
id: ADMIN_USER.id,
176+
access_right: 'admin'
177+
}
178+
]
179+
}
180+
});
181+
expect(caseIncidentResponseQueryResult).not.toBeNull();
182+
expect(caseIncidentResponseQueryResult?.data?.caseIncidentAdd.authorized_members).not.toBeUndefined();
183+
expect(caseIncidentResponseQueryResult?.data?.caseIncidentAdd.authorized_members).toEqual([
184+
{
185+
id: ADMIN_USER.id,
186+
access_right: 'admin'
187+
}
188+
]);
189+
});
190+
it('should Case Incident Response deleted', async () => {
191+
// Delete the case
192+
await queryAsAdmin({
193+
query: DELETE_QUERY,
194+
variables: { id: caseIncidentResponse.id },
195+
});
196+
// Verify is no longer found
197+
const queryResult = await queryAsAdmin({ query: READ_QUERY, variables: { id: caseIncidentResponse.id } });
198+
expect(queryResult).not.toBeNull();
199+
expect(queryResult?.data?.caseIncident).toBeNull();
200+
});
201+
});
202+
203+
describe('Case Incident Response standard behavior with authorized_members activated via settings', () => {
138204
let caseIncidentResponseAuthorizedMembers: CaseIncident;
139-
it('should Case Incident Response created with authorized_members activated via settings', async () => {
205+
it('should Case Incident Response created', async () => {
140206
// Activate authorized members for IR
141207
const ENTITY_SETTINGS_READ_QUERY_BY_TARGET_TYPE = gql`
142208
query entitySettingsByTargetType($targetType: String!) {

0 commit comments

Comments
 (0)