From 89319a43de8e0bf878cf6c12b7f8eaada78748c1 Mon Sep 17 00:00:00 2001 From: akumar Date: Fri, 19 Apr 2024 15:41:48 +0200 Subject: [PATCH] fix(acs-client): added unit test for validating policy filters for matching both Org and User scoping entities case --- packages/acs-client/cfg/config.json | 4 + packages/acs-client/src/acs/decorators.ts | 2 +- packages/acs-client/src/utils.ts | 20 +- packages/acs-client/test/acs.spec.test.ts | 308 +++++++++++++++++- .../fulfillment/gql/schema.generated.ts | 20 +- .../modules/ordering/gql/schema.generated.ts | 62 ++-- 6 files changed, 359 insertions(+), 57 deletions(-) diff --git a/packages/acs-client/cfg/config.json b/packages/acs-client/cfg/config.json index 60ca9aaa..d497b737 100644 --- a/packages/acs-client/cfg/config.json +++ b/packages/acs-client/cfg/config.json @@ -142,6 +142,10 @@ { "scopingEntity": "urn:test:acs:model:organization.Organization", "value": "orgKey" + }, + { + "scopingEntity": "urn:test:acs:model:user.User", + "value": "userKey" } ], "hierarchicalResources": [ diff --git a/packages/acs-client/src/acs/decorators.ts b/packages/acs-client/src/acs/decorators.ts index e3032c18..b65420ea 100644 --- a/packages/acs-client/src/acs/decorators.ts +++ b/packages/acs-client/src/acs/decorators.ts @@ -189,7 +189,7 @@ export function access_controlled_function(kwargs: { obligation => obligation.property ); - return appResponse //_.omitDeep(appResponse, property); + return appResponse; // _.omitDeep(appResponse, property); } catch (err) { return { diff --git a/packages/acs-client/src/utils.ts b/packages/acs-client/src/utils.ts index fea86fdf..6324c8d0 100644 --- a/packages/acs-client/src/utils.ts +++ b/packages/acs-client/src/utils.ts @@ -108,7 +108,7 @@ const checkSubjectMatch = (user: ResolvedSubject, ruleSubjectAttributes: Attribu )); logger.debug('Role scoped instances for matching entity', { id: user?.id, ruleRoleScopeEntityName, matchingRoleScopedInstance }); // validate HR scope root ID contains the role scope instances - const hrScopeExist = user?.hierarchical_scopes?.every((hrScope) => matchingRoleScopedInstance.includes(hrScope.id)); + const hrScopeExist = user?.hierarchical_scopes?.some((hrScope) => matchingRoleScopedInstance.includes(hrScope.id)); logger.debug('HR Scopes exist', { hrScopeExist }); if (!hrScopeExist) { logger.info('Hierarchial scopes for matching role does not exist', { role: ruleRoleValue, instances: matchingRoleScopedInstance }); @@ -121,15 +121,13 @@ const checkSubjectMatch = (user: ResolvedSubject, ruleSubjectAttributes: Attribu reducedUserScope, hierarchicalRoleScopingCheck ); - } else { + } else if (hrScopeExist && !user.scope) { // HR scope match exist but user has not provided scope so still a match is considered - if (!user?.scope) { - logger.debug('Target scope not provided using full HR tree for matched role', { role: ruleRoleValue }); - // if no scope is provided then use the complete HR tree for user scopes - user?.hierarchical_scopes?.filter((hrScope) => matchingRoleScopedInstance?.includes(hrScope?.id) && hrScope?.role === ruleRoleValue).forEach((eachHRScope) => { - reduceUserScope(eachHRScope, reducedUserScope, hierarchicalRoleScopingCheck); - }); - } + logger.debug('Target scope not provided using full HR tree for matched role', { role: ruleRoleValue }); + // if no scope is provided then use the complete HR tree for user scopes + user?.hierarchical_scopes?.filter((hrScope) => matchingRoleScopedInstance?.includes(hrScope?.id) && hrScope?.role === ruleRoleValue).forEach((eachHRScope) => { + reduceUserScope(eachHRScope, reducedUserScope, hierarchicalRoleScopingCheck); + }); return hrScopeExist; } } else if (ruleRoleValue) { @@ -354,8 +352,8 @@ export const buildFilterPermissions = async ( } } else { - subject.hierarchical_scopes ??= []; - subject.role_associations ??= []; + subject.hierarchical_scopes ??=[]; + subject.role_associations ??=[]; } const urns = cfg.get('authorization:urns'); diff --git a/packages/acs-client/test/acs.spec.test.ts b/packages/acs-client/test/acs.spec.test.ts index f7c60461..5f4915df 100644 --- a/packages/acs-client/test/acs.spec.test.ts +++ b/packages/acs-client/test/acs.spec.test.ts @@ -49,6 +49,31 @@ const permitRule: RuleRQ = { effect: Effect.PERMIT }; +const permitRuleUserScope: RuleRQ = { + id: 'permit_rule_id', + target: { + actions: [], + resources: [{ + id: 'urn:restorecommerce:acs:names:model:entity', + value: 'urn:test:acs:model:Test.Test' + }], + subjects: [ + { + id: 'urn:restorecommerce:acs:names:role', + value: 'user-role' + }, + { + id: 'urn:restorecommerce:acs:names:roleScopingEntity', + value: 'urn:test:acs:model:user.User' + }, + { + id: 'urn:restorecommerce:acs:names:hierarchicalRoleScoping', + value: 'false' + }] + }, + effect: Effect.PERMIT +}; + const permitRuleNoHrs: RuleRQ = { id: 'no_hrs_permit_rule_id', target: { @@ -351,7 +376,7 @@ describe('Testing acs-client', () => { ); }); - it('Should PERMIT creating Test resource with valid user Ctx', async () => { + it('Should PERMIT creating Test resource with valid Org scope matching Ctx', async () => { // test resource to be created const resources: CtxResource[] = updateMetaData([{ id: 'test_id', @@ -393,6 +418,57 @@ describe('Testing acs-client', () => { should.equal(response.operation_status?.message, 'success'); }); + it('Should PERMIT creating Test resource with valid User scope matching Ctx', async () => { + // test resource to be created + const resources: CtxResource[] = updateMetaData([{ + id: 'test_id', + name: 'Test', + description: 'This is a test description', + meta: { + owners: [ + { + id: 'urn:restorecommerce:acs:names:ownerIndicatoryEntity', + value: 'urn:test:acs:model:user.User', + attributes: [{ + id: 'urn:restorecommerce:acs:names:ownerInstance', + value: 'test_user_id' + }] + } + ] + } + }]); + + // user ctx data updated in session + const subject = { + id: 'test_user_id', + name: 'test_user', + scope: 'targetScope', + token: 'valid_token', + role_associations: [ + { + role: 'user-role' + } + ] + }; + + const ctx: ACSClientContext = { + subject, + resources, + }; + + // call accessRequest(), the response is from mock ACS + const response = await accessRequest( + subject, + [{ resource: 'Test', id: resources[0].id }], + AuthZAction.CREATE, + ctx + ) as DecisionResponse; + + should.equal(response.decision, Response_Decision.PERMIT); + should.equal(response.operation_status?.code, 200); + should.equal(response.operation_status?.message, 'success'); + }); + it('Should DENY reading Test resource (DENY rule)', async () => { // PolicySet contains DENY rule PolicySetRQFactory.rules = [denyRule]; @@ -520,6 +596,236 @@ describe('Testing acs-client', () => { } ); + it( + 'Should DENY reading Test resource (PERMIT rule) with invalid user target scope', + async () => { + // PolicySet contains PERMIT rule + PolicySetRQFactory.rules = [permitRuleUserScope]; + + // test resource to be read of type 'ReadRequest' + const resources: CtxResource[] = [{ + id: 'test_id', + meta: { + owners: [] + } + }]; + + // user ctx data updated in session + const subject = { + id: 'test_user_id', + scope: 'invalidUserId', + token: 'valid_token', + role_associations: [ + { + role: 'user-role', + attributes: [ + { + id: 'urn:restorecommerce:acs:names:roleScopingEntity', + value: 'urn:test:acs:model:user.User', + attributes: [{ + id: 'urn:restorecommerce:acs:names:roleScopingInstance', + value: 'test_user_id' + }] + } + ] + } + ], + hierarchical_scopes: [{ + id: 'test_user_id', + role: 'user-role', + children: [] + }] + }; + + const ctx: ACSClientContext = { + subject, + resources, + }; + + // call accessRequest(), the response is from mock ACS + const readResponse = await accessRequest( + subject, + [{ resource: 'Test', id: resources[0].id }], + AuthZAction.READ, + ctx, { operation: Operation.whatIsAllowed, database: 'postgres' } + ) as PolicySetRQResponse; + + should.equal(readResponse.decision, Response_Decision.DENY); + should.equal(readResponse.operation_status?.code, 403); + should.equal( + readResponse.operation_status?.message, + 'Access not allowed for request with subject:test_user_id, ' + + 'resource:Test, action:READ, target_scope:invalidUserId; the response was DENY' + ); + } + ); + + it( + 'Should PERMIT reading Test resource (PERMIT rule User scope) without target scope and verify input filter ' + + 'is extended to enforce applicable policies for matching user role', + async () => { + // PolicySet contains PERMIT rule + PolicySetRQFactory.rules = [permitRuleUserScope]; + + // test resource to be read of type 'ReadRequest' + const resources: CtxResource[] = [{ + id: 'test_id', + meta: { + owners: [] + } + }]; + + // user ctx data updated in session + const subject = { + id: 'test_user_id', + // scope: 'targetScope', + token: 'valid_token', + role_associations: [ + { + role: 'user-role', + attributes: [ + { + id: 'urn:restorecommerce:acs:names:roleScopingEntity', + value: 'urn:test:acs:model:user.User', + attributes: [{ + id: 'urn:restorecommerce:acs:names:roleScopingInstance', + value: 'test_user_id' + }] + } + ] + } + ], + hierarchical_scopes: [{ + id: 'test_user_id', + role: 'user-role', + children: [] + }] + }; + + const ctx: ACSClientContext = { + subject, + resources, + }; + + // call accessRequest(), the response is from mock ACS + const readResponse = await accessRequest( + subject, + [{ resource: 'Test', id: resources[0].id }], + AuthZAction.READ, + ctx, { operation: Operation.whatIsAllowed, database: 'postgres' } + ) as PolicySetRQResponse; + + should.exist(readResponse.decision); + should.equal(readResponse.decision, Response_Decision.PERMIT); + should.equal(readResponse.operation_status?.code, 200); + should.equal(readResponse.operation_status?.message, 'success'); + // verify input is modified to enforce the applicapble poilicies + const filterParamKey = cfg.get('authorization:filterParamKey')[1].value; + const expectedFilterResponse = [{ + field: filterParamKey, + operation: 'eq', + value: 'test_user_id' + }]; + should.equal(readResponse.filters?.[0]?.resource, 'Test'); + const filterEntityMap = readResponse.filters; + const filters = filterEntityMap?.[0].filters; + should.deepEqual(filters?.[0]?.filters?.[0], expectedFilterResponse[0]); + } + ); + + it( + 'Should PERMIT reading Test resource (PERMIT rule Org Scope and User scope) without target scope and ' + + 'verify input filter is extended to enforce applicable policies for matching user role', + async () => { + // PolicySet contains PERMIT rule for OrgScoping and UserScoping entity + PolicySetRQFactory.rules = [permitRule, permitRuleUserScope]; + + // test resource to be read of type 'ReadRequest' + const resources: CtxResource[] = [{ + id: 'test_id', + meta: { + owners: [] + } + }]; + + // user ctx data updated in session + const subject = { + id: 'test_user_id', + // scope: 'targetScope', + token: 'valid_token', + role_associations: [ + { + role: 'test-role', + attributes: [ + { + id: 'urn:restorecommerce:acs:names:roleScopingEntity', + value: 'urn:test:acs:model:organization.Organization', + attributes: [{ + id: 'urn:restorecommerce:acs:names:roleScopingInstance', + value: 'targetScope' + }] + } + ] + }, + { + role: 'user-role', + attributes: [ + { + id: 'urn:restorecommerce:acs:names:roleScopingEntity', + value: 'urn:test:acs:model:user.User', + attributes: [{ + id: 'urn:restorecommerce:acs:names:roleScopingInstance', + value: 'test_user_id' + }] + } + ] + } + ], + hierarchical_scopes: [{ + id: 'targetScope', + role: 'test-role', + children: [{ + id: 'targetSubScope' + }] + }, { + id: 'test_user_id', + role: 'user-role', + children: [] + }] + }; + + const ctx: ACSClientContext = { + subject, + resources, + }; + + // call accessRequest(), the response is from mock ACS + const readResponse = await accessRequest( + subject, + [{ resource: 'Test', id: resources[0].id }], + AuthZAction.READ, + ctx, { operation: Operation.whatIsAllowed, database: 'postgres' } + ) as PolicySetRQResponse; + + should.exist(readResponse.decision); + should.equal(readResponse.decision, Response_Decision.PERMIT); + should.equal(readResponse.operation_status?.code, 200); + should.equal(readResponse.operation_status?.message, 'success'); + // verify input is modified to enforce the applicapble both Org and User poilicy + const orgFilterKey = cfg.get('authorization:filterParamKey')[0].value; + const userFilterKey = cfg.get('authorization:filterParamKey')[1].value; + const expectedFilterResponse = [ + { field: orgFilterKey, operation: "eq", value: "targetScope" }, + { field: orgFilterKey, operation: "eq", value: "targetSubScope" }, + { field: userFilterKey, operation: "eq", value: "test_user_id" } + ]; + should.equal(readResponse.filters?.[0]?.resource, 'Test'); + const filterEntityMap = readResponse.filters; + const filters = filterEntityMap?.[0].filters; + should.deepEqual(filters?.[0]?.filters?.[0], expectedFilterResponse[0]); + } + ); + it( 'Should PERMIT reading Test resource (PERMIT rule) with HR scoping enabled and verify input filter ' + 'is extended to enforce applicable policies', diff --git a/packages/facade/src/modules/fulfillment/gql/schema.generated.ts b/packages/facade/src/modules/fulfillment/gql/schema.generated.ts index 647de2ba..94f2fc67 100644 --- a/packages/facade/src/modules/fulfillment/gql/schema.generated.ts +++ b/packages/facade/src/modules/fulfillment/gql/schema.generated.ts @@ -75,7 +75,7 @@ export type IoRestorecommerceFulfillmentFulfillment = { labels?: Maybe>; trackings?: Maybe>; totalAmounts?: Maybe>; - state?: Maybe; + fulfillmentState?: Maybe; }; export type IoRestorecommerceMetaMeta = { @@ -549,11 +549,11 @@ export type IoRestorecommerceFulfillmentLabel = { png?: Maybe; parcelId?: Maybe; shipmentNumber?: Maybe; - state?: Maybe; + state?: Maybe; status?: Maybe; }; -export enum IoRestorecommerceFulfillmentState { +export enum IoRestorecommerceFulfillmentFulfillmentState { Pending = 0, Invalid = 1, Submitted = 2, @@ -1004,7 +1004,7 @@ export type IIoRestorecommerceFulfillmentFulfillment = { labels?: InputMaybe>; trackings?: InputMaybe>; totalAmounts?: InputMaybe>; - state?: InputMaybe; + fulfillmentState?: InputMaybe; }; export type IIoRestorecommerceFulfillmentPackaging = { @@ -1052,7 +1052,7 @@ export type IIoRestorecommerceFulfillmentLabel = { png?: InputMaybe; parcelId?: InputMaybe; shipmentNumber?: InputMaybe; - state?: InputMaybe; + state?: InputMaybe; status?: InputMaybe; }; @@ -1700,7 +1700,7 @@ export type ResolversTypes = ResolversObject<{ IoRestorecommerceAddressShippingAddress: ResolverTypeWrapper; IoRestorecommerceAddressContact: ResolverTypeWrapper; IoRestorecommerceFulfillmentLabel: ResolverTypeWrapper; - IoRestorecommerceFulfillmentState: IoRestorecommerceFulfillmentState; + IoRestorecommerceFulfillmentFulfillmentState: IoRestorecommerceFulfillmentFulfillmentState; IoRestorecommerceStatusStatus: ResolverTypeWrapper; IoRestorecommerceFulfillmentTracking: ResolverTypeWrapper; IoRestorecommerceFulfillmentEvent: ResolverTypeWrapper; @@ -2032,7 +2032,7 @@ export type IoRestorecommerceFulfillmentFulfillmentResolvers>, ParentType, ContextType>; trackings?: Resolver>, ParentType, ContextType>; totalAmounts?: Resolver>, ParentType, ContextType>; - state?: Resolver, ParentType, ContextType>; + fulfillmentState?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -2501,12 +2501,12 @@ export type IoRestorecommerceFulfillmentLabelResolvers, ParentType, ContextType>; parcelId?: Resolver, ParentType, ContextType>; shipmentNumber?: Resolver, ParentType, ContextType>; - state?: Resolver, ParentType, ContextType>; + state?: Resolver, ParentType, ContextType>; status?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type IoRestorecommerceFulfillmentStateResolvers = { PENDING: 0, INVALID: 1, SUBMITTED: 2, IN_TRANSIT: 3, COMPLETED: 4, WITHDRAWN: 5, CANCELLED: 6, FAILED: 7 }; +export type IoRestorecommerceFulfillmentFulfillmentStateResolvers = { PENDING: 0, INVALID: 1, SUBMITTED: 2, IN_TRANSIT: 3, COMPLETED: 4, WITHDRAWN: 5, CANCELLED: 6, FAILED: 7 }; export type IoRestorecommerceStatusStatusResolvers = ResolversObject<{ id?: Resolver, ParentType, ContextType>; @@ -3046,7 +3046,7 @@ export type Resolvers = ResolversObject<{ IoRestorecommerceAddressShippingAddress?: IoRestorecommerceAddressShippingAddressResolvers; IoRestorecommerceAddressContact?: IoRestorecommerceAddressContactResolvers; IoRestorecommerceFulfillmentLabel?: IoRestorecommerceFulfillmentLabelResolvers; - IoRestorecommerceFulfillmentState?: IoRestorecommerceFulfillmentStateResolvers; + IoRestorecommerceFulfillmentFulfillmentState?: IoRestorecommerceFulfillmentFulfillmentStateResolvers; IoRestorecommerceStatusStatus?: IoRestorecommerceStatusStatusResolvers; IoRestorecommerceFulfillmentTracking?: IoRestorecommerceFulfillmentTrackingResolvers; IoRestorecommerceFulfillmentEvent?: IoRestorecommerceFulfillmentEventResolvers; diff --git a/packages/facade/src/modules/ordering/gql/schema.generated.ts b/packages/facade/src/modules/ordering/gql/schema.generated.ts index 36154781..3bc73956 100644 --- a/packages/facade/src/modules/ordering/gql/schema.generated.ts +++ b/packages/facade/src/modules/ordering/gql/schema.generated.ts @@ -70,8 +70,6 @@ export type IoRestorecommerceOrderOrder = { shop?: Maybe; items?: Maybe>; orderState?: Maybe; - fulfillmentState?: Maybe; - paymentState?: Maybe; totalAmounts?: Maybe>; shippingAddress?: Maybe; billingAddress?: Maybe; @@ -668,22 +666,6 @@ export enum IoRestorecommerceOrderOrderState { Cancelled = 4 } -export enum IoRestorecommerceFulfillmentState { - Pending = 0, - Invalid = 1, - Submitted = 2, - InTransit = 3, - Completed = 4, - Withdrawn = 5, - Cancelled = 6, - Failed = 7 -} - -export enum IoRestorecommerceInvoicePaymentState { - Unpayed = 0, - Payed = 1 -} - export type IoRestorecommerceAddressShippingAddress = { __typename?: 'IoRestorecommerceAddressShippingAddress'; address?: Maybe; @@ -937,8 +919,6 @@ export type IIoRestorecommerceOrderOrder = { shopId?: InputMaybe; items?: InputMaybe>; orderState?: InputMaybe; - fulfillmentState?: InputMaybe; - paymentState?: InputMaybe; totalAmounts?: InputMaybe>; shippingAddress?: InputMaybe; billingAddress?: InputMaybe; @@ -1099,7 +1079,7 @@ export type IoRestorecommerceFulfillmentFulfillment = { labels?: Maybe>; trackings?: Maybe>; totalAmounts?: Maybe>; - state?: Maybe; + fulfillmentState?: Maybe; }; export type IoRestorecommerceReferenceReference = { @@ -1186,10 +1166,21 @@ export type IoRestorecommerceFulfillmentLabel = { png?: Maybe; parcelId?: Maybe; shipmentNumber?: Maybe; - state?: Maybe; + state?: Maybe; status?: Maybe; }; +export enum IoRestorecommerceFulfillmentFulfillmentState { + Pending = 0, + Invalid = 1, + Submitted = 2, + InTransit = 3, + Completed = 4, + Withdrawn = 5, + Cancelled = 6, + Failed = 7 +} + export type IoRestorecommerceFulfillmentTracking = { __typename?: 'IoRestorecommerceFulfillmentTracking'; shipmentNumber?: Maybe; @@ -1238,6 +1229,11 @@ export type IoRestorecommerceInvoiceInvoice = { withdrawn?: Maybe; }; +export enum IoRestorecommerceInvoicePaymentState { + Unpayed = 0, + Payed = 1 +} + export type IoRestorecommerceInvoiceSection = { __typename?: 'IoRestorecommerceInvoiceSection'; id?: Maybe; @@ -1576,8 +1572,6 @@ export type ResolversTypes = ResolversObject<{ IoRestorecommerceTaxTax: ResolverTypeWrapper; IoRestorecommerceTaxTypeTaxType: ResolverTypeWrapper; IoRestorecommerceOrderOrderState: IoRestorecommerceOrderOrderState; - IoRestorecommerceFulfillmentState: IoRestorecommerceFulfillmentState; - IoRestorecommerceInvoicePaymentState: IoRestorecommerceInvoicePaymentState; IoRestorecommerceAddressShippingAddress: ResolverTypeWrapper; IoRestorecommerceAddressContact: ResolverTypeWrapper; IoRestorecommerceAddressBillingAddress: ResolverTypeWrapper; @@ -1635,10 +1629,12 @@ export type ResolversTypes = ResolversObject<{ IoRestorecommerceFulfillmentProductVariant: ResolverTypeWrapper; IoRestorecommerceFulfillmentItem: ResolverTypeWrapper; IoRestorecommerceFulfillmentLabel: ResolverTypeWrapper; + IoRestorecommerceFulfillmentFulfillmentState: IoRestorecommerceFulfillmentFulfillmentState; IoRestorecommerceFulfillmentTracking: ResolverTypeWrapper; IoRestorecommerceFulfillmentEvent: ResolverTypeWrapper; IoRestorecommerceInvoiceInvoiceResponse: ResolverTypeWrapper; IoRestorecommerceInvoiceInvoice: ResolverTypeWrapper; + IoRestorecommerceInvoicePaymentState: IoRestorecommerceInvoicePaymentState; IoRestorecommerceInvoiceSection: ResolverTypeWrapper; IoRestorecommerceInvoicePosition: ResolverTypeWrapper; IoRestorecommerceInvoiceProductItem: ResolverTypeWrapper; @@ -1864,8 +1860,6 @@ export type IoRestorecommerceOrderOrderResolvers, ParentType, ContextType>; items?: Resolver>, ParentType, ContextType>; orderState?: Resolver, ParentType, ContextType>; - fulfillmentState?: Resolver, ParentType, ContextType>; - paymentState?: Resolver, ParentType, ContextType>; totalAmounts?: Resolver>, ParentType, ContextType>; shippingAddress?: Resolver, ParentType, ContextType>; billingAddress?: Resolver, ParentType, ContextType>; @@ -2448,10 +2442,6 @@ export type IoRestorecommerceTaxTypeTaxTypeResolvers = ResolversObject<{ address?: Resolver, ParentType, ContextType>; contact?: Resolver, ParentType, ContextType>; @@ -2567,7 +2557,7 @@ export type IoRestorecommerceFulfillmentFulfillmentResolvers>, ParentType, ContextType>; trackings?: Resolver>, ParentType, ContextType>; totalAmounts?: Resolver>, ParentType, ContextType>; - state?: Resolver, ParentType, ContextType>; + fulfillmentState?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -2654,11 +2644,13 @@ export type IoRestorecommerceFulfillmentLabelResolvers, ParentType, ContextType>; parcelId?: Resolver, ParentType, ContextType>; shipmentNumber?: Resolver, ParentType, ContextType>; - state?: Resolver, ParentType, ContextType>; + state?: Resolver, ParentType, ContextType>; status?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; +export type IoRestorecommerceFulfillmentFulfillmentStateResolvers = { PENDING: 0, INVALID: 1, SUBMITTED: 2, IN_TRANSIT: 3, COMPLETED: 4, WITHDRAWN: 5, CANCELLED: 6, FAILED: 7 }; + export type IoRestorecommerceFulfillmentTrackingResolvers = ResolversObject<{ shipmentNumber?: Resolver, ParentType, ContextType>; events?: Resolver>, ParentType, ContextType>; @@ -2707,6 +2699,8 @@ export type IoRestorecommerceInvoiceInvoiceResolvers; }>; +export type IoRestorecommerceInvoicePaymentStateResolvers = { UNPAYED: 0, PAYED: 1 }; + export type IoRestorecommerceInvoiceSectionResolvers = ResolversObject<{ id?: Resolver, ParentType, ContextType>; customerRemark?: Resolver, ParentType, ContextType>; @@ -2904,8 +2898,6 @@ export type Resolvers = ResolversObject<{ IoRestorecommerceTaxTax?: IoRestorecommerceTaxTaxResolvers; IoRestorecommerceTaxTypeTaxType?: IoRestorecommerceTaxTypeTaxTypeResolvers; IoRestorecommerceOrderOrderState?: IoRestorecommerceOrderOrderStateResolvers; - IoRestorecommerceFulfillmentState?: IoRestorecommerceFulfillmentStateResolvers; - IoRestorecommerceInvoicePaymentState?: IoRestorecommerceInvoicePaymentStateResolvers; IoRestorecommerceAddressShippingAddress?: IoRestorecommerceAddressShippingAddressResolvers; IoRestorecommerceAddressContact?: IoRestorecommerceAddressContactResolvers; IoRestorecommerceAddressBillingAddress?: IoRestorecommerceAddressBillingAddressResolvers; @@ -2935,10 +2927,12 @@ export type Resolvers = ResolversObject<{ IoRestorecommerceFulfillmentProductVariant?: IoRestorecommerceFulfillmentProductVariantResolvers; IoRestorecommerceFulfillmentItem?: IoRestorecommerceFulfillmentItemResolvers; IoRestorecommerceFulfillmentLabel?: IoRestorecommerceFulfillmentLabelResolvers; + IoRestorecommerceFulfillmentFulfillmentState?: IoRestorecommerceFulfillmentFulfillmentStateResolvers; IoRestorecommerceFulfillmentTracking?: IoRestorecommerceFulfillmentTrackingResolvers; IoRestorecommerceFulfillmentEvent?: IoRestorecommerceFulfillmentEventResolvers; IoRestorecommerceInvoiceInvoiceResponse?: IoRestorecommerceInvoiceInvoiceResponseResolvers; IoRestorecommerceInvoiceInvoice?: IoRestorecommerceInvoiceInvoiceResolvers; + IoRestorecommerceInvoicePaymentState?: IoRestorecommerceInvoicePaymentStateResolvers; IoRestorecommerceInvoiceSection?: IoRestorecommerceInvoiceSectionResolvers; IoRestorecommerceInvoicePosition?: IoRestorecommerceInvoicePositionResolvers; IoRestorecommerceInvoiceProductItem?: IoRestorecommerceInvoiceProductItemResolvers;