Skip to content

Commit

Permalink
[backend] crop effective confidence level in [0-100] (#5759)
Browse files Browse the repository at this point in the history
  • Loading branch information
labo-flg authored and Archidoit committed Jun 3, 2024
1 parent 3d81896 commit f9aae34
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 121 deletions.
38 changes: 3 additions & 35 deletions opencti-platform/opencti-graphql/src/domain/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../modules/organization/organ
import { ENTITY_TYPE_WORKSPACE } from '../modules/workspace/workspace-types';
import { extractFilterKeys } from '../utils/filtering/filtering-utils';
import { testFilterGroup, testStringFilter } from '../utils/filtering/boolean-logic-engine';
import { computeUserEffectiveConfidenceLevel } from '../utils/confidence-level';

const BEARER = 'Bearer ';
const BASIC = 'Basic ';
Expand Down Expand Up @@ -1223,7 +1224,7 @@ export const buildCompleteUser = async (context, client) => {
const default_hidden_types = uniq(defaultHiddenTypesGroups.concat(defaultHiddenTypesOrgs));

// effective confidence level
const effective_confidence_level = computeUserEffectiveConfidenceLevel({ ...client, groups, organizations });
const effective_confidence_level = computeUserEffectiveConfidenceLevel({ ...client, groups });

return {
...client,
Expand Down Expand Up @@ -1463,39 +1464,6 @@ export const userEditContext = async (context, user, userId, input) => {
// endregion

export const getUserEffectiveConfidenceLevel = async (userId, context) => {
const user = await findById(context, context.user, userId);
const user = await findById(context, context.user, userId); // this returns a complete user, with groups
return computeUserEffectiveConfidenceLevel(user);
};

export const computeUserEffectiveConfidenceLevel = (user) => {
// if user has a specific confidence level, it overrides everything and we return it
if (user.user_confidence_level) {
return {
...user.user_confidence_level,
source: user,
};
}

// otherwise we get all groups for this user, and select the lowest max_confidence found
let minLevel = null;
let source = null;
for (let i = 0; i < user.groups.length; i += 1) {
const groupLevel = user.groups[i].group_confidence_level?.max_confidence ?? null;
if (minLevel === null || (groupLevel !== null && groupLevel < minLevel)) {
minLevel = groupLevel;
source = user.groups[i];
}
}

if (minLevel !== null) {
return {
max_confidence: minLevel,
// TODO: handle overrides and their sources
overrides: [],
source,
};
}

// finally, if this user has no effective confidence level, we can return null
return null;
};
2 changes: 2 additions & 0 deletions opencti-platform/opencti-graphql/src/types/group.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { BasicStoreCommon } from './store';
import type { ConfidenceLevel } from '../generated/graphql';

interface DefaultMarking {
entity_type: string,
Expand All @@ -7,4 +8,5 @@ interface DefaultMarking {

interface Group extends BasicStoreCommon {
default_marking?: Array<DefaultMarking>;
group_confidence_level: ConfidenceLevel
}
3 changes: 3 additions & 0 deletions opencti-platform/opencti-graphql/src/types/user.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { BasicStoreCommon, BasicStoreIdentifier, StoreMarkingDefinition } from './store';
import type { Group } from './group';
import type { ConfidenceLevel } from '../generated/graphql';

interface UserRole extends BasicStoreIdentifier {
name: string;
Expand Down Expand Up @@ -41,6 +42,8 @@ interface AuthUser extends BasicStoreIdentifier {
all_marking: Array<StoreMarkingDefinition>;
api_token: string;
account_status: string;
effective_confidence_level: ConfidenceLevel | null;
user_confidence_level: ConfidenceLevel | null;
}

interface AuthContext {
Expand Down
34 changes: 34 additions & 0 deletions opencti-platform/opencti-graphql/src/utils/access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ export const SYSTEM_USER: AuthUser = {
account_lock_after_date: undefined,
account_status: ACCOUNT_STATUS_ACTIVE,
administrated_organizations: [],
effective_confidence_level: {
max_confidence: 100,
overrides: [],
},
user_confidence_level: {
max_confidence: 100,
overrides: [],
},
};

export const RETENTION_MANAGER_USER: AuthUser = {
Expand All @@ -100,6 +108,14 @@ export const RETENTION_MANAGER_USER: AuthUser = {
account_lock_after_date: undefined,
account_status: ACCOUNT_STATUS_ACTIVE,
administrated_organizations: [],
effective_confidence_level: {
max_confidence: 100,
overrides: [],
},
user_confidence_level: {
max_confidence: 100,
overrides: [],
},
};

export const RULE_MANAGER_USER: AuthUser = {
Expand All @@ -123,6 +139,14 @@ export const RULE_MANAGER_USER: AuthUser = {
account_lock_after_date: undefined,
account_status: ACCOUNT_STATUS_ACTIVE,
administrated_organizations: [],
effective_confidence_level: {
max_confidence: 100,
overrides: [],
},
user_confidence_level: {
max_confidence: 100,
overrides: [],
},
};

export const AUTOMATION_MANAGER_USER: AuthUser = {
Expand All @@ -146,6 +170,14 @@ export const AUTOMATION_MANAGER_USER: AuthUser = {
account_lock_after_date: undefined,
account_status: ACCOUNT_STATUS_ACTIVE,
administrated_organizations: [],
effective_confidence_level: {
max_confidence: 100,
overrides: [],
},
user_confidence_level: {
max_confidence: 100,
overrides: [],
},
};

export const REDACTED_USER: AuthUser = {
Expand All @@ -169,6 +201,8 @@ export const REDACTED_USER: AuthUser = {
api_token: '',
account_lock_after_date: undefined,
account_status: ACCOUNT_STATUS_ACTIVE,
effective_confidence_level: null,
user_confidence_level: null,
};

export interface AuthorizedMember { id: string, access_right: string }
Expand Down
44 changes: 44 additions & 0 deletions opencti-platform/opencti-graphql/src/utils/confidence-level.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { AuthUser } from '../types/user';
import { cropNumber } from './math';

export const computeUserEffectiveConfidenceLevel = (user: AuthUser) => {
// if user has a specific confidence level, it overrides everything and we return it
if (user.user_confidence_level) {
return {
// we make sure the levels are cropped in between 0-100
// other values were possibly injected in octi <6.0 though API calls
max_confidence: cropNumber(user.user_confidence_level.max_confidence, 0, 100),
overrides: user.user_confidence_level.overrides.map((override) => ({
max_confidence: cropNumber(override.max_confidence, 0, 100),
entity_type: override.entity_type,
})),
source: user,
};
}

// otherwise we get all groups for this user, and select the lowest max_confidence found
let minLevel = null;
let source = null;
if (user.groups) {
for (let i = 0; i < user.groups.length; i += 1) {
// groups were not migrated when introducing group_confidence_level, so group_confidence_level might be null
const groupLevel = user.groups[i].group_confidence_level?.max_confidence ?? null;
if (groupLevel !== null && (minLevel === null || groupLevel < minLevel)) {
minLevel = groupLevel;
source = user.groups[i];
}
}
}

if (minLevel !== null) {
return {
max_confidence: cropNumber(minLevel, 0, 100),
// TODO: handle overrides and their sources
overrides: [],
source,
};
}

// finally, if this user has no effective confidence level, we can return null
return null;
};
12 changes: 12 additions & 0 deletions opencti-platform/opencti-graphql/src/utils/math.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FunctionalError } from '../config/errors';

export const cropNumber = (value: number, min: number, max: number) => {
if (!Number.isFinite(value) || !Number.isFinite(min) || !Number.isFinite(max)) {
throw FunctionalError('Cannot process non-finite input');
}
if (min > max) {
throw FunctionalError('min cannot be greater than max');
}

return Math.max(Math.min(value, max), min);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { testContext } from '../../utils/testQuery';
import { checkPasswordInlinePolicy, computeUserEffectiveConfidenceLevel } from '../../../src/domain/user';
import { checkPasswordInlinePolicy } from '../../../src/domain/user';

describe('password checker', () => {
it('should no policy applied', async () => {
Expand Down Expand Up @@ -75,61 +75,3 @@ describe('password checker', () => {
expect(checkPasswordInlinePolicy(testContext, policy03, 'ju!lA').length).toBe(0);
});
});

describe('Effective max confidence level', () => {
const group70 = {
id: 'group70',
group_confidence_level: {
max_confidence: 70,
overrides: [],
}
};

const group80 = {
id: 'group80',
group_confidence_level: {
max_confidence: 70,
overrides: [],
}
};

it("user's confidence level overrides groups and orgs", async () => {
// minimal subset of a real User
const userA = {
id: 'userA',
user_confidence_level: {
max_confidence: 30,
overrides: [],
},
groups: [group70, group80],
};
expect(computeUserEffectiveConfidenceLevel(userA)).toEqual({
max_confidence: 30,
overrides: [],
source: userA,
});

const userB = {
id: 'userB',
user_confidence_level: null,
groups: [group70, group80],
};
expect(computeUserEffectiveConfidenceLevel(userB)).toEqual({
max_confidence: 70,
overrides: [],
source: group70,
});

const userD = {
user_confidence_level: null,
groups: [{ group_confidence_level: null }, { group_confidence_level: null }],
};

const userE = {
user_confidence_level: null,
groups: [],
};
expect(computeUserEffectiveConfidenceLevel(userD)).toBeNull();
expect(computeUserEffectiveConfidenceLevel(userE)).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, expect, it } from 'vitest';
import { computeUserEffectiveConfidenceLevel } from '../../../src/utils/confidence-level';
import type { AuthUser } from '../../../src/types/user';

describe('Confidence level utilities', () => {
it('computeUserEffectiveConfidenceLevel should correctly compute the effective level', async () => {
const group70 = {
id: 'group70',
group_confidence_level: {
max_confidence: 70,
overrides: [],
}
};

const group80 = {
id: 'group80',
group_confidence_level: {
max_confidence: 70,
overrides: [],
}
};

const groupNull = {
id: 'groupNull',
group_confidence_level: null
};

// minimal subset of a real User
const userA = {
id: 'userA',
user_confidence_level: {
max_confidence: 30,
overrides: [],
},
groups: [group70, group80],
};
expect(computeUserEffectiveConfidenceLevel(userA as unknown as AuthUser)).toEqual({
max_confidence: 30,
overrides: [],
source: userA,
});

const userB = {
id: 'userB',
user_confidence_level: null,
groups: [group70, group80],
};
expect(computeUserEffectiveConfidenceLevel(userB as unknown as AuthUser)).toEqual({
max_confidence: 70,
overrides: [],
source: group70,
});

const userC = {
user_confidence_level: null,
groups: [groupNull, group70, groupNull],
};
expect(computeUserEffectiveConfidenceLevel(userC as unknown as AuthUser)).toEqual({
max_confidence: 70,
overrides: [],
source: group70,
});

const userD = {
user_confidence_level: null,
groups: [groupNull, groupNull],
};
expect(computeUserEffectiveConfidenceLevel(userD as unknown as AuthUser)).toBeNull();

const userE = {
user_confidence_level: null,
groups: [],
};
expect(computeUserEffectiveConfidenceLevel(userE as unknown as AuthUser)).toBeNull();
});
});
20 changes: 20 additions & 0 deletions opencti-platform/opencti-graphql/tests/01-unit/utils/math-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest';
import { cropNumber } from '../../../src/utils/math';
import { FunctionalError } from '../../../src/config/errors';

describe('Math utilities: cropNumber', () => {
it('should crop value with various min/max inputs', () => {
expect(cropNumber(50, 10, 100)).toEqual(50);
expect(cropNumber(50, 60, 100)).toEqual(60);
expect(cropNumber(50, 10, 30)).toEqual(30);

expect(() => cropNumber(50, 100, 10))
.toThrow(FunctionalError('min cannot be greater than max'));
expect(() => cropNumber(NaN, 10, 80))
.toThrow(FunctionalError('Cannot process non-finite input'));
expect(() => cropNumber(50, NaN, 80))
.toThrow(FunctionalError('Cannot process non-finite input'));
expect(() => cropNumber(50, 10, NaN))
.toThrow(FunctionalError('Cannot process non-finite input'));
});
});
Loading

0 comments on commit f9aae34

Please sign in to comment.