Skip to content

Commit

Permalink
[backend] Fix the detection and usage of dedicated headers (#8528)
Browse files Browse the repository at this point in the history
  • Loading branch information
richard-julien committed Sep 29, 2024
1 parent 337f272 commit 81f35d6
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 48 deletions.
18 changes: 10 additions & 8 deletions opencti-platform/opencti-graphql/src/database/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -2410,14 +2410,16 @@ const buildRelationDeduplicationFilters = (input) => {
};

const isOutdatedUpdate = (context, element, attributeKey) => {
const attributesMap = new Map((element[iAttributes.name] ?? []).map((obj) => [obj.name, obj]));
const { updated_at: lastAttributeUpdateDate } = attributesMap.get(attributeKey) ?? {};
if (lastAttributeUpdateDate && context.eventId) {
try {
const eventDate = utcDate(parseInt(context.eventId.split('-')[0], 10)).toISOString();
return utcDate(lastAttributeUpdateDate).isAfter(eventDate);
} catch (e) {
logApp.error('Error evaluating event id', { key: attributeKey, event_id: context.eventId });
if (context.eventId) {
const attributesMap = new Map((element[iAttributes.name] ?? []).map((obj) => [obj.name, obj]));
const { updated_at: lastAttributeUpdateDate } = attributesMap.get(attributeKey) ?? {};
if (lastAttributeUpdateDate) {
try {
const eventDate = utcDate(parseInt(context.eventId.split('-')[0], 10)).toISOString();
return utcDate(lastAttributeUpdateDate).isAfter(eventDate);
} catch (e) {
logApp.error('Error evaluating event id', { key: attributeKey, event_id: context.eventId });
}
}
}
return false;
Expand Down
23 changes: 1 addition & 22 deletions opencti-platform/opencti-graphql/src/graphql/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import { constraintDirectiveDocumentation } from 'graphql-constraint-directive';
import { GraphQLError } from 'graphql/error';
import { createApollo4QueryValidationPlugin } from 'graphql-constraint-directive/apollo4';
import createSchema from './schema';
import conf, { basePath, DEV_MODE, ENABLED_TRACING, GRAPHQL_ARMOR_ENABLED, logApp, PLAYGROUND_ENABLED, PLAYGROUND_INTROSPECTION_DISABLED } from '../config/conf';
import { authenticateUserFromRequest, userWithOrigin } from '../domain/user';
import conf, { basePath, DEV_MODE, ENABLED_TRACING, GRAPHQL_ARMOR_ENABLED, PLAYGROUND_ENABLED, PLAYGROUND_INTROSPECTION_DISABLED } from '../config/conf';
import { ForbiddenAccess, ValidationError } from '../config/errors';
import loggerPlugin from './loggerPlugin';
import telemetryPlugin from './telemetryPlugin';
import httpResponsePlugin from './httpResponsePlugin';
import { executionContext } from '../utils/access';

const createApolloServer = () => {
let schema = createSchema();
Expand Down Expand Up @@ -103,25 +101,6 @@ const createApolloServer = () => {
persistedQueries: false,
validationRules: apolloValidationRules,
csrfPrevention: false, // CSRF is handled by helmet
async context({ req, res }) {
const executeContext = executionContext('api');
executeContext.req = req;
executeContext.res = res;
// Building context from request headers
executeContext.workId = req.headers['opencti-work-id']; // Api call comes from a worker processing
executeContext.eventId = req.headers['opencti-event-id']; // Api call is due to listening event
executeContext.previousStandard = req.headers['previous-standard']; // Previous standard id
executeContext.synchronizedUpsert = req.headers['synchronized-upsert'] === 'true'; // If full sync needs to be done
try {
const user = await authenticateUserFromRequest(executeContext, req, res);
if (user) {
executeContext.user = userWithOrigin(req, user);
}
} catch (error) {
logApp.error(error);
}
return executeContext;
},
tracing: DEV_MODE,
plugins: apolloPlugins,
formatError: (formattedError) => {
Expand Down
15 changes: 11 additions & 4 deletions opencti-platform/opencti-graphql/src/http/httpServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,17 @@ const createHttpServer = async () => {
const executeContext = executionContext('api');
executeContext.req = req;
executeContext.res = res;
executeContext.workId = req.headers['opencti-work-id'];
const user = await authenticateUserFromRequest(executeContext, req, res);
if (user) {
executeContext.user = userWithOrigin(req, user);
executeContext.workId = req.headers['opencti-work-id']; // Api call comes from a worker processing
executeContext.eventId = req.headers['opencti-event-id']; // Api call is due to listening event
executeContext.previousStandard = req.headers['previous-standard']; // Previous standard id
executeContext.synchronizedUpsert = req.headers['synchronized-upsert'] === 'true'; // If full sync needs to be done
try {
const user = await authenticateUserFromRequest(executeContext, req, res);
if (user) {
executeContext.user = userWithOrigin(req, user);
}
} catch (error) {
logApp.error(error);
}
return executeContext;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@
import { expect, it, describe } from 'vitest';
import { elLoadById, } from '../../../src/database/engine';
import { ADMIN_USER, testContext } from '../../utils/testQuery';
import {
generateStandardId,
isStandardIdDowngraded,
isStandardIdSameWay,
isStandardIdUpgraded
} from '../../../src/schema/identifier';
import { generateStandardId, isStandardIdDowngraded, isStandardIdSameWay, isStandardIdUpgraded } from '../../../src/schema/identifier';
import { ENTITY_USER_ACCOUNT } from '../../../src/schema/stixCyberObservable';

describe('Identifier generation test', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ADMIN_USER, editorQuery, testContext, USER_PARTICIPATE } from '../../ut
import { elLoadById } from '../../../src/database/engine';
import { MARKING_TLP_GREEN, MARKING_TLP_RED } from '../../../src/schema/identifier';
import { queryAsUserIsExpectedForbidden } from '../../utils/testQueryHelper';
import { utcDate } from '../../../src/utils/format';

const LIST_QUERY = gql`
query administrativeAreas(
Expand Down Expand Up @@ -108,6 +109,11 @@ describe('AdministrativeArea resolver standard behavior', () => {
id
name
description
standard_id
objectLabel {
id
value
}
}
}
`;
Expand All @@ -117,6 +123,7 @@ describe('AdministrativeArea resolver standard behavior', () => {
name: 'Administrative-Area',
stix_id: administrativeAreaStixId,
description: 'Administrative-Area description',
objectLabel: ['report', 'note', 'malware']
},
};
const queryResult = await editorQuery({
Expand All @@ -126,8 +133,42 @@ describe('AdministrativeArea resolver standard behavior', () => {
expect(queryResult).not.toBeNull();
expect(queryResult.data.administrativeAreaAdd).not.toBeNull();
expect(queryResult.data.administrativeAreaAdd.name).toEqual('Administrative-Area');
expect(queryResult.data.administrativeAreaAdd.standard_id).toEqual('location--9904c841-f308-58bf-a39a-6ecd6024d3e0');
expect(queryResult.data.administrativeAreaAdd.objectLabel.length).toEqual(3);
administrativeAreaInternalId = queryResult.data.administrativeAreaAdd.id; // bc1f31d7-4d9d-4754-89b1-9a7813c7c521
});
it('should administrativeArea upsert with synchronized-upsert', async () => {
const CREATE_QUERY = gql`
mutation AdministrativeAreaAdd($input: AdministrativeAreaAddInput!) {
administrativeAreaAdd(input: $input) {
id
name
description
objectLabel {
id
value
}
}
}
`;
// Create the administrativeArea
const ADMINISTRATIVEAREA_TO_CREATE = {
input: {
name: 'Administrative-Area',
stix_id: administrativeAreaStixId,
objectLabel: ['opinion']
},
};
const queryResult = await editorQuery({
query: CREATE_QUERY,
variables: ADMINISTRATIVEAREA_TO_CREATE
}, { synchronizedUpsert: 'true' });
expect(queryResult).not.toBeNull();
expect(queryResult.data.administrativeAreaAdd).not.toBeNull();
expect(queryResult.data.administrativeAreaAdd.name).toEqual('Administrative-Area');
expect(queryResult.data.administrativeAreaAdd.objectLabel.length).toEqual(1);
expect(queryResult.data.administrativeAreaAdd.objectLabel[0].value).toEqual('opinion');
});
it('should administrativeArea loaded by internal id', async () => {
const queryResult = await editorQuery({ query: READ_QUERY, variables: { id: administrativeAreaInternalId } });
expect(queryResult).not.toBeNull();
Expand Down Expand Up @@ -162,14 +203,63 @@ describe('AdministrativeArea resolver standard behavior', () => {
administrativeAreaFieldPatch(id: $id, input: $input) {
id
name
description
standard_id
}
}
`;
const queryResult = await editorQuery({
query: UPDATE_QUERY,
variables: { id: administrativeAreaInternalId, input: { key: 'name', value: ['Administrative-Area - test'] } },
variables: {
id: administrativeAreaInternalId,
input: [
{ key: 'name', value: ['Administrative-Area - test'] },
{ key: 'description', value: ['Administrative-Area - test'] }
]
},
});
expect(queryResult.data.administrativeAreaFieldPatch.name).toEqual('Administrative-Area - test');
expect(queryResult.data.administrativeAreaFieldPatch.standard_id).toEqual('location--345ba2b4-3c57-5b5e-bc6d-b79aaa36d941');
expect(queryResult.data.administrativeAreaFieldPatch.description).toEqual('Administrative-Area - test');
});
it('should update administrativeArea via previous standard', async () => {
const CREATE_QUERY = gql`
mutation AdministrativeAreaAdd($input: AdministrativeAreaAddInput!) {
administrativeAreaAdd(input: $input) {
id
name
}
}
`;
const ADMINISTRATIVEAREA_TO_CREATE = {
input: {
name: 'Administrative-Area by previous'
},
};
const previousStandard = 'location--345ba2b4-3c57-5b5e-bc6d-b79aaa36d941';
const queryResult = await editorQuery({ query: CREATE_QUERY, variables: ADMINISTRATIVEAREA_TO_CREATE }, { previousStandard });
expect(queryResult.data.administrativeAreaAdd.name).toEqual('Administrative-Area by previous');
});
it('should not upsert administrativeArea if outdated', async () => {
const eventId = `${utcDate().subtract(1, 'minute').valueOf()}-0`;
const CREATE_QUERY = gql`
mutation AdministrativeAreaAdd($input: AdministrativeAreaAddInput!) {
administrativeAreaAdd(input: $input) {
id
name
description
}
}
`;
const ADMINISTRATIVEAREA_TO_CREATE = {
input: {
name: 'Administrative-Area description previous standard',
stix_id: administrativeAreaStixId,
description: 'Administrative-Area description'
},
};
const queryResult = await editorQuery({ query: CREATE_QUERY, variables: ADMINISTRATIVEAREA_TO_CREATE }, { eventId });
expect(queryResult.data.administrativeAreaAdd.description).toEqual('Administrative-Area - test');
});
it('should context patch administrativeArea', async () => {
const CONTEXT_PATCH_QUERY = gql`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('Raw streams tests', () => {
expect(updateEventsByTypes['malware'].length).toBe(16);
expect(updateEventsByTypes['intrusion-set'].length).toBe(4);
expect(updateEventsByTypes['data-component'].length).toBe(4);
expect(updateEventsByTypes['location'].length).toBe(12);
expect(updateEventsByTypes['location'].length).toBe(14);
expect(updateEventsByTypes['attack-pattern'].length).toBe(3);
expect(updateEventsByTypes['feedback'].length).toBe(1);
expect(updateEventsByTypes['course-of-action'].length).toBe(3);
Expand All @@ -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(153);
expect(updateEvents.length).toBe(155);
for (let updateIndex = 0; updateIndex < updateEvents.length; updateIndex += 1) {
const event = updateEvents[updateIndex];
const { data: insideData, origin, type } = event;
Expand Down
21 changes: 16 additions & 5 deletions opencti-platform/opencti-graphql/tests/utils/testQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 1065;
export const RAW_EVENTS_SIZE = 1067;
export const SYNC_LIVE_EVENTS_SIZE = 600;

export const PYTHON_PATH = './src/python/testing';
Expand Down Expand Up @@ -75,8 +75,19 @@ export const executeExternalQuery = async (client: AxiosInstance, uri: string, q
return data;
};

export const executeInternalQuery = async (client: AxiosInstance, query: unknown, variables = {}) => {
const response = await client.post(`${API_URI}/graphql`, { query, variables }, { withCredentials: true });
interface QueryOption {
workId?: string
eventId?: string
previousStandard?: string
synchronizedUpsert?: string
}
export const executeInternalQuery = async (client: AxiosInstance, query: unknown, variables = {}, options: QueryOption = {}) => {
const headers: any = {};
if (options.workId) headers['opencti-work-id'] = options.workId;
if (options.eventId) headers['opencti-event-id'] = options.eventId;
if (options.previousStandard) headers['previous-standard'] = options.previousStandard;
if (options.synchronizedUpsert) headers['synchronized-upsert'] = options.synchronizedUpsert;
const response = await client.post(`${API_URI}/graphql`, { query, variables }, { withCredentials: true, headers });
return response.data;
};
const adminClient = createHttpClient();
Expand Down Expand Up @@ -491,8 +502,8 @@ export const adminQuery = async (request: any) => {
return internalAdminQuery(print(request.query), request.variables);
};

export const editorQuery = async (request: any) => {
return executeInternalQuery(USER_EDITOR.client, print(request.query), request.variables);
export const editorQuery = async (request: any, options: QueryOption = {}) => {
return executeInternalQuery(USER_EDITOR.client, print(request.query), request.variables, options);
};

export const securityQuery = async (request: any) => {
Expand Down

0 comments on commit 81f35d6

Please sign in to comment.