Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[backend] handle inferred organizations in organization sharing #9558

Merged
merged 5 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import SelectField from '../../../../../components/fields/SelectField';
import { SubscriptionFocus } from '../../../../../components/Subscription';
import MarkdownField from '../../../../../components/fields/MarkdownField';
import ObjectOrganizationField from '../../../common/form/ObjectOrganizationField';
import { convertOrganizations } from '../../../../../utils/edition';
import { useFormatter } from '../../../../../components/i18n';
import DateTimePickerField from '../../../../../components/DateTimePickerField';
import { fieldSpacingContainerStyle } from '../../../../../utils/field';
Expand Down Expand Up @@ -105,7 +104,6 @@ UserEditionOverviewComponentProps
const { t_i18n } = useFormatter();
const { me, settings } = useAuth();
const theme = useTheme<Theme>();

const [commitFocus] = useApiMutation(userEditionOverviewFocus);
const [commitFieldPatch] = useApiMutation(userMutationFieldPatch);
const [commitOrganizationAdd] = useApiMutation(userMutationOrganizationAdd);
Expand All @@ -114,8 +112,10 @@ UserEditionOverviewComponentProps

const userIsOnlyOrganizationAdmin = isOnlyOrganizationAdmin();
const external = user.external === true;
const objectOrganization = convertOrganizations(user);

const objectOrganization = (user.objectAssignedOrganization?.edges ?? []).map((n) => ({
label: n.node.name,
value: n.node.id,
}));
const initialValues = {
name: user.name,
user_email: user.user_email,
Expand Down Expand Up @@ -159,7 +159,7 @@ UserEditionOverviewComponentProps
name: string,
values: { label: string; value: string }[],
) => {
const currentValues = (user?.objectOrganization?.edges ?? []).map((n) => ({
const currentValues = (user?.objectAssignedOrganization?.edges ?? []).map((n) => ({
label: n.node.name,
value: n.node.id,
}));
Expand Down Expand Up @@ -389,7 +389,7 @@ const UserEditionOverview = createFragmentContainer(
id
name
}
objectOrganization(orderBy: $organizationsOrderBy, orderMode: $organizationsOrderMode) {
objectAssignedOrganization(orderBy: $organizationsOrderBy, orderMode: $organizationsOrderMode) {
edges {
node {
id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1654,6 +1654,7 @@ type User implements BasicObject & InternalObject {
restrict_delete: Boolean
groups(orderBy: GroupsOrdering, orderMode: OrderingMode): GroupConnection
objectOrganization(orderBy: OrganizationsOrdering, orderMode: OrderingMode): OrganizationConnection
objectAssignedOrganization(orderBy: OrganizationsOrdering, orderMode: OrderingMode): OrganizationConnection
created_at: DateTime!
updated_at: DateTime!
sessions: [SessionDetail]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,10 @@ type User implements BasicObject & InternalObject {
orderBy: OrganizationsOrdering
orderMode: OrderingMode
): OrganizationConnection @auth(for: [SETTINGS_SETACCESSES, VIRTUAL_ORGANIZATION_ADMIN])
objectAssignedOrganization(
orderBy: OrganizationsOrdering
orderMode: OrderingMode
): OrganizationConnection @auth(for: [SETTINGS_SETACCESSES, VIRTUAL_ORGANIZATION_ADMIN])
created_at: DateTime!
updated_at: DateTime!
sessions: [SessionDetail] @auth(for: [SETTINGS_SETACCESSES, VIRTUAL_ORGANIZATION_ADMIN])
Expand Down
61 changes: 43 additions & 18 deletions opencti-platform/opencti-graphql/src/database/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ import {
offsetToCursor,
pascalize,
READ_DATA_INDICES,
READ_DATA_INDICES_WITHOUT_INFERRED,
READ_DATA_INDICES_WITHOUT_INTERNAL_WITHOUT_INFERRED,
READ_ENTITIES_INDICES,
READ_ENTITIES_INDICES_WITHOUT_INFERRED,
READ_INDEX_INFERRED_ENTITIES,
READ_INDEX_INFERRED_RELATIONSHIPS,
READ_INDEX_INTERNAL_OBJECTS,
Expand All @@ -41,6 +43,7 @@ import {
READ_INDEX_STIX_SIGHTING_RELATIONSHIPS,
READ_PLATFORM_INDICES,
READ_RELATIONSHIPS_INDICES,
READ_RELATIONSHIPS_INDICES_WITHOUT_INFERRED,
UPDATE_OPERATION_ADD,
waitInSec,
WRITE_PLATFORM_INDICES
Expand Down Expand Up @@ -501,7 +504,7 @@ export const buildDataRestrictions = async (context, user, opts = {}) => {
// Data with Empty granted_refs are not visible
// Data with granted_refs users that participate to at least one
const should = [excludedEntityMatches];
const shouldOrgs = user.allowed_organizations
const shouldOrgs = user.organizations
.map((m) => ({ match: { [buildRefRelationSearchKey(RELATION_GRANTED_TO)]: m.internal_id } }));
should.push(...shouldOrgs);
// User individual or data created by this individual must be accessible
Expand Down Expand Up @@ -1474,50 +1477,72 @@ export const elConvertHits = async (data, opts = {}) => {
return convertedHits;
};

export const computeQueryIndices = (indices, typeOrTypes) => {
const withInferencesEntities = (indices, withInferences) => {
return withInferences ? [READ_INDEX_INFERRED_ENTITIES, ...indices] : indices;
};
const withInferencesRels = (indices, withInferences) => {
return withInferences ? [READ_INDEX_INFERRED_RELATIONSHIPS, ...indices] : indices;
};
export const computeQueryIndices = (indices, typeOrTypes, withInferences = true) => {
const types = (Array.isArray(typeOrTypes) || isEmptyField(typeOrTypes)) ? typeOrTypes : [typeOrTypes];
// If indices are explicitly defined, just rely on the definition
if (isEmptyField(indices)) {
// If not and have no clue about the expected types, ask for all indices.
// Worst case scenario that need to be avoided.
if (isEmptyField(types)) {
return READ_DATA_INDICES;
return withInferences ? READ_DATA_INDICES : READ_DATA_INDICES_WITHOUT_INFERRED;
}
// If types are defined we need to infer from them the correct indices
return R.uniq(types.map((findType) => {
// If defined types are abstract, try to restrict the indices as much as possible
if (isAbstract(findType)) {
// For objects
if (isBasicObject(findType)) {
if (isInternalObject(findType)) return [READ_INDEX_INFERRED_ENTITIES, READ_INDEX_INTERNAL_OBJECTS];
if (isStixMetaObject(findType)) return [READ_INDEX_INFERRED_ENTITIES, READ_INDEX_STIX_META_OBJECTS];
if (isStixDomainObject(findType)) return [READ_INDEX_INFERRED_ENTITIES, READ_INDEX_STIX_DOMAIN_OBJECTS];
if (isStixCoreObject(findType)) return [READ_INDEX_INFERRED_ENTITIES, READ_INDEX_STIX_DOMAIN_OBJECTS, READ_INDEX_STIX_CYBER_OBSERVABLES];
if (isStixObject(findType)) return [READ_INDEX_INFERRED_ENTITIES, READ_INDEX_STIX_META_OBJECTS, READ_INDEX_STIX_DOMAIN_OBJECTS, READ_INDEX_STIX_CYBER_OBSERVABLES];
return READ_ENTITIES_INDICES;
if (isInternalObject(findType)) {
return withInferencesEntities([READ_INDEX_INTERNAL_OBJECTS], withInferences);
}
if (isStixMetaObject(findType)) {
return withInferencesEntities([READ_INDEX_STIX_META_OBJECTS], withInferences);
}
if (isStixDomainObject(findType)) {
return withInferencesEntities([READ_INDEX_STIX_DOMAIN_OBJECTS], withInferences);
}
if (isStixCoreObject(findType)) {
return withInferencesEntities([READ_INDEX_STIX_DOMAIN_OBJECTS, READ_INDEX_STIX_CYBER_OBSERVABLES], withInferences);
}
if (isStixObject(findType)) {
return withInferencesEntities([READ_INDEX_STIX_META_OBJECTS, READ_INDEX_STIX_DOMAIN_OBJECTS, READ_INDEX_STIX_CYBER_OBSERVABLES], withInferences);
}
return withInferences ? READ_ENTITIES_INDICES : READ_ENTITIES_INDICES_WITHOUT_INFERRED;
}
// For relationships
if (isBasicRelationship(findType) || STIX_REF_RELATIONSHIP_TYPES.includes(findType)) {
if (isInternalRelationship(findType)) return [READ_INDEX_INFERRED_RELATIONSHIPS, READ_INDEX_INTERNAL_RELATIONSHIPS];
if (isStixSightingRelationship(findType)) return [READ_INDEX_INFERRED_RELATIONSHIPS, READ_INDEX_STIX_SIGHTING_RELATIONSHIPS];
if (isStixCoreRelationship(findType)) return [READ_INDEX_INFERRED_RELATIONSHIPS, READ_INDEX_STIX_CORE_RELATIONSHIPS];
if (isInternalRelationship(findType)) {
return withInferencesRels([READ_INDEX_INTERNAL_RELATIONSHIPS], withInferences);
}
if (isStixSightingRelationship(findType)) {
return withInferencesRels([READ_INDEX_STIX_SIGHTING_RELATIONSHIPS], withInferences);
}
if (isStixCoreRelationship(findType)) {
return withInferencesRels([READ_INDEX_STIX_CORE_RELATIONSHIPS], withInferences);
}
if (isStixRefRelationship(findType) || STIX_REF_RELATIONSHIP_TYPES.includes(findType)) {
return [READ_INDEX_INFERRED_RELATIONSHIPS, READ_INDEX_STIX_META_RELATIONSHIPS, READ_INDEX_STIX_CYBER_OBSERVABLE_RELATIONSHIPS];
return withInferencesRels([READ_INDEX_STIX_META_RELATIONSHIPS, READ_INDEX_STIX_CYBER_OBSERVABLE_RELATIONSHIPS], withInferences);
}
if (isStixRelationship(findType)) {
return [READ_INDEX_INFERRED_RELATIONSHIPS, READ_INDEX_STIX_CORE_RELATIONSHIPS, READ_INDEX_STIX_SIGHTING_RELATIONSHIPS, READ_INDEX_STIX_META_RELATIONSHIPS,
READ_INDEX_STIX_CYBER_OBSERVABLE_RELATIONSHIPS];
return withInferencesRels([READ_INDEX_STIX_CORE_RELATIONSHIPS, READ_INDEX_STIX_SIGHTING_RELATIONSHIPS, READ_INDEX_STIX_META_RELATIONSHIPS,
READ_INDEX_STIX_CYBER_OBSERVABLE_RELATIONSHIPS], withInferences);
}
return READ_RELATIONSHIPS_INDICES;
return withInferences ? READ_RELATIONSHIPS_INDICES : READ_RELATIONSHIPS_INDICES_WITHOUT_INFERRED;
}
// Fallback
throw UnsupportedError('Fail to compute indices for unknown type', { type: findType });
}
// If concrete type, infer the index from the type
if (isBasicObject(findType)) {
return [READ_INDEX_INFERRED_ENTITIES, `${inferIndexFromConceptType(findType)}*`];
return withInferencesEntities([`${inferIndexFromConceptType(findType)}*`], withInferences);
}
return [READ_INDEX_INFERRED_RELATIONSHIPS, `${inferIndexFromConceptType(findType)}*`];
return withInferencesRels([`${inferIndexFromConceptType(findType)}*`], withInferences);
}).flat());
}
return indices;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export interface EntityOptions<T extends BasicStoreCommon> extends EntityFilters
ids?: Array<string>
indices?: Array<string>
includeAuthorities?: boolean | null
withInferences?: boolean
includeDeletedInDraft?: boolean | null
}

Expand Down Expand Up @@ -313,7 +314,7 @@ export const listRelationsPaginated = async <T extends BasicStoreRelation>(conte
export const listAllRelations = async <T extends StoreProxyRelation>(context: AuthContext, user: AuthUser, type: string | Array<string>,
args: RelationOptions<T> = {}): Promise<Array<T>> => {
const { indices } = args;
const computedIndices = computeQueryIndices(indices, type);
const computedIndices = computeQueryIndices(indices, type, args.withInferences);
const paginateArgs = buildRelationsFilter(type, args);
return elList(context, user, computedIndices, paginateArgs);
};
Expand Down Expand Up @@ -525,7 +526,7 @@ export const listEntitiesThroughRelationsPaginated = async <T extends BasicStore
}],
filterGroups: [],
};
const connectedRelations = await listAllRelations<BasicStoreRelation>(context, user, relationType, { filters, connectionFormat: false });
const connectedRelations = await listAllRelations<BasicStoreRelation>(context, user, relationType, { withInferences: args.withInferences, filters, connectionFormat: false });
if (connectedRelations.length === 0) {
// no connection found (because of relation direction), just return an empty result
return emptyPaginationResult();
Expand Down
14 changes: 8 additions & 6 deletions opencti-platform/opencti-graphql/src/domain/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ export const batchCreators = async (context, user, userListIds) => {
return userIds.map((ids) => ids.map((id) => INTERNAL_USERS[id] || buildCreatorUser(platformUsers.get(id)) || SYSTEM_USER));
};

export const userOrganizationsPaginatedWithoutInferences = async (context, user, userId, opts) => {
const args = { ...opts, withInferences: false };
return listEntitiesThroughRelationsPaginated(context, user, userId, RELATION_PARTICIPATE_TO, ENTITY_TYPE_IDENTITY_ORGANIZATION, false, args);
};

export const userOrganizationsPaginated = async (context, user, userId, opts) => {
return listEntitiesThroughRelationsPaginated(context, user, userId, RELATION_PARTICIPATE_TO, ENTITY_TYPE_IDENTITY_ORGANIZATION, false, opts);
};
Expand Down Expand Up @@ -1260,7 +1265,6 @@ const buildSessionUser = (origin, impersonate, provider, settings) => {
default_hidden_types: user.default_hidden_types,
group_ids: user.groups?.map((g) => g.internal_id) ?? [],
organizations: user.organizations ?? [],
allowed_organizations: user.allowed_organizations,
administrated_organizations: user.administrated_organizations ?? [],
inside_platform_organization: user.inside_platform_organization,
allowed_marking: user.allowed_marking.map((m) => ({
Expand Down Expand Up @@ -1350,10 +1354,9 @@ export const buildCompleteUser = async (context, client) => {
);
const userGroupsPromise = listAllToEntitiesThroughRelations(context, SYSTEM_USER, client.id, RELATION_MEMBER_OF, ENTITY_TYPE_GROUP);
const settings = await getEntityFromCache(context, SYSTEM_USER, ENTITY_TYPE_SETTINGS);
const allowed_organizations = await listAllToEntitiesThroughRelations(context, SYSTEM_USER, client.id, RELATION_PARTICIPATE_TO, ENTITY_TYPE_IDENTITY_ORGANIZATION);
const userOrganizations = allowed_organizations.map((m) => m.internal_id);
const isUserPlatform = settings.platform_organization ? userOrganizations.includes(settings.platform_organization) : true;
const [individuals, organizations, groups] = await Promise.all([individualsPromise, organizationsPromise, userGroupsPromise]);
const userOrganizationIds = organizations.map((m) => m.internal_id);
const isUserPlatform = settings.platform_organization ? userOrganizationIds.includes(settings.platform_organization) : true;
const roles = await getRoles(context, groups);
const capabilities = await getCapabilities(context, client.id, roles);
const isByPass = R.find((s) => s.name === BYPASS, capabilities) !== undefined;
Expand All @@ -1366,7 +1369,7 @@ export const buildCompleteUser = async (context, client) => {

// Default hidden types
const defaultHiddenTypesGroups = getDefaultHiddenTypes(groups);
const defaultHiddenTypesOrgs = getDefaultHiddenTypes(allowed_organizations);
const defaultHiddenTypesOrgs = getDefaultHiddenTypes(organizations);
const default_hidden_types = uniq(defaultHiddenTypesGroups.concat(defaultHiddenTypesOrgs));

// effective confidence level
Expand All @@ -1386,7 +1389,6 @@ export const buildCompleteUser = async (context, client) => {
default_hidden_types,
groups,
organizations,
allowed_organizations,
administrated_organizations,
individual_id: individualId,
inside_platform_organization: isUserPlatform,
Expand Down
18 changes: 13 additions & 5 deletions opencti-platform/opencti-graphql/src/generated/graphql.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions opencti-platform/opencti-graphql/src/resolvers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
userGroupsPaginated,
userIdDeleteRelation,
userOrganizationsPaginated,
userOrganizationsPaginatedWithoutInferences,
userRenewToken,
userWithOrigin
} from '../domain/user';
Expand Down Expand Up @@ -88,6 +89,7 @@ const userResolvers = {
roles: (current, args, context) => rolesUsersLoader.load(current.id, context, context.user, args),
groups: (current, args, context) => userGroupsPaginated(context, context.user, current.id, args),
objectOrganization: (current, args, context) => userOrganizationsPaginated(context, context.user, current.id, args),
objectAssignedOrganization: (current, args, context) => userOrganizationsPaginatedWithoutInferences(context, context.user, current.id, args),
editContext: (current) => fetchEditContext(current.id),
sessions: (current) => findUserSessions(current.id),
effective_confidence_level: (current, args, context) => usersConfidenceLoader.load(current, context, context.user),
Expand Down
1 change: 0 additions & 1 deletion opencti-platform/opencti-graphql/src/types/user.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ interface AuthUser extends BasicStoreIdentifier {
roles: Array<UserRole>
groups: Array<Group>
organizations: Array<BasicStoreCommon>
allowed_organizations: Array<BasicStoreCommon>
administrated_organizations: Array<BasicStoreCommon>
capabilities: Array<UserCapability>
allowed_marking: Array<StoreMarkingDefinition>
Expand Down
16 changes: 4 additions & 12 deletions opencti-platform/opencti-graphql/src/utils/access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ export const SYSTEM_USER: AuthUser = {
groups: [],
capabilities: [{ name: BYPASS }],
organizations: [],
allowed_organizations: [],
allowed_marking: [],
default_marking: [],
max_shareable_marking: [],
Expand Down Expand Up @@ -125,7 +124,6 @@ export const RETENTION_MANAGER_USER: AuthUser = {
groups: [],
capabilities: [{ name: BYPASS }],
organizations: [],
allowed_organizations: [],
allowed_marking: [],
max_shareable_marking: [],
default_marking: [],
Expand Down Expand Up @@ -159,7 +157,6 @@ export const RULE_MANAGER_USER: AuthUser = {
groups: [],
capabilities: [{ name: BYPASS }],
organizations: [],
allowed_organizations: [],
allowed_marking: [],
max_shareable_marking: [],
default_marking: [],
Expand Down Expand Up @@ -193,7 +190,6 @@ export const AUTOMATION_MANAGER_USER: AuthUser = {
groups: [],
capabilities: [{ name: BYPASS }],
organizations: [],
allowed_organizations: [],
allowed_marking: [],
max_shareable_marking: [],
default_marking: [],
Expand Down Expand Up @@ -227,7 +223,6 @@ export const DECAY_MANAGER_USER: AuthUser = {
groups: [],
capabilities: [{ name: BYPASS }],
organizations: [],
allowed_organizations: [],
allowed_marking: [],
max_shareable_marking: [],
default_marking: [],
Expand Down Expand Up @@ -261,7 +256,6 @@ export const GARBAGE_COLLECTION_MANAGER_USER: AuthUser = {
groups: [],
capabilities: [{ name: BYPASS }],
organizations: [],
allowed_organizations: [],
allowed_marking: [],
max_shareable_marking: [],
default_marking: [],
Expand Down Expand Up @@ -296,7 +290,6 @@ export const REDACTED_USER: AuthUser = {
groups: [],
capabilities: [],
organizations: [],
allowed_organizations: [],
allowed_marking: [],
max_shareable_marking: [],
default_marking: [],
Expand All @@ -323,7 +316,6 @@ export const TELEMETRY_MANAGER_USER: AuthUser = {
groups: [],
capabilities: [{ name: BYPASS }],
organizations: [],
allowed_organizations: [],
allowed_marking: [],
max_shareable_marking: [],
default_marking: [],
Expand Down Expand Up @@ -433,7 +425,7 @@ export const userFilterStoreElements = async (context: AuthContext, user: AuthUs
}
// Check restricted elements
const elementOrganizations = element[RELATION_GRANTED_TO] ?? [];
const userOrganizations = user.allowed_organizations.map((o) => o.internal_id);
const userOrganizations = user.organizations.map((o) => o.internal_id);
// If platform organization is set
if (settings.platform_organization) {
// If user part of platform organization, is granted by default
Expand Down Expand Up @@ -488,7 +480,7 @@ export const isUserCanAccessStixElement = async (context: AuthContext, user: Aut
// Check restricted elements
const settings = await getEntityFromCache<BasicStoreSettings>(context, user, ENTITY_TYPE_SETTINGS);
const elementOrganizations = instance.extensions?.[STIX_EXT_OCTI]?.granted_refs ?? [];
const userOrganizations = user.allowed_organizations.map((o) => o.standard_id);
const userOrganizations = user.organizations.map((o) => o.standard_id);
// If platform organization is set
if (settings.platform_organization) {
// If user part of platform organization, is granted by default
Expand All @@ -509,8 +501,8 @@ export const isUserCanAccessStixElement = async (context: AuthContext, user: Aut
// returns all user member access ids : his id, his organizations ids (and parent organizations), his groups ids
export const computeUserMemberAccessIds = (user: AuthUser) => {
const memberAccessIds = [user.id];
if (user.allowed_organizations) {
const userOrganizationsIds = user.allowed_organizations.map((org) => org.internal_id);
if (user.organizations) {
const userOrganizationsIds = user.organizations.map((org) => org.internal_id);
memberAccessIds.push(...userOrganizationsIds);
}
if (user.groups) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ describe('File storage file listing', () => {
user_email: 'user-no-capa@opencti.io',
inside_platform_organization: true,
allowed_marking: [],
allowed_organizations: [],
roles: [{ internal_id: '08f558bc-b93d-40dc-8e86-f70309d9e1a6', id: '08f558bc-b93d-40dc-8e86-f70309d9e1a6', name: 'No capa' }],
groups: [],
capabilities: [],
Expand Down
Loading
Loading