mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-02 01:37:07 +02:00
feat: Block UI updates for instance / project roles if provisioning enabed (#22095)
This commit is contained in:
parent
24a4de8cf9
commit
72cdca23d6
|
|
@ -2563,6 +2563,8 @@
|
|||
"settings.provisioningConfirmDialog.button.generateCsvExport": "Generate access settings CSV export",
|
||||
"settings.provisioningConfirmDialog.button.downloadProjectRolesCsv": "Download existing project access settings csv",
|
||||
"settings.provisioningConfirmDialog.button.downloadInstanceRolesCsv": "Download existing instance role settings csv",
|
||||
"settings.provisioningInstanceRolesHandledBySsoProvider.description": "User management and instance roles are controlled by your SSO provider. Contact your n8n instance owner or admin to make changes.",
|
||||
"settings.provisioningProjectRolesHandledBySsoProvider.description": "User management and project roles are controlled by your SSO provider. Contact your n8n instance owner or admin to make changes.",
|
||||
"settings.externalSecrets.title": "External Secrets",
|
||||
"settings.externalSecrets.info": "Connect external secrets tools for centralized credentials management across environments, and to enhance system security.",
|
||||
"settings.externalSecrets.info.link": "More info",
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ describe('ProjectMembersTable', () => {
|
|||
currentUserId: '2',
|
||||
projectRoles: mockProjectRoles,
|
||||
tableOptions: mockTableOptions,
|
||||
canEditRole: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -354,6 +355,18 @@ describe('ProjectMembersTable', () => {
|
|||
// The text should be rendered by N8nText component
|
||||
expect(screen.getByText('Editor')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show a role dropdown if canEditRole is false', () => {
|
||||
renderComponent({
|
||||
props: {
|
||||
canEditRole: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('role-dropdown-1')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('role-dropdown-2')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('role-dropdown-3')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration with ProjectMembersRoleCell', () => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const props = defineProps<{
|
|||
currentUserId?: string;
|
||||
projectRoles: AllRolesMap['project'];
|
||||
actions?: Array<UserAction<ProjectMemberData>>;
|
||||
canEditRole: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -76,7 +77,8 @@ const roleActions = computed<Array<ActionDropdownItem<string>>>(() =>
|
|||
})),
|
||||
);
|
||||
|
||||
const canUpdateRole = (member: ProjectMemberData): boolean => member.id !== props.currentUserId;
|
||||
const canUpdateRole = (member: ProjectMemberData): boolean =>
|
||||
member.id !== props.currentUserId && props.canEditRole;
|
||||
|
||||
const onRoleChange = ({ role, userId }: { role: Role['slug']; userId: string }) => {
|
||||
emit('update:role', { role, userId });
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import { isIconOrEmoji, type IconOrEmoji } from '@n8n/design-system/components/N
|
|||
import type { TableOptions } from '@n8n/design-system/components/N8nDataTableServer';
|
||||
import type { UserAction } from '@n8n/design-system';
|
||||
import { isProjectRole } from '@/app/utils/typeGuards';
|
||||
import { useUserRoleProvisioningStore } from '@/features/settings/sso/provisioning/composables/userRoleProvisioning.store';
|
||||
import { N8nAlert } from '@n8n/design-system';
|
||||
|
||||
import {
|
||||
N8nButton,
|
||||
|
|
@ -45,6 +47,7 @@ const i18n = useI18n();
|
|||
const projectsStore = useProjectsStore();
|
||||
const rolesStore = useRolesStore();
|
||||
const cloudPlanStore = useCloudPlanStore();
|
||||
const userRoleProvisioningStore = useUserRoleProvisioningStore();
|
||||
const toast = useToast();
|
||||
const router = useRouter();
|
||||
const telemetry = useTelemetry();
|
||||
|
|
@ -475,9 +478,15 @@ onBeforeMount(async () => {
|
|||
await usersStore.fetchUsers();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const isProjectRoleProvisioningEnabled = computed(
|
||||
() => userRoleProvisioningStore.provisioningConfig?.scopesProvisionProjectRoles || false,
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(i18n.baseText('projects.settings'));
|
||||
selectProjectNameIfMatchesDefault();
|
||||
|
||||
await userRoleProvisioningStore.getProvisioningConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -567,6 +576,7 @@ onMounted(() => {
|
|||
:placeholder="i18n.baseText('workflows.shareModal.select.placeholder')"
|
||||
data-test-id="project-members-select"
|
||||
@update:model-value="onAddMember"
|
||||
:disabled="isProjectRoleProvisioningEnabled"
|
||||
>
|
||||
<template #prefix>
|
||||
<N8nIcon icon="search" />
|
||||
|
|
@ -586,6 +596,14 @@ onMounted(() => {
|
|||
</template>
|
||||
</N8nInput>
|
||||
</div>
|
||||
<div v-if="isProjectRoleProvisioningEnabled" class="mb-m">
|
||||
<N8nAlert
|
||||
type="info"
|
||||
:title="
|
||||
i18n.baseText('settings.provisioningProjectRolesHandledBySsoProvider.description')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="relationUsers.length > 0" :class="$style.membersTableContainer">
|
||||
<ProjectMembersTable
|
||||
v-model:table-options="membersTableState"
|
||||
|
|
@ -594,6 +612,7 @@ onMounted(() => {
|
|||
:current-user-id="usersStore.currentUser?.id"
|
||||
:project-roles="rolesStore.processedProjectRoles"
|
||||
:actions="projectMembersActions"
|
||||
:can-edit-role="!isProjectRoleProvisioningEnabled"
|
||||
@update:options="onUpdateMembersTableOptions"
|
||||
@update:role="onUpdateMemberRole"
|
||||
@action="onMembersListAction"
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ describe('SettingsUsersTable', () => {
|
|||
data: mockUsersList,
|
||||
actions: mockActions,
|
||||
loading: false,
|
||||
canEditRole: true,
|
||||
},
|
||||
});
|
||||
hasPermission.mockReturnValue(true); // Default to having permission
|
||||
|
|
@ -176,6 +177,16 @@ describe('SettingsUsersTable', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should not render role update component when canEditRole is false', () => {
|
||||
renderComponent({
|
||||
props: {
|
||||
canEditRole: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('user-role')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should emit "update:role" when a new role is selected', () => {
|
||||
const { emitted } = renderComponent();
|
||||
emitters.settingsUsersRoleCell.emit('update:role', { role: 'global:admin', userId: '2' });
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const props = defineProps<{
|
|||
data: UsersList;
|
||||
actions: Array<UserAction<IUser>>;
|
||||
loading?: boolean;
|
||||
canEditRole: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -122,7 +123,11 @@ const roleActions = computed<Array<ActionDropdownItem<Role>>>(() => [
|
|||
]);
|
||||
|
||||
const canUpdateRole = computed((): boolean => {
|
||||
return hasPermission(['rbac'], { rbac: { scope: ['user:update', 'user:changeRole'] } });
|
||||
if (!hasPermission(['rbac'], { rbac: { scope: ['user:update', 'user:changeRole'] } }))
|
||||
return false;
|
||||
if (!props.canEditRole) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const filterActions = (user: UsersList['items'][number]) => {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ import { useDocumentTitle } from '@/app/composables/useDocumentTitle';
|
|||
import { usePageRedirectionHelper } from '@/app/composables/usePageRedirectionHelper';
|
||||
import SettingsUsersTable from '../components/SettingsUsersTable.vue';
|
||||
import { I18nT } from 'vue-i18n';
|
||||
|
||||
import { useUserRoleProvisioningStore } from '@/features/settings/sso/provisioning/composables/userRoleProvisioning.store';
|
||||
import { ElSwitch } from 'element-plus';
|
||||
import N8nAlert from '@n8n/design-system/components/N8nAlert/Alert.vue';
|
||||
import {
|
||||
N8nActionBox,
|
||||
N8nBadge,
|
||||
|
|
@ -50,6 +51,7 @@ const usersStore = useUsersStore();
|
|||
const ssoStore = useSSOStore();
|
||||
const documentTitle = useDocumentTitle();
|
||||
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||
const userRoleProvisioningStore = useUserRoleProvisioningStore();
|
||||
|
||||
const tooltipKey = 'settings.personal.mfa.enforce.unlicensed_tooltip';
|
||||
|
||||
|
|
@ -70,12 +72,18 @@ const isEnforceMFAEnabled = computed(
|
|||
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.EnforceMFA],
|
||||
);
|
||||
|
||||
const isInstanceRoleProvisioningEnabled = computed(
|
||||
() => userRoleProvisioningStore.provisioningConfig?.scopesProvisionInstanceRole || false,
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
documentTitle.set(i18n.baseText('settings.users'));
|
||||
|
||||
if (!showUMSetupWarning.value) {
|
||||
await updateUsersTableData(usersTableState.value);
|
||||
}
|
||||
|
||||
await userRoleProvisioningStore.getProvisioningConfig();
|
||||
});
|
||||
|
||||
const usersListActions = computed((): Array<UserAction<IUser>> => {
|
||||
|
|
@ -448,6 +456,12 @@ async function onUpdateMfaEnforced(value: string | number | boolean) {
|
|||
</EnterpriseEdition>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isInstanceRoleProvisioningEnabled" :class="$style.container">
|
||||
<N8nAlert
|
||||
type="info"
|
||||
:title="i18n.baseText('settings.provisioningInstanceRolesHandledBySsoProvider.description')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!showUMSetupWarning" :class="$style.buttonContainer">
|
||||
<N8nInput
|
||||
:class="$style.search"
|
||||
|
|
@ -467,7 +481,11 @@ async function onUpdateMfaEnforced(value: string | number | boolean) {
|
|||
</template>
|
||||
<div>
|
||||
<N8nButton
|
||||
:disabled="ssoStore.isSamlLoginEnabled || !usersStore.usersLimitNotReached"
|
||||
:disabled="
|
||||
ssoStore.isSamlLoginEnabled ||
|
||||
!usersStore.usersLimitNotReached ||
|
||||
isInstanceRoleProvisioningEnabled
|
||||
"
|
||||
:label="i18n.baseText('settings.users.invite')"
|
||||
size="large"
|
||||
data-test-id="settings-users-invite-button"
|
||||
|
|
@ -485,6 +503,7 @@ async function onUpdateMfaEnforced(value: string | number | boolean) {
|
|||
<SettingsUsersTable
|
||||
v-model:table-options="usersTableState"
|
||||
data-test-id="settings-users-table"
|
||||
:can-edit-role="!isInstanceRoleProvisioningEnabled"
|
||||
:data="usersStore.usersList.state"
|
||||
:loading="usersStore.usersList.isLoading"
|
||||
:actions="usersListActions"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user