From 1dfc8d4d74149b52229379beb1128bae0c0008c8 Mon Sep 17 00:00:00 2001 From: Gerald Baulig Date: Mon, 17 Jun 2024 15:24:37 +0200 Subject: [PATCH] fix(meta): fix default meta and subject resolving, load protos from node_modules for testing --- {test/cfg => cfg}/config_test.json | 2 +- src/service.ts | 223 +++++++++--------- src/utils.ts | 56 +++-- src/worker.ts | 10 +- .../io/restorecommerce/access_control.proto | 106 --------- test/protos/io/restorecommerce/user.proto | 76 ------ test/resource_srv.spec.ts | 3 +- test/resource_srv_acs.spec.ts | 40 +--- 8 files changed, 170 insertions(+), 346 deletions(-) rename {test/cfg => cfg}/config_test.json (99%) delete mode 100644 test/protos/io/restorecommerce/access_control.proto delete mode 100644 test/protos/io/restorecommerce/user.proto diff --git a/test/cfg/config_test.json b/cfg/config_test.json similarity index 99% rename from test/cfg/config_test.json rename to cfg/config_test.json index 8c4a3ae..4415344 100644 --- a/test/cfg/config_test.json +++ b/cfg/config_test.json @@ -2,7 +2,7 @@ "logger": { "console": { "handleExceptions": false, - "level": "silly", + "level": "crit", "colorize": true, "prettyPrint": true } diff --git a/src/service.ts b/src/service.ts index 5f18509..5163e78 100644 --- a/src/service.ts +++ b/src/service.ts @@ -4,47 +4,48 @@ import { RedisClientType } from 'redis'; import { ResourcesAPIBase, ServiceBase } from '@restorecommerce/resource-base-interface'; import { ACSAuthZ, DecisionResponse, Operation, PolicySetRQResponse, ResolvedSubject } from '@restorecommerce/acs-client'; import { AuthZAction } from '@restorecommerce/acs-client'; -import { checkAccessRequest, getACSFilters } from './utils.js'; +import { checkAccessRequest, getACSFilters, resolveSubject } from './utils.js'; import * as uuid from 'uuid'; import { Response_Decision } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/access_control.js'; -import { ReadRequest, DeleteRequest, DeepPartial, DeleteResponse } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base.js'; -import { Filter_Operation } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/filter.js'; +import { + ReadRequest, + DeleteRequest, + DeepPartial, + DeleteResponse, + Resource, + ResourceListResponse, + ResourceList +} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base.js'; +import { Filter_Operation, Filter_ValueType } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/filter.js'; +import { Subject } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/auth.js'; + +export class ResourceService extends ServiceBase { + private readonly urns: any; -export class ResourceService extends ServiceBase { - authZ: ACSAuthZ; - redisClient: RedisClientType; - cfg: any; - resourceName: string; constructor( - resourceName: string, + private readonly resourceName: string, resourceEvents: any, cfg: any, logger: Logger, resourceAPI: ResourcesAPIBase, isEventsEnabled: boolean, - authZ: ACSAuthZ, - redisClientSubject: RedisClientType, ) { super(resourceName, resourceEvents, logger, resourceAPI, isEventsEnabled); - this.authZ = authZ; - this.cfg = cfg; - this.resourceName = resourceName; - this.redisClient = redisClientSubject; + this.urns = cfg.get('authorization:urns'); } async create(request: any, ctx: any) { - let data = request.items; - let subject = request.subject; + const subject = await resolveSubject(request.subject); // update meta data for owners information - const acsResources = await this.createMetadata(data, AuthZAction.CREATE, subject); + request.items = await this.createMetadata(request.items, AuthZAction.CREATE, subject); let acsResponse: DecisionResponse; try { ctx ??= {}; ctx.subject = subject; - ctx.resources = acsResources; + ctx.resources = request.items; acsResponse = await checkAccessRequest( ctx, - [{ resource: this.resourceName, id: acsResources.map((item: any) => item.id) }], + [{ resource: this.resourceName, id: request.items.map((item: any) => item.id) }], AuthZAction.CREATE, Operation.isAllowed ); @@ -64,7 +65,7 @@ export class ResourceService extends ServiceBase { } async read(request: ReadRequest, ctx: any): Promise> { - const subject = request.subject; + const subject = await resolveSubject(request.subject); let acsResponse: PolicySetRQResponse; try { if (!ctx) { ctx = {}; }; @@ -102,7 +103,7 @@ export class ResourceService extends ServiceBase { } async update(request: any, ctx: any) { - let subject = request.subject; + const subject = await resolveSubject(request.subject); // update meta data for owner information const acsResources = await this.createMetadata(request.items, AuthZAction.MODIFY, subject); let acsResponse: DecisionResponse; @@ -132,7 +133,7 @@ export class ResourceService extends ServiceBase { } async upsert(request: any, ctx: any) { - let subject = request.subject; + const subject = await resolveSubject(request.subject); const acsResources = await this.createMetadata(request.items, AuthZAction.MODIFY, subject); let acsResponse: DecisionResponse; try { @@ -163,8 +164,8 @@ export class ResourceService extends ServiceBase { async delete(request: DeleteRequest, ctx: any): Promise> { let resourceIDs = request.ids; let resources = []; - let acsResources = []; - let subject = request.subject; + let acsResources = new Array(); + const subject = await resolveSubject(request.subject); let action = AuthZAction.DELETE; if (resourceIDs) { if (Array.isArray(resourceIDs)) { @@ -175,7 +176,7 @@ export class ResourceService extends ServiceBase { resources = [{ id: resourceIDs }]; } Object.assign(resources, { id: resourceIDs }); - acsResources = await this.createMetadata(resources, action, subject as ResolvedSubject); + acsResources = await this.createMetadata(resources, action, subject as ResolvedSubject); } if (request.collection) { action = AuthZAction.DROP; @@ -208,97 +209,107 @@ export class ResourceService extends ServiceBase { } /** - * reads meta data from DB and updates owner information in resource if action is UPDATE / DELETE - * @param reaources list of resources - * @param entity entity name - * @param action resource action - */ - async createMetadata(resources: any, action: string, subject?: ResolvedSubject): Promise { - let orgOwnerAttributes = []; - if (resources && !Array.isArray(resources)) { + * reads meta data from DB and updates owners information in resource if action is UPDATE / DELETE + * @param reaources list of resources + * @param entity entity name + * @param action resource action + */ + async createMetadata( + resources: T | T[], + action: string, + subject?: Subject + ): Promise { + + if (!Array.isArray(resources)) { resources = [resources]; } - const urns = this.cfg.get('authorization:urns'); - if (subject && subject.scope && (action === AuthZAction.CREATE || action === AuthZAction.MODIFY)) { - // add user and subject scope as default owner - orgOwnerAttributes.push( - { - id: urns?.ownerIndicatoryEntity, - value: urns?.organization, - attributes: [{ - id: urns?.ownerInstance, - value: subject?.scope - }] - }); - } - if (resources?.length > 0) { - for (let resource of resources) { - if (!resource.meta) { - resource.meta = {}; - } - if (action === AuthZAction.MODIFY || action === AuthZAction.DELETE) { - let result = await super.read(ReadRequest.fromPartial({ - filters: [{ - filters: [{ - field: 'id', - operation: Filter_Operation.eq, - value: resource?.id + const setDefaultMeta = (resource: T) => { + if (!resource.id?.length) { + resource.id = uuid.v4().replace(/-/g, ''); + } + + if (!resource.meta) { + resource.meta = {}; + resource.meta.owners = []; + + if (subject?.scope) { + resource.meta.owners.push( + { + id: this.urns.ownerIndicatoryEntity, + value: this.urns.organization, + attributes: [{ + id: this.urns.ownerInstance, + value: subject.scope }] - }] - }) as any, {}); - // update owner info - if (result?.items?.length === 1) { - let item = result.items[0].payload; - resource.meta.owners = item?.meta?.owners; - } else if (result?.items?.length === 0) { - if (!resource?.id?.length) { - resource.id = uuid.v4().replace(/-/g, ''); } - let ownerAttributes; - if (!resource?.meta?.owners) { - ownerAttributes = _.cloneDeep(orgOwnerAttributes); - } else { - ownerAttributes = resource.meta.owners; - } - if (subject?.id) { - ownerAttributes.push( - { - id: urns?.ownerIndicatoryEntity, - value: urns?.user, - attributes: [{ - id: urns?.ownerInstance, - value: subject?.id - }] - }); + ); + } + + if (subject?.id) { + resource.meta.owners.push( + { + id: this.urns.ownerIndicatoryEntity, + value: this.urns.user, + attributes: [{ + id: this.urns.ownerInstance, + value: subject.id + }] } - resource.meta.owners = ownerAttributes; - } - } else if (action === AuthZAction.CREATE) { - if (!resource?.id?.length) { - resource.id = uuid.v4().replace(/-/g, ''); - } - let ownerAttributes; - if (!resource?.meta?.owners) { - ownerAttributes = _.cloneDeep(orgOwnerAttributes); - } else { - ownerAttributes = resource.meta.owners; - } - if (subject?.id) { - ownerAttributes.push( + ); + } + } + }; + + if (action === AuthZAction.MODIFY || action === AuthZAction.DELETE) { + const ids = [ + ...new Set( + resources.map( + r => r.id + ).filter( + id => id + ) + ).values() + ]; + const filters = ReadRequest.fromPartial({ + filters: [ + { + filters: [ { - id: urns?.ownerIndicatoryEntity, - value: urns?.user, - attributes: [{ - id: urns?.ownerInstance, - value: subject?.id - }] - }); + field: 'id', + operation: Filter_Operation.in, + value: JSON.stringify(ids), + type: Filter_ValueType.ARRAY + } + ] } - resource.meta.owners = ownerAttributes; + ], + limit: ids.length + }); + + const result_map = await super.read(filters, {}).then( + resp => new Map( + resp.items?.map( + item => [item.payload?.id, item?.payload] + ) + ) + ); + + for (let resource of resources) { + if (!resource.meta && result_map.has(resource?.id)) { + resource.meta = result_map.get(resource?.id).meta; + } + else { + setDefaultMeta(resource); } } } + else if (action === AuthZAction.CREATE) { + for (let resource of resources) { + setDefaultMeta(resource); + } + } + return resources; } } diff --git a/src/utils.ts b/src/utils.ts index 02b209e..727959c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,7 +5,8 @@ import { Operation, PolicySetRQResponse, ResolvedSubject, - HierarchicalScope + HierarchicalScope, + ACSClientOptions } from '@restorecommerce/acs-client'; import { createServiceConfig } from '@restorecommerce/service-config'; import { @@ -28,7 +29,7 @@ import { // Create a ids client instance let idsClientInstance: UserClient; const cfg = createServiceConfig(process.cwd()); -export const getUserServiceClient = async () => { +export const getUserServiceClient = () => { if (!idsClientInstance) { // identity-srv client to resolve subject ID by token const grpcIDSConfig = cfg.get('client:user'); @@ -91,6 +92,18 @@ export interface GQLClientContext { resources?: CtxResource[]; } +/* eslint-disable prefer-arrow-functions/prefer-arrow-functions */ +export async function resolveSubject(subject: Subject) { + if (subject) { + const idsClient = getUserServiceClient(); + const resp = await idsClient?.findByToken({ token: subject.token }); + if (resp?.payload?.id) { + subject.id = resp.payload.id; + } + } + return subject; +} + export async function checkAccessRequest(ctx: GQLClientContext, resource: Resource[], action: AuthZAction, operation: Operation.isAllowed, useCache?: boolean): Promise; export async function checkAccessRequest(ctx: GQLClientContext, resource: Resource[], action: AuthZAction, operation: Operation.whatIsAllowed, useCache?: boolean): Promise; @@ -103,19 +116,16 @@ export async function checkAccessRequest(ctx: GQLClientContext, resource: Resour * @param entity The entity type to check access against */ /* eslint-disable prefer-arrow-functions/prefer-arrow-functions */ -export async function checkAccessRequest(ctx: GQLClientContext, resource: Resource[], action: AuthZAction, - operation: Operation, useCache = true): Promise { - let subject = ctx.subject as Subject; - let dbSubject; +export async function checkAccessRequest( + ctx: GQLClientContext, + resource: Resource[], + action: AuthZAction, + operation: Operation, +): Promise { + const subject = ctx.subject as Subject; // resolve subject id using findByToken api and update subject with id - if (subject && subject.token) { - const idsClient = await getUserServiceClient(); - if (idsClient) { - dbSubject = await idsClient.findByToken({ token: subject.token }); - if (dbSubject && dbSubject.payload && dbSubject.payload.id) { - subject.id = dbSubject.payload.id; - } - } + if (!subject?.id && subject?.token) { + await resolveSubject(subject); } let result: DecisionResponse | PolicySetRQResponse; @@ -128,7 +138,7 @@ export async function checkAccessRequest(ctx: GQLClientContext, resource: Resour { operation, roleScopingEntityURN: cfg?.get('authorization:urns:roleScopingEntityURN') - }); + } as ACSClientOptions); } catch (err: any) { return { decision: Response_Decision.DENY, @@ -149,13 +159,13 @@ export async function checkAccessRequest(ctx: GQLClientContext, resource: Resour * @param accessResponse ACS response * @param enitity enitity name */ -export const getACSFilters = (accessResponse: PolicySetRQResponse, resource: string): FilterOp[] => { - return accessResponse?.filters?.filter( - (e) => !e.resource && e.resource === resource - ).flatMap( - e => e.filters - ) ?? []; -}; +export const getACSFilters = ( + accessResponse: PolicySetRQResponse, + resource: string +): FilterOp[] => accessResponse?.filters?.find( + (e) => e?.resource === resource + && e?.filters[0]?.filters?.length +)?.filters ?? []; const setNestedChildOrgs = (hrScope: any, targetOrgID: string, subOrgs: any[]) => { if (!hrScope) { @@ -216,8 +226,6 @@ export const getSubTreeOrgs = async ( } } - console.log(JSON.stringify(traversalResponse, undefined, 2)); - for (let item of traversalResponse) { let targetID = item.id; const subOrgs = traversalResponse.filter((e: any) => e.parent_id === targetID); diff --git a/src/worker.ts b/src/worker.ts index 907db7c..2fb52b9 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -306,8 +306,14 @@ export class Worker { resourceFieldConfig, edgeCfg, graphName); const resourceEvents = await events.topic(`${resourcesServiceNamePrefix}${resourceName}s.resource`); // TODO provide typing on ResourceService - this.services[resourceName] = new ResourceService(resourceName, - resourceEvents, cfg, logger, resourceAPI, isEventsEnabled, authZ, redisClientSubject); + this.services[resourceName] = new ResourceService( + resourceName, + resourceEvents, + cfg, + logger, + resourceAPI, + isEventsEnabled, + ); const resourceServiceDefinition = ServiceDefinitions.filter((obj: any) => obj.fullName.split('.')[2] === resourceName); // todo add bindConfig typing await server.bind(`${resourcesServiceConfigPrefix}${resourceName}-srv`, { diff --git a/test/protos/io/restorecommerce/access_control.proto b/test/protos/io/restorecommerce/access_control.proto deleted file mode 100644 index eabca6a..0000000 --- a/test/protos/io/restorecommerce/access_control.proto +++ /dev/null @@ -1,106 +0,0 @@ -syntax = "proto3"; - -package io.restorecommerce.access_control; - -/** -* Access control service interface. -*/ - -service AccessControlService { - rpc IsAllowed (Request) returns (Response); - rpc WhatIsAllowed(Request) returns (ReverseQuery); -} - -message Request { - Target target = 1; - // generic data structure which can be provided - // to a contextQuery (see io/restorecommerce/rule.proto) - Context context = 2; -} - -message Context { - Any subject = 1; - repeated Any resources = 2; - Any security = 3; -} - -message Any { - string type_url = 1; - bytes value = 2; -} - -message Response { - enum Decision { - PERMIT = 0; - DENY = 1; - NOT_APPLICABLE = 2; - INDETERMINATE = 3; - } - Decision decision = 1; - string obligation = 2; -} - -/** -* Target specified by a Rule or a Request. -*/ -message Target { - repeated Attribute subjects = 1; - repeated Attribute resources = 2; - repeated Attribute actions = 3; -} - -/** -* Key-value pair. -*/ -message Attribute { - string id = 1; - string value = 2; - repeated Attribute attributes = 3; -} - -/** -* Resulting effect from a Policy or Rule. -*/ -enum Effect { - PERMIT = 0; - DENY = 1; -} - -message ReverseQuery { - repeated PolicySetRQ policy_sets = 1; -} - -message PolicySetRQ { - string id = 1; - Target target = 2; - string combining_algorithm = 3; - repeated PolicyRQ policies = 4; - Effect effect = 5; -} - -message PolicyRQ { - string id = 1; - Target target = 2; - string combining_algorithm = 3; - repeated RuleRQ rules = 4; - Effect effect = 5; - bool has_rules = 6; -} - -message RuleRQ { // used for `whatIsAllowed` / reverse queries - string id = 1; - Target target = 2; - Effect effect = 3; - string condition = 4; - ContextQuery context_query = 5; -} - -message ContextQuery { - message Filter { - string field = 1; - string operation = 2; - string value = 3; - } - repeated Filter filters = 1; - string query = 2; -} diff --git a/test/protos/io/restorecommerce/user.proto b/test/protos/io/restorecommerce/user.proto deleted file mode 100644 index e194307..0000000 --- a/test/protos/io/restorecommerce/user.proto +++ /dev/null @@ -1,76 +0,0 @@ -syntax = "proto3"; - -package io.restorecommerce.user; - -/** - * The microservice for the user resource. - */ -service UserService { - rpc FindByToken (FindByTokenRequest) returns (UserResponse); -} - -message FindByTokenRequest { - string token = 1; -} - -message Attribute { - string id = 1; - string value = 2; - repeated Attribute attributes = 3; -} - -message RoleAssociation { - string role = 1; // role ID - repeated Attribute attributes = 2; // useful attributes for RBAC/ABAC like organizational scope - string id = 3; // identifier for role_association -} - -message Tokens { - string name = 1; // token name - double expires_in = 2; // expiration date for token - string token = 3; // token - repeated string scopes = 4; // identifier for role_association - string type = 5; // type of token eg: access_token, refresh_token - bool interactive = 6; - double last_login = 7; -} - -message Status { - string id = 1; - uint32 code = 2; - string message = 3; -} - -message UserResponse { - User payload = 1; - Status status = 2; -} - - -/** - * A User resource. - */ -message User { - string id = 1; /// User ID, unique, key - string name = 3; // The name of the user, can be used for login - string first_name = 4; - string last_name = 5; - string email = 6; /// Email address, can be used for login - string new_email = 7; /// New email address; set by `requestEmailChange` and overrides actual email upon `confirmEmailChange` - bool active = 8; /// If the user was activated via the activation process - string activation_code = 9; /// Activation code used in the activation process - string password = 10; /// Raw password, not stored - string password_hash = 11; /// Encrypted password, stored - repeated RoleAssociation role_associations = 12; // A user can have multiple roles and different attributes coupled with each role - string timezone_id = 13; // timezone_id specifications - string locale_id = 14; // locale specifications - string default_scope = 15; // default hierarchical scope - bool unauthenticated = 16; // true in case in case of `register`; set to false after activation - bool guest = 17; /// Is the user a guest. A guest is a automatically generated user which can later be turned in a non-guest user. - bool invite = 20; // For user invitation - string invited_by_user_name = 21; // user who is inviting - string invited_by_user_first_name = 22; // First name of user inviting - string invited_by_user_last_name = 23; // Last name of user inviting - repeated Tokens tokens = 24; - double last_access = 25; -} \ No newline at end of file diff --git a/test/resource_srv.spec.ts b/test/resource_srv.spec.ts index 1bef0e6..5e0a3c4 100644 --- a/test/resource_srv.spec.ts +++ b/test/resource_srv.spec.ts @@ -1,3 +1,4 @@ +import {} from 'mocha'; import should from 'should'; import { createChannel, createClient } from '@restorecommerce/grpc-client'; import { Events, Topic, registerProtoMeta } from '@restorecommerce/kafka-client'; @@ -12,7 +13,7 @@ import { ContactPointServiceDefinition as contact_point } from '@restorecommerce import { ReadRequest, Sort_SortOrder } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base.js'; import { Filter_Operation } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/filter.js'; -const cfg = createServiceConfig(process.cwd() + '/test'); +const cfg = createServiceConfig(process.cwd()); const logger = createLogger(cfg.get('logger')); const ServiceDefinitionList = [command, organization, contact_point]; diff --git a/test/resource_srv_acs.spec.ts b/test/resource_srv_acs.spec.ts index e6e74ce..a9d945c 100644 --- a/test/resource_srv_acs.spec.ts +++ b/test/resource_srv_acs.spec.ts @@ -1,3 +1,4 @@ +import {} from 'mocha'; import should from 'should'; import { createChannel, createClient } from '@restorecommerce/grpc-client'; import { Events, Topic } from '@restorecommerce/kafka-client'; @@ -14,7 +15,7 @@ import { ContactPointServiceDefinition as contact_point } from '@restorecommerce import { createClient as RedisCreateClient, RedisClientType } from 'redis'; import { updateConfig } from '@restorecommerce/acs-client'; -const cfg = createServiceConfig(process.cwd() + '/test'); +const cfg = createServiceConfig(process.cwd()); const logger = createLogger(cfg.get('logger')); const ServiceDefinitionList = [command, organization, contact_point]; let redisClient: RedisClientType; @@ -24,28 +25,15 @@ let tokenRedisClient: RedisClientType; * Note: To run below tests a running Kafka, Redis and ArangoDB instance is required. * Kafka can be disabled if the config 'enableEvents' is set to false. */ -const meta = { - modified_by: 'AdminID', - owners: [{ - "id": "urn:restorecommerce:acs:names:ownerIndicatoryEntity", - "value": "urn:restorecommerce:acs:model:organization.Organization", - "attributes": [{ - "id": "urn:restorecommerce:acs:names:ownerInstance", - "value": "orgC" - }] - }] -}; const listOfContactPoints = [ { id: 'contact_point_1', - website: 'http://TestOrg1.de', - meta + website: 'http://TestOrg1.de' }, { id: 'contact_point_2', - website: 'http://TestOrg2.de', - meta + website: 'http://TestOrg2.de' }, ]; @@ -129,12 +117,6 @@ let subject = { ] }; -interface serverRule { - method: string, - input: any, - output: any -} - interface MethodWithOutput { method: string, output: any @@ -145,7 +127,7 @@ const PKG_NAME: string = 'io.restorecommerce.access_control'; const SERVICE_NAME: string = 'AccessControlService'; const pkgDef: grpc.GrpcObject = grpc.loadPackageDefinition( proto_loader.loadSync(PROTO_PATH, { - includeDirs: ['test/protos'], + includeDirs: ['node_modules/@restorecommerce/protos'], keepCase: true, longs: String, enums: String, @@ -185,7 +167,7 @@ const startGrpcMockServer = async (methodWithOutput: MethodWithOutput[]) => { }; try { mockServer.addService(PROTO_PATH, PKG_NAME, SERVICE_NAME, implementations, { - includeDirs: ['test/protos/'], + includeDirs: ['node_modules/@restorecommerce/protos'], keepCase: true, longs: String, enums: String, @@ -199,7 +181,7 @@ const startGrpcMockServer = async (methodWithOutput: MethodWithOutput[]) => { } }; -const IDS_PROTO_PATH = 'test/protos/io/restorecommerce/user.proto'; +const IDS_PROTO_PATH = 'io/restorecommerce/user.proto'; const IDS_PKG_NAME = 'io.restorecommerce.user'; const IDS_SERVICE_NAME = 'UserService'; @@ -218,7 +200,7 @@ const startIDSGrpcMockServer = async (methodWithOutput: MethodWithOutput[]) => { }; try { mockServerIDS.addService(IDS_PROTO_PATH, IDS_PKG_NAME, IDS_SERVICE_NAME, implementations, { - includeDirs: ['test/protos/'], + includeDirs: ['node_modules/@restorecommerce/protos'], keepCase: true, longs: String, enums: String, @@ -414,8 +396,7 @@ describe('resource-srv testing with ACS enabled', () => { it('should throw error when trying to update contact point not existing with valid subject scope', async function deleteContactPoint() { const contactPoint = [{ id: 'contact_point_3', - website: 'http://TestOrg3.de', - meta + website: 'http://TestOrg3.de' }]; const updateResult = await contactPointsService.update({ items: contactPoint, subject }); should.exist(updateResult.operation_status); @@ -430,8 +411,7 @@ describe('resource-srv testing with ACS enabled', () => { it('should upsert contact point with valid subject scope', async function deleteContactPoint() { const contactPoint = [{ id: 'contact_point_3', - website: 'http://TestOrg3.de', - meta + website: 'http://TestOrg3.de' }]; const upsertResult = await contactPointsService.upsert({ items: contactPoint, subject }); baseValidation(upsertResult);