diff --git a/packages/@n8n/permissions/src/types.ts b/packages/@n8n/permissions/src/types.ts
index edb4e4c099..0a14440922 100644
--- a/packages/@n8n/permissions/src/types.ts
+++ b/packages/@n8n/permissions/src/types.ts
@@ -18,7 +18,7 @@ export type WildcardScope = `${Resource}:*` | '*';
export type WorkflowScope = ResourceScope<'workflow', DefaultOperations | 'share'>;
export type TagScope = ResourceScope<'tag'>;
export type UserScope = ResourceScope<'user'>;
-export type CredentialScope = ResourceScope<'credential'>;
+export type CredentialScope = ResourceScope<'credential', DefaultOperations | 'share'>;
export type VariableScope = ResourceScope<'variable'>;
export type SourceControlScope = ResourceScope<'sourceControl', 'pull' | 'push' | 'manage'>;
export type ExternalSecretStoreScope = ResourceScope<
diff --git a/packages/cli/src/permissions/roles.ts b/packages/cli/src/permissions/roles.ts
index 95cabb03ca..131406aee8 100644
--- a/packages/cli/src/permissions/roles.ts
+++ b/packages/cli/src/permissions/roles.ts
@@ -17,6 +17,7 @@ export const ownerPermissions: Scope[] = [
'credential:update',
'credential:delete',
'credential:list',
+ 'credential:share',
'variable:create',
'variable:read',
'variable:update',
diff --git a/packages/editor-ui/src/components/CredentialCard.vue b/packages/editor-ui/src/components/CredentialCard.vue
index 80ba4b5498..a6671b82c4 100644
--- a/packages/editor-ui/src/components/CredentialCard.vue
+++ b/packages/editor-ui/src/components/CredentialCard.vue
@@ -142,7 +142,7 @@ export default defineComponent({
},
async onAction(action: string) {
if (action === CREDENTIAL_LIST_ITEM_ACTIONS.OPEN) {
- await this.onClick();
+ await this.onClick(new Event('click'));
} else if (action === CREDENTIAL_LIST_ITEM_ACTIONS.DELETE) {
const deleteConfirmed = await this.confirm(
this.$locale.baseText(
diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
index d0988a16ee..062358cab1 100644
--- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
+++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue
@@ -31,28 +31,18 @@
/>
-
-
- {{ $locale.baseText('credentialEdit.credentialSharing.info.owner') }}
-
-
- {{
- $locale.baseText('credentialEdit.credentialSharing.info.sharee', {
- interpolate: { credentialOwnerName },
- })
- }}
-
+
+ {{ $locale.baseText('credentialEdit.credentialSharing.info.owner') }}
-
- {{ $locale.baseText('credentialEdit.credentialSharing.info.instanceOwner') }}
+
+ {{
+ $locale.baseText('credentialEdit.credentialSharing.info.sharee', {
+ interpolate: { credentialOwnerName },
+ })
+ }}
+
+
+ {{ $locale.baseText('credentialEdit.credentialSharing.info.reader') }}
sharee.id === user.id,
);
+ const isOwner = this.credentialData.ownedBy.id === user.id;
- return !isCurrentUser && !isAlreadySharedWithUser;
+ return !isCurrentUser && !isAlreadySharedWithUser && !isOwner;
});
},
sharedWithList(): IUser[] {
diff --git a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
index dfb2b4d844..bd9ebc0f08 100644
--- a/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
+++ b/packages/editor-ui/src/components/WorkflowShareModal.ee.vue
@@ -23,7 +23,7 @@
-
+
{{
$locale.baseText('workflows.shareModal.info.sharee', {
interpolate: { workflowOwnerName },
@@ -213,8 +213,9 @@ export default defineComponent({
const isAlreadySharedWithUser = (this.sharedWith || []).find(
(sharee) => sharee.id === user.id,
);
+ const isOwner = this.workflow?.ownedBy?.id === user.id;
- return !isCurrentUser && !isAlreadySharedWithUser;
+ return !isCurrentUser && !isAlreadySharedWithUser && !isOwner;
});
},
sharedWithList(): Array> {
diff --git a/packages/editor-ui/src/permissions.ts b/packages/editor-ui/src/permissions.ts
index 47b1dee9d3..995e9ad6f0 100644
--- a/packages/editor-ui/src/permissions.ts
+++ b/packages/editor-ui/src/permissions.ts
@@ -64,6 +64,7 @@ export const parsePermissionsTable = (
export const getCredentialPermissions = (user: IUser | null, credential: ICredentialsResponse) => {
const settingsStore = useSettingsStore();
+ const rbacStore = useRBACStore();
const isSharingEnabled = settingsStore.isEnterpriseFeatureEnabled(
EnterpriseEditionFeature.Sharing,
);
@@ -77,10 +78,14 @@ export const getCredentialPermissions = (user: IUser | null, credential: ICreden
name: UserRole.ResourceSharee,
test: () => !!credential?.sharedWith?.find((sharee) => sharee.id === user?.id),
},
+ { name: 'read', test: () => rbacStore.hasScope('credential:read') },
{ name: 'save', test: [UserRole.ResourceOwner, UserRole.InstanceOwner] },
{ name: 'updateName', test: [UserRole.ResourceOwner, UserRole.InstanceOwner] },
{ name: 'updateConnection', test: [UserRole.ResourceOwner] },
- { name: 'updateSharing', test: [UserRole.ResourceOwner] },
+ {
+ name: 'updateSharing',
+ test: (permissions) => rbacStore.hasScope('credential:share') || !!permissions.isOwner,
+ },
{ name: 'updateNodeAccess', test: [UserRole.ResourceOwner] },
{ name: 'delete', test: [UserRole.ResourceOwner, UserRole.InstanceOwner] },
{ name: 'use', test: [UserRole.ResourceOwner, UserRole.ResourceSharee] },
@@ -104,7 +109,7 @@ export const getWorkflowPermissions = (user: IUser | null, workflow: IWorkflowDb
},
{
name: 'updateSharing',
- test: (permissions) => rbacStore.hasScope('workflow:update') || !!permissions.isOwner,
+ test: (permissions) => rbacStore.hasScope('workflow:share') || !!permissions.isOwner,
},
{
name: 'delete',
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index b3dde531fc..4635c040ee 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -416,7 +416,7 @@
"credentialEdit.oAuthButton.connectMyAccount": "Connect my account",
"credentialEdit.oAuthButton.signInWithGoogle": "Sign in with Google",
"credentialEdit.credentialSharing.info.owner": "Sharing a credential allows people to use it in their workflows. They cannot access credential details.",
- "credentialEdit.credentialSharing.info.instanceOwner": "You can view this credential because you are the instance owner (and rename or delete it too). To use it in a workflow, ask the credential owner to share it with you.",
+ "credentialEdit.credentialSharing.info.reader": "You can view this credential because you have permission to read and share (and rename or delete it too). To use it in a workflow, ask the credential owner to share it with you.",
"credentialEdit.credentialSharing.info.sharee": "Only {credentialOwnerName} can change who this credential is shared with",
"credentialEdit.credentialSharing.info.sharee.fallback": "the owner",
"credentialEdit.credentialSharing.select.placeholder": "Add users...",
@@ -1998,7 +1998,7 @@
"workflows.shareModal.changesHint": "You made changes",
"workflows.shareModal.isDefaultUser.description": "You first need to set up your owner account to enable workflow sharing features.",
"workflows.shareModal.isDefaultUser.button": "Go to settings",
- "workflows.shareModal.info.sharee": "Only {workflowOwnerName} can change who this workflow is shared with",
+ "workflows.shareModal.info.sharee": "Only {workflowOwnerName} or users with workflow sharing permission can change who this workflow is shared with",
"workflows.shareModal.info.sharee.fallback": "the owner",
"workflows.roles.editor": "Editor",
"workflows.concurrentChanges.confirmMessage.title": "Workflow was changed by someone else",
diff --git a/packages/editor-ui/src/stores/users.store.ts b/packages/editor-ui/src/stores/users.store.ts
index 0b2e1e787a..a05c67379a 100644
--- a/packages/editor-ui/src/stores/users.store.ts
+++ b/packages/editor-ui/src/stores/users.store.ts
@@ -43,10 +43,9 @@ import { useRBACStore } from '@/stores/rbac.store';
import type { Scope, ScopeLevel } from '@n8n/permissions';
import { inviteUsers, acceptInvitation } from '@/api/invitation';
-const isDefaultUser = (user: IUserResponse | null) =>
- user?.isPending && user?.globalRole?.name === ROLE.Owner;
const isPendingUser = (user: IUserResponse | null) => !!user?.isPending;
const isInstanceOwner = (user: IUserResponse | null) => user?.globalRole?.name === ROLE.Owner;
+const isDefaultUser = (user: IUserResponse | null) => isInstanceOwner(user) && isPendingUser(user);
export const useUsersStore = defineStore(STORES.USERS, {
state: (): IUsersState => ({