Skip to content

Commit 77f66c6

Browse files
[backend] Check authorized members for element access (#4538)
Co-authored-by: marie flores <[email protected]>
1 parent 5266681 commit 77f66c6

File tree

2 files changed

+218
-95
lines changed

2 files changed

+218
-95
lines changed

opencti-platform/opencti-graphql/src/utils/access.ts

+122-87
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type { BasicStoreSettings } from '../types/settings';
1818
import { ACCOUNT_STATUS_ACTIVE } from '../config/conf';
1919
import { schemaAttributesDefinition } from '../schema/schema-attributes';
2020
import { FunctionalError } from '../config/errors';
21-
import { isNotEmptyField, REDACTED_INFORMATION } from '../database/utils';
21+
import { extractIdsFromStoreObject, isNotEmptyField, REDACTED_INFORMATION } from '../database/utils';
2222
import { isStixObject } from '../schema/stixCoreObject';
2323

2424
export const DEFAULT_INVALID_CONF_VALUE = 'ChangeMe';
@@ -400,39 +400,101 @@ export const isOnlyOrgaAdmin = (user: AuthUser) => {
400400
return !isUserHasCapability(user, SETTINGS_SET_ACCESSES) && isUserHasCapability(user, VIRTUAL_ORGANIZATION_ADMIN);
401401
};
402402

403-
export const isOrganizationAllowed = (element: BasicStoreCommon, user: AuthUser, settings:BasicStoreSettings) => {
404-
const elementOrganizations = element[RELATION_GRANTED_TO] ?? [];
403+
// returns all user member access ids : his id, his organizations ids (and parent organizations), his groups ids
404+
export const computeUserMemberAccessIds = (user: AuthUser) => {
405+
const memberAccessIds = [user.id];
406+
if (user.organizations) {
407+
const userOrganizationsIds = user.organizations.map((org) => org.internal_id);
408+
memberAccessIds.push(...userOrganizationsIds);
409+
}
410+
if (user.groups) {
411+
const userGroupsIds = user.groups.map((group) => group.internal_id);
412+
memberAccessIds.push(...userGroupsIds);
413+
}
414+
if (user.roles) {
415+
const userRolesIds = user.roles.map((role) => role.internal_id);
416+
memberAccessIds.push(...userRolesIds);
417+
}
418+
return memberAccessIds;
419+
};
420+
421+
// region entity access by user
422+
423+
export const getUserAccessRight = (user: AuthUser, element: any) => {
424+
if (!element.authorized_members || element.authorized_members.length === 0) { // no restricted user access on element
425+
return MEMBER_ACCESS_RIGHT_ADMIN;
426+
}
427+
const accessMembers = [...element.authorized_members];
428+
const userMemberAccessIds = computeUserMemberAccessIds(user);
429+
const foundAccessMembers = accessMembers.filter((u) => u.id === MEMBER_ACCESS_ALL || userMemberAccessIds.includes(u.id));
430+
// If user have extended capabilities, is an admin
431+
if ((element.authorized_authorities ?? []).some((c: string) => userMemberAccessIds.includes(c) || isUserHasCapability(user, c))) {
432+
return MEMBER_ACCESS_RIGHT_ADMIN;
433+
}
434+
// if user is bypass, user has admin access (needed for data management usage)
435+
if (isBypassUser(user)) {
436+
return MEMBER_ACCESS_RIGHT_ADMIN;
437+
}
438+
if (!foundAccessMembers.length) { // user has no access
439+
return null;
440+
}
441+
if (foundAccessMembers.some((m) => m.access_right === MEMBER_ACCESS_RIGHT_ADMIN)) {
442+
return MEMBER_ACCESS_RIGHT_ADMIN;
443+
}
444+
if (foundAccessMembers.some((m) => m.access_right === MEMBER_ACCESS_RIGHT_EDIT)) {
445+
return MEMBER_ACCESS_RIGHT_EDIT;
446+
}
447+
return MEMBER_ACCESS_RIGHT_VIEW;
448+
};
449+
export const hasAuthorizedMemberAccess = (user: AuthUser, element: { authorized_members?: AuthorizedMember[], authorized_authorities?: string[] }) => {
450+
const userAccessRight = getUserAccessRight(user, element);
451+
return !!userAccessRight;
452+
};
405453

454+
const isEntityOrganizationsAllowed = (
455+
entityInternalId: string,
456+
entityOrganizations: string[],
457+
user: AuthUser,
458+
hasPlatformOrg: boolean,
459+
) => {
406460
// If platform organization is set
407-
if (settings.platform_organization) {
408-
const userOrganizations = user.organizations.map((o) => o.internal_id);
461+
if (hasPlatformOrg) {
462+
const userOrganizations = user.organizations.map((o) => extractIdsFromStoreObject(o)).flat();
409463

410464
// If user part of platform organization, is granted by default
411465
if (user.inside_platform_organization) {
412466
return true;
413467
}
414468
// Grant access to the user individual
415-
if (element.internal_id === user.individual_id) {
469+
if (entityInternalId === user.individual_id) {
416470
return true;
417471
}
418472
// If not, user is by design inside an organization
419473
// If element has no current sharing organization, it can be accessed (secure by default)
420474
// If element is shared, user must have a matching sharing organization
421-
return elementOrganizations.some((r) => userOrganizations.includes(r));
475+
return entityOrganizations.some((r) => userOrganizations.includes(r));
422476
}
423477
return true;
424478
};
425479

480+
export const isOrganizationAllowed = (element: BasicStoreCommon, user: AuthUser, hasPlatformOrg: boolean) => {
481+
const elementOrganizations = element[RELATION_GRANTED_TO] ?? [];
482+
return isEntityOrganizationsAllowed(element.internal_id, elementOrganizations, user, hasPlatformOrg);
483+
};
484+
485+
const isOrganizationUnrestrictedForEntityType = (entityType: string) => {
486+
const types = [entityType, ...getParentTypes(entityType)];
487+
if (STIX_ORGANIZATIONS_UNRESTRICTED.some((r) => types.includes(r))) {
488+
return true;
489+
}
490+
return false;
491+
};
426492
/**
427493
* Organization unrestricted mean that this element is visible whatever the organization the user belongs to.
428494
* @param element
429495
*/
430496
export const isOrganizationUnrestricted = (element: BasicStoreCommon) => {
431-
const types = [element.entity_type, ...getParentTypes(element.entity_type)];
432-
if (STIX_ORGANIZATIONS_UNRESTRICTED.some((r) => types.includes(r))) {
433-
return true;
434-
}
435-
return false;
497+
return isOrganizationUnrestrictedForEntityType(element.entity_type);
436498
};
437499

438500
export const isMarkingAllowed = (element: BasicStoreCommon, userAuthorizedMarkings: string[]) => {
@@ -445,16 +507,42 @@ export const isMarkingAllowed = (element: BasicStoreCommon, userAuthorizedMarkin
445507

446508
export const canRequestAccess = async (context: AuthContext, user: AuthUser, elements: Array<BasicStoreCommon>) => {
447509
const settings = await getEntityFromCache<BasicStoreSettings>(context, user, ENTITY_TYPE_SETTINGS);
510+
const hasPlatformOrg = !!settings.platform_organization;
448511
const elementsThatRequiresAccess: Array<BasicStoreCommon> = [];
449512
for (let i = 0; i < elements.length; i += 1) {
450-
if (!isOrganizationAllowed(elements[i], user, settings)) {
513+
if (!isOrganizationAllowed(elements[i], user, hasPlatformOrg)) {
451514
elementsThatRequiresAccess.push(elements[i]);
452515
}
453516
// TODO before removing ORGA_SHARING_REQUEST_FF: When it's ready check Authorized members
454517
}
455518
return elementsThatRequiresAccess;
456519
};
457520

521+
export const checkUserFilterStoreElements = (
522+
user: AuthUser,
523+
element: BasicStoreCommon,
524+
authorizedMarkings: string[],
525+
hasPlatformOrg: boolean
526+
) => {
527+
// 1. Check markings
528+
if (!isMarkingAllowed(element, authorizedMarkings)) {
529+
return false;
530+
}
531+
// 2. check authorized members
532+
if (!hasAuthorizedMemberAccess(user, element)) {
533+
return false;
534+
}
535+
// 3. Check organizations
536+
// Allow unrestricted entities
537+
if (isOrganizationUnrestricted(element)) {
538+
return true;
539+
}
540+
// Check restricted elements
541+
// either allowed by orga sharing or has authorized members access if authorized_members are defined (bypass orga sharing)
542+
return isOrganizationAllowed(element, user, hasPlatformOrg)
543+
|| (element.authorized_members && element.authorized_members.length > 0 && hasAuthorizedMemberAccess(user, element));
544+
};
545+
458546
export const userFilterStoreElements = async (context: AuthContext, user: AuthUser, elements: Array<BasicStoreCommon>) => {
459547
const userFilterStoreElementsFn = async () => {
460548
// If user have bypass, grant access to all
@@ -463,19 +551,10 @@ export const userFilterStoreElements = async (context: AuthContext, user: AuthUs
463551
}
464552
// If not filter by the inner markings
465553
const settings = await getEntityFromCache<BasicStoreSettings>(context, user, ENTITY_TYPE_SETTINGS);
554+
const hasPlatformOrg = !!settings.platform_organization;
466555
const authorizedMarkings = user.allowed_marking.map((a) => a.internal_id);
467556
return elements.filter((element) => {
468-
// 1. Check markings
469-
if (!isMarkingAllowed(element, authorizedMarkings)) {
470-
return false;
471-
}
472-
// 2. Check organizations
473-
// Allow unrestricted entities
474-
if (isOrganizationUnrestricted(element)) {
475-
return true;
476-
}
477-
// Check restricted elements
478-
return isOrganizationAllowed(element, user, settings);
557+
return checkUserFilterStoreElements(user, element, authorizedMarkings, hasPlatformOrg);
479558
});
480559
};
481560
return telemetry(context, user, 'FILTERING store filter', {
@@ -489,7 +568,7 @@ export const isUserCanAccessStoreElement = async (context: AuthContext, user: Au
489568
return elements.length === 1;
490569
};
491570

492-
export const isUserCanAccessStixElement = async (context: AuthContext, user: AuthUser, instance: StixObject) => {
571+
export const checkUserCanAccessStixElement = (user: AuthUser, instance: StixObject, hasPlatformOrg: boolean) => {
493572
// If user have bypass, grant access to all
494573
if (isBypassUser(user)) {
495574
return true;
@@ -503,51 +582,33 @@ export const isUserCanAccessStixElement = async (context: AuthContext, user: Aut
503582
return false;
504583
}
505584
}
506-
// 2. Check organizations
585+
const authorized_members = instance.extensions?.[STIX_EXT_OCTI]?.authorized_members ?? [];
586+
const authorizedMemberAllowed = hasAuthorizedMemberAccess(user, { authorized_members });
587+
// 2. check authorized members
588+
if (!authorizedMemberAllowed) {
589+
return false;
590+
}
591+
// 3. Check organizations
507592
// Allow unrestricted entities
508593
const entityType = instance.extensions?.[STIX_EXT_OCTI]?.type ?? generateInternalType(instance);
509-
const types = [entityType, ...getParentTypes(entityType)];
510-
if (STIX_ORGANIZATIONS_UNRESTRICTED.some((r) => types.includes(r))) {
594+
if (isOrganizationUnrestrictedForEntityType(entityType)) {
511595
return true;
512596
}
513597
// Check restricted elements
514-
const settings = await getEntityFromCache<BasicStoreSettings>(context, user, ENTITY_TYPE_SETTINGS);
515598
const elementOrganizations = instance.extensions?.[STIX_EXT_OCTI]?.granted_refs ?? [];
516-
const userOrganizations = user.organizations.map((o) => o.standard_id);
517-
// If platform organization is set
518-
if (settings.platform_organization) {
519-
// If user part of platform organization, is granted by default
520-
if (user.inside_platform_organization) {
521-
return true;
522-
}
523-
// If not, user is by design inside an organization
524-
// If element has no current sharing organization, it can be accessed (secure by default)
525-
// If element is shared, user must have a matching sharing organization
526-
return elementOrganizations.some((r) => userOrganizations.includes(r));
527-
}
528-
// If no platform organization is set, user can access
529-
return true;
599+
const organizationAllowed = isEntityOrganizationsAllowed(instance.id, elementOrganizations, user, hasPlatformOrg);
600+
// either allowed by organization or authorized members
601+
return organizationAllowed || (authorized_members.length > 0 && authorizedMemberAllowed);
530602
};
531603

532-
// region member access
533-
534-
// returns all user member access ids : his id, his organizations ids (and parent organizations), his groups ids
535-
export const computeUserMemberAccessIds = (user: AuthUser) => {
536-
const memberAccessIds = [user.id];
537-
if (user.organizations) {
538-
const userOrganizationsIds = user.organizations.map((org) => org.internal_id);
539-
memberAccessIds.push(...userOrganizationsIds);
540-
}
541-
if (user.groups) {
542-
const userGroupsIds = user.groups.map((group) => group.internal_id);
543-
memberAccessIds.push(...userGroupsIds);
544-
}
545-
if (user.roles) {
546-
const userRolesIds = user.roles.map((role) => role.internal_id);
547-
memberAccessIds.push(...userRolesIds);
548-
}
549-
return memberAccessIds;
604+
export const isUserCanAccessStixElement = async (context: AuthContext, user: AuthUser, instance: StixObject) => {
605+
const settings = await getEntityFromCache<BasicStoreSettings>(context, user, ENTITY_TYPE_SETTINGS);
606+
const hasPlatformOrg = !!settings.platform_organization;
607+
return checkUserCanAccessStixElement(user, instance, hasPlatformOrg);
550608
};
609+
// end region
610+
611+
// region member access
551612

552613
// user access methods
553614
export const isDirectAdministrator = (user: AuthUser, element: any) => {
@@ -557,32 +618,6 @@ export const isDirectAdministrator = (user: AuthUser, element: any) => {
557618
const userMemberAccessIds = computeUserMemberAccessIds(user);
558619
return elementAccessIds.some((a: string) => userMemberAccessIds.includes(a));
559620
};
560-
export const getUserAccessRight = (user: AuthUser, element: any) => {
561-
if (!element.authorized_members || element.authorized_members.length === 0) { // no restricted user access on element
562-
return MEMBER_ACCESS_RIGHT_ADMIN;
563-
}
564-
const accessMembers = [...element.authorized_members];
565-
const userMemberAccessIds = computeUserMemberAccessIds(user);
566-
const foundAccessMembers = accessMembers.filter((u) => u.id === MEMBER_ACCESS_ALL || userMemberAccessIds.includes(u.id));
567-
// If user have extended capabilities, is an admin
568-
if ((element.authorized_authorities ?? []).some((c: string) => userMemberAccessIds.includes(c) || isUserHasCapability(user, c))) {
569-
return MEMBER_ACCESS_RIGHT_ADMIN;
570-
}
571-
// if user is bypass, user has admin access (needed for data management usage)
572-
if (isBypassUser(user)) {
573-
return MEMBER_ACCESS_RIGHT_ADMIN;
574-
}
575-
if (!foundAccessMembers.length) { // user has no access
576-
return null;
577-
}
578-
if (foundAccessMembers.some((m) => m.access_right === MEMBER_ACCESS_RIGHT_ADMIN)) {
579-
return MEMBER_ACCESS_RIGHT_ADMIN;
580-
}
581-
if (foundAccessMembers.some((m) => m.access_right === MEMBER_ACCESS_RIGHT_EDIT)) {
582-
return MEMBER_ACCESS_RIGHT_EDIT;
583-
}
584-
return MEMBER_ACCESS_RIGHT_VIEW;
585-
};
586621

587622
// ensure that user can access the element (operation: edit / delete / manage-access)
588623
export const validateUserAccessOperation = (user: AuthUser, element: any, operation: 'edit' | 'delete' | 'manage-access' | 'manage-authorities-access') => {

0 commit comments

Comments
 (0)