diff --git a/CHANGELOG.md b/CHANGELOG.md index edd9c70b..64b6962d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Added +- Traffic-Monitor Authorization should support Multi-Organization [#141](https://github.com/Axway-API-Management-Plus/apigateway-openlogging-elk/issues/141) + ## [3.4.0] 2021-09-02 ### Fixed - Service name filtering is not working as expected [#129](https://github.com/Axway-API-Management-Plus/apigateway-openlogging-elk/issues/129) diff --git a/UPDATE.md b/UPDATE.md index 8f2a7014..0eb24505 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -56,6 +56,7 @@ On the other hand, the API builder Docker image, as a central component of the s | 3.3.1 | [X](#api-builderlogstashmemcached) | - | - | - | - | - | [X](#parameters)|- | 7.12.1 | | | 3.3.2 | [X](#api-builderlogstashmemcached) | - | - | - | - | - | - |- | 7.12.1 | | | 3.4.0 | [X](#api-builderlogstashmemcached) | [X](#api-builderlogstashmemcached) | - | - | - | [X](#dashboards)| [X](#parameters)|[X](#elastic-config)| 7.14.0 | | +| 3.5.0 | [X](#api-builderlogstashmemcached) | - | - | - | - | - | - |- | 7.14.0 |Unreleased | ### Update from Version 1.0.0 diff --git a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/src/actions.js b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/src/actions.js index a8024e38..f9e43598 100644 --- a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/src/actions.js +++ b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/src/actions.js @@ -67,24 +67,37 @@ async function addApiManagerOrganizationFilter(params, options) { if (user.gatewayManager.isUnrestricted) { return elasticQuery; } - var filters = elasticQuery.bool.must; + // Initialize the filter array, if not given + if(!elasticQuery.bool.filter) { + elasticQuery.bool.filter = []; + } + var filters = elasticQuery.bool.filter; var filter; // If the user is an API-Manager Admin, he should see all traffic passed API-Manager (has a ServiceContext) if (user.apiManager.role == "admin") { // The serviceContext may be at different places depending on the queried index - filter = { + + filters.push( { bool: { should: [ + // Either transactionSummary.serviceContext or serviceContext should be found { exists: { "field" : "transactionSummary.serviceContext" } }, { exists: { "field" : "serviceContext" } } ] } - }; + }); } else { - filter = { term: {} }; - filter.term[indexProperty] = user.apiManager.organizationName; + filter = { terms: {} }; + filter.terms[indexProperty] = [user.apiManager.organizationName]; + // If the user has multiple organizations add them all to the terms filter + // This might even include Non-Dev Orgs - It doesn't harm as they just never match to any of the documents + if(user.apiManager.orgs2Name) { + for (const [key, val] of Object.entries(user.apiManager.orgs2Name)) { + filter.terms[indexProperty].push(val); + } + } + filters.push(filter); } - filters.push(filter); return elasticQuery; } diff --git a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/test/mock/noAdminUserObjectMultiOrg.json b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/test/mock/noAdminUserObjectMultiOrg.json new file mode 100644 index 00000000..16b3d051 --- /dev/null +++ b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/test/mock/noAdminUserObjectMultiOrg.json @@ -0,0 +1,30 @@ +{ + "gatewayManager": { + "isUnrestricted": false + }, + "loginName": "anna", + "apiManager": { + "id": "0dded6b7-98da-4af0-b4c7-799fbadb7e8d", + "organizationId": "f5e79a5a-eadf-48ce-a8c2-c23f7b111f85", + "name": "Anna Owen", + "description": "Anna is the Application Owner", + "loginName": "anna", + "email": "anna@demo.axway.com", + "role": "oadmin", + "image": "/api/portal/v1.3/users/0dded6b7-98da-4af0-b4c7-799fbadb7e8d/image", + "enabled": true, + "createdOn": 1557135802934, + "state": "approved", + "type": "external", + "dn": "cn=anna,o=API Development,ou=organizations,ou=APIPortal", + "organizationName": "API Development", + "orgs2Name": { + "3fec2611-17a1-46fa-be9f-dd00862ace7c": "FastCars", + "ea62bff5-2859-46d7-a29e-1731b2717795": "Partners" + }, + "orgs2Role": { + "3fec2611-17a1-46fa-be9f-dd00862ace7c": "oadmin", + "ea62bff5-2859-46d7-a29e-1731b2717795": "user" + } + } +} \ No newline at end of file diff --git a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/test/testAPIManagerOrgAuthZ.js b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/test/testAPIManagerOrgAuthZ.js index a0e37dc0..bf7f02e3 100644 --- a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/test/testAPIManagerOrgAuthZ.js +++ b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-authorization/test/testAPIManagerOrgAuthZ.js @@ -110,9 +110,7 @@ describe('flow-node Authorization', () => { user: user, elasticQuery: elasticQuery }); - expectedQuery.bool.must.push({ - term: { "serviceContext.apiOrg" : "API Development" } - }); + expectedQuery.bool.filter = [ { terms: { "serviceContext.apiOrg": ["API Development"]} } ]; expect(value).to.be.instanceOf(Object); expect(value).to.deep.equal(expectedQuery); @@ -127,15 +125,11 @@ describe('flow-node Authorization', () => { const { value, output } = await flowNode.addApiManagerOrganizationFilter({ user: adminUser, elasticQuery: elasticQuery }); - - expectedQuery.bool.must.push({ - bool: { - should: [ - { exists: { "field" : "transactionSummary.serviceContext" } }, - { exists: { "field" : "serviceContext" } } - ] - } - }); + + expectedQuery.bool.filter = [{bool: { should: [ + { "exists": { "field": "transactionSummary.serviceContext" }}, + { "exists": { "field": "serviceContext" }} + ] } }]; expect(value).to.be.instanceOf(Object); expect(value).to.deep.equal(expectedQuery); @@ -151,10 +145,23 @@ describe('flow-node Authorization', () => { user: user, elasticQuery: elasticQuery, indexProperty: "transactionSummary.serviceContext.apiOrg" }); - expectedQuery.bool.must.push({ - term: { "transactionSummary.serviceContext.apiOrg" : "API Development" } + expectedQuery.bool.filter = [{ "terms" : {"transactionSummary.serviceContext.apiOrg" : ["API Development"] } }]; + + expect(value).to.be.instanceOf(Object); + expect(value).to.deep.equal(expectedQuery); + expect(output).to.equal('next'); + }); + + it('should add the filter for Multiple Organizations for a Non-Admin user to the query', async () => { + var user = JSON.parse(fs.readFileSync('./test/mock/noAdminUserObjectMultiOrg.json'), null); + var elasticQuery = JSON.parse(fs.readFileSync('./test/mock/givenElasticQuery.json'), null); + let expectedQuery = JSON.parse(JSON.stringify(elasticQuery)); + + const { value, output } = await flowNode.addApiManagerOrganizationFilter({ + user: user, elasticQuery: elasticQuery }); + expectedQuery.bool.filter = [ { "terms": { "serviceContext.apiOrg": [ "API Development", "FastCars", "Partners" ] } } ]; expect(value).to.be.instanceOf(Object); expect(value).to.deep.equal(expectedQuery); expect(output).to.equal('next'); diff --git a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/src/actions.js b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/src/actions.js index 0862c98b..b49717f7 100644 --- a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/src/actions.js +++ b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/src/actions.js @@ -108,6 +108,7 @@ async function lookupCurrentUser(params, options) { throw new Error(`User: '${user.loginName}' not found in API-Manager.`); } user.apiManager = users[0]; + // Get the name of the primary organization var org = await _getOrganization(user.apiManager, null, null, options); user.apiManager.organizationName = org.name; logger.debug(`User: '${user.loginName}' (Role: ${user.apiManager.role}) found in API-Manager. Organization: '${user.apiManager.organizationName}'`); diff --git a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/test/test-userlookup.js b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/test/test-userlookup.js index 454b26a2..b228cfdd 100644 --- a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/test/test-userlookup.js +++ b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/test/test-userlookup.js @@ -200,6 +200,47 @@ describe('Tests User-Lookup with complete configuration parameters', () => { expect(output).to.equal('next'); }); + it('should result into a Multi-Org user (NOT HAVING permission: adminusers_modify), which requires user lookup to the API-Manager', async () => { + nock('https://mocked-api-gateway:8190').get('/api/rbac/currentuser').reply(200, { "result": "chris" }); + nock('https://mocked-api-gateway:8190').get('/api/rbac/permissions/currentuser').replyWithFile(200, './test/testReplies/gateway/operatorRoleOnlyPermissions.json'); + nock('https://mocked-api-gateway:8175').get(`/api/portal/v1.3/users?field=loginName&op=eq&value=chris${enabledField}`).replyWithFile(200, './test/testReplies/apimanager/apiManagerUserChrisMultiOrg.json'); + nock('https://mocked-api-gateway:8175').get(`/api/portal/v1.3/organizations/2bfaa1c2-49ab-4059-832d-f833ca1c0a74`).replyWithFile(200, './test/testReplies/apimanager/organizationAPIDevelopment.json'); + + const { value, output } = await flowNode.lookupCurrentUser({ + requestHeaders: {"host":"api-gateway:8090","max-forwards":"20", "cookie":"VIDUSR=1597381095-XTawGDtJhBA7Zw==;", "csrf-token": "CF2796B3BD18C1B0B5AB1C8E95B75662E92FBC04BD799DEB97838FC5B9C39348"} + }); + + expect(value).to.deep.equal({ + "loginName": "chris", + "gatewayManager": { + "isUnrestricted": false + }, + "apiManager": { + "id": "d66a42d6-b9c7-4efd-b33a-de8b88545861", + "organizationId": "2bfaa1c2-49ab-4059-832d-f833ca1c0a74", + "organizationName": "API Development", + "name": "Chris", + "loginName": "chris", + "email": "chris@axway.com", + "role": "oadmin", + "enabled": true, + "createdOn": 1597338071490, + "state": "approved", + "type": "internal", + "dn": "cn=chris,o=API Development,ou=organizations,ou=APIPortal", + "orgs2Name": { + "3fec2611-17a1-46fa-be9f-dd00862ace7c": "API Development", + "ea62bff5-2859-46d7-a29e-1731b2717795": "Partners" + }, + "orgs2Role": { + "3fec2611-17a1-46fa-be9f-dd00862ace7c": "oadmin", + "ea62bff5-2859-46d7-a29e-1731b2717795": "user" + } + } + }); + expect(output).to.equal('next'); + }); + it('should should cache the result', async () => { nock('https://mocked-api-gateway:8190').get('/api/rbac/currentuser').reply(200, { "result": "chris" }); nock('https://mocked-api-gateway:8190').get('/api/rbac/permissions/currentuser').replyWithFile(200, './test/testReplies/gateway/operatorRoleOnlyPermissions.json'); diff --git a/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/test/testReplies/apimanager/apiManagerUserChrisMultiOrg.json b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/test/testReplies/apimanager/apiManagerUserChrisMultiOrg.json new file mode 100644 index 00000000..631f0a53 --- /dev/null +++ b/apibuilder4elastic/custom_flow_nodes/api-builder-plugin-axway-api-management/test/testReplies/apimanager/apiManagerUserChrisMultiOrg.json @@ -0,0 +1,23 @@ +[ + { + "id": "d66a42d6-b9c7-4efd-b33a-de8b88545861", + "organizationId": "2bfaa1c2-49ab-4059-832d-f833ca1c0a74", + "name": "Chris", + "loginName": "chris", + "email": "chris@axway.com", + "role": "oadmin", + "enabled": true, + "createdOn": 1597338071490, + "state": "approved", + "type": "internal", + "dn": "cn=chris,o=API Development,ou=organizations,ou=APIPortal", + "orgs2Role": { + "ea62bff5-2859-46d7-a29e-1731b2717795": "user", + "3fec2611-17a1-46fa-be9f-dd00862ace7c": "oadmin" + }, + "orgs2Name": { + "ea62bff5-2859-46d7-a29e-1731b2717795": "Partners", + "3fec2611-17a1-46fa-be9f-dd00862ace7c": "API Development" + } + } +] \ No newline at end of file