| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
- import { CurrencyCode, DeletionResult, LanguageCode, Permission } from '@vendure/common/lib/generated-types';
- import { omit } from '@vendure/common/lib/omit';
- import {
- CUSTOMER_ROLE_CODE,
- DEFAULT_CHANNEL_CODE,
- SUPER_ADMIN_ROLE_CODE,
- } from '@vendure/common/lib/shared-constants';
- import {
- createErrorResultGuard,
- createTestEnvironment,
- E2E_DEFAULT_CHANNEL_TOKEN,
- ErrorResultGuard,
- } from '@vendure/testing';
- import path from 'path';
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
- import { initialData } from '../../../e2e-common/e2e-initial-data';
- import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
- import { administratorFragment, channelFragment, roleFragment } from './graphql/fragments-admin';
- import { FragmentOf, graphql, ResultOf } from './graphql/graphql-admin';
- import {
- createAdministratorDocument,
- createChannelDocument,
- createRoleDocument,
- getChannelsDocument,
- updateAdministratorDocument,
- updateRoleDocument,
- } from './graphql/shared-definitions';
- import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
- import { sortById } from './utils/test-order-utils';
- describe('Role resolver', () => {
- const { server, adminClient } = createTestEnvironment(testConfig());
- let createdRole: FragmentOf<typeof roleFragment>;
- let defaultRoles: Array<FragmentOf<typeof roleFragment>>;
- type ChannelFragment = FragmentOf<typeof channelFragment>;
- const channelGuard: ErrorResultGuard<ChannelFragment> = createErrorResultGuard(
- input => !!input.defaultLanguageCode,
- );
- beforeAll(async () => {
- await server.init({
- initialData,
- productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
- customerCount: 1,
- });
- await adminClient.asSuperAdmin();
- }, TEST_SETUP_TIMEOUT_MS);
- afterAll(async () => {
- await server.destroy();
- });
- it('roles', async () => {
- const result = await adminClient.query(getRolesDocument);
- defaultRoles = result.roles.items;
- expect(result.roles.items.length).toBe(2);
- expect(result.roles.totalItems).toBe(2);
- });
- it('createRole with invalid permission', async () => {
- try {
- await adminClient.query(createRoleDocument, {
- input: {
- code: 'test',
- description: 'test role',
- permissions: ['ReadCatalogx' as any],
- },
- });
- fail('Should have thrown');
- } catch (e: any) {
- expect(e.response.errors[0]?.extensions.code).toBe('BAD_USER_INPUT');
- }
- });
- it('createRole with no permissions includes Authenticated', async () => {
- const { createRole } = await adminClient.query(createRoleDocument, {
- input: {
- code: 'test',
- description: 'test role',
- permissions: [],
- },
- });
- expect(omit(createRole, ['channels'])).toEqual({
- code: 'test',
- description: 'test role',
- id: 'T_3',
- permissions: [Permission.Authenticated],
- });
- });
- it('createRole deduplicates permissions', async () => {
- const { createRole } = await adminClient.query(createRoleDocument, {
- input: {
- code: 'test2',
- description: 'test role2',
- permissions: [Permission.ReadSettings, Permission.ReadSettings],
- },
- });
- expect(omit(createRole, ['channels'])).toEqual({
- code: 'test2',
- description: 'test role2',
- id: 'T_4',
- permissions: [Permission.Authenticated, Permission.ReadSettings],
- });
- });
- it('createRole with permissions', async () => {
- const result = await adminClient.query(createRoleDocument, {
- input: {
- code: 'test',
- description: 'test role',
- permissions: [Permission.ReadCustomer, Permission.UpdateCustomer],
- },
- });
- createdRole = result.createRole;
- expect(createdRole).toEqual({
- code: 'test',
- description: 'test role',
- id: 'T_5',
- permissions: [Permission.Authenticated, Permission.ReadCustomer, Permission.UpdateCustomer],
- channels: [
- {
- code: DEFAULT_CHANNEL_CODE,
- id: 'T_1',
- token: 'e2e-default-channel',
- },
- ],
- });
- });
- it('role', async () => {
- const result = await adminClient.query(getRoleDocument, {
- id: createdRole.id,
- });
- expect(result.role).toEqual(createdRole);
- });
- describe('updateRole', () => {
- it('updates role', async () => {
- const result = await adminClient.query(updateRoleDocument, {
- input: {
- id: createdRole.id,
- code: 'test-modified',
- description: 'test role modified',
- permissions: [
- Permission.ReadCustomer,
- Permission.UpdateCustomer,
- Permission.DeleteCustomer,
- ],
- },
- });
- expect(omit(result.updateRole, ['channels'])).toEqual({
- code: 'test-modified',
- description: 'test role modified',
- id: 'T_5',
- permissions: [
- Permission.Authenticated,
- Permission.ReadCustomer,
- Permission.UpdateCustomer,
- Permission.DeleteCustomer,
- ],
- });
- });
- it('works with partial input', async () => {
- const result = await adminClient.query(updateRoleDocument, {
- input: {
- id: createdRole.id,
- code: 'test-modified-again',
- },
- });
- expect(result.updateRole.code).toBe('test-modified-again');
- expect(result.updateRole.description).toBe('test role modified');
- expect(result.updateRole.permissions).toEqual([
- Permission.Authenticated,
- Permission.ReadCustomer,
- Permission.UpdateCustomer,
- Permission.DeleteCustomer,
- ]);
- });
- it('deduplicates permissions', async () => {
- const result = await adminClient.query(updateRoleDocument, {
- input: {
- id: createdRole.id,
- permissions: [
- Permission.Authenticated,
- Permission.Authenticated,
- Permission.ReadCustomer,
- Permission.ReadCustomer,
- ],
- },
- });
- expect(result.updateRole.permissions).toEqual([
- Permission.Authenticated,
- Permission.ReadCustomer,
- ]);
- });
- it(
- 'does not allow setting non-assignable permissions - Owner',
- assertThrowsWithMessage(async () => {
- await adminClient.query(updateRoleDocument, {
- input: {
- id: createdRole.id,
- permissions: [Permission.Owner],
- },
- });
- }, 'The permission "Owner" may not be assigned'),
- );
- it(
- 'does not allow setting non-assignable permissions - Public',
- assertThrowsWithMessage(async () => {
- await adminClient.query(updateRoleDocument, {
- input: {
- id: createdRole.id,
- permissions: [Permission.Public],
- },
- });
- }, 'The permission "Public" may not be assigned'),
- );
- it(
- 'does not allow setting SuperAdmin permission',
- assertThrowsWithMessage(async () => {
- await adminClient.query(updateRoleDocument, {
- input: {
- id: createdRole.id,
- permissions: [Permission.SuperAdmin],
- },
- });
- }, 'The permission "SuperAdmin" may not be assigned'),
- );
- it(
- 'is not allowed for SuperAdmin role',
- assertThrowsWithMessage(async () => {
- const superAdminRole = defaultRoles.find(r => r.code === SUPER_ADMIN_ROLE_CODE);
- if (!superAdminRole) {
- fail('Could not find SuperAdmin role');
- return;
- }
- return adminClient.query(updateRoleDocument, {
- input: {
- id: superAdminRole.id,
- code: 'superadmin-modified',
- description: 'superadmin modified',
- permissions: [Permission.Authenticated],
- },
- });
- }, `The role "${SUPER_ADMIN_ROLE_CODE}" cannot be modified`),
- );
- it(
- 'is not allowed for Customer role',
- assertThrowsWithMessage(async () => {
- const customerRole = defaultRoles.find(r => r.code === CUSTOMER_ROLE_CODE);
- if (!customerRole) {
- fail('Could not find Customer role');
- return;
- }
- return adminClient.query(updateRoleDocument, {
- input: {
- id: customerRole.id,
- code: 'customer-modified',
- description: 'customer modified',
- permissions: [Permission.Authenticated, Permission.DeleteAdministrator],
- },
- });
- }, `The role "${CUSTOMER_ROLE_CODE}" cannot be modified`),
- );
- });
- it(
- 'deleteRole is not allowed for Customer role',
- assertThrowsWithMessage(async () => {
- const customerRole = defaultRoles.find(r => r.code === CUSTOMER_ROLE_CODE);
- if (!customerRole) {
- fail('Could not find Customer role');
- return;
- }
- return adminClient.query(deleteRoleDocument, {
- id: customerRole.id,
- });
- }, `The role "${CUSTOMER_ROLE_CODE}" cannot be deleted`),
- );
- it(
- 'deleteRole is not allowed for SuperAdmin role',
- assertThrowsWithMessage(async () => {
- const superAdminRole = defaultRoles.find(r => r.code === SUPER_ADMIN_ROLE_CODE);
- if (!superAdminRole) {
- fail('Could not find Customer role');
- return;
- }
- return adminClient.query(deleteRoleDocument, {
- id: superAdminRole.id,
- });
- }, `The role "${SUPER_ADMIN_ROLE_CODE}" cannot be deleted`),
- );
- it('deleteRole deletes a role', async () => {
- const { deleteRole } = await adminClient.query(deleteRoleDocument, {
- id: createdRole.id,
- });
- expect(deleteRole.result).toBe(DeletionResult.DELETED);
- const { role } = await adminClient.query(getRoleDocument, {
- id: createdRole.id,
- });
- expect(role).toBeNull();
- });
- describe('multi-channel', () => {
- let secondChannel: ChannelFragment;
- let multiChannelRole: ResultOf<typeof createRoleDocument>['createRole'];
- beforeAll(async () => {
- const { createChannel } = await adminClient.query(createChannelDocument, {
- input: {
- code: 'second-channel',
- token: 'second-channel-token',
- defaultLanguageCode: LanguageCode.en,
- currencyCode: CurrencyCode.GBP,
- pricesIncludeTax: true,
- defaultShippingZoneId: 'T_1',
- defaultTaxZoneId: 'T_1',
- },
- });
- channelGuard.assertSuccess(createChannel);
- secondChannel = createChannel;
- });
- it('createRole with specified channel', async () => {
- const { createRole } = await adminClient.query(createRoleDocument, {
- input: {
- code: 'multi-test',
- description: 'multi channel test role',
- permissions: [Permission.ReadCustomer],
- channelIds: [secondChannel.id],
- },
- });
- multiChannelRole = createRole;
- expect(createRole).toEqual({
- code: 'multi-test',
- description: 'multi channel test role',
- id: 'T_6',
- permissions: [Permission.Authenticated, Permission.ReadCustomer],
- channels: [
- {
- code: 'second-channel',
- id: 'T_2',
- token: 'second-channel-token',
- },
- ],
- });
- });
- it('updateRole with specified channel', async () => {
- const { updateRole } = await adminClient.query(updateRoleDocument, {
- input: {
- id: multiChannelRole.id,
- channelIds: ['T_1', 'T_2'],
- },
- });
- expect(updateRole.channels.sort(sortById)).toEqual([
- {
- code: DEFAULT_CHANNEL_CODE,
- id: 'T_1',
- token: 'e2e-default-channel',
- },
- {
- code: 'second-channel',
- id: 'T_2',
- token: 'second-channel-token',
- },
- ]);
- });
- });
- // https://github.com/vendure-ecommerce/vendure/issues/1874
- describe('role escalation', () => {
- type SimpleChannel = ResultOf<typeof getChannelsDocument>['channels']['items'][number];
- let defaultChannel: SimpleChannel;
- let secondChannel: SimpleChannel;
- let limitedAdmin: FragmentOf<typeof administratorFragment>;
- let orderReaderRole: ResultOf<typeof createRoleDocument>['createRole'];
- let adminCreatorRole: ResultOf<typeof createRoleDocument>['createRole'];
- let adminCreatorAdministrator: FragmentOf<typeof administratorFragment>;
- beforeAll(async () => {
- const { channels } = await adminClient.query(getChannelsDocument);
- defaultChannel = channels.items.find(c => c.token === E2E_DEFAULT_CHANNEL_TOKEN)!;
- secondChannel = channels.items.find(c => c.token !== E2E_DEFAULT_CHANNEL_TOKEN)!;
- adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
- await adminClient.asSuperAdmin();
- const { createRole } = await adminClient.query(createRoleDocument, {
- input: {
- code: 'second-channel-admin-manager',
- description: '',
- channelIds: [secondChannel.id],
- permissions: [
- Permission.CreateAdministrator,
- Permission.ReadAdministrator,
- Permission.UpdateAdministrator,
- Permission.DeleteAdministrator,
- ],
- },
- });
- const { createAdministrator } = await adminClient.query(createAdministratorDocument, {
- input: {
- firstName: 'channel2',
- lastName: 'admin manager',
- emailAddress: 'channel2@test.com',
- roleIds: [createRole.id],
- password: 'test',
- },
- });
- limitedAdmin = createAdministrator;
- const { createRole: createRole2 } = await adminClient.query(createRoleDocument, {
- input: {
- code: 'second-channel-order-manager',
- description: '',
- channelIds: [secondChannel.id],
- permissions: [Permission.ReadOrder],
- },
- });
- orderReaderRole = createRole2;
- adminClient.setChannelToken(secondChannel.token);
- await adminClient.asUserWithCredentials(limitedAdmin.emailAddress, 'test');
- });
- it('limited admin cannot view Roles which require permissions they do not have', async () => {
- const result = await adminClient.query(getRolesDocument);
- const roleCodes = result.roles.items.map(r => r.code);
- expect(roleCodes).toEqual(['second-channel-admin-manager']);
- });
- it('limited admin cannot view Role which requires permissions they do not have', async () => {
- const result = await adminClient.query(getRoleDocument, { id: orderReaderRole.id });
- expect(result.role).toBeNull();
- });
- it(
- 'limited admin cannot create Role with SuperAdmin permission',
- assertThrowsWithMessage(async () => {
- await adminClient.query(createRoleDocument, {
- input: {
- code: 'evil-superadmin',
- description: '',
- channelIds: [secondChannel.id],
- permissions: [Permission.SuperAdmin],
- },
- });
- }, 'The permission "SuperAdmin" may not be assigned'),
- );
- it(
- 'limited admin cannot create Administrator with SuperAdmin role',
- assertThrowsWithMessage(async () => {
- const superAdminRole = defaultRoles.find(r => r.code === SUPER_ADMIN_ROLE_CODE)!;
- await adminClient.query(createAdministratorDocument, {
- input: {
- firstName: 'Dr',
- lastName: 'Evil',
- emailAddress: 'drevil@test.com',
- roleIds: [superAdminRole.id],
- password: 'test',
- },
- });
- }, 'Active user does not have sufficient permissions'),
- );
- it(
- 'limited admin cannot create Role with permissions it itself does not have',
- assertThrowsWithMessage(async () => {
- await adminClient.query(createRoleDocument, {
- input: {
- code: 'evil-order-manager',
- description: '',
- channelIds: [secondChannel.id],
- permissions: [Permission.ReadOrder],
- },
- });
- }, 'Active user does not have sufficient permissions'),
- );
- it(
- 'limited admin cannot create Role on channel it does not have permissions on',
- assertThrowsWithMessage(async () => {
- await adminClient.query(createRoleDocument, {
- input: {
- code: 'evil-order-manager',
- description: '',
- channelIds: [defaultChannel.id],
- permissions: [Permission.CreateAdministrator],
- },
- });
- }, 'You are not currently authorized to perform this action'),
- );
- it(
- 'limited admin cannot create Administrator with a Role with greater permissions than they themselves have',
- assertThrowsWithMessage(async () => {
- await adminClient.query(createAdministratorDocument, {
- input: {
- firstName: 'Dr',
- lastName: 'Evil',
- emailAddress: 'drevil@test.com',
- roleIds: [orderReaderRole.id],
- password: 'test',
- },
- });
- }, 'Active user does not have sufficient permissions'),
- );
- it('limited admin can create Role with permissions it itself has', async () => {
- const { createRole } = await adminClient.query(createRoleDocument, {
- input: {
- code: 'good-admin-creator',
- description: '',
- channelIds: [secondChannel.id],
- permissions: [Permission.CreateAdministrator],
- },
- });
- expect(createRole.code).toBe('good-admin-creator');
- adminCreatorRole = createRole;
- });
- it('limited admin can create Administrator with permissions it itself has', async () => {
- const { createAdministrator } = await adminClient.query(createAdministratorDocument, {
- input: {
- firstName: 'Admin',
- lastName: 'Creator',
- emailAddress: 'admincreator@test.com',
- roleIds: [adminCreatorRole.id],
- password: 'test',
- },
- });
- expect(createAdministrator.emailAddress).toBe('admincreator@test.com');
- adminCreatorAdministrator = createAdministrator;
- });
- it(
- 'limited admin cannot update Role with permissions it itself lacks',
- assertThrowsWithMessage(async () => {
- await adminClient.query(updateRoleDocument, {
- input: {
- id: adminCreatorRole.id,
- permissions: [Permission.ReadOrder],
- },
- });
- }, 'Active user does not have sufficient permissions'),
- );
- it(
- 'limited admin cannot update Administrator with Role containing permissions it itself lacks',
- assertThrowsWithMessage(async () => {
- await adminClient.query(updateAdministratorDocument, {
- input: {
- id: adminCreatorAdministrator.id,
- roleIds: [adminCreatorRole.id, orderReaderRole.id],
- },
- });
- }, 'Active user does not have sufficient permissions'),
- );
- });
- });
- export const getRolesDocument = graphql(
- `
- query GetRoles($options: RoleListOptions) {
- roles(options: $options) {
- items {
- ...Role
- }
- totalItems
- }
- }
- `,
- [roleFragment],
- );
- export const getRoleDocument = graphql(
- `
- query GetRole($id: ID!) {
- role(id: $id) {
- ...Role
- }
- }
- `,
- [roleFragment],
- );
- export const deleteRoleDocument = graphql(`
- mutation DeleteRole($id: ID!) {
- deleteRole(id: $id) {
- result
- message
- }
- }
- `);
|