Skip to content

Commit af402d5

Browse files
[backend] WIP Update model: authorized members activation via entity(#4538)
1 parent cd07267 commit af402d5

File tree

6 files changed

+119
-7
lines changed

6 files changed

+119
-7
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-3
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ describe('Case Incident Response resolver standard behavior', () => {
120120
});
121121
expect(queryResult?.data?.stixDomainObjectEdit.fieldPatch.name).toEqual('Case - updated');
122122
});
123-
// TODO ADD context test even if i don't understand what it is?
124123
it('should Case Incident Response deleted', async () => {
125124
// Delete the case
126125
await queryAsAdmin({
@@ -134,9 +133,75 @@ describe('Case Incident Response resolver standard behavior', () => {
134133
});
135134
});
136135

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

0 commit comments

Comments
 (0)