settings-store-rw-permissions.e2e-spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. import { DefaultLogger, LogLevel, mergeConfig, Permission } from '@vendure/core';
  2. import { createTestEnvironment, SimpleGraphQLClient } from '@vendure/testing';
  3. import gql from 'graphql-tag';
  4. import path from 'path';
  5. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  6. import { initialData } from '../../../e2e-common/e2e-initial-data';
  7. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  8. import {
  9. dashboardSavedViewsPermission,
  10. SettingsStoreRwPermissionsPlugin,
  11. } from './fixtures/test-plugins/settings-store-rw-permissions-plugin';
  12. import * as Codegen from './graphql/generated-e2e-admin-types';
  13. import { CREATE_ADMINISTRATOR, CREATE_ROLE } from './graphql/shared-definitions';
  14. const GET_SETTINGS_STORE_VALUE = gql`
  15. query GetSettingsStoreValue($key: String!) {
  16. getSettingsStoreValue(key: $key)
  17. }
  18. `;
  19. const SET_SETTINGS_STORE_VALUE = gql`
  20. mutation SetSettingsStoreValue($input: SettingsStoreInput!) {
  21. setSettingsStoreValue(input: $input) {
  22. key
  23. result
  24. error
  25. }
  26. }
  27. `;
  28. describe('Settings Store Read/Write Permissions', () => {
  29. const { server, adminClient } = createTestEnvironment(
  30. mergeConfig(testConfig(), {
  31. logger: new DefaultLogger({ level: LogLevel.Warn }),
  32. plugins: [SettingsStoreRwPermissionsPlugin],
  33. }),
  34. );
  35. let readCatalogAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  36. let updateCatalogAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  37. let readWriteCatalogAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  38. let customReadAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  39. let customWriteAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  40. let customReadWriteAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  41. let readSettingsAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  42. let updateSettingsAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  43. let authenticatedOnlyAdmin: Codegen.CreateAdministratorMutation['createAdministrator'];
  44. beforeAll(async () => {
  45. await server.init({
  46. initialData,
  47. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  48. customerCount: 1,
  49. });
  50. await adminClient.asSuperAdmin();
  51. // Create admins with different permission sets
  52. readCatalogAdmin = await createAdminWithPermissions({
  53. adminClient,
  54. name: 'ReadCatalog',
  55. permissions: [Permission.ReadCatalog],
  56. });
  57. updateCatalogAdmin = await createAdminWithPermissions({
  58. adminClient,
  59. name: 'UpdateCatalog',
  60. permissions: [Permission.UpdateCatalog],
  61. });
  62. readWriteCatalogAdmin = await createAdminWithPermissions({
  63. adminClient,
  64. name: 'ReadWriteCatalog',
  65. permissions: [Permission.ReadCatalog, Permission.UpdateCatalog],
  66. });
  67. // Create admins with custom RwPermissionDefinition permissions
  68. customReadAdmin = await createAdminWithPermissions({
  69. adminClient,
  70. name: 'CustomRead',
  71. permissions: ['ReadDashboardSavedViews'],
  72. });
  73. customWriteAdmin = await createAdminWithPermissions({
  74. adminClient,
  75. name: 'CustomWrite',
  76. permissions: ['WriteDashboardSavedViews'],
  77. });
  78. customReadWriteAdmin = await createAdminWithPermissions({
  79. adminClient,
  80. name: 'CustomReadWrite',
  81. permissions: ['ReadDashboardSavedViews', 'WriteDashboardSavedViews'],
  82. });
  83. readSettingsAdmin = await createAdminWithPermissions({
  84. adminClient,
  85. name: 'ReadSettings',
  86. permissions: [Permission.ReadSettings],
  87. });
  88. updateSettingsAdmin = await createAdminWithPermissions({
  89. adminClient,
  90. name: 'UpdateSettings',
  91. permissions: [Permission.UpdateSettings],
  92. });
  93. authenticatedOnlyAdmin = await createAdminWithPermissions({
  94. adminClient,
  95. name: 'AuthenticatedOnly',
  96. permissions: [Permission.Authenticated],
  97. });
  98. // Set up initial test data as super admin
  99. await adminClient.asSuperAdmin();
  100. await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  101. input: { key: 'rwtest.separateReadWrite', value: 'initial-separate-value' },
  102. });
  103. await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  104. input: { key: 'rwtest.dashboardSavedViews', value: { viewName: 'Test View', filters: [] } },
  105. });
  106. await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  107. input: { key: 'rwtest.multipleReadPermissions', value: 'multi-read-value' },
  108. });
  109. await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  110. input: { key: 'rwtest.backwardCompatible', value: 'backward-compatible-value' },
  111. });
  112. await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  113. input: { key: 'rwtest.publicRead', value: 'public-read-value' },
  114. });
  115. }, TEST_SETUP_TIMEOUT_MS);
  116. afterAll(async () => {
  117. await server.destroy();
  118. });
  119. describe('Separate read/write permissions (object syntax)', () => {
  120. it('user with read permission can read but not write', async () => {
  121. await adminClient.asUserWithCredentials(readCatalogAdmin.emailAddress, 'test');
  122. // Should be able to read
  123. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  124. key: 'rwtest.separateReadWrite',
  125. });
  126. expect(getSettingsStoreValue).toBe('initial-separate-value');
  127. // Should not be able to write
  128. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  129. input: {
  130. key: 'rwtest.separateReadWrite',
  131. value: 'test-value',
  132. },
  133. });
  134. expect(setSettingsStoreValue.result).toBe(false);
  135. expect(setSettingsStoreValue.error).toContain('permissions');
  136. });
  137. it('user with write permission can write but not read', async () => {
  138. await adminClient.asUserWithCredentials(updateCatalogAdmin.emailAddress, 'test');
  139. // Should not be able to read
  140. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  141. key: 'rwtest.separateReadWrite',
  142. });
  143. expect(getSettingsStoreValue).toBeNull();
  144. // Should be able to write
  145. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  146. input: {
  147. key: 'rwtest.separateReadWrite',
  148. value: 'write-only-value',
  149. },
  150. });
  151. expect(setSettingsStoreValue.result).toBe(true);
  152. expect(setSettingsStoreValue.error).toBeNull();
  153. });
  154. it('user with both permissions can read and write', async () => {
  155. await adminClient.asUserWithCredentials(readWriteCatalogAdmin.emailAddress, 'test');
  156. // Should be able to write
  157. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  158. input: {
  159. key: 'rwtest.separateReadWrite',
  160. value: 'read-write-value',
  161. },
  162. });
  163. expect(setSettingsStoreValue.result).toBe(true);
  164. // Should be able to read
  165. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  166. key: 'rwtest.separateReadWrite',
  167. });
  168. expect(getSettingsStoreValue).toBe('read-write-value');
  169. });
  170. });
  171. describe('Custom RwPermissionDefinition', () => {
  172. it('user with custom read permission can only read', async () => {
  173. await adminClient.asUserWithCredentials(customReadAdmin.emailAddress, 'test');
  174. // Should be able to read
  175. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  176. key: 'rwtest.dashboardSavedViews',
  177. });
  178. expect(getSettingsStoreValue).toEqual({ viewName: 'Test View', filters: [] });
  179. // Should not be able to write
  180. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  181. input: {
  182. key: 'rwtest.dashboardSavedViews',
  183. value: { viewName: 'Modified View', filters: [] },
  184. },
  185. });
  186. expect(setSettingsStoreValue.result).toBe(false);
  187. expect(setSettingsStoreValue.error).toContain('permissions');
  188. });
  189. it('user with custom write permission can only write', async () => {
  190. await adminClient.asUserWithCredentials(customWriteAdmin.emailAddress, 'test');
  191. // Should not be able to read
  192. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  193. key: 'rwtest.dashboardSavedViews',
  194. });
  195. expect(getSettingsStoreValue).toBeNull();
  196. // Should be able to write
  197. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  198. input: {
  199. key: 'rwtest.dashboardSavedViews',
  200. value: { viewName: 'Write-Only View', filters: [] },
  201. },
  202. });
  203. expect(setSettingsStoreValue.result).toBe(true);
  204. });
  205. it('user with both custom permissions can read and write', async () => {
  206. await adminClient.asUserWithCredentials(customReadWriteAdmin.emailAddress, 'test');
  207. // Should be able to write
  208. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  209. input: {
  210. key: 'rwtest.dashboardSavedViews',
  211. value: { viewName: 'Custom RW View', filters: ['filter1'] },
  212. },
  213. });
  214. expect(setSettingsStoreValue.result).toBe(true);
  215. // Should be able to read
  216. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  217. key: 'rwtest.dashboardSavedViews',
  218. });
  219. expect(getSettingsStoreValue).toEqual({ viewName: 'Custom RW View', filters: ['filter1'] });
  220. });
  221. it('user without custom permissions cannot access', async () => {
  222. await adminClient.asUserWithCredentials(authenticatedOnlyAdmin.emailAddress, 'test');
  223. // Should not be able to read
  224. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  225. key: 'rwtest.dashboardSavedViews',
  226. });
  227. expect(getSettingsStoreValue).toBeNull();
  228. // Should not be able to write
  229. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  230. input: {
  231. key: 'rwtest.dashboardSavedViews',
  232. value: { viewName: 'Unauthorized View', filters: [] },
  233. },
  234. });
  235. expect(setSettingsStoreValue.result).toBe(false);
  236. expect(setSettingsStoreValue.error).toContain('permissions');
  237. });
  238. it('demonstrates that RwPermissionDefinition generates correct permission names', () => {
  239. // This test documents how to use the new RwPermissionDefinition
  240. expect(dashboardSavedViewsPermission.Read).toBe('ReadDashboardSavedViews');
  241. expect(dashboardSavedViewsPermission.Write).toBe('WriteDashboardSavedViews');
  242. });
  243. });
  244. describe('Multiple read permissions (OR logic)', () => {
  245. it('user with one of the read permissions can read', async () => {
  246. await adminClient.asUserWithCredentials(readSettingsAdmin.emailAddress, 'test');
  247. // Can read with ReadSettings permission (one of the allowed)
  248. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  249. key: 'rwtest.multipleReadPermissions',
  250. });
  251. expect(getSettingsStoreValue).toBe('multi-read-value');
  252. // Cannot write (needs UpdateSettings)
  253. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  254. input: {
  255. key: 'rwtest.multipleReadPermissions',
  256. value: 'unauthorized-write',
  257. },
  258. });
  259. expect(setSettingsStoreValue.result).toBe(false);
  260. expect(setSettingsStoreValue.error).toContain('permissions');
  261. });
  262. it('user with the other read permission can also read', async () => {
  263. await adminClient.asUserWithCredentials(readCatalogAdmin.emailAddress, 'test');
  264. // Can read with ReadCatalog permission (the other allowed)
  265. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  266. key: 'rwtest.multipleReadPermissions',
  267. });
  268. expect(getSettingsStoreValue).toBe('multi-read-value');
  269. });
  270. it('user with write permission can write', async () => {
  271. await adminClient.asUserWithCredentials(updateSettingsAdmin.emailAddress, 'test');
  272. // Should not be able to read (doesn't have ReadCatalog or ReadSettings)
  273. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  274. key: 'rwtest.multipleReadPermissions',
  275. });
  276. expect(getSettingsStoreValue).toBeNull();
  277. // Should be able to write
  278. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  279. input: {
  280. key: 'rwtest.multipleReadPermissions',
  281. value: 'write-authorized-value',
  282. },
  283. });
  284. expect(setSettingsStoreValue.result).toBe(true);
  285. });
  286. });
  287. describe('Backward compatibility', () => {
  288. it('user without required permission cannot read or write', async () => {
  289. await adminClient.asUserWithCredentials(readCatalogAdmin.emailAddress, 'test');
  290. // Cannot read (needs UpdateSettings)
  291. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  292. key: 'rwtest.backwardCompatible',
  293. });
  294. expect(getSettingsStoreValue).toBeNull();
  295. // Cannot write (needs UpdateSettings)
  296. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  297. input: {
  298. key: 'rwtest.backwardCompatible',
  299. value: 'unauthorized-value',
  300. },
  301. });
  302. expect(setSettingsStoreValue.result).toBe(false);
  303. expect(setSettingsStoreValue.error).toContain('permissions');
  304. });
  305. it('user with required permission can read and write', async () => {
  306. await adminClient.asUserWithCredentials(updateSettingsAdmin.emailAddress, 'test');
  307. // Should be able to read
  308. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  309. key: 'rwtest.backwardCompatible',
  310. });
  311. expect(getSettingsStoreValue).toBe('backward-compatible-value');
  312. // Should be able to write
  313. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  314. input: {
  315. key: 'rwtest.backwardCompatible',
  316. value: 'authorized-backward-value',
  317. },
  318. });
  319. expect(setSettingsStoreValue.result).toBe(true);
  320. });
  321. });
  322. describe('Public read with restricted write', () => {
  323. it('authenticated user can read but not write', async () => {
  324. await adminClient.asUserWithCredentials(authenticatedOnlyAdmin.emailAddress, 'test');
  325. // Should be able to read (only requires Authenticated)
  326. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  327. key: 'rwtest.publicRead',
  328. });
  329. expect(getSettingsStoreValue).toBe('public-read-value');
  330. // Should not be able to write (requires CreateAdministrator)
  331. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  332. input: {
  333. key: 'rwtest.publicRead',
  334. value: 'unauthorized-public-write',
  335. },
  336. });
  337. expect(setSettingsStoreValue.result).toBe(false);
  338. expect(setSettingsStoreValue.error).toContain('permissions');
  339. });
  340. it('super admin can write', async () => {
  341. await adminClient.asSuperAdmin();
  342. // Should be able to write
  343. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  344. input: {
  345. key: 'rwtest.publicRead',
  346. value: 'super-admin-write-value',
  347. },
  348. });
  349. expect(setSettingsStoreValue.result).toBe(true);
  350. });
  351. });
  352. describe('Read-only fields with permissions', () => {
  353. it('read-only field prevents writes even with correct permissions', async () => {
  354. await adminClient.asUserWithCredentials(readSettingsAdmin.emailAddress, 'test');
  355. // Should be able to read
  356. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  357. key: 'rwtest.readOnlyAccess',
  358. });
  359. expect(getSettingsStoreValue).toBeNull(); // No initial value set for this field
  360. // Should fail to write because field is readonly
  361. const { setSettingsStoreValue } = await adminClient.query(SET_SETTINGS_STORE_VALUE, {
  362. input: {
  363. key: 'rwtest.readOnlyAccess',
  364. value: 'readonly-attempt',
  365. },
  366. });
  367. expect(setSettingsStoreValue.result).toBe(false);
  368. expect(setSettingsStoreValue.error).toContain('readonly');
  369. });
  370. it('user without read permission cannot read readonly field', async () => {
  371. await adminClient.asUserWithCredentials(authenticatedOnlyAdmin.emailAddress, 'test');
  372. // Should not be able to read (needs ReadSettings)
  373. const { getSettingsStoreValue } = await adminClient.query(GET_SETTINGS_STORE_VALUE, {
  374. key: 'rwtest.readOnlyAccess',
  375. });
  376. expect(getSettingsStoreValue).toBeNull();
  377. });
  378. });
  379. });
  380. async function createAdminWithPermissions(input: {
  381. adminClient: SimpleGraphQLClient;
  382. name: string;
  383. permissions: Array<Permission | string>;
  384. }) {
  385. const { adminClient, name, permissions } = input;
  386. // All permissions are standard - use the typed mutation
  387. const { createRole } = await adminClient.query<
  388. Codegen.CreateRoleMutation,
  389. Codegen.CreateRoleMutationVariables
  390. >(CREATE_ROLE, {
  391. input: {
  392. code: name,
  393. description: name,
  394. permissions: permissions as Permission[],
  395. },
  396. });
  397. const { createAdministrator } = await adminClient.query<
  398. Codegen.CreateAdministratorMutation,
  399. Codegen.CreateAdministratorMutationVariables
  400. >(CREATE_ADMINISTRATOR, {
  401. input: {
  402. firstName: name,
  403. lastName: 'LastName',
  404. emailAddress: `${name}@test.com`,
  405. roleIds: [createRole.id],
  406. password: 'test',
  407. },
  408. });
  409. return createAdministrator;
  410. }