From 859e7c94f98947cd7e51d0b6269a3acb0656d10b Mon Sep 17 00:00:00 2001
From: Marie <51697796+ijreilly@users.noreply.github.com>
Date: Fri, 7 Feb 2025 15:33:17 +0100
Subject: [PATCH] [permissions] Add settingsPermissions to getCurrentUser
(#10054)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
For a member with admin role:
For a member without admin role:
For a member of a workspace that does not have the feature flag enabled:
---
.../twenty-front/src/generated/graphql.tsx | 21 ++++++++--
.../hooks/__mocks__/useFieldMetadataItem.ts | 6 +++
.../graphql/fragments/userQueryFragment.ts | 3 ++
.../user-workspace/user-workspace.entity.ts | 10 ++++-
.../engine/core-modules/user/user.entity.ts | 7 ++++
.../engine/core-modules/user/user.module.ts | 2 +
.../engine/core-modules/user/user.resolver.ts | 39 ++++++++++++++++++-
.../src/constants/SettingsFeatures.ts | 2 +-
8 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx
index d8b11e3be..4cdcb02e4 100644
--- a/packages/twenty-front/src/generated/graphql.tsx
+++ b/packages/twenty-front/src/generated/graphql.tsx
@@ -1551,6 +1551,16 @@ export enum ServerlessFunctionSyncStatus {
READY = 'READY'
}
+export enum SettingsFeatures {
+ ADMIN_PANEL = 'ADMIN_PANEL',
+ API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
+ DATA_MODEL = 'DATA_MODEL',
+ ROLES = 'ROLES',
+ SECURITY_SETTINGS = 'SECURITY_SETTINGS',
+ WORKSPACE_SETTINGS = 'WORKSPACE_SETTINGS',
+ WORKSPACE_USERS = 'WORKSPACE_USERS'
+}
+
export type SetupOidcSsoInput = {
clientID: Scalars['String'];
clientSecret: Scalars['String'];
@@ -1773,7 +1783,8 @@ export type User = {
analyticsTinybirdJwts?: Maybe;
canImpersonate: Scalars['Boolean'];
createdAt: Scalars['DateTime'];
- currentWorkspace: Workspace;
+ currentUserWorkspace?: Maybe;
+ currentWorkspace?: Maybe;
defaultAvatarUrl?: Maybe;
deletedAt?: Maybe;
disabled?: Maybe;
@@ -1838,6 +1849,7 @@ export type UserWorkspace = {
createdAt: Scalars['DateTime'];
deletedAt?: Maybe;
id: Scalars['UUID'];
+ settingsPermissions?: Maybe>;
updatedAt: Scalars['DateTime'];
user: User;
userId: Scalars['String'];
@@ -2289,7 +2301,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, 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, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | 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, hostname?: 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 }> }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, hostname?: 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, 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, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | 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, hostname?: 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, hostname?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
@@ -2306,7 +2318,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, 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, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | 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, hostname?: 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 }> }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, hostname?: 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, 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, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | 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, hostname?: 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, hostname?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
export type ActivateWorkflowVersionMutationVariables = Exact<{
workflowVersionId: Scalars['String'];
@@ -2567,6 +2579,9 @@ export const UserQueryFragmentFragmentDoc = gql`
workspaceMembers {
...WorkspaceMemberQueryFragment
}
+ currentUserWorkspace {
+ settingsPermissions
+ }
currentWorkspace {
id
displayName
diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts
index 83c153d57..11372e1de 100644
--- a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts
+++ b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts
@@ -145,6 +145,9 @@ export const queries = {
workspaceMembers {
...WorkspaceMemberQueryFragment
}
+ currentUserWorkspace {
+ settingsPermissions
+ }
currentWorkspace {
id
displayName
@@ -304,6 +307,9 @@ export const responseData = {
timeFormat: '24',
},
workspaceMembers: [],
+ currentUserWorkspace: {
+ settingsPermissions: ['DATA_MODEL']
+ },
currentWorkspace: {
id: 'test-workspace-id',
displayName: 'Test Workspace',
diff --git a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts
index 54666b0a1..d71982735 100644
--- a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts
+++ b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts
@@ -24,6 +24,9 @@ export const USER_QUERY_FRAGMENT = gql`
workspaceMembers {
...WorkspaceMemberQueryFragment
}
+ currentUserWorkspace {
+ settingsPermissions
+ }
currentWorkspace {
id
displayName
diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts
index 973672ad3..d26834adf 100644
--- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts
+++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts
@@ -1,6 +1,7 @@
-import { Field, ObjectType } from '@nestjs/graphql';
+import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
+import { SettingsFeatures } from 'twenty-shared';
import {
Column,
CreateDateColumn,
@@ -19,6 +20,10 @@ import { TwoFactorMethod } from 'src/engine/core-modules/two-factor-method/two-f
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
+registerEnumType(SettingsFeatures, {
+ name: 'SettingsFeatures',
+});
+
@Entity({ name: 'userWorkspace', schema: 'core' })
@ObjectType()
@Unique('IndexOnUserIdAndWorkspaceIdUnique', ['userId', 'workspaceId'])
@@ -66,4 +71,7 @@ export class UserWorkspace {
(twoFactorMethod) => twoFactorMethod.userWorkspace,
)
twoFactorMethods: Relation;
+
+ @Field(() => [SettingsFeatures], { nullable: true })
+ settingsPermissions?: SettingsFeatures[];
}
diff --git a/packages/twenty-server/src/engine/core-modules/user/user.entity.ts b/packages/twenty-server/src/engine/core-modules/user/user.entity.ts
index 565eaec4e..e701261a1 100644
--- a/packages/twenty-server/src/engine/core-modules/user/user.entity.ts
+++ b/packages/twenty-server/src/engine/core-modules/user/user.entity.ts
@@ -19,6 +19,7 @@ import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-p
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
+import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
registerEnumType(OnboardingStatus, {
name: 'OnboardingStatus',
@@ -99,4 +100,10 @@ export class User {
@Field(() => OnboardingStatus, { nullable: true })
onboardingStatus: OnboardingStatus;
+
+ @Field(() => Workspace, { nullable: true })
+ currentWorkspace: Relation;
+
+ @Field(() => UserWorkspace, { nullable: true })
+ currentUserWorkspace?: Relation;
}
diff --git a/packages/twenty-server/src/engine/core-modules/user/user.module.ts b/packages/twenty-server/src/engine/core-modules/user/user.module.ts
index 5bde7e278..e06e4c769 100644
--- a/packages/twenty-server/src/engine/core-modules/user/user.module.ts
+++ b/packages/twenty-server/src/engine/core-modules/user/user.module.ts
@@ -21,6 +21,7 @@ import { UserResolver } from 'src/engine/core-modules/user/user.resolver';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
+import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
import { userAutoResolverOpts } from './user.auto-resolver-opts';
@@ -48,6 +49,7 @@ import { UserService } from './services/user.service';
DomainManagerModule,
UserRoleModule,
FeatureFlagModule,
+ PermissionsModule,
],
exports: [UserService],
providers: [UserService, UserResolver, TypeORMService],
diff --git a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts
index 4b05f4774..ad7db350a 100644
--- a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts
+++ b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts
@@ -13,6 +13,7 @@ import crypto from 'crypto';
import { GraphQLJSONObject } from 'graphql-type-json';
import { FileUpload, GraphQLUpload } from 'graphql-upload';
+import { SettingsFeatures } from 'twenty-shared';
import { In, Repository } from 'typeorm';
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
@@ -47,6 +48,7 @@ import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
+import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
@@ -77,11 +79,15 @@ export class UserResolver {
@InjectRepository(UserWorkspace, 'core')
private readonly userWorkspaceRepository: Repository,
private readonly userRoleService: UserRoleService,
+ private readonly permissionsService: PermissionsService,
private readonly featureFlagService: FeatureFlagService,
) {}
@Query(() => User)
- async currentUser(@AuthUser() { id: userId }: User): Promise {
+ async currentUser(
+ @AuthUser() { id: userId }: User,
+ @AuthWorkspace() workspace: Workspace,
+ ): Promise {
const user = await this.userRepository.findOne({
where: {
id: userId,
@@ -94,7 +100,36 @@ export class UserResolver {
new AuthException('User not found', AuthExceptionCode.USER_NOT_FOUND),
);
- return user;
+ const permissionsEnabled = await this.featureFlagService.isFeatureEnabled(
+ FeatureFlagKey.IsPermissionsEnabled,
+ workspace.id,
+ );
+
+ if (permissionsEnabled === true) {
+ const currentUserWorkspace = user.workspaces.find(
+ (userWorkspace) => userWorkspace.workspace.id === workspace.id,
+ );
+
+ if (!currentUserWorkspace) {
+ throw new Error('Current user workspace not found');
+ }
+ const permissions =
+ await this.permissionsService.getUserWorkspaceSettingsPermissions({
+ userWorkspaceId: currentUserWorkspace.id,
+ });
+
+ const permittedFeatures: SettingsFeatures[] = (
+ Object.keys(permissions) as SettingsFeatures[]
+ ).filter((feature) => permissions[feature] === true);
+
+ currentUserWorkspace.settingsPermissions = permittedFeatures;
+ user.currentUserWorkspace = currentUserWorkspace;
+ }
+
+ return {
+ ...user,
+ currentWorkspace: workspace,
+ };
}
@ResolveField(() => GraphQLJSONObject)
diff --git a/packages/twenty-shared/src/constants/SettingsFeatures.ts b/packages/twenty-shared/src/constants/SettingsFeatures.ts
index 9588ef53d..d2804190f 100644
--- a/packages/twenty-shared/src/constants/SettingsFeatures.ts
+++ b/packages/twenty-shared/src/constants/SettingsFeatures.ts
@@ -2,7 +2,7 @@ export enum SettingsFeatures {
API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS',
WORKSPACE_SETTINGS = 'WORKSPACE_SETTINGS',
WORKSPACE_USERS = 'WORKSPACE_USERS',
- ROLES = 'WORKSPACE_ROLES',
+ ROLES = 'ROLES',
DATA_MODEL = 'DATA_MODEL',
ADMIN_PANEL = 'ADMIN_PANEL',
SECURITY_SETTINGS = 'SECURITY_SETTINGS',