[permissions] Add object records permissions to role entity (#10255)
Closes https://github.com/twentyhq/core-team-issues/issues/388 - Add object records-related permissions to role entity - Add it to queriable `currentUserWorkspace` (used in FE)
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
/* eslint-disable */
|
||||
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared';
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
@ -2050,6 +2051,7 @@ export type UserWorkspace = {
|
||||
deletedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
id: Scalars['UUID']['output'];
|
||||
settingsPermissions?: Maybe<Array<SettingsFeatures>>;
|
||||
objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
user: User;
|
||||
userId: Scalars['String']['output'];
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as Apollo from '@apollo/client';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
@ -1172,6 +1172,13 @@ export type PageInfo = {
|
||||
startCursor?: Maybe<Scalars['ConnectionCursor']>;
|
||||
};
|
||||
|
||||
export enum PermissionsOnAllObjectRecords {
|
||||
DESTROY_ALL_OBJECT_RECORDS = 'DESTROY_ALL_OBJECT_RECORDS',
|
||||
READ_ALL_OBJECT_RECORDS = 'READ_ALL_OBJECT_RECORDS',
|
||||
SOFT_DELETE_ALL_OBJECT_RECORDS = 'SOFT_DELETE_ALL_OBJECT_RECORDS',
|
||||
UPDATE_ALL_OBJECT_RECORDS = 'UPDATE_ALL_OBJECT_RECORDS'
|
||||
}
|
||||
|
||||
export type PostgresCredentials = {
|
||||
__typename?: 'PostgresCredentials';
|
||||
id: Scalars['UUID'];
|
||||
@ -1428,6 +1435,10 @@ export type ResendEmailVerificationTokenOutput = {
|
||||
|
||||
export type Role = {
|
||||
__typename?: 'Role';
|
||||
canDestroyAllObjectRecords: Scalars['Boolean'];
|
||||
canReadAllObjectRecords: Scalars['Boolean'];
|
||||
canSoftDeleteAllObjectRecords: Scalars['Boolean'];
|
||||
canUpdateAllObjectRecords: Scalars['Boolean'];
|
||||
canUpdateAllSettings: Scalars['Boolean'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['String'];
|
||||
@ -1827,6 +1838,7 @@ export type UserWorkspace = {
|
||||
createdAt: Scalars['DateTime'];
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['UUID'];
|
||||
objectRecordsPermissions?: Maybe<Array<PermissionsOnAllObjectRecords>>;
|
||||
settingsPermissions?: Maybe<Array<SettingsFeatures>>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
user: User;
|
||||
@ -2292,7 +2304,7 @@ export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key:
|
||||
|
||||
export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
||||
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsFeatures> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsFeatures> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||
|
||||
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@ -2309,7 +2321,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsFeatures> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsFeatures> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||
|
||||
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||
workflowVersionId: Scalars['String'];
|
||||
@ -2582,6 +2594,7 @@ export const UserQueryFragmentFragmentDoc = gql`
|
||||
}
|
||||
currentUserWorkspace {
|
||||
settingsPermissions
|
||||
objectRecordsPermissions
|
||||
}
|
||||
currentWorkspace {
|
||||
id
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { FieldMetadataType, PermissionsOnAllObjectRecords } from '~/generated/graphql';
|
||||
|
||||
export const FIELD_METADATA_ID = '2c43466a-fe9e-4005-8d08-c5836067aa6c';
|
||||
export const FIELD_RELATION_METADATA_ID =
|
||||
@ -147,6 +147,7 @@ export const queries = {
|
||||
}
|
||||
currentUserWorkspace {
|
||||
settingsPermissions
|
||||
objectRecordsPermissions
|
||||
}
|
||||
currentWorkspace {
|
||||
id
|
||||
@ -310,6 +311,12 @@ export const responseData = {
|
||||
workspaceMembers: [],
|
||||
currentUserWorkspace: {
|
||||
settingsPermissions: ['DATA_MODEL'],
|
||||
objectRecordsPermissions: [
|
||||
PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS,
|
||||
PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS,
|
||||
PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS,
|
||||
PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS,
|
||||
],
|
||||
},
|
||||
currentWorkspace: {
|
||||
id: 'test-workspace-id',
|
||||
|
||||
@ -26,6 +26,7 @@ export const USER_QUERY_FRAGMENT = gql`
|
||||
}
|
||||
currentUserWorkspace {
|
||||
settingsPermissions
|
||||
objectRecordsPermissions
|
||||
}
|
||||
currentWorkspace {
|
||||
id
|
||||
|
||||
@ -2,20 +2,6 @@ import { DataSource } from 'typeorm';
|
||||
|
||||
const tableName = 'featureFlag';
|
||||
|
||||
// export const seedFeatureFlags = async (
|
||||
// workspaceDataSource: DataSource,
|
||||
// schemaName: string,
|
||||
// workspaceId: string,
|
||||
// ) => {
|
||||
// await workspaceDataSource
|
||||
// .createQueryBuilder()
|
||||
// .insert()
|
||||
// .into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value'])
|
||||
// .orIgnore()
|
||||
// .values([])
|
||||
// .execute();
|
||||
// };
|
||||
|
||||
export const deleteFeatureFlags = async (
|
||||
workspaceDataSource: DataSource,
|
||||
schemaName: string,
|
||||
|
||||
@ -80,6 +80,11 @@ export const seedFeatureFlags = async (
|
||||
workspaceId: workspaceId,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IsPermissionsEnabled,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ export const DEV_SEED_USER_WORKSPACE_IDS = {
|
||||
TIM: '20202020-9e3b-46d4-a556-88b9ddc2b035',
|
||||
JONY: '20202020-3957-4908-9c36-2929a23f8353',
|
||||
PHIL: '20202020-7169-42cf-bc47-1cfef15264b1',
|
||||
TIM_ACME: '20202020-9e3b-46d4-a556-88b9ddc2b436',
|
||||
TIM_ACME: '20202020-e10a-4c27-a90b-b08c57b02d44',
|
||||
};
|
||||
|
||||
export const seedUserWorkspaces = async (
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateRoleTable1739795699972 implements MigrationInterface {
|
||||
name = 'UpdateRoleTable1739795699972';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."role" ADD "canReadAllObjectRecords" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."role" ADD "canUpdateAllObjectRecords" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."role" ADD "canSoftDeleteAllObjectRecords" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."role" ADD "canDestroyAllObjectRecords" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."role" DROP COLUMN "canDestroyAllObjectRecords"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."role" DROP COLUMN "canSoftDeleteAllObjectRecords"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."role" DROP COLUMN "canUpdateAllObjectRecords"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."role" DROP COLUMN "canReadAllObjectRecords"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -199,6 +199,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId: authContext.userWorkspaceId,
|
||||
_setting: permissionRequired,
|
||||
workspaceId: authContext.workspace.id,
|
||||
});
|
||||
|
||||
if (!userHasPermission) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { SettingsFeatures } from 'twenty-shared';
|
||||
import { PermissionsOnAllObjectRecords, SettingsFeatures } from 'twenty-shared';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
@ -25,6 +25,10 @@ registerEnumType(SettingsFeatures, {
|
||||
name: 'SettingsFeatures',
|
||||
});
|
||||
|
||||
registerEnumType(PermissionsOnAllObjectRecords, {
|
||||
name: 'PermissionsOnAllObjectRecords',
|
||||
});
|
||||
|
||||
@Entity({ name: 'userWorkspace', schema: 'core' })
|
||||
@ObjectType()
|
||||
@Unique('IndexOnUserIdAndWorkspaceIdUnique', ['userId', 'workspaceId'])
|
||||
@ -75,4 +79,7 @@ export class UserWorkspace {
|
||||
|
||||
@Field(() => [SettingsFeatures], { nullable: true })
|
||||
settingsPermissions?: SettingsFeatures[];
|
||||
|
||||
@Field(() => [PermissionsOnAllObjectRecords], { nullable: true })
|
||||
objectRecordsPermissions?: PermissionsOnAllObjectRecords[];
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import crypto from 'crypto';
|
||||
|
||||
import { GraphQLJSONObject } from 'graphql-type-json';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
import { SettingsFeatures } from 'twenty-shared';
|
||||
import { PermissionsOnAllObjectRecords, SettingsFeatures } from 'twenty-shared';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
|
||||
@ -113,16 +113,23 @@ export class UserResolver {
|
||||
if (!currentUserWorkspace) {
|
||||
throw new Error('Current user workspace not found');
|
||||
}
|
||||
const permissions =
|
||||
await this.permissionsService.getUserWorkspaceSettingsPermissions({
|
||||
const { settingsPermissions, objectRecordsPermissions } =
|
||||
await this.permissionsService.getUserWorkspacePermissions({
|
||||
userWorkspaceId: currentUserWorkspace.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
const permittedFeatures: SettingsFeatures[] = (
|
||||
Object.keys(permissions) as SettingsFeatures[]
|
||||
).filter((feature) => permissions[feature] === true);
|
||||
Object.keys(settingsPermissions) as SettingsFeatures[]
|
||||
).filter((feature) => settingsPermissions[feature] === true);
|
||||
|
||||
const permittedObjectRecordsPermissions = (
|
||||
Object.keys(objectRecordsPermissions) as PermissionsOnAllObjectRecords[]
|
||||
).filter((permission) => objectRecordsPermissions[permission] === true);
|
||||
|
||||
currentUserWorkspace.settingsPermissions = permittedFeatures;
|
||||
currentUserWorkspace.objectRecordsPermissions =
|
||||
permittedObjectRecordsPermissions;
|
||||
user.currentUserWorkspace = currentUserWorkspace;
|
||||
}
|
||||
|
||||
@ -216,9 +223,12 @@ export class UserResolver {
|
||||
);
|
||||
|
||||
rolesByUserWorkspaces =
|
||||
await this.userRoleService.getRolesByUserWorkspaces(
|
||||
userWorkspaces.map((userWorkspace) => userWorkspace.id),
|
||||
);
|
||||
await this.userRoleService.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: userWorkspaces.map(
|
||||
(userWorkspace) => userWorkspace.id,
|
||||
),
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
for (const workspaceMemberEntity of workspaceMemberEntities) {
|
||||
@ -254,6 +264,11 @@ export class UserResolver {
|
||||
description: roleEntity.description,
|
||||
isEditable: roleEntity.isEditable,
|
||||
userWorkspaceRoles: roleEntity.userWorkspaceRoles,
|
||||
canReadAllObjectRecords: roleEntity.canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords: roleEntity.canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords:
|
||||
roleEntity.canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords: roleEntity.canDestroyAllObjectRecords,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -152,11 +152,13 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
await this.validateSecurityPermissions({
|
||||
payload,
|
||||
userWorkspaceId,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
await this.validateWorkspacePermissions({
|
||||
payload,
|
||||
userWorkspaceId,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@ -378,9 +380,11 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
private async validateSecurityPermissions({
|
||||
payload,
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
}: {
|
||||
payload: Partial<Workspace>;
|
||||
userWorkspaceId?: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
if (
|
||||
'isGoogleAuthEnabled' in payload ||
|
||||
@ -396,6 +400,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
_setting: SettingsFeatures.SECURITY,
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
|
||||
if (!userHasPermission) {
|
||||
@ -410,9 +415,11 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
private async validateWorkspacePermissions({
|
||||
payload,
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
}: {
|
||||
payload: Partial<Workspace>;
|
||||
userWorkspaceId?: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
if (
|
||||
'displayName' in payload ||
|
||||
@ -427,6 +434,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
const userHasPermission =
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
_setting: SettingsFeatures.WORKSPACE,
|
||||
});
|
||||
|
||||
|
||||
@ -47,6 +47,7 @@ export const SettingsPermissionsGuard = (
|
||||
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
_setting: requiredPermission,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (hasPermission === true) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { SettingsFeatures } from 'twenty-shared';
|
||||
import { PermissionsOnAllObjectRecords, SettingsFeatures } from 'twenty-shared';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
@ -12,13 +12,21 @@ export class PermissionsService {
|
||||
private readonly userRoleService: UserRoleService,
|
||||
) {}
|
||||
|
||||
public async getUserWorkspaceSettingsPermissions({
|
||||
public async getUserWorkspacePermissions({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
}: {
|
||||
userWorkspaceId: string;
|
||||
}): Promise<Record<SettingsFeatures, boolean>> {
|
||||
workspaceId: string;
|
||||
}): Promise<{
|
||||
settingsPermissions: Record<SettingsFeatures, boolean>;
|
||||
objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>;
|
||||
}> {
|
||||
const [roleOfUserWorkspace] = await this.userRoleService
|
||||
.getRolesByUserWorkspaces([userWorkspaceId])
|
||||
.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspaceId],
|
||||
workspaceId,
|
||||
})
|
||||
.then((roles) => roles?.get(userWorkspaceId) ?? []);
|
||||
|
||||
let hasPermissionOnSettingFeature = false;
|
||||
@ -27,24 +35,48 @@ export class PermissionsService {
|
||||
hasPermissionOnSettingFeature = true;
|
||||
}
|
||||
|
||||
return Object.keys(SettingsFeatures).reduce(
|
||||
const settingsPermissionsMap = Object.keys(SettingsFeatures).reduce(
|
||||
(acc, feature) => ({
|
||||
...acc,
|
||||
[feature]: hasPermissionOnSettingFeature,
|
||||
}),
|
||||
{} as Record<SettingsFeatures, boolean>,
|
||||
);
|
||||
|
||||
const objectRecordsPermissionsMap: Record<
|
||||
PermissionsOnAllObjectRecords,
|
||||
boolean
|
||||
> = {
|
||||
[PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace?.canReadAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace?.canUpdateAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace?.canSoftDeleteAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace?.canDestroyAllObjectRecords ?? false,
|
||||
};
|
||||
|
||||
return {
|
||||
settingsPermissions: settingsPermissionsMap,
|
||||
objectRecordsPermissions: objectRecordsPermissionsMap,
|
||||
};
|
||||
}
|
||||
|
||||
public async userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
_setting,
|
||||
}: {
|
||||
userWorkspaceId: string;
|
||||
workspaceId: string;
|
||||
_setting: SettingsFeatures;
|
||||
}): Promise<boolean> {
|
||||
const [roleOfUserWorkspace] = await this.userRoleService
|
||||
.getRolesByUserWorkspaces([userWorkspaceId])
|
||||
.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspaceId],
|
||||
workspaceId,
|
||||
})
|
||||
.then((roles) => roles?.get(userWorkspaceId) ?? []);
|
||||
|
||||
if (roleOfUserWorkspace?.canUpdateAllSettings === true) {
|
||||
|
||||
@ -13,9 +13,6 @@ export class RoleDTO {
|
||||
@Field({ nullable: false })
|
||||
label: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
canUpdateAllSettings: boolean;
|
||||
|
||||
@Field({ nullable: true })
|
||||
description: string;
|
||||
|
||||
@ -27,4 +24,19 @@ export class RoleDTO {
|
||||
|
||||
@Field(() => [WorkspaceMember], { nullable: true })
|
||||
workspaceMembers?: WorkspaceMember[];
|
||||
|
||||
@Field({ nullable: false })
|
||||
canUpdateAllSettings: boolean;
|
||||
|
||||
@Field({ nullable: false })
|
||||
canReadAllObjectRecords: boolean;
|
||||
|
||||
@Field({ nullable: false })
|
||||
canUpdateAllObjectRecords: boolean;
|
||||
|
||||
@Field({ nullable: false })
|
||||
canSoftDeleteAllObjectRecords: boolean;
|
||||
|
||||
@Field({ nullable: false })
|
||||
canDestroyAllObjectRecords: boolean;
|
||||
}
|
||||
|
||||
@ -21,6 +21,18 @@ export class RoleEntity {
|
||||
@Column({ nullable: false, default: false })
|
||||
canUpdateAllSettings: boolean;
|
||||
|
||||
@Column({ nullable: false, default: false })
|
||||
canReadAllObjectRecords: boolean;
|
||||
|
||||
@Column({ nullable: false, default: false })
|
||||
canUpdateAllObjectRecords: boolean;
|
||||
|
||||
@Column({ nullable: false, default: false })
|
||||
canSoftDeleteAllObjectRecords: boolean;
|
||||
|
||||
@Column({ nullable: false, default: false })
|
||||
canDestroyAllObjectRecords: boolean;
|
||||
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
description: string;
|
||||
|
||||
|
||||
@ -38,13 +38,17 @@ export class RoleResolver {
|
||||
return roles.map((role) => ({
|
||||
id: role.id,
|
||||
label: role.label,
|
||||
canUpdateAllSettings: role.canUpdateAllSettings,
|
||||
description: role.description,
|
||||
workspaceId: role.workspaceId,
|
||||
createdAt: role.createdAt,
|
||||
updatedAt: role.updatedAt,
|
||||
isEditable: role.isEditable,
|
||||
userWorkspaceRoles: role.userWorkspaceRoles,
|
||||
canUpdateAllSettings: role.canUpdateAllSettings,
|
||||
canReadAllObjectRecords: role.canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords: role.canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords: role.canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords: role.canDestroyAllObjectRecords,
|
||||
}));
|
||||
}
|
||||
|
||||
@ -81,7 +85,10 @@ export class RoleResolver {
|
||||
}
|
||||
|
||||
const roles = await this.userRoleService
|
||||
.getRolesByUserWorkspaces([userWorkspace.id])
|
||||
.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspace.id],
|
||||
workspaceId: workspace.id,
|
||||
})
|
||||
.then(
|
||||
(rolesByUserWorkspaces) =>
|
||||
rolesByUserWorkspaces?.get(userWorkspace.id) ?? [],
|
||||
|
||||
@ -30,6 +30,10 @@ export class RoleService {
|
||||
label: ADMIN_ROLE_LABEL,
|
||||
description: 'Admin role',
|
||||
canUpdateAllSettings: true,
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: true,
|
||||
isEditable: false,
|
||||
workspaceId,
|
||||
});
|
||||
@ -44,6 +48,10 @@ export class RoleService {
|
||||
label: MEMBER_ROLE_LABEL,
|
||||
description: 'Member role',
|
||||
canUpdateAllSettings: false,
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: true,
|
||||
isEditable: false,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
@ -60,7 +60,10 @@ export class UserRoleService {
|
||||
);
|
||||
}
|
||||
|
||||
const roles = await this.getRolesByUserWorkspaces([userWorkspace.id]);
|
||||
const roles = await this.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspace.id],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const currentRole = roles.get(userWorkspace.id)?.[0];
|
||||
|
||||
@ -88,8 +91,10 @@ export class UserRoleService {
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
await this.validatesUserWorkspaceIsNotLastAdminIfUnassigningAdminRoleOrThrow(
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
{
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
await this.userWorkspaceRoleRepository.delete({
|
||||
@ -98,9 +103,13 @@ export class UserRoleService {
|
||||
});
|
||||
}
|
||||
|
||||
public async getRolesByUserWorkspaces(
|
||||
userWorkspaceIds: string[],
|
||||
): Promise<Map<string, RoleDTO[]>> {
|
||||
public async getRolesByUserWorkspaces({
|
||||
userWorkspaceIds,
|
||||
workspaceId,
|
||||
}: {
|
||||
userWorkspaceIds: string[];
|
||||
workspaceId: string;
|
||||
}): Promise<Map<string, RoleDTO[]>> {
|
||||
if (!userWorkspaceIds.length) {
|
||||
return new Map();
|
||||
}
|
||||
@ -108,6 +117,7 @@ export class UserRoleService {
|
||||
const allUserWorkspaceRoles = await this.userWorkspaceRoleRepository.find({
|
||||
where: {
|
||||
userWorkspaceId: In(userWorkspaceIds),
|
||||
workspaceId,
|
||||
},
|
||||
relations: {
|
||||
role: true,
|
||||
@ -176,11 +186,17 @@ export class UserRoleService {
|
||||
return workspaceMembers;
|
||||
}
|
||||
|
||||
private async validatesUserWorkspaceIsNotLastAdminIfUnassigningAdminRoleOrThrow(
|
||||
userWorkspaceId: string,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const roles = await this.getRolesByUserWorkspaces([userWorkspaceId]);
|
||||
private async validatesUserWorkspaceIsNotLastAdminIfUnassigningAdminRoleOrThrow({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
}: {
|
||||
userWorkspaceId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
const roles = await this.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspaceId],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const currentRoles = roles.get(userWorkspaceId);
|
||||
|
||||
|
||||
@ -4,6 +4,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DEV_SEED_USER_WORKSPACE_IDS } from 'src/database/typeorm-seeds/core/user-workspaces';
|
||||
import {
|
||||
SEED_ACME_WORKSPACE_ID,
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
} from 'src/database/typeorm-seeds/core/workspaces';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
@ -42,6 +47,7 @@ export class WorkspaceManagerService {
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly userRoleService: UserRoleService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -261,20 +267,34 @@ export class WorkspaceManagerService {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.userRoleService.assignRoleToUserWorkspace({
|
||||
workspaceId,
|
||||
userWorkspaceId: DEV_SEED_USER_WORKSPACE_IDS.TIM,
|
||||
roleId: adminRole.id,
|
||||
});
|
||||
let adminUserWorkspaceId: string | undefined;
|
||||
let memberUserWorkspaceId: string | undefined;
|
||||
|
||||
if (workspaceId === SEED_APPLE_WORKSPACE_ID) {
|
||||
adminUserWorkspaceId = DEV_SEED_USER_WORKSPACE_IDS.TIM;
|
||||
memberUserWorkspaceId = DEV_SEED_USER_WORKSPACE_IDS.JONY;
|
||||
} else if (workspaceId === SEED_ACME_WORKSPACE_ID) {
|
||||
adminUserWorkspaceId = DEV_SEED_USER_WORKSPACE_IDS.TIM_ACME;
|
||||
}
|
||||
|
||||
if (adminUserWorkspaceId) {
|
||||
await this.userRoleService.assignRoleToUserWorkspace({
|
||||
workspaceId,
|
||||
userWorkspaceId: adminUserWorkspaceId,
|
||||
roleId: adminRole.id,
|
||||
});
|
||||
}
|
||||
|
||||
const memberRole = await this.roleService.createMemberRole({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.userRoleService.assignRoleToUserWorkspace({
|
||||
workspaceId,
|
||||
userWorkspaceId: DEV_SEED_USER_WORKSPACE_IDS.JONY,
|
||||
roleId: memberRole.id,
|
||||
});
|
||||
if (memberUserWorkspaceId) {
|
||||
await this.userRoleService.assignRoleToUserWorkspace({
|
||||
workspaceId,
|
||||
userWorkspaceId: memberUserWorkspaceId,
|
||||
roleId: memberRole.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
export enum PermissionsOnAllObjectRecords {
|
||||
READ_ALL_OBJECT_RECORDS = 'READ_ALL_OBJECT_RECORDS',
|
||||
UPDATE_ALL_OBJECT_RECORDS = 'UPDATE_ALL_OBJECT_RECORDS',
|
||||
SOFT_DELETE_ALL_OBJECT_RECORDS = 'SOFT_DELETE_ALL_OBJECT_RECORDS',
|
||||
DESTROY_ALL_OBJECT_RECORDS = 'DESTROY_ALL_OBJECT_RECORDS',
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
export * from './FieldForTotalCountAggregateOperation';
|
||||
export * from './PermissionsOnAllObjectRecords';
|
||||
export * from './SettingsFeatures';
|
||||
export * from './TwentyCompaniesBaseUrl';
|
||||
export * from './TwentyIconsBaseUrl';
|
||||
|
||||
Reference in New Issue
Block a user