role.e2e-spec.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { CurrencyCode, DeletionResult, LanguageCode, Permission } from '@vendure/common/lib/generated-types';
  3. import { omit } from '@vendure/common/lib/omit';
  4. import {
  5. CUSTOMER_ROLE_CODE,
  6. DEFAULT_CHANNEL_CODE,
  7. SUPER_ADMIN_ROLE_CODE,
  8. } from '@vendure/common/lib/shared-constants';
  9. import {
  10. createErrorResultGuard,
  11. createTestEnvironment,
  12. E2E_DEFAULT_CHANNEL_TOKEN,
  13. ErrorResultGuard,
  14. } from '@vendure/testing';
  15. import path from 'path';
  16. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  17. import { initialData } from '../../../e2e-common/e2e-initial-data';
  18. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  19. import { administratorFragment, channelFragment, roleFragment } from './graphql/fragments-admin';
  20. import { FragmentOf, graphql, ResultOf } from './graphql/graphql-admin';
  21. import {
  22. createAdministratorDocument,
  23. createChannelDocument,
  24. createRoleDocument,
  25. getChannelsDocument,
  26. updateAdministratorDocument,
  27. updateRoleDocument,
  28. } from './graphql/shared-definitions';
  29. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  30. import { sortById } from './utils/test-order-utils';
  31. describe('Role resolver', () => {
  32. const { server, adminClient } = createTestEnvironment(testConfig());
  33. let createdRole: FragmentOf<typeof roleFragment>;
  34. let defaultRoles: Array<FragmentOf<typeof roleFragment>>;
  35. type ChannelFragment = FragmentOf<typeof channelFragment>;
  36. const channelGuard: ErrorResultGuard<ChannelFragment> = createErrorResultGuard(
  37. input => !!input.defaultLanguageCode,
  38. );
  39. beforeAll(async () => {
  40. await server.init({
  41. initialData,
  42. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  43. customerCount: 1,
  44. });
  45. await adminClient.asSuperAdmin();
  46. }, TEST_SETUP_TIMEOUT_MS);
  47. afterAll(async () => {
  48. await server.destroy();
  49. });
  50. it('roles', async () => {
  51. const result = await adminClient.query(getRolesDocument);
  52. defaultRoles = result.roles.items;
  53. expect(result.roles.items.length).toBe(2);
  54. expect(result.roles.totalItems).toBe(2);
  55. });
  56. it('createRole with invalid permission', async () => {
  57. try {
  58. await adminClient.query(createRoleDocument, {
  59. input: {
  60. code: 'test',
  61. description: 'test role',
  62. permissions: ['ReadCatalogx' as any],
  63. },
  64. });
  65. fail('Should have thrown');
  66. } catch (e: any) {
  67. expect(e.response.errors[0]?.extensions.code).toBe('BAD_USER_INPUT');
  68. }
  69. });
  70. it('createRole with no permissions includes Authenticated', async () => {
  71. const { createRole } = await adminClient.query(createRoleDocument, {
  72. input: {
  73. code: 'test',
  74. description: 'test role',
  75. permissions: [],
  76. },
  77. });
  78. expect(omit(createRole, ['channels'])).toEqual({
  79. code: 'test',
  80. description: 'test role',
  81. id: 'T_3',
  82. permissions: [Permission.Authenticated],
  83. });
  84. });
  85. it('createRole deduplicates permissions', async () => {
  86. const { createRole } = await adminClient.query(createRoleDocument, {
  87. input: {
  88. code: 'test2',
  89. description: 'test role2',
  90. permissions: [Permission.ReadSettings, Permission.ReadSettings],
  91. },
  92. });
  93. expect(omit(createRole, ['channels'])).toEqual({
  94. code: 'test2',
  95. description: 'test role2',
  96. id: 'T_4',
  97. permissions: [Permission.Authenticated, Permission.ReadSettings],
  98. });
  99. });
  100. it('createRole with permissions', async () => {
  101. const result = await adminClient.query(createRoleDocument, {
  102. input: {
  103. code: 'test',
  104. description: 'test role',
  105. permissions: [Permission.ReadCustomer, Permission.UpdateCustomer],
  106. },
  107. });
  108. createdRole = result.createRole;
  109. expect(createdRole).toEqual({
  110. code: 'test',
  111. description: 'test role',
  112. id: 'T_5',
  113. permissions: [Permission.Authenticated, Permission.ReadCustomer, Permission.UpdateCustomer],
  114. channels: [
  115. {
  116. code: DEFAULT_CHANNEL_CODE,
  117. id: 'T_1',
  118. token: 'e2e-default-channel',
  119. },
  120. ],
  121. });
  122. });
  123. it('role', async () => {
  124. const result = await adminClient.query(getRoleDocument, {
  125. id: createdRole.id,
  126. });
  127. expect(result.role).toEqual(createdRole);
  128. });
  129. describe('updateRole', () => {
  130. it('updates role', async () => {
  131. const result = await adminClient.query(updateRoleDocument, {
  132. input: {
  133. id: createdRole.id,
  134. code: 'test-modified',
  135. description: 'test role modified',
  136. permissions: [
  137. Permission.ReadCustomer,
  138. Permission.UpdateCustomer,
  139. Permission.DeleteCustomer,
  140. ],
  141. },
  142. });
  143. expect(omit(result.updateRole, ['channels'])).toEqual({
  144. code: 'test-modified',
  145. description: 'test role modified',
  146. id: 'T_5',
  147. permissions: [
  148. Permission.Authenticated,
  149. Permission.ReadCustomer,
  150. Permission.UpdateCustomer,
  151. Permission.DeleteCustomer,
  152. ],
  153. });
  154. });
  155. it('works with partial input', async () => {
  156. const result = await adminClient.query(updateRoleDocument, {
  157. input: {
  158. id: createdRole.id,
  159. code: 'test-modified-again',
  160. },
  161. });
  162. expect(result.updateRole.code).toBe('test-modified-again');
  163. expect(result.updateRole.description).toBe('test role modified');
  164. expect(result.updateRole.permissions).toEqual([
  165. Permission.Authenticated,
  166. Permission.ReadCustomer,
  167. Permission.UpdateCustomer,
  168. Permission.DeleteCustomer,
  169. ]);
  170. });
  171. it('deduplicates permissions', async () => {
  172. const result = await adminClient.query(updateRoleDocument, {
  173. input: {
  174. id: createdRole.id,
  175. permissions: [
  176. Permission.Authenticated,
  177. Permission.Authenticated,
  178. Permission.ReadCustomer,
  179. Permission.ReadCustomer,
  180. ],
  181. },
  182. });
  183. expect(result.updateRole.permissions).toEqual([
  184. Permission.Authenticated,
  185. Permission.ReadCustomer,
  186. ]);
  187. });
  188. it(
  189. 'does not allow setting non-assignable permissions - Owner',
  190. assertThrowsWithMessage(async () => {
  191. await adminClient.query(updateRoleDocument, {
  192. input: {
  193. id: createdRole.id,
  194. permissions: [Permission.Owner],
  195. },
  196. });
  197. }, 'The permission "Owner" may not be assigned'),
  198. );
  199. it(
  200. 'does not allow setting non-assignable permissions - Public',
  201. assertThrowsWithMessage(async () => {
  202. await adminClient.query(updateRoleDocument, {
  203. input: {
  204. id: createdRole.id,
  205. permissions: [Permission.Public],
  206. },
  207. });
  208. }, 'The permission "Public" may not be assigned'),
  209. );
  210. it(
  211. 'does not allow setting SuperAdmin permission',
  212. assertThrowsWithMessage(async () => {
  213. await adminClient.query(updateRoleDocument, {
  214. input: {
  215. id: createdRole.id,
  216. permissions: [Permission.SuperAdmin],
  217. },
  218. });
  219. }, 'The permission "SuperAdmin" may not be assigned'),
  220. );
  221. it(
  222. 'is not allowed for SuperAdmin role',
  223. assertThrowsWithMessage(async () => {
  224. const superAdminRole = defaultRoles.find(r => r.code === SUPER_ADMIN_ROLE_CODE);
  225. if (!superAdminRole) {
  226. fail('Could not find SuperAdmin role');
  227. return;
  228. }
  229. return adminClient.query(updateRoleDocument, {
  230. input: {
  231. id: superAdminRole.id,
  232. code: 'superadmin-modified',
  233. description: 'superadmin modified',
  234. permissions: [Permission.Authenticated],
  235. },
  236. });
  237. }, `The role "${SUPER_ADMIN_ROLE_CODE}" cannot be modified`),
  238. );
  239. it(
  240. 'is not allowed for Customer role',
  241. assertThrowsWithMessage(async () => {
  242. const customerRole = defaultRoles.find(r => r.code === CUSTOMER_ROLE_CODE);
  243. if (!customerRole) {
  244. fail('Could not find Customer role');
  245. return;
  246. }
  247. return adminClient.query(updateRoleDocument, {
  248. input: {
  249. id: customerRole.id,
  250. code: 'customer-modified',
  251. description: 'customer modified',
  252. permissions: [Permission.Authenticated, Permission.DeleteAdministrator],
  253. },
  254. });
  255. }, `The role "${CUSTOMER_ROLE_CODE}" cannot be modified`),
  256. );
  257. });
  258. it(
  259. 'deleteRole is not allowed for Customer role',
  260. assertThrowsWithMessage(async () => {
  261. const customerRole = defaultRoles.find(r => r.code === CUSTOMER_ROLE_CODE);
  262. if (!customerRole) {
  263. fail('Could not find Customer role');
  264. return;
  265. }
  266. return adminClient.query(deleteRoleDocument, {
  267. id: customerRole.id,
  268. });
  269. }, `The role "${CUSTOMER_ROLE_CODE}" cannot be deleted`),
  270. );
  271. it(
  272. 'deleteRole is not allowed for SuperAdmin role',
  273. assertThrowsWithMessage(async () => {
  274. const superAdminRole = defaultRoles.find(r => r.code === SUPER_ADMIN_ROLE_CODE);
  275. if (!superAdminRole) {
  276. fail('Could not find Customer role');
  277. return;
  278. }
  279. return adminClient.query(deleteRoleDocument, {
  280. id: superAdminRole.id,
  281. });
  282. }, `The role "${SUPER_ADMIN_ROLE_CODE}" cannot be deleted`),
  283. );
  284. it('deleteRole deletes a role', async () => {
  285. const { deleteRole } = await adminClient.query(deleteRoleDocument, {
  286. id: createdRole.id,
  287. });
  288. expect(deleteRole.result).toBe(DeletionResult.DELETED);
  289. const { role } = await adminClient.query(getRoleDocument, {
  290. id: createdRole.id,
  291. });
  292. expect(role).toBeNull();
  293. });
  294. describe('multi-channel', () => {
  295. let secondChannel: ChannelFragment;
  296. let multiChannelRole: ResultOf<typeof createRoleDocument>['createRole'];
  297. beforeAll(async () => {
  298. const { createChannel } = await adminClient.query(createChannelDocument, {
  299. input: {
  300. code: 'second-channel',
  301. token: 'second-channel-token',
  302. defaultLanguageCode: LanguageCode.en,
  303. currencyCode: CurrencyCode.GBP,
  304. pricesIncludeTax: true,
  305. defaultShippingZoneId: 'T_1',
  306. defaultTaxZoneId: 'T_1',
  307. },
  308. });
  309. channelGuard.assertSuccess(createChannel);
  310. secondChannel = createChannel;
  311. });
  312. it('createRole with specified channel', async () => {
  313. const { createRole } = await adminClient.query(createRoleDocument, {
  314. input: {
  315. code: 'multi-test',
  316. description: 'multi channel test role',
  317. permissions: [Permission.ReadCustomer],
  318. channelIds: [secondChannel.id],
  319. },
  320. });
  321. multiChannelRole = createRole;
  322. expect(createRole).toEqual({
  323. code: 'multi-test',
  324. description: 'multi channel test role',
  325. id: 'T_6',
  326. permissions: [Permission.Authenticated, Permission.ReadCustomer],
  327. channels: [
  328. {
  329. code: 'second-channel',
  330. id: 'T_2',
  331. token: 'second-channel-token',
  332. },
  333. ],
  334. });
  335. });
  336. it('updateRole with specified channel', async () => {
  337. const { updateRole } = await adminClient.query(updateRoleDocument, {
  338. input: {
  339. id: multiChannelRole.id,
  340. channelIds: ['T_1', 'T_2'],
  341. },
  342. });
  343. expect(updateRole.channels.sort(sortById)).toEqual([
  344. {
  345. code: DEFAULT_CHANNEL_CODE,
  346. id: 'T_1',
  347. token: 'e2e-default-channel',
  348. },
  349. {
  350. code: 'second-channel',
  351. id: 'T_2',
  352. token: 'second-channel-token',
  353. },
  354. ]);
  355. });
  356. });
  357. // https://github.com/vendure-ecommerce/vendure/issues/1874
  358. describe('role escalation', () => {
  359. type SimpleChannel = ResultOf<typeof getChannelsDocument>['channels']['items'][number];
  360. let defaultChannel: SimpleChannel;
  361. let secondChannel: SimpleChannel;
  362. let limitedAdmin: FragmentOf<typeof administratorFragment>;
  363. let orderReaderRole: ResultOf<typeof createRoleDocument>['createRole'];
  364. let adminCreatorRole: ResultOf<typeof createRoleDocument>['createRole'];
  365. let adminCreatorAdministrator: FragmentOf<typeof administratorFragment>;
  366. beforeAll(async () => {
  367. const { channels } = await adminClient.query(getChannelsDocument);
  368. defaultChannel = channels.items.find(c => c.token === E2E_DEFAULT_CHANNEL_TOKEN)!;
  369. secondChannel = channels.items.find(c => c.token !== E2E_DEFAULT_CHANNEL_TOKEN)!;
  370. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  371. await adminClient.asSuperAdmin();
  372. const { createRole } = await adminClient.query(createRoleDocument, {
  373. input: {
  374. code: 'second-channel-admin-manager',
  375. description: '',
  376. channelIds: [secondChannel.id],
  377. permissions: [
  378. Permission.CreateAdministrator,
  379. Permission.ReadAdministrator,
  380. Permission.UpdateAdministrator,
  381. Permission.DeleteAdministrator,
  382. ],
  383. },
  384. });
  385. const { createAdministrator } = await adminClient.query(createAdministratorDocument, {
  386. input: {
  387. firstName: 'channel2',
  388. lastName: 'admin manager',
  389. emailAddress: 'channel2@test.com',
  390. roleIds: [createRole.id],
  391. password: 'test',
  392. },
  393. });
  394. limitedAdmin = createAdministrator;
  395. const { createRole: createRole2 } = await adminClient.query(createRoleDocument, {
  396. input: {
  397. code: 'second-channel-order-manager',
  398. description: '',
  399. channelIds: [secondChannel.id],
  400. permissions: [Permission.ReadOrder],
  401. },
  402. });
  403. orderReaderRole = createRole2;
  404. adminClient.setChannelToken(secondChannel.token);
  405. await adminClient.asUserWithCredentials(limitedAdmin.emailAddress, 'test');
  406. });
  407. it('limited admin cannot view Roles which require permissions they do not have', async () => {
  408. const result = await adminClient.query(getRolesDocument);
  409. const roleCodes = result.roles.items.map(r => r.code);
  410. expect(roleCodes).toEqual(['second-channel-admin-manager']);
  411. });
  412. it('limited admin cannot view Role which requires permissions they do not have', async () => {
  413. const result = await adminClient.query(getRoleDocument, { id: orderReaderRole.id });
  414. expect(result.role).toBeNull();
  415. });
  416. it(
  417. 'limited admin cannot create Role with SuperAdmin permission',
  418. assertThrowsWithMessage(async () => {
  419. await adminClient.query(createRoleDocument, {
  420. input: {
  421. code: 'evil-superadmin',
  422. description: '',
  423. channelIds: [secondChannel.id],
  424. permissions: [Permission.SuperAdmin],
  425. },
  426. });
  427. }, 'The permission "SuperAdmin" may not be assigned'),
  428. );
  429. it(
  430. 'limited admin cannot create Administrator with SuperAdmin role',
  431. assertThrowsWithMessage(async () => {
  432. const superAdminRole = defaultRoles.find(r => r.code === SUPER_ADMIN_ROLE_CODE)!;
  433. await adminClient.query(createAdministratorDocument, {
  434. input: {
  435. firstName: 'Dr',
  436. lastName: 'Evil',
  437. emailAddress: 'drevil@test.com',
  438. roleIds: [superAdminRole.id],
  439. password: 'test',
  440. },
  441. });
  442. }, 'Active user does not have sufficient permissions'),
  443. );
  444. it(
  445. 'limited admin cannot create Role with permissions it itself does not have',
  446. assertThrowsWithMessage(async () => {
  447. await adminClient.query(createRoleDocument, {
  448. input: {
  449. code: 'evil-order-manager',
  450. description: '',
  451. channelIds: [secondChannel.id],
  452. permissions: [Permission.ReadOrder],
  453. },
  454. });
  455. }, 'Active user does not have sufficient permissions'),
  456. );
  457. it(
  458. 'limited admin cannot create Role on channel it does not have permissions on',
  459. assertThrowsWithMessage(async () => {
  460. await adminClient.query(createRoleDocument, {
  461. input: {
  462. code: 'evil-order-manager',
  463. description: '',
  464. channelIds: [defaultChannel.id],
  465. permissions: [Permission.CreateAdministrator],
  466. },
  467. });
  468. }, 'You are not currently authorized to perform this action'),
  469. );
  470. it(
  471. 'limited admin cannot create Administrator with a Role with greater permissions than they themselves have',
  472. assertThrowsWithMessage(async () => {
  473. await adminClient.query(createAdministratorDocument, {
  474. input: {
  475. firstName: 'Dr',
  476. lastName: 'Evil',
  477. emailAddress: 'drevil@test.com',
  478. roleIds: [orderReaderRole.id],
  479. password: 'test',
  480. },
  481. });
  482. }, 'Active user does not have sufficient permissions'),
  483. );
  484. it('limited admin can create Role with permissions it itself has', async () => {
  485. const { createRole } = await adminClient.query(createRoleDocument, {
  486. input: {
  487. code: 'good-admin-creator',
  488. description: '',
  489. channelIds: [secondChannel.id],
  490. permissions: [Permission.CreateAdministrator],
  491. },
  492. });
  493. expect(createRole.code).toBe('good-admin-creator');
  494. adminCreatorRole = createRole;
  495. });
  496. it('limited admin can create Administrator with permissions it itself has', async () => {
  497. const { createAdministrator } = await adminClient.query(createAdministratorDocument, {
  498. input: {
  499. firstName: 'Admin',
  500. lastName: 'Creator',
  501. emailAddress: 'admincreator@test.com',
  502. roleIds: [adminCreatorRole.id],
  503. password: 'test',
  504. },
  505. });
  506. expect(createAdministrator.emailAddress).toBe('admincreator@test.com');
  507. adminCreatorAdministrator = createAdministrator;
  508. });
  509. it(
  510. 'limited admin cannot update Role with permissions it itself lacks',
  511. assertThrowsWithMessage(async () => {
  512. await adminClient.query(updateRoleDocument, {
  513. input: {
  514. id: adminCreatorRole.id,
  515. permissions: [Permission.ReadOrder],
  516. },
  517. });
  518. }, 'Active user does not have sufficient permissions'),
  519. );
  520. it(
  521. 'limited admin cannot update Administrator with Role containing permissions it itself lacks',
  522. assertThrowsWithMessage(async () => {
  523. await adminClient.query(updateAdministratorDocument, {
  524. input: {
  525. id: adminCreatorAdministrator.id,
  526. roleIds: [adminCreatorRole.id, orderReaderRole.id],
  527. },
  528. });
  529. }, 'Active user does not have sufficient permissions'),
  530. );
  531. });
  532. });
  533. export const getRolesDocument = graphql(
  534. `
  535. query GetRoles($options: RoleListOptions) {
  536. roles(options: $options) {
  537. items {
  538. ...Role
  539. }
  540. totalItems
  541. }
  542. }
  543. `,
  544. [roleFragment],
  545. );
  546. export const getRoleDocument = graphql(
  547. `
  548. query GetRole($id: ID!) {
  549. role(id: $id) {
  550. ...Role
  551. }
  552. }
  553. `,
  554. [roleFragment],
  555. );
  556. export const deleteRoleDocument = graphql(`
  557. mutation DeleteRole($id: ID!) {
  558. deleteRole(id: $id) {
  559. result
  560. message
  561. }
  562. }
  563. `);