From 132d691cbf983f60293c7423de0077fb7c97e0af Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Mon, 4 Dec 2023 10:02:54 +0100 Subject: [PATCH] fix(editor): Replace isInstanceOwner checks with scopes where applicable (#7858) Co-authored-by: Alex Grozav --- .../editor-ui/src/api/workflow-webhooks.ts | 16 ++++++------ .../components/MainHeader/WorkflowDetails.vue | 13 ++++------ .../editor-ui/src/components/MainSidebar.vue | 2 +- .../components/MainSidebarSourceControl.vue | 14 +++++----- .../EventDestinationCard.ee.vue | 6 ++--- .../EventDestinationSettingsModal.ee.vue | 19 +++++++------- .../src/components/WorkflowSettings.vue | 9 ++++--- .../MainSidebarSourceControl.test.ts | 10 +++---- .../src/components/banners/V1Banner.vue | 8 +++--- packages/editor-ui/src/mixins/nodeHelpers.ts | 10 ++++--- .../src/rbac/__tests__/permissions.test.ts | 3 +++ .../checks/__tests__/isInstanceOwner.test.ts | 26 +++++++++++++++++++ packages/editor-ui/src/rbac/checks/index.ts | 1 + .../src/rbac/checks/isInstanceOwner.ts | 5 ++++ packages/editor-ui/src/rbac/permissions.ts | 2 ++ .../editor-ui/src/stores/cloudPlan.store.ts | 5 ++-- .../src/stores/externalSecrets.ee.store.ts | 6 ++--- packages/editor-ui/src/stores/ui.store.ts | 6 ++--- packages/editor-ui/src/stores/users.store.ts | 2 +- packages/editor-ui/src/types/rbac.ts | 3 +++ .../src/views/SettingsLogStreamingView.vue | 12 ++++----- 21 files changed, 111 insertions(+), 67 deletions(-) create mode 100644 packages/editor-ui/src/rbac/checks/__tests__/isInstanceOwner.test.ts create mode 100644 packages/editor-ui/src/rbac/checks/isInstanceOwner.ts diff --git a/packages/editor-ui/src/api/workflow-webhooks.ts b/packages/editor-ui/src/api/workflow-webhooks.ts index 30fea1314b..369730085f 100644 --- a/packages/editor-ui/src/api/workflow-webhooks.ts +++ b/packages/editor-ui/src/api/workflow-webhooks.ts @@ -7,25 +7,25 @@ const CONTACT_EMAIL_SUBMISSION_ENDPOINT = '/accounts/onboarding'; export async function fetchNextOnboardingPrompt( instanceId: string, - currentUer: IUser, + currentUser: IUser, ): Promise { return get(N8N_API_BASE_URL, ONBOARDING_PROMPTS_ENDPOINT, { instance_id: instanceId, - user_id: `${instanceId}#${currentUer.id}`, - is_owner: currentUer.isOwner, - survey_results: currentUer.personalizationAnswers, + user_id: `${instanceId}#${currentUser.id}`, + is_owner: currentUser.isOwner, + survey_results: currentUser.personalizationAnswers, }); } export async function applyForOnboardingCall( instanceId: string, - currentUer: IUser, + currentUser: IUser, email: string, ): Promise { try { const response = await post(N8N_API_BASE_URL, ONBOARDING_PROMPTS_ENDPOINT, { instance_id: instanceId, - user_id: `${instanceId}#${currentUer.id}`, + user_id: `${instanceId}#${currentUser.id}`, email, }); return response; @@ -36,13 +36,13 @@ export async function applyForOnboardingCall( export async function submitEmailOnSignup( instanceId: string, - currentUer: IUser, + currentUser: IUser, email: string | undefined, agree: boolean, ): Promise { return post(N8N_API_BASE_URL, CONTACT_EMAIL_SUBMISSION_ENDPOINT, { instance_id: instanceId, - user_id: `${instanceId}#${currentUer.id}`, + user_id: `${instanceId}#${currentUser.id}`, email, agree, }); diff --git a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue index b8507b5e84..c4f164cec1 100644 --- a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue +++ b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue @@ -184,6 +184,7 @@ import { getWorkflowPermissions } from '@/permissions'; import { createEventBus } from 'n8n-design-system/utils'; import { nodeViewEventBus } from '@/event-bus'; import { genericHelpers } from '@/mixins/genericHelpers'; +import { hasPermission } from '@/rbac/permissions'; const hasChanged = (prev: string[], curr: string[]) => { if (prev.length !== curr.length) { @@ -247,10 +248,7 @@ export default defineComponent({ currentUser(): IUser | null { return this.usersStore.currentUser; }, - currentUserIsOwner(): boolean { - return this.usersStore.currentUser?.isOwner ?? false; - }, - contextBasedTranslationKeys(): NestedRecord { + contextBasedTranslationKeys() { return this.uiStore.contextBasedTranslationKeys; }, isWorkflowActive(): boolean { @@ -298,7 +296,7 @@ export default defineComponent({ ].includes(this.$route.name || ''); }, workflowPermissions(): IPermissions { - return getWorkflowPermissions(this.usersStore.currentUser, this.workflow); + return getWorkflowPermissions(this.currentUser, this.workflow); }, workflowMenuItems(): Array<{}> { const actions = [ @@ -330,7 +328,7 @@ export default defineComponent({ ); } - if (this.currentUserIsOwner) { + if (hasPermission(['rbac'], { rbac: { scope: 'sourceControl:push' } })) { actions.push({ id: WORKFLOW_MENU_ACTIONS.PUSH, label: this.$locale.baseText('menuActions.push'), @@ -338,8 +336,7 @@ export default defineComponent({ !this.sourceControlStore.isEnterpriseSourceControlEnabled || !this.onWorkflowPage || this.onExecutionsTab || - this.readOnlyEnv || - !this.currentUserIsOwner, + this.readOnlyEnv, }); } diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue index 1ba1996a8c..384452311f 100644 --- a/packages/editor-ui/src/components/MainSidebar.vue +++ b/packages/editor-ui/src/components/MainSidebar.vue @@ -266,7 +266,7 @@ export default defineComponent({ position: 'bottom', label: 'Admin Panel', icon: 'home', - available: this.settingsStore.isCloudDeployment && this.usersStore.isInstanceOwner, + available: this.settingsStore.isCloudDeployment && hasPermission(['instanceOwner']), }, { id: 'settings', diff --git a/packages/editor-ui/src/components/MainSidebarSourceControl.vue b/packages/editor-ui/src/components/MainSidebarSourceControl.vue index bbab868a33..b3a4b424d5 100644 --- a/packages/editor-ui/src/components/MainSidebarSourceControl.vue +++ b/packages/editor-ui/src/components/MainSidebarSourceControl.vue @@ -3,12 +3,11 @@ import { computed, nextTick, ref } from 'vue'; import { useRouter } from 'vue-router'; import { createEventBus } from 'n8n-design-system/utils'; import { useI18n } from '@/composables/useI18n'; -import { useMessage } from '@/composables/useMessage'; +import { hasPermission } from '@/rbac/permissions'; import { useToast } from '@/composables/useToast'; import { useLoadingService } from '@/composables/useLoadingService'; import { useUIStore } from '@/stores/ui.store'; import { useSourceControlStore } from '@/stores/sourceControl.store'; -import { useUsersStore } from '@/stores/users.store'; import { SOURCE_CONTROL_PULL_MODAL_KEY, SOURCE_CONTROL_PUSH_MODAL_KEY, VIEWS } from '@/constants'; import type { SourceControlAggregatedFile } from '../Interface'; import { sourceControlEventBus } from '@/event-bus/source-control'; @@ -24,9 +23,7 @@ const responseStatuses = { const router = useRouter(); const loadingService = useLoadingService(); const uiStore = useUIStore(); -const usersStore = useUsersStore(); const sourceControlStore = useSourceControlStore(); -const message = useMessage(); const toast = useToast(); const i18n = useI18n(); @@ -36,8 +33,11 @@ const tooltipOpenDelay = ref(300); const currentBranch = computed(() => { return sourceControlStore.preferences.branchName; }); -const isInstanceOwner = computed(() => usersStore.isInstanceOwner); -const setupButtonTooltipPlacement = computed(() => (props.isCollapsed ? 'right' : 'top')); +const sourceControlAvailable = computed( + () => + sourceControlStore.isEnterpriseSourceControlEnabled && + hasPermission(['rbac'], { rbac: { scope: 'sourceControl:manage' } }), +); async function pushWorkfolder() { loadingService.startLoading(); @@ -125,7 +125,7 @@ const goToSourceControlSetup = async () => {