Browse Source

fix(core): Fix access to protected custom fields in Shop API

Fixes #2878
Michael Bromley 1 year ago
parent
commit
090ff9be3d

+ 10 - 0
docs/docs/guides/developer-guide/custom-fields/index.md

@@ -637,6 +637,16 @@ const config = {
 };
 ```
 
+:::note
+
+The `requiresPermission` property only affects the _Admin API_. Access to a custom field via the _Shop API_ is controlled by the `public` property.
+
+If you need special logic to control access to a custom field in the Shop API, you can set `public: false` and then implement
+a custom [field resolver](/guides/developer-guide/extend-graphql-api/#add-fields-to-existing-types) which contains the necessary logic, and returns
+the entity's custom field value if the current customer meets the requirements.
+
+:::
+
 ### Properties for `string` fields
 
 In addition to the common properties, the `string` custom fields have some type-specific properties:

+ 34 - 32
packages/core/e2e/custom-field-permissions.e2e-spec.ts

@@ -20,30 +20,35 @@ describe('Custom field permissions', () => {
                     {
                         name: 'publicField',
                         type: 'string',
+                        public: true,
                         defaultValue: 'publicField Value',
                     },
                     {
                         name: 'authenticatedField',
                         type: 'string',
                         defaultValue: 'authenticatedField Value',
+                        public: true,
                         requiresPermission: Permission.Authenticated,
                     },
                     {
                         name: 'updateProductField',
                         type: 'string',
                         defaultValue: 'updateProductField Value',
+                        public: true,
                         requiresPermission: Permission.UpdateProduct,
                     },
                     {
                         name: 'updateProductOrCustomerField',
                         type: 'string',
                         defaultValue: 'updateProductOrCustomerField Value',
+                        public: false,
                         requiresPermission: [Permission.UpdateProduct, Permission.UpdateCustomer],
                     },
                     {
                         name: 'superadminField',
                         type: 'string',
                         defaultValue: 'superadminField Value',
+                        public: false,
                         requiresPermission: Permission.SuperAdmin,
                     },
                 ],
@@ -93,38 +98,6 @@ describe('Custom field permissions', () => {
             }
         `);
 
-    it('anonymous user can only read public custom field', async () => {
-        await shopClient.asAnonymousUser();
-
-        const { product } = await shopClient.query(GET_PRODUCT_WITH_CUSTOM_FIELDS, {
-            id: 'T_1',
-        });
-
-        expect(product.customFields).toEqual({
-            publicField: 'publicField Value',
-            authenticatedField: null,
-            updateProductField: null,
-            updateProductOrCustomerField: null,
-            superadminField: null,
-        });
-    });
-
-    it('authenticated user can read public and authenticated custom fields', async () => {
-        await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
-
-        const { product } = await shopClient.query(GET_PRODUCT_WITH_CUSTOM_FIELDS, {
-            id: 'T_1',
-        });
-
-        expect(product.customFields).toEqual({
-            publicField: 'publicField Value',
-            authenticatedField: 'authenticatedField Value',
-            updateProductField: null,
-            updateProductOrCustomerField: null,
-            superadminField: null,
-        });
-    });
-
     it('readProductUpdateProductAdmin can read public and updateProduct custom fields', async () => {
         await adminClient.asUserWithCredentials(readProductUpdateProductAdmin.emailAddress, 'test');
 
@@ -268,6 +241,35 @@ describe('Custom field permissions', () => {
             expect(e.message).toBe('You are not currently authorized to perform this action');
         }
     });
+
+    describe('Shop API', () => {
+        const GET_PRODUCT_WITH_PUBLIC_CUSTOM_FIELDS = gql(`
+            query {
+                product(id: "T_1") {
+                    id
+                    customFields {
+                        publicField
+                        authenticatedField
+                        updateProductField
+                    }
+                }
+            }
+        `);
+
+        it('all public fields are accessible in Shop API regardless of permissions', async () => {
+            await shopClient.asAnonymousUser();
+
+            const { product } = await shopClient.query(GET_PRODUCT_WITH_PUBLIC_CUSTOM_FIELDS, {
+                id: 'T_1',
+            });
+
+            expect(product.customFields).toEqual({
+                publicField: 'new publicField Value',
+                authenticatedField: 'new authenticatedField Value',
+                updateProductField: 'new updateProductField Value 2',
+            });
+        });
+    });
 });
 
 async function createAdminWithPermissions(input: {

+ 3 - 0
packages/core/src/api/common/user-has-permissions-on-custom-field.ts

@@ -5,6 +5,9 @@ import { CustomFieldConfig } from '../../config/custom-field/custom-field-types'
 import { RequestContext } from './request-context';
 
 export function userHasPermissionsOnCustomField(ctx: RequestContext, fieldDef: CustomFieldConfig) {
+    if (ctx.apiType === 'shop' && fieldDef.public === true) {
+        return true;
+    }
     const requiresPermission = (fieldDef.requiresPermission as Permission[]) ?? [];
     const permissionsArray = Array.isArray(requiresPermission) ? requiresPermission : [requiresPermission];
     if (permissionsArray.length === 0) {