From b971e4ae87e7b5634fa05d119974cb78d6d62357 Mon Sep 17 00:00:00 2001 From: Xavier Homs Date: Tue, 30 Apr 2019 09:00:13 +0200 Subject: [PATCH] bump to version v0.3 --- .circleci/config.yml | 38 ++ README.md | 327 +++++++++++++++++- dist/core.d.ts | 11 +- dist/core.js | 10 +- dist/credentialprovider.d.ts | 63 +++- dist/credentialprovider.js | 41 ++- dist/credentials.d.ts | 2 +- dist/credentials.js | 4 +- dist/ephimeralstorage.ts | 0 dist/fscredentialprovider.d.ts | 12 +- dist/fscredentialprovider.js | 31 +- dist/hubhelper.d.ts | 45 ++- dist/hubhelper.js | 35 +- dist/index.d.ts | 6 +- dist/index.js | 2 + dist/loggingservice.d.ts | 15 +- dist/loggingservice.js | 27 +- dist/oa2credentials.d.ts | 180 ---------- dist/oa2credentials.js | 324 ----------------- lib/core.ts | 13 +- lib/credentialprovider.ts | 106 +++++- lib/credentials.ts | 6 +- lib/fscredentialprovider.ts | 50 ++- lib/hubhelper.ts | 64 +++- lib/index.ts | 6 +- lib/loggingservice.ts | 46 ++- package.json | 10 +- test/credential.js | 18 + {example => test}/credential.ts | 0 test/credential_basic.js | 14 + {example => test}/credential_basic.ts | 0 test/credential_fs.js | 15 + {example => test}/credential_fs.ts | 0 test/dnsdecode.js | 15 + {example => test}/dnsdecode.ts | 0 test/dss.js | 41 +++ {example => test}/dss.ts | 0 test/dss_attributes.js | 11 + {example => test}/dss_attributes.ts | 0 test/dss_count.js | 13 + {example => test}/dss_count.ts | 3 +- test/dss_domains.js | 14 + {example => test}/dss_domains.ts | 0 test/dss_query_computers.js | 20 ++ {example => test}/dss_query_computers.ts | 0 test/dss_query_containers.js | 20 ++ {example => test}/dss_query_containers.ts | 0 test/dss_query_filter_users.js | 31 ++ {example => test}/dss_query_filter_users.ts | 0 test/dss_query_groups.js | 20 ++ {example => test}/dss_query_groups.ts | 0 test/dss_query_ous.js | 20 ++ {example => test}/dss_query_ous.ts | 0 test/dss_query_sublist_users.js | 27 ++ {example => test}/dss_query_sublist_users.ts | 0 test/dss_query_users.js | 20 ++ {example => test}/dss_query_users.ts | 0 test/eventservice.js | 43 +++ {example => test}/eventservice.ts | 0 test/eventservice_ack.js | 10 + {example => test}/eventservice_ack.ts | 0 test/eventservice_async_pcap.js | 50 +++ {example => test}/eventservice_async_pcap.ts | 0 test/eventservice_async_poll.js | 52 +++ {example => test}/eventservice_async_poll.ts | 0 test/eventservice_clearfilter.js | 13 + {example => test}/eventservice_clearfilter.ts | 0 test/eventservice_correlation.js | 63 ++++ {example => test}/eventservice_correlation.ts | 0 test/eventservice_flush.js | 13 + {example => test}/eventservice_flush.ts | 0 test/eventservice_generator.js | 34 ++ {example => test}/eventservice_generator.ts | 0 test/eventservice_getfilter.js | 18 + {example => test}/eventservice_getfilter.ts | 0 test/eventservice_nack.js | 13 + {example => test}/eventservice_nack.ts | 0 test/eventservice_poll.js | 16 + {example => test}/eventservice_poll.ts | 0 test/eventservice_setfilter.js | 22 ++ {example => test}/eventservice_setfilter.ts | 0 test/loggingservice.js | 35 ++ {example => test}/loggingservice.ts | 0 test/loggingservice_async_dns_poll.js | 36 ++ .../loggingservice_async_dns_poll.ts | 0 test/loggingservice_async_pcap.js | 32 ++ .../loggingservice_async_pcap.ts | 0 test/loggingservice_async_poll.js | 42 +++ .../loggingservice_async_poll.ts | 0 test/loggingservice_basic.js | 23 ++ {example => test}/loggingservice_basic.ts | 0 test/loggingservice_cancel_async_poll.js | 45 +++ .../loggingservice_cancel_async_poll.ts | 0 test/loggingservice_complex_async_poll.js | 84 +++++ .../loggingservice_complex_async_poll.ts | 0 test/loggingservice_poll.js | 70 ++++ {example => test}/loggingservice_poll.ts | 6 +- test/pcap.js | 13 + {example => test}/pcap.ts | 0 {example => test}/tsconfig.json | 7 +- tsconfig.json | 1 + 101 files changed, 1876 insertions(+), 641 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 dist/ephimeralstorage.ts delete mode 100644 dist/oa2credentials.d.ts delete mode 100644 dist/oa2credentials.js create mode 100644 test/credential.js rename {example => test}/credential.ts (100%) create mode 100644 test/credential_basic.js rename {example => test}/credential_basic.ts (100%) create mode 100644 test/credential_fs.js rename {example => test}/credential_fs.ts (100%) create mode 100644 test/dnsdecode.js rename {example => test}/dnsdecode.ts (100%) create mode 100644 test/dss.js rename {example => test}/dss.ts (100%) create mode 100644 test/dss_attributes.js rename {example => test}/dss_attributes.ts (100%) create mode 100644 test/dss_count.js rename {example => test}/dss_count.ts (70%) create mode 100644 test/dss_domains.js rename {example => test}/dss_domains.ts (100%) create mode 100644 test/dss_query_computers.js rename {example => test}/dss_query_computers.ts (100%) create mode 100644 test/dss_query_containers.js rename {example => test}/dss_query_containers.ts (100%) create mode 100644 test/dss_query_filter_users.js rename {example => test}/dss_query_filter_users.ts (100%) create mode 100644 test/dss_query_groups.js rename {example => test}/dss_query_groups.ts (100%) create mode 100644 test/dss_query_ous.js rename {example => test}/dss_query_ous.ts (100%) create mode 100644 test/dss_query_sublist_users.js rename {example => test}/dss_query_sublist_users.ts (100%) create mode 100644 test/dss_query_users.js rename {example => test}/dss_query_users.ts (100%) create mode 100644 test/eventservice.js rename {example => test}/eventservice.ts (100%) create mode 100644 test/eventservice_ack.js rename {example => test}/eventservice_ack.ts (100%) create mode 100644 test/eventservice_async_pcap.js rename {example => test}/eventservice_async_pcap.ts (100%) create mode 100644 test/eventservice_async_poll.js rename {example => test}/eventservice_async_poll.ts (100%) create mode 100644 test/eventservice_clearfilter.js rename {example => test}/eventservice_clearfilter.ts (100%) create mode 100644 test/eventservice_correlation.js rename {example => test}/eventservice_correlation.ts (100%) create mode 100644 test/eventservice_flush.js rename {example => test}/eventservice_flush.ts (100%) create mode 100644 test/eventservice_generator.js rename {example => test}/eventservice_generator.ts (100%) create mode 100644 test/eventservice_getfilter.js rename {example => test}/eventservice_getfilter.ts (100%) create mode 100644 test/eventservice_nack.js rename {example => test}/eventservice_nack.ts (100%) create mode 100644 test/eventservice_poll.js rename {example => test}/eventservice_poll.ts (100%) create mode 100644 test/eventservice_setfilter.js rename {example => test}/eventservice_setfilter.ts (100%) create mode 100644 test/loggingservice.js rename {example => test}/loggingservice.ts (100%) create mode 100644 test/loggingservice_async_dns_poll.js rename {example => test}/loggingservice_async_dns_poll.ts (100%) create mode 100644 test/loggingservice_async_pcap.js rename {example => test}/loggingservice_async_pcap.ts (100%) create mode 100644 test/loggingservice_async_poll.js rename {example => test}/loggingservice_async_poll.ts (100%) create mode 100644 test/loggingservice_basic.js rename {example => test}/loggingservice_basic.ts (100%) create mode 100644 test/loggingservice_cancel_async_poll.js rename {example => test}/loggingservice_cancel_async_poll.ts (100%) create mode 100644 test/loggingservice_complex_async_poll.js rename {example => test}/loggingservice_complex_async_poll.ts (100%) create mode 100644 test/loggingservice_poll.js rename {example => test}/loggingservice_poll.ts (91%) create mode 100644 test/pcap.js rename {example => test}/pcap.ts (100%) rename {example => test}/tsconfig.json (66%) diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..d729e37 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,38 @@ +# Javascript Node CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-javascript/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + - image: circleci/node:8.0 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/mongo:3.4.4 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: yarn install + + - save_cache: + paths: + - node_modules + key: v1-dependencies-{{ checksum "package.json" }} + + # run tests! + - run: yarn test + diff --git a/README.md b/README.md index 6805ef1..febb513 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,325 @@ -# pancloud-nodejs -Palo Alto Networks Application Framework NodeJS SDK +# Palo Alto Networks Cloud NodeJS SDK +NodeJS idiomatic SDK for the Palo Alto Networks Application Framework. -## EXPERIMENTAL \ No newline at end of file +The Palo Alto Networks Cloud NodeJS SDK (or pancloud for short) was created to assist developers with programmatically interacting with the Palo Alto Networks Cortex Framework. + +The primary goal is to provide full, low-level API coverage for the following Application Framework services: + +* Logging Service +* Directory Sync Service +* Event Service + +The secondary goal is to provide coverage, in the form of helpers, for common tasks/operations. (Log/event pagination, OAuth 2.0 and token refreshing ...) + +# Quick Starting +1. Install `pancloud` in your project as a dependency +```$ npm install pancloud``` +2. Use the `autoCredentials()` function to instantiate a `Credentials` object based on your environmental variables + * _Option 1_: Set the variable `PAN_ACCESS_TOKEN` to retrieve a `Credentials` instance (valid to interface with a datalake as long as the access token is not expired / no auto-refresh available) + * _Option 2_: Set the variables `PAN_CLIENT_ID`, `PAN_CLIENT_SECRET` and `PAN_REFRESH_TOKEN` to instantiate a memory-based credentials provider and retrieve a `Credentials` object bound to the datalake for which the provided refresh token was issued (auto-refresh available) + * _Option 3_: Set the variable `PAN_DEVELOPER_TOKEN` to retieve a `Credentials` object that will leverage your API Explorer tenant to issue access tokens on your behalf +3. Instantiate a `LoggingService` object using the `Credentials` object you obtained in the step 2 +4. Perform a query using the `query()` method of your `LoggingService` object. + +## Quick Starting Examples +create a file named `index.js` in your project forder with the following content +``` +const pancloud = require('pancloud'); +pancloud.autoCredentials() + .then(credentials => pancloud.LoggingService.factory(credentials)) + .then(loggingService => loggingService.query({ + query: "SELECT * FROM panw.dpi WHERE subtype='dhcp' LIMIT 1", + startTime: 0, // 1970 + endTime: 2000000000, // 2033 + maxWaitTime: 30000 // wait up to 30 seconds for the query to complete + })) + .then(jobResult => console.log(JSON.stringify(jobResult, undefined, ' '))) +``` +* **Running the example with an OAUTH2 access token (use case: interactive lab testing)** +``` +$ export PAN_ACCESS_TOKEN=eyJhbGciOi......BwSldUIn0.eyJzd......iJ9.GFjG......iaW0N_PCA +$ node index +PANCLOUD: {"source":"AutoCredentials","message":"Environmental variable PAN_ENTRYPOINT not set. Assuming https://api.us.paloaltonetworks.com"} +PANCLOUD: {"source":"AutoCredentials","message":"Using startic credentials. No refresh available."} +PANCLOUD: {"source":"LoggingService","message":"Creating new LoggingService object for entryPoint https://api.us.paloaltonetworks.com"} +PANCLOUD: {"source":"LoggingService","message":"*queries* post request. Query: {\"query\":\"SELECT * FROM panw.dpi WHERE subtype='dhcp' LIMIT 1\",\"startTime\":0,\"endTime\":2000000000,\"maxWaitTime\":30000}"} +PANCLOUD: {"source":"LoggingService","message":"updated authorization header"} +{ + "queryId": "c1ce3558-0d33-42c5-bdfc-b71dbbc673eb", + "sequenceNo": 0, + "queryStatus": "JOB_FINISHED", + "clientParameters": {}, + "result": { + "esResult": { + "took": 290, + "hits": { + "total": 838, + "maxScore": 14.113734, + "hits": [ + { + "_index": "117270020_panw.dpi_2019040700-2019042700_000000", + "_type": "dpi", + "_id": "117270020_lcaas:1:1253209:657", + "_score": 14.113734, + "_source": { + "dhcp-rsp-dns-suffix": "domain.name", + "direction_reversed": "true", + "sessionid": 59070, + "dhcp-rsp-ciaddr": "00000000000000000000ffff0a640b0b", + "type": "DPI", + "content_ver": "8138-5378", + "txn_id": 1, + "receptor_txn_start": 1554755925, + "subtype": "dhcp", + "client_sw": "8.1.4", + "recsize": 959, + "dhcp-rsp-chaddr": "00:00:00:00:00:00", + "dhcp-rsp-router-option": [ + { + "dhcp-rsp-router-addr": "00000000000000000000ffffc0a80101" + } + ], + "dhcp-rsp-yiaddr": "00000000000000000000ffff00000000", + "dhcp-rsp-giaddr": "00000000000000000000ffff00000000", + "dhcp-rsp-siaddr": "00000000000000000000ffff00000000", + "receive_time": 1554755946, + "dhcp-rsp-msg-type": 5, + "dhcp-rsp-subnet-mask": "00000000000000000000ffffffffff00", + "time_generated": 1554755922, + "dhcp-rsp-domain-name-server-option": [ + { + "dhcp-rsp-dns-addr": "00000000000000000000ffff503a3dfa" + }, + { + "dhcp-rsp-dns-addr": "00000000000000000000ffff503a3dfe" + } + ], + "customer-id": "117270020", + "serial": "", + "dhcp-rsp-transaction-id": 3782930258, + "dhcp-rsp-opcode": 2 + } + } + ] + }, + "id": "c1ce3558-0d33-42c5-bdfc-b71dbbc673eb", + "from": 0, + "size": 1, + "completed": true, + "state": "COMPLETED", + "timed_out": false + }, + "esQuery": { + "table": [ + "panw.dpi" + ], + "query": { + "aggregations": {}, + "query": { + "term": { + "{{field_0}}": "{{value_0}}" + } + }, + "size": 1 + }, + "selections": [], + "params": { + "field_0": "subtype", + "value_0": "dhcp" + } + } + } +} +``` + +* **Running the example with an OAUTH2 client-id, client-secret and refresh-token (use case: script lab testing)** +``` +$ export PAN_CLIENT_ID= +$ export PAN_CLIENT_SECRET= +$ export PAN_REFRESH_TOKEN= +$ node index +PANCLOUD: {"source":"AutoCredentials","message":"Environmental variable PAN_ENTRYPOINT not set. Assuming https://api.us.paloaltonetworks.com"} +PANCLOUD: {"source":"AutoCredentials","message":"Using memory based credentials provider"} +PANCLOUD: {"source":"defaultCredentialsFactory","message":"Got 'client_id'"} +PANCLOUD: {"source":"defaultCredentialsFactory","message":"Got 'client_secret'"} +PANCLOUD: {"source":"DefaultCredentialsProvider","message":"Stateless credential provider. Returning an empty item list to load() request"} +PANCLOUD: {"source":"CortexCredentialProvider","message":"Successfully restored 0 items"} +PANCLOUD: {"source":"CortexCredentialProvider","message":"Authorization token successfully retrieved","name":"IDENTITY"} +PANCLOUD: {"source":"CortexCredentialProvider","message":"Retrieved Access Token for datalake ID DEFAULT from Identity Provider"} +PANCLOUD: {"source":"CortexCredentialProvider","message":"Instantiated new credential object from the factory for datalake id DEFAULT"} +PANCLOUD: {"source":"DefaultCredentialsProvider","message":"Stateless credential provider. Discarding new item issued"} +PANCLOUD: {"source":"CortexCredentialProvider","message":"Issued new Credentials Object for datalake ID DEFAULT"} +PANCLOUD: {"source":"LoggingService","message":"Creating new LoggingService object for entryPoint https://api.us.paloaltonetworks.com"} +PANCLOUD: {"source":"LoggingService","message":"*queries* post request. Query: {\"query\":\"SELECT * FROM panw.dpi WHERE subtype='dhcp' LIMIT 1\",\"startTime\":0,\"endTime\":2000000000,\"maxWaitTime\":30000}"} +PANCLOUD: {"source":"LoggingService","message":"updated authorization header"} +{ + "queryId": "de0dc306-f2a4-4247-8ace-a47cf92ff558", + "sequenceNo": 0, + "queryStatus": "JOB_FINISHED", + "clientParameters": {}, + "result": { + "esResult": { + "took": 807, + "hits": { + "total": 846, + "maxScore": 14.103332, + "hits": [ + { + "_index": "117270020_panw.dpi_2019040700-2019042700_000000", + "_type": "dpi", + "_id": "117270020_lcaas:1:1253209:657", + "_score": 14.103332, + "_source": { + "dhcp-rsp-dns-suffix": "domain.name", + "direction_reversed": "true", + "sessionid": 59070, + "dhcp-rsp-ciaddr": "00000000000000000000ffff0a640b0b", + "type": "DPI", + "content_ver": "8138-5378", + "txn_id": 1, + "receptor_txn_start": 1554755925, + "subtype": "dhcp", + "client_sw": "8.1.4", + "recsize": 959, + "dhcp-rsp-chaddr": "00:00:00:00:00:00", + "dhcp-rsp-router-option": [ + { + "dhcp-rsp-router-addr": "00000000000000000000ffffc0a80101" + } + ], + "dhcp-rsp-yiaddr": "00000000000000000000ffff00000000", + "dhcp-rsp-giaddr": "00000000000000000000ffff00000000", + "dhcp-rsp-siaddr": "00000000000000000000ffff00000000", + "receive_time": 1554755946, + "dhcp-rsp-msg-type": 5, + "dhcp-rsp-subnet-mask": "00000000000000000000ffffffffff00", + "time_generated": 1554755922, + "dhcp-rsp-domain-name-server-option": [ + { + "dhcp-rsp-dns-addr": "00000000000000000000ffff503a3dfa" + }, + { + "dhcp-rsp-dns-addr": "00000000000000000000ffff503a3dfe" + } + ], + "customer-id": "117270020", + "serial": "", + "dhcp-rsp-transaction-id": 3782930258, + "dhcp-rsp-opcode": 2 + } + } + ] + }, + "id": "de0dc306-f2a4-4247-8ace-a47cf92ff558", + "from": 0, + "size": 1, + "completed": true, + "state": "COMPLETED", + "timed_out": false + }, + "esQuery": { + "table": [ + "panw.dpi" + ], + "query": { + "aggregations": {}, + "query": { + "term": { + "{{field_0}}": "{{value_0}}" + } + }, + "size": 1 + }, + "selections": [], + "params": { + "field_0": "subtype", + "value_0": "dhcp" + } + } + } +} +``` + +* **Running the example with an API Explorer developer-token (use case: script lab testing)** +``` +$ export PAN_DEVELOPER_TOKEN= +$ node index +PANCLOUD: {"source":"AutoCredentials","message":"Environmental variable PAN_ENTRYPOINT not set. Assuming https://api.us.paloaltonetworks.com"} +PANCLOUD: {"source":"AutoCredentials","message":"Neither \"PAN_ACCESS_TOKEN\" (for static credentials) nor \"PAN_CLIENT_ID\", \"PAN_CLIENT_SECRET\" and \"PAN_REFRESH_TOKEN\" for a memory-based credentials provider where provider. Will try with developer token credetials"} +PANCLOUD: {"source":"LoggingService","message":"Creating new LoggingService object for entryPoint https://api.us.paloaltonetworks.com"} +PANCLOUD: {"source":"LoggingService","message":"*queries* post request. Query: {\"query\":\"SELECT * FROM panw.dpi WHERE subtype='dhcp' LIMIT 1\",\"startTime\":0,\"endTime\":2000000000,\"maxWaitTime\":30000}"} +PANCLOUD: {"source":"LoggingService","message":"updated authorization header"} +{ + "queryId": "1e7ded92-d49a-4afa-97a3-5314f708f950", + "sequenceNo": 0, + "queryStatus": "JOB_FINISHED", + "clientParameters": {}, + "result": { + "esResult": { + "took": 102, + "hits": { + "total": 1568, + "maxScore": 12.697968, + "hits": [ + { + "_index": "117270018_panw.dpi_2019040800-2019042800_000000", + "_type": "dpi", + "_id": "117270018_lcaas:0:5351784:864", + "_score": 12.697968, + "_source": { + "dhcp-req-msg-type": 1, + "dhcp-req-opcode": 1, + "receive_time": 1554699492, + "sessionid": 239993, + "time_generated": 1554699473, + "dhcp-req-yiaddr": "00000000000000000000ffff00000000", + "type": "DPI", + "dhcp-req-host-name": "HIuBtmcklSawAHLVKHBkJbXQBDfKCvo", + "content_ver": "8138-5378", + "dhcp-req-giaddr": "00000000000000000000ffff00000000", + "dhcp-req-chaddr": "db:7b:65:4b:09:c4", + "txn_id": 1, + "dhcp-req-transaction-id": 407499899, + "customer-id": "117270018", + "serial": "", + "receptor_txn_start": 1554699472, + "subtype": "dhcp", + "dhcp-req-lease-time": 4294967295, + "dhcp-req-siaddr": "00000000000000000000ffff00000000", + "client_sw": "8.1.4", + "recsize": 727, + "dhcp-req-vendor-class": "Linux 2.4.22 i686", + "dhcp-req-ciaddr": "00000000000000000000ffff00000000" + } + } + ] + }, + "id": "1e7ded92-d49a-4afa-97a3-5314f708f950", + "from": 0, + "size": 1, + "completed": true, + "state": "COMPLETED", + "timed_out": false + }, + "esQuery": { + "table": [ + "panw.dpi" + ], + "query": { + "aggregations": {}, + "query": { + "term": { + "{{field_0}}": "{{value_0}}" + } + }, + "size": 1 + }, + "selections": [], + "params": { + "field_0": "subtype", + "value_0": "dhcp" + } + } + } +} +``` diff --git a/dist/core.d.ts b/dist/core.d.ts index f148500..3819b1b 100644 --- a/dist/core.d.ts +++ b/dist/core.d.ts @@ -1,7 +1,3 @@ -/** - * Implements the abstract coreClass that implements common methods for higher-end classes like Event Service - * and Logging Service - */ import { HttpMethod } from './fetch'; import { Credentials } from './credentials'; import { LogLevel } from './common'; @@ -44,6 +40,10 @@ export declare class CoreClass { * Credential object to be used by this instance */ protected cred: Credentials; + /** + * Last known valid until value of the access token + */ + protected validUntil: Number; /** * Master Application Framework API entry point */ @@ -72,8 +72,9 @@ export declare class CoreClass { private setFetchHeaders; /** * Triggers the credential object access-token refresh procedure and updates the HTTP headers + * DEPRECATED 190429 (rename it to `refresh` if needed) */ - protected refresh(): Promise; + protected _refresh(): Promise; private checkAutoRefresh; private fetchXWrap; /** diff --git a/dist/core.js b/dist/core.js index 22984c3..75a8473 100644 --- a/dist/core.js +++ b/dist/core.js @@ -16,6 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); * Implements the abstract coreClass that implements common methods for higher-end classes like Event Service * and Logging Service */ +const url_1 = require("url"); const fetch_1 = require("./fetch"); const error_1 = require("./error"); const common_1 = require("./common"); @@ -32,7 +33,7 @@ class CoreClass { constructor(cred, basePath, ops) { this.className = "coreClass"; this.cred = cred; - this.baseUrl = new URL(basePath, cred.getEntryPoint()).toString(); + this.baseUrl = new url_1.URL(basePath, cred.getEntryPoint()).toString(); if (ops && ops.level != undefined && ops.level != common_1.LogLevel.INFO) { common_1.commonLogger.level = ops.level; } @@ -55,13 +56,16 @@ class CoreClass { } /** * Triggers the credential object access-token refresh procedure and updates the HTTP headers + * DEPRECATED 190429 (rename it to `refresh` if needed) */ - async refresh() { + async _refresh() { await this.cred.retrieveAccessToken(); await this.setFetchHeaders(); } async checkAutoRefresh() { - if (await this.cred.autoRefresh()) { + let currentValidUntil = await this.cred.autoRefresh(); + if (this.validUntil != currentValidUntil) { + this.validUntil = currentValidUntil; await this.setFetchHeaders(); } } diff --git a/dist/credentialprovider.d.ts b/dist/credentialprovider.d.ts index c4d5b11..6f08a49 100644 --- a/dist/credentialprovider.d.ts +++ b/dist/credentialprovider.d.ts @@ -64,9 +64,10 @@ export interface CredentialProviderOptions { } /** * Abstract class to provide credentials for multiple datalakes. If you want to extend this class - * then you must implement its storage-related methods. + * then you must implement its storage-related methods. *T* describes the type of the optional + * metadata that can be attached to any datalake's credentials */ -export declare abstract class CortexCredentialProvider { +export declare abstract class CortexCredentialProvider { private clientId; private clientSecret; private idpTokenUrl; @@ -78,16 +79,19 @@ export declare abstract class CortexCredentialProvider { private retrierAttempts?; private retrierDelay?; private accTokenGuardTime; + protected tenantKey?: K; static className: string; /** * Class constructor * @param ops constructor options. Mandatory fields being OAUTH2 `clientId` and `clientSecret` + * @param tenantKey metadata feature, if used, mult solve at least the multi tenancy use case. That means that the metadata + * object of type `T` must include a property `K` that could be used for tenant membership identification */ protected constructor(ops: CredentialProviderOptions & { clientId: string; clientSecret: string; idpAuthUrl?: string; - }); + }, tenantKey?: K); /** * @returns this CredentialProvider class OAUTH2 `[clientId, clientSecret]` */ @@ -117,7 +121,7 @@ export declare abstract class CortexCredentialProvider { issueWithRefreshToken(datalakeId: string, entryPoint: EntryPoint, refreshToken: string, prefetch?: { accessToken: string; validUntil: number; - }): Promise; + }, metadata?: T): Promise; /** * Registers a datalake using its `refresh_token` value and returns a Credentials object bound * to it @@ -125,7 +129,10 @@ export declare abstract class CortexCredentialProvider { * @param entryPoint Cortex Datalake regional entry point * @param refreshToken OAUTH2 `refresh_token` value */ - registerManualDatalake(datalakeId: string, entryPoint: EntryPoint, refreshToken: string): Promise; + registerManualDatalake(datalakeId: string, entryPoint: EntryPoint, refreshToken: string, prefetch?: { + accessToken: string; + validUntil: number; + }, metadata?: T): Promise; /** * Retrieves the Credentials object for a given datalake * @param datalakeId ID of the datalake the Credentials object should be bound to @@ -144,16 +151,60 @@ export declare abstract class CortexCredentialProvider { */ retrieveCortexAccessToken(datalakeId: string): Promise; private parseIdpResponse; + /** + * Returns a basic `Credentials` subclass that just calls this provider's `retrieveCortexAccessToken` + * method when a new access_token is needed. + * @param datalakeId The datalake we want a credentials object for + * @param entryPoint The Cortex Datalake regional API entry point + * @param accTokenGuardTime Amount of seconds before expiration credentials object should use cached value + * @param prefetch Optinal prefetched access_token + */ protected defaultCredentialsObjectFactory(datalakeId: string, entryPoint: EntryPoint, accTokenGuardTime: number, prefetch?: { accessToken: string; validUntil: number; }): Promise; - protected abstract createCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem): Promise; + /** + * Implementation dependant. It is called by the abstract class each time a new set of credenentials have been + * created (either by manual refresh or by OAUTH2 code grant flow handled by a `CortexHubHelper` companion). + * The implementator is expected to store them somewhere + * @param datalakeId datalake identificator + * @param credentialsItem credential attributes + * @param metadata optional metadata (used by multitenant applications to attach tenant ID) + */ + protected abstract createCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem, metadata?: T): Promise; + /** + * Implementation dependant. It is called by the abstract class when a refresh token operation returns not + * only a new access_token but a new refresh_token as well. The implementator is expected to update the record + * @param datalakeId datalake identificator + * @param credentialsItem credential attributes + */ protected abstract updateCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem): Promise; + /** + * Implementation dependant. It is called by the abstract class as a response to a successful revocation of + * the refresh_token. The implementator is expected to delete the record from the store + * @param datalakeId datalake identificator + */ protected abstract deleteCredentialsItem(datalakeId: string): Promise; + /** + * Convenience method to allow a companion `CortexHubHelper` object retrieve metadata previously stored. + * Most implementations will return only these records matching the provided metadata (use case: to get + * all activated datalake ID's for a specific tenant) + */ + abstract selectDatalakeByTenant(tenantId: T[K]): Promise; + /** + * Implementation dependant. Expected to load all records from the store + */ protected abstract loadCredentialsDb(): Promise<{ [dlid: string]: CredentialsItem; }>; + /** + * Implementation dependant. Its purpose is to initialize and return a suitable credentials object from this + * credential provider for the specific datalake and attached attributes + * @param datalakeId The datalake we want a credentials object for + * @param entryPoint The Cortex Datalake regional API entry point + * @param accTokenGuardTime Amount of seconds before expiration credentials object should use cached value + * @param prefetch Optinal prefetched access_token + */ protected abstract credentialsObjectFactory(datalakeId: string, entryPoint: EntryPoint, accTokenGuardTime: number, prefetch?: { accessToken: string; validUntil: number; diff --git a/dist/credentialprovider.js b/dist/credentialprovider.js index 5872123..c02288b 100644 --- a/dist/credentialprovider.js +++ b/dist/credentialprovider.js @@ -37,14 +37,17 @@ function isIdpErrorResponse(obj) { } /** * Abstract class to provide credentials for multiple datalakes. If you want to extend this class - * then you must implement its storage-related methods. + * then you must implement its storage-related methods. *T* describes the type of the optional + * metadata that can be attached to any datalake's credentials */ class CortexCredentialProvider { /** * Class constructor * @param ops constructor options. Mandatory fields being OAUTH2 `clientId` and `clientSecret` + * @param tenantKey metadata feature, if used, mult solve at least the multi tenancy use case. That means that the metadata + * object of type `T` must include a property `K` that could be used for tenant membership identification */ - constructor(ops) { + constructor(ops, tenantKey) { this.clientId = ops.clientId; this.clientSecret = ops.clientSecret; this.idpTokenUrl = (ops.idpTokenUrl) ? ops.idpTokenUrl : IDP_TOKEN_URL; @@ -52,6 +55,7 @@ class CortexCredentialProvider { this.accTokenGuardTime = (ops.accTokenGuardTime) ? ops.accTokenGuardTime : ACCESS_GUARD; this.retrierAttempts = ops.retrierAttempts; this.retrierDelay = ops.retrierDelay; + this.tenantKey = tenantKey; if (this.accTokenGuardTime > 3300) { throw new error_1.PanCloudError(CortexCredentialProvider, 'CONFIG', `Property 'accTokenGuardTime' must be, at max 3300 seconds (${this.accTokenGuardTime})`); } @@ -146,7 +150,10 @@ class CortexCredentialProvider { * @param prefetch You can provide the `access_token` and `valid_until` values if you also have * access to them to avoid the initial token refresh operation */ - async issueWithRefreshToken(datalakeId, entryPoint, refreshToken, prefetch) { + async issueWithRefreshToken(datalakeId, entryPoint, refreshToken, prefetch, metadata) { + if (metadata !== undefined && this.tenantKey === undefined) { + throw new error_1.PanCloudError(CortexCredentialProvider, 'CONFIG', 'Metadata provided without proper initialization of the tenantKey property. Review your subclass constructor.'); + } if (!this.credentials) { await this.restoreState(); } @@ -177,7 +184,7 @@ class CortexCredentialProvider { validUntil: validUntil }); this.credentialsObject[datalakeId] = credentialsObject; - await this.createCredentialsItem(datalakeId, credItem); + await this.createCredentialsItem(datalakeId, credItem, metadata); common_1.commonLogger.info(CortexCredentialProvider, `Issued new Credentials Object for datalake ID ${datalakeId}`); return credentialsObject; } @@ -188,15 +195,15 @@ class CortexCredentialProvider { * @param entryPoint Cortex Datalake regional entry point * @param refreshToken OAUTH2 `refresh_token` value */ - async registerManualDatalake(datalakeId, entryPoint, refreshToken) { - return this.issueWithRefreshToken(datalakeId, entryPoint, refreshToken); + async registerManualDatalake(datalakeId, entryPoint, refreshToken, prefetch, metadata) { + return this.issueWithRefreshToken(datalakeId, entryPoint, refreshToken, prefetch, metadata); } /** * Retrieves the Credentials object for a given datalake * @param datalakeId ID of the datalake the Credentials object should be bound to */ async getCredentialsObject(datalakeId) { - if (!this.credentials) { + if (this.credentials === undefined || this.credentials[datalakeId] === undefined) { await this.restoreState(); } if (!this.credentialsObject[datalakeId]) { @@ -210,9 +217,13 @@ class CortexCredentialProvider { * @param datalakeId ID of the datalake to be removed */ async deleteDatalake(datalakeId) { - if (!this.credentials) { + if (this.credentials === undefined || this.credentials[datalakeId] === undefined) { await this.restoreState(); } + if (this.credentials[datalakeId] === undefined) { + common_1.commonLogger.info(CortexCredentialProvider, `Request to delete a non existant datalake ${datalakeId}. Ignoring it`); + return; + } let param = { method: 'POST', headers: { @@ -244,7 +255,7 @@ class CortexCredentialProvider { * @param datalakeId ID of the datalake to obtain `access_token` from */ async retrieveCortexAccessToken(datalakeId) { - if (!this.credentials) { + if (this.credentials === undefined || this.credentials[datalakeId] === undefined) { await this.restoreState(); } if (!(datalakeId in this.credentials)) { @@ -283,6 +294,14 @@ class CortexCredentialProvider { } throw new error_1.PanCloudError(CortexCredentialProvider, 'PARSER', `Invalid response received by IDP provider`); } + /** + * Returns a basic `Credentials` subclass that just calls this provider's `retrieveCortexAccessToken` + * method when a new access_token is needed. + * @param datalakeId The datalake we want a credentials object for + * @param entryPoint The Cortex Datalake regional API entry point + * @param accTokenGuardTime Amount of seconds before expiration credentials object should use cached value + * @param prefetch Optinal prefetched access_token + */ async defaultCredentialsObjectFactory(datalakeId, entryPoint, accTokenGuardTime, prefetch) { let credObject = new DefaultCredentials(datalakeId, entryPoint, accTokenGuardTime, this, prefetch); common_1.commonLogger.info(CortexCredentialProvider, `Instantiated new credential object from the factory for datalake id ${datalakeId}`); @@ -306,6 +325,10 @@ class DefaultCredentialsProvider extends CortexCredentialProvider { async deleteCredentialsItem(datalakeId) { common_1.commonLogger.info(this, 'Stateless credential provider. Discarding deleted item'); } + selectDatalakeByTenant(tenantId) { + common_1.commonLogger.info(this, 'Stateless credential provider. Do not support credentials metadata'); + return Promise.resolve([]); + } async loadCredentialsDb() { common_1.commonLogger.info(this, 'Stateless credential provider. Returning an empty item list to load() request'); return {}; diff --git a/dist/credentials.d.ts b/dist/credentials.d.ts index c2ca033..d48772e 100644 --- a/dist/credentials.d.ts +++ b/dist/credentials.d.ts @@ -24,7 +24,7 @@ export declare abstract class Credentials implements PancloudClass { * Checks the access token expiration time and automaticaly refreshes it if going to expire * inside the next 5 minutes */ - autoRefresh(): Promise; + autoRefresh(): Promise; /** * Triggers an access token refresh request */ diff --git a/dist/credentials.js b/dist/credentials.js index 3203384..d74e656 100644 --- a/dist/credentials.js +++ b/dist/credentials.js @@ -64,13 +64,13 @@ class Credentials { try { common_1.commonLogger.info(this, 'Cached access token about to expire. Requesting a new one.'); await this.retrieveAccessToken(); - return true; + return this.validUntil; } catch (_a) { common_1.commonLogger.info(this, 'Failed to get a new access token'); } } - return false; + return this.validUntil; } } exports.Credentials = Credentials; diff --git a/dist/ephimeralstorage.ts b/dist/ephimeralstorage.ts deleted file mode 100644 index e69de29..0000000 diff --git a/dist/fscredentialprovider.d.ts b/dist/fscredentialprovider.d.ts index 1e5712d..af3f48c 100644 --- a/dist/fscredentialprovider.d.ts +++ b/dist/fscredentialprovider.d.ts @@ -2,10 +2,11 @@ import { EntryPoint } from './common'; import { Credentials } from './credentials'; import { CortexCredentialProvider, CredentialProviderOptions, CredentialsItem } from './credentialprovider'; -declare class FsCredProvider extends CortexCredentialProvider { +declare class FsCredProvider extends CortexCredentialProvider { private key; private iv; private configFileName; + private metadata; className: string; constructor(ops: CredentialProviderOptions & { clientId: string; @@ -14,11 +15,12 @@ declare class FsCredProvider extends CortexCredentialProvider { key: Buffer; iv: Buffer; configFileName: string; - }); + }, tenantKey?: K); private fullSync; - protected createCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem): Promise; + protected createCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem, metadata?: T | undefined): Promise; protected updateCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem): Promise; protected deleteCredentialsItem(datalakeId: string): Promise; + selectDatalakeByTenant(tenantId: T[K]): Promise; protected loadCredentialsDb(): Promise<{ [dlid: string]: CredentialsItem; }>; @@ -38,10 +40,10 @@ declare class FsCredProvider extends CortexCredentialProvider { * @param ops.envClientSecret environmental variable that keeps the OAUTH2 `client_secret` value in * case it is not provided explicitly. Defaults to `{ops.envPrefix}_CLIENT_SECRET` */ -export declare function fsCredentialsFactory(ops: CredentialProviderOptions & { +export declare function fsCredentialsFactory(ops: CredentialProviderOptions & { envPrefix?: string; clientId?: string; clientSecret?: string; secret: string; -}): Promise; +}, tenantKey?: K): Promise>; export {}; diff --git a/dist/fscredentialprovider.js b/dist/fscredentialprovider.js index 1c08dbb..62ba316 100644 --- a/dist/fscredentialprovider.js +++ b/dist/fscredentialprovider.js @@ -20,20 +20,23 @@ const crypto_1 = require("crypto"); const fs = require("fs"); function isConfigFile(obj) { return typeof obj == 'object' && + obj.metadata && obj.credentialItems && typeof obj.credentialItems == 'object' && Object.entries(obj.credentialItems).every(v => typeof v[0] == 'string' && credentialprovider_1.isCredentialItem(v[1])); } class FsCredProvider extends credentialprovider_1.CortexCredentialProvider { - constructor(ops) { - super(ops); + constructor(ops, tenantKey) { + super(ops, tenantKey); this.className = 'FsCredProvider'; this.key = ops.key; this.iv = ops.iv; this.configFileName = ops.configFileName; + this.metadata = {}; } async fullSync() { let configFile = { - credentialItems: this.credentials + credentialItems: this.credentials, + metadata: this.metadata }; Object.entries(this.credentials).forEach(v => { let aes = crypto_1.createCipheriv('aes128', this.key, this.iv); @@ -45,7 +48,10 @@ class FsCredProvider extends credentialprovider_1.CortexCredentialProvider { }); await promifyFs(this, fs.writeFile, this.configFileName, JSON.stringify(configFile, undefined, ' ')); } - createCredentialsItem(datalakeId, credentialsItem) { + createCredentialsItem(datalakeId, credentialsItem, metadata) { + if (metadata) { + this.metadata[datalakeId] = metadata; + } common_1.commonLogger.info(this, `Lazy implementation of CREATE credentials request for datalake ${datalakeId} with a full synch operation`); return this.fullSync(); } @@ -57,6 +63,15 @@ class FsCredProvider extends credentialprovider_1.CortexCredentialProvider { common_1.commonLogger.info(this, `Lazy implementation of DELETE credentials request for datalake ${datalakeId} with a full synch operation`); return this.fullSync(); } + selectDatalakeByTenant(tenantId) { + let matchingDatalakes = Object.keys(this.metadata).filter(datalakeId => { + if (this.tenantKey === undefined) { + return false; + } + return this.metadata[datalakeId][this.tenantKey] == tenantId; + }); + return Promise.resolve(matchingDatalakes); + } async loadCredentialsDb() { let jsonConfig; try { @@ -69,6 +84,7 @@ class FsCredProvider extends credentialprovider_1.CortexCredentialProvider { if (!isConfigFile(jsonConfig)) { throw new error_1.PanCloudError(this, 'PARSER', `Invalid configuration file format in ${this.configFileName}`); } + this.metadata = jsonConfig.metadata; Object.entries(jsonConfig.credentialItems).forEach(v => { let aes = crypto_1.createDecipheriv('aes128', this.key, this.iv); let payload = Buffer.concat([aes.update(Buffer.from(v[1].refreshToken, 'base64')), aes.final()]).toString('utf8'); @@ -97,7 +113,7 @@ const CONFIG_FILE = 'PANCLOUD_CONFIG.json'; * @param ops.envClientSecret environmental variable that keeps the OAUTH2 `client_secret` value in * case it is not provided explicitly. Defaults to `{ops.envPrefix}_CLIENT_SECRET` */ -async function fsCredentialsFactory(ops) { +async function fsCredentialsFactory(ops, tenantKey) { let { key, iv } = passIvGenerator(ops.secret); let ePrefix = (ops && ops.envPrefix) ? ops.envPrefix : ENV_PREFIX; let envClientId = `${ePrefix}_CLIENT_ID`; @@ -119,7 +135,8 @@ async function fsCredentialsFactory(ops) { catch (e) { common_1.commonLogger.info({ className: 'fsCredProvider' }, `${configFileName} does not exist. Creating it`); let blankConfig = { - credentialItems: {} + credentialItems: {}, + metadata: {} }; await promifyFs(this, fs.writeFile, configFileName, JSON.stringify(blankConfig)); } @@ -129,7 +146,7 @@ async function fsCredentialsFactory(ops) { catch (e) { throw new error_1.PanCloudError({ className: 'fsCredProvider' }, 'CONFIG', `Invalid permissions in configuration file ${configFileName}`); } - return new FsCredProvider(Object.assign({ clientId: cId, clientSecret: cSec, key: key, iv: iv, configFileName: configFileName }, ops)); + return new FsCredProvider(Object.assign({ clientId: cId, clientSecret: cSec, key: key, iv: iv, configFileName: configFileName }, ops), tenantKey); } exports.fsCredentialsFactory = fsCredentialsFactory; function passIvGenerator(secret) { diff --git a/dist/hubhelper.d.ts b/dist/hubhelper.d.ts index 67a85be..641e9e4 100644 --- a/dist/hubhelper.d.ts +++ b/dist/hubhelper.d.ts @@ -1,6 +1,7 @@ /// import { EntryPoint, OAUTH2SCOPE } from './common'; import { CortexCredentialProvider } from './credentialprovider'; +import { Credentials } from './credentials'; import { URL } from 'url'; /** * Interface that describes the object pushed by the `authCallbackHandler` method into the @@ -11,6 +12,10 @@ export interface HubIdpCallback { * Would describe the error during the callback processing (if any) */ error?: string; + /** + * Optional message in the response + */ + message?: string; /** * Datalake ID if successfully processed and stored */ @@ -77,7 +82,7 @@ export declare function isCortexClientParams { /** * Requester Tenant ID */ @@ -86,6 +91,7 @@ export interface HubIdpStateData { * Requested datalakeID */ datalakeId: string; + metadata: M; } /** * Optional configuration attributes for the `CortexHubHelper` class @@ -108,10 +114,13 @@ export interface CortexHelperOptions { * @param U interface used by the `req.user` object provided by a *PassportJS-like* enabled * application willing to use this class `authCallbackHandler` method. * @param K the string-like property in `U` containing the requester TenantID + * @param M interface describing the metadata that will be attached to datalakes in CortexCredentialProvider + * for multi-tenancy applications. CortexHubHelper will add/replace a property named `tenantId` in M so take this into + * consideration when defining the interface `M` */ export declare abstract class CortexHubHelper { +}, U, K extends keyof U, M> { private clientId; private clientSecret; private idpAuthUrl; @@ -128,7 +137,9 @@ export declare abstract class CortexHubHelper, tenantKey?: K, ops?: CortexHelperOptions); /** * Static class method to exchange a 60 seconds OAUTH2 code for valid credentials * @param code OAUTH2 app 60 seconds one time `code` @@ -142,7 +153,7 @@ export declare abstract class CortexHubHelper; + idpAuthRequest(tenantId: string, datalakeId: string, scope: OAUTH2SCOPE[], metadata: M): Promise; /** * ExpressJS handler (middleware) that deals with IDP Authentication Callback. The method * relies on some properties and methods of `this` so be remember to `bind()` the method @@ -163,10 +174,9 @@ export declare abstract class CortexHubHelper; - }[]>; + listDatalake(tenantId: string): Promise<({ + id: string; + } & CortexClientParams)[]>; /** * Gets metadata of a given Datalake ID as a `CortexClientParams` object * @param tenantId requesting Tenant ID @@ -186,15 +196,22 @@ export declare abstract class CortexHubHelper; - protected abstract _listDatalake(tenantId: string): Promise<{ - datalakeId: string; - clientParams: CortexClientParams; - }[]>; + /** + * Abstraction that allows the `CortexHubHelper` subclass implementation reach out its bound `CortexCredentialProvider` + * The typical use case if for the `CortexHubHelper` to ask the `CortexCredentialProvider` the list of datalake ID's + * it holds (activated) for a given tenant ID + * @param tenantId + */ + datalakeActiveList(tenantId: string): Promise; + getCredentialsObject(tenantId: string, datalakeId: string): Promise; + protected abstract _listDatalake(tenantId: string): Promise<({ + id: string; + } & CortexClientParams)[]>; protected abstract _getDatalake(tenantId: string, datalakeId: string): Promise>; protected abstract _upsertDatalake(tenantId: string, datalakeId: string, clientParams: CortexClientParams): Promise; protected abstract _deleteDatalake(tenantId: string, datalakeId: string): Promise; - protected abstract requestAuthState(stateData: HubIdpStateData): Promise; - protected abstract restoreAuthState(stateId: string): Promise; + protected abstract requestAuthState(stateData: HubIdpStateData): Promise; + protected abstract restoreAuthState(stateId: string): Promise>; protected abstract deleteAuthState(stateId: string): Promise; } export {}; diff --git a/dist/hubhelper.js b/dist/hubhelper.js index 9601774..9285aae 100644 --- a/dist/hubhelper.js +++ b/dist/hubhelper.js @@ -38,6 +38,9 @@ exports.isCortexClientParams = isCortexClientParams; * @param U interface used by the `req.user` object provided by a *PassportJS-like* enabled * application willing to use this class `authCallbackHandler` method. * @param K the string-like property in `U` containing the requester TenantID + * @param M interface describing the metadata that will be attached to datalakes in CortexCredentialProvider + * for multi-tenancy applications. CortexHubHelper will add/replace a property named `tenantId` in M so take this into + * consideration when defining the interface `M` */ class CortexHubHelper { /** @@ -85,12 +88,12 @@ class CortexHubHelper { * @param scope OAUTH2 Data access Scope(s) * @returns a URI ready to be consumed (typically to be used for a client 302 redirect) */ - async idpAuthRequest(tenantId, datalakeId, scope) { + async idpAuthRequest(tenantId, datalakeId, scope, metadata) { let clientParams = await this.getDatalake(tenantId, datalakeId); if (!this.idpCallbackUrl) { throw new error_1.PanCloudError(credentialprovider_1.CortexCredentialProvider, 'CONFIG', `idpCallbackUrl was not provided in the ops passed to the constructor. Can't request auth without it.`); } - let stateId = await this.requestAuthState({ tenantId: tenantId, datalakeId: datalakeId }); + let stateId = await this.requestAuthState({ tenantId: tenantId, datalakeId: datalakeId, metadata: metadata }); let qsParams = { response_type: 'code', client_id: this.clientId, @@ -126,8 +129,9 @@ class CortexHubHelper { } let tenantId; let datalakeId; + let metadata; try { - ({ tenantId, datalakeId } = await this.restoreAuthState(state)); + ({ tenantId, datalakeId, metadata } = await this.restoreAuthState(state)); } catch (e) { common_1.commonLogger.alert(CortexHubHelper, `Unable to restore state ${state} in callback helper`); @@ -161,7 +165,7 @@ class CortexHubHelper { return; } let reqTenantId = req.user[tKey]; - if (!(typeof reqTenantId == 'string' && reqTenantId == tenantId)) { + if (!(typeof reqTenantId == 'string' && reqTenantId != tenantId)) { common_1.commonLogger.alert(CortexHubHelper, `Tenant validation failed: state tenantId ${tenantId} not equal to request tenantId ${JSON.stringify(reqTenantId)}`); callbackStatus = { error: 'tenantId in request does not match the one in the stored state' }; req.callbackIdp = callbackStatus; @@ -201,8 +205,8 @@ class CortexHubHelper { return; } try { - await this.credProvider.issueWithRefreshToken(`${tenantId}@${datalakeId}`, clientParams.location.entryPoint, idpResponse.refresh_token, { accessToken: idpResponse.access_token, validUntil: idpResponse.validUntil }); - callbackStatus = { datalakeId: datalakeId }; + await this.credProvider.issueWithRefreshToken(datalakeId, clientParams.location.entryPoint, idpResponse.refresh_token, { accessToken: idpResponse.access_token, validUntil: idpResponse.validUntil }, Object.assign({ tenantId: tenantId }, metadata)); + callbackStatus = { message: 'OK', datalakeId: datalakeId }; req.callbackIdp = callbackStatus; next(); } @@ -296,6 +300,25 @@ class CortexHubHelper { await this._deleteDatalake(tenantId, datalakeId); common_1.commonLogger.info(CortexHubHelper, `Successfully deleted datalake ${tenantId}/${datalakeId} from hub helper`); } + /** + * Abstraction that allows the `CortexHubHelper` subclass implementation reach out its bound `CortexCredentialProvider` + * The typical use case if for the `CortexHubHelper` to ask the `CortexCredentialProvider` the list of datalake ID's + * it holds (activated) for a given tenant ID + * @param tenantId + */ + async datalakeActiveList(tenantId) { + let activeList = await this.credProvider.selectDatalakeByTenant(tenantId); + common_1.commonLogger.info(CortexHubHelper, `Retrieved ${activeList} items from CredentialProvide for tenantid ${tenantId}`); + return activeList; + } + async getCredentialsObject(tenantId, datalakeId) { + let activeList = await this.credProvider.selectDatalakeByTenant(tenantId); + if (!activeList.includes(datalakeId)) { + common_1.commonLogger.alert(CortexHubHelper, `Attempt request to access the datalake ${datalakeId} not present in ${tenantId} credentials store`); + throw new error_1.PanCloudError(CortexHubHelper, 'CONFIG', `datalake ${datalakeId} not found`); + } + return this.credProvider.getCredentialsObject(datalakeId); + } } CortexHubHelper.className = 'CortexHubHelper'; exports.CortexHubHelper = CortexHubHelper; diff --git a/dist/index.d.ts b/dist/index.d.ts index 064dfc4..8e41ef3 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,11 +1,11 @@ export { Credentials, defaultCredentialsFactory } from './credentials'; export { DevTokenCredentialsOptions, DevTokenCredentials } from './devtokencredentials'; export { autoCredentials } from './autocredentials'; -export { LoggingService, LsOptions, LsQueryCfg } from './loggingservice'; +export { LoggingService, LsOptions, LsQueryCfg, LsControlMessage } from './loggingservice'; export { EventService, EsOptions, EsFilterBuilderCfg, EsFilterCfg } from './eventservice'; -export { DirectorySyncService, DssOptions, DssQueryFilter } from './directorysyncservice'; +export { DirectorySyncService, DssOptions, DssQueryFilter, DssObjClass } from './directorysyncservice'; export { EmitterInterface, L2correlation } from './emitter'; -export { LogLevel, retrier, commonLogger, OAUTH2SCOPE, EntryPoint } from './common'; +export { LogLevel, retrier, commonLogger, OAUTH2SCOPE, EntryPoint, setLogLevel, setLogger } from './common'; export { isSdkError, PanCloudError } from './error'; export { Util } from './util'; export { CortexCredentialProvider, CredentialProviderOptions, CredentialsItem, RefreshResult, defaultCredentialsProviderFactory, isCredentialItem } from './credentialprovider'; diff --git a/dist/index.js b/dist/index.js index 2ee9c9c..2deda7b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -29,6 +29,8 @@ var common_1 = require("./common"); exports.LogLevel = common_1.LogLevel; exports.retrier = common_1.retrier; exports.commonLogger = common_1.commonLogger; +exports.setLogLevel = common_1.setLogLevel; +exports.setLogger = common_1.setLogger; var error_1 = require("./error"); exports.isSdkError = error_1.isSdkError; exports.PanCloudError = error_1.PanCloudError; diff --git a/dist/loggingservice.d.ts b/dist/loggingservice.d.ts index bb41422..fe0aad9 100644 --- a/dist/loggingservice.d.ts +++ b/dist/loggingservice.d.ts @@ -115,13 +115,19 @@ export interface JobResult { queryStatus: jobStatus; clientParameters: any; result: { - esResult: null | { + esResult?: { hits: { hits: { _index: string; _type: string; _source: any; }[]; + total?: number; + }; + response?: { + result: { + aggregations?: any; + }; }; }; }; @@ -144,6 +150,11 @@ interface WriteResult { */ uuids: string[]; } +export interface LsControlMessage { + queryId: string; + lastKnownStatus: jobStatus; + totalHits?: number; +} /** * Options for the LoggingService class factory */ @@ -152,6 +163,7 @@ export interface LsOptions extends EmitterOptions { * Amount of milliseconds to wait between consecutive autopoll() attempts. Defaults to **200ms** */ autoPollSleep?: number; + controlListener?: (message: LsControlMessage) => void; } /** * High-level class that implements an Application Framework Logging Service client. It supports both sync @@ -164,6 +176,7 @@ export declare class LoggingService extends Emitter { private jobQueue; private lastProcElement; private pendingQueries; + private controlEmitter?; protected stats: LsStats; /** * Private constructor. Use the class's static `factory()` method instead diff --git a/dist/loggingservice.js b/dist/loggingservice.js index 5dc4303..4a9210b 100644 --- a/dist/loggingservice.js +++ b/dist/loggingservice.js @@ -19,6 +19,7 @@ const common_1 = require("./common"); const emitter_1 = require("./emitter"); const error_1 = require("./error"); const timers_1 = require("timers"); +const events_1 = require("events"); /** * Default delay (in milliseconds) between successive polls (auto-poll feature). It can be overrided in the * function signature @@ -78,6 +79,10 @@ class LoggingService extends emitter_1.Emitter { this.jobQueue = {}; this.lastProcElement = 0; this.pendingQueries = []; + if (ops && ops.controlListener) { + this.controlEmitter = new events_1.EventEmitter(); + this.controlEmitter.addListener('on', ops.controlListener); + } this.stats = Object.assign({ records: 0, deletes: 0, polls: 0, queries: 0, writes: 0 }, this.stats); } /** @@ -146,6 +151,16 @@ class LoggingService extends emitter_1.Emitter { }); this.pendingQueries = Object.keys(this.jobQueue); this.eventEmitter(rJson); + if (rJson.result.esResult) { + if (this.controlEmitter) { + let ctrlMessage = { + lastKnownStatus: rJson.queryStatus, + queryId: rJson.queryId, + totalHits: rJson.result.esResult.hits.total + }; + this.controlEmitter.emit('on', ctrlMessage); + } + } if (rJson.queryStatus == "JOB_FINISHED") { let jobResolver = this.jobQueue[rJson.queryId].resolve; this.emitterCleanup(rJson); @@ -208,7 +223,7 @@ class LoggingService extends emitter_1.Emitter { let jobR = { queryId: "", queryStatus: "RUNNING", - result: { esResult: null }, + result: {}, sequenceNo: 0, clientParameters: {} }; @@ -220,6 +235,16 @@ class LoggingService extends emitter_1.Emitter { } else { ls.eventEmitter(jobR); + if (jobR.result.esResult) { + if (ls.controlEmitter) { + let ctrlMessage = { + lastKnownStatus: jobR.queryStatus, + queryId: jobR.queryId, + totalHits: jobR.result.esResult.hits.total + }; + ls.controlEmitter.emit('on', ctrlMessage); + } + } if (jobR.queryStatus == "FINISHED") { currentJob.sequenceNo++; } diff --git a/dist/oa2credentials.d.ts b/dist/oa2credentials.d.ts deleted file mode 100644 index 8cba7be..0000000 --- a/dist/oa2credentials.d.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { Credentials, CredentialsOptions } from './credentials'; -/** - * Represents an Application Framework credential set - */ -interface IdpResponse { - access_token: string; - refresh_token?: string; - expires_in: string; -} -interface OA2BaseCredentialsOptions extends CredentialsOptions { - /** - * Application Framework's `client_id` string - */ - clientId: string; - /** - * Application Framework's `client_secret` string - */ - clientSecret: string; -} -declare abstract class OA2BaseCredentials extends Credentials { - private refreshToken; - private clientId; - private clientSecret; - private idpTokenUrl; - static className: string; - protected constructor(clientId: string, clientSecret: string, accessToken: string, refreshToken: string, idpTokenUrl: string, expiresIn?: number); - /** - * Implements the Application Framework OAUTH2 refresh token operation - * @param clientId OAUTH2 app `client_id` - * @param clientSecret OAUTH2 app `client_secret` - * @param refreshToken Current OAUTH2 app `refresh_token` value - * @param idpTokenUrl OAUTH2 Identity Provider URL entry point - * @returns a new set of tokens - */ - protected static refreshTokens(clientId: string, clientSecret: string, refreshToken: string, idpTokenUrl: string): Promise; - /** - * Attempts to refresh the current `access_token`. It might throw exceptions - */ - refreshAccessToken(): Promise; - /** - * Use this method when a customer is unsubscribing the OAUTH2 application to revoke the granted `refresh_token` - */ - revokeToken(): Promise; -} -/** - * Options to factorize an EmbeddedCredentials class object - */ -export interface EmbeddedCredentialsOptions extends OA2BaseCredentialsOptions { - /** - * The access_token if available. Otherwise it will be auto-grenerated from the refresh_token - */ - accessToken?: string; - /** - * Application Framework's `refresh_token` string - */ - refreshToken: string; -} -/** - * EmbeddedCredentials class keeps data and methods needed to maintain Application Framework access token alive - */ -export declare class EmbeddedCredentials extends OA2BaseCredentials { - static className: string; - /** - * class constructor not exposed. You must use the static **EmbeddedCredentials.factory()** instead - */ - private constructor(); - /** - * Factory method to instantiate a new **Credentials** class based on the options provided - * @param opt object of **CredentialsOptions** class with instantiation options - * @returns a **Credentials** class instantiated with the provided `access_token` and - * `refresh_token` or fetching a fresh `access_token` using the provided `refresh_token` - */ - static factory(opt: EmbeddedCredentialsOptions): Promise; -} -/** - * Options to factorize an OA2CodeCredentials class object - */ -export interface OA2CodeCredentialsOptions extends OA2BaseCredentialsOptions { - /** - * One time code (valid for 60 seconds) to be exchange for tokens from the Identity Provider - */ - code: string; - /** - * Redirect URI that was registered in the manifest file - */ - redirectUri: string; -} -/** - * OA2CodeCredentials class keeps data and methods needed to maintain Application Framework access token alive - */ -export declare class OA2CodeCredentials extends OA2BaseCredentials { - static className: string; - /** - * class constructor not exposed. You must use the static **OA2CodeCredentials.factory()** instead - */ - private constructor(); - /** - * Factory method to instantiate a new **Credentials** class based on the options provided - * @param opt object of **OA2CodeCredentialsOptions** class with instantiation options - * @returns a **Credentials** class instantiated with a new credential set of the OAUTH2 `code` is provided - */ - static factory(opt: OA2CodeCredentialsOptions): Promise; - /** - * Static class method to exchange a 60 seconds OAUTH2 code for valid credentials - * @param clientId OAUTH2 app `client_id` - * @param clientSecret OAUTH2 app `client_secret` - * @param code OAUTH2 app 60 seconds one time `code` - * @param idpTokenUrl OAUTH2 Identity Provider URL entry point - * @param redirectUri OAUTH2 app `redirect_uri` callback - * @returns a new set of tokens - */ - private static fetchTokens; -} -/** - * Options to factorize an EnvCredentials class object - */ -export interface EnvCredentialsOptions extends CredentialsOptions { - /** - * Environmental variable containing the `refresh_token` - */ - envRefreshToken?: string; - /** - * Environmental variable containing the `client_id` - */ - envClientId?: string; - /** - * Environmental variable containing the `client_secret` - */ - envClientSecret?: string; -} -/** - * EnvCredentials class keeps data and methods needed to maintain Application Framework access token alive - */ -export declare class EnvCredentials extends OA2BaseCredentials { - static className: string; - /** - * Factory method to instantiate a new **Credentials** class based on the options provided - * @param opt object of **EnvCredentialsOptions** class with instantiation options - * @returns a **Credentials** class instantiated with the provided `client_id`, `client_secret`, - * `access_token` and `refresh_token` or fetching a fresh `access_token` getting values from - * environmental variables - */ - static factory(opt?: EnvCredentialsOptions): Promise; -} -/** - * Options to factorize an FileCredentials class object - */ -export interface FileCredentialsOptions extends CredentialsOptions { - /** - * Filename containing the credentials. Defaults to 'credentials.json' - */ - fileName?: string; - /** - * Profile to process. Defaults to '1' - */ - profile?: string; - /** - * File content encoding: Defaults to 'utf8' - */ - fileEncoding?: string; -} -/** - * EnvCredentials class keeps data and methods needed to maintain Application Framework access token alive - */ -export declare class FileCredentials extends OA2BaseCredentials { - static className: string; - /** - * Factory method to instantiate a new **Credentials** class based on the options provided - * @param opt object of **EnvCredentialsOptions** class with instantiation options - * @returns a **Credentials** class instantiated with the provided `client_id`, `client_secret`, - * `access_token` and `refresh_token` or fetching a fresh `access_token` getting values from - * a credentials file - */ - static factory(opt?: FileCredentialsOptions): Promise; -} -export declare class OA2AutoCredentials extends OA2BaseCredentials { - static className: string; - static factory(opt?: FileCredentialsOptions | EnvCredentialsOptions): Promise; -} -export {}; diff --git a/dist/oa2credentials.js b/dist/oa2credentials.js deleted file mode 100644 index a1cba43..0000000 --- a/dist/oa2credentials.js +++ /dev/null @@ -1,324 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -/** - * The Application Framework Identity Provider URL entry point - */ -const fetch_1 = require("./fetch"); -const common_1 = require("./common"); -const error_1 = require("./error"); -const credentials_1 = require("./credentials"); -const process_1 = require("process"); -const fs_1 = require("fs"); -const IDP_TOKEN_URL = 'https://api.paloaltonetworks.com/api/oauth2/RequestToken'; -const IDP_REVOKE_URL = 'https://api.paloaltonetworks.com/api/oauth2/RevokeToken'; -const IDP_BASE_URL = 'https://identity.paloaltonetworks.com/as/authorization.oauth2'; -function isIdpResponse(obj) { - return (typeof obj.access_token == 'string' && - typeof obj.expires_in == 'string' && - (obj.refresh_tokens === undefined || typeof obj.refresh_tokens == 'string')); -} -function isIdpErrorResponse(obj) { - return (obj.error !== undefined && typeof obj.error == 'string' && - obj.error_description !== undefined && typeof obj.error_description == 'string'); -} -class OA2BaseCredentials extends credentials_1.Credentials { - constructor(clientId, clientSecret, accessToken, refreshToken, idpTokenUrl, expiresIn) { - super(expiresIn); - this.clientId = clientId; - this.clientSecret = clientSecret; - this.refreshToken = refreshToken; - this.idpTokenUrl = idpTokenUrl; - } - /** - * Implements the Application Framework OAUTH2 refresh token operation - * @param clientId OAUTH2 app `client_id` - * @param clientSecret OAUTH2 app `client_secret` - * @param refreshToken Current OAUTH2 app `refresh_token` value - * @param idpTokenUrl OAUTH2 Identity Provider URL entry point - * @returns a new set of tokens - */ - static async refreshTokens(clientId, clientSecret, refreshToken, idpTokenUrl) { - let res = await common_1.retrier(EmbeddedCredentials, undefined, undefined, fetch_1.fetch, idpTokenUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: JSON.stringify({ - "client_id": clientId, - "client_secret": clientSecret, - "refresh_token": refreshToken, - "grant_type": "refresh_token" - }), - timeout: 30000 - }); - if (!res.ok) { - throw new error_1.PanCloudError(EmbeddedCredentials, 'IDENTITY', `HTTP Error from IDP refresh operation ${res.status} ${res.statusText}`); - } - let rJson; - try { - rJson = await res.json(); - } - catch (exception) { - throw new error_1.PanCloudError(EmbeddedCredentials, 'PARSER', `Invalid JSON refresh response: ${exception.message}`); - } - if (isIdpResponse(rJson)) { - common_1.commonLogger.info(EmbeddedCredentials, 'Authorization token successfully retrieved', 'IDENTITY'); - return rJson; - } - if (isIdpErrorResponse(rJson)) { - throw new error_1.PanCloudError(EmbeddedCredentials, 'IDENTITY', rJson.error_description); - } - throw new error_1.PanCloudError(EmbeddedCredentials, 'PARSER', `Unparseable response received from IDP refresh operation: "${JSON.stringify(rJson)}"`); - } - /** - * Attempts to refresh the current `access_token`. It might throw exceptions - */ - async refreshAccessToken() { - let tk = await EmbeddedCredentials.refreshTokens(this.clientId, this.clientSecret, this.refreshToken, this.idpTokenUrl); - this.setAccessToken(tk.access_token, parseInt(tk.expires_in)); - if (tk.refresh_token) { - this.refreshToken = tk.refresh_token; - } - } - /** - * Use this method when a customer is unsubscribing the OAUTH2 application to revoke the granted `refresh_token` - */ - async revokeToken() { - if (!this.refreshToken) { - throw new error_1.PanCloudError(EmbeddedCredentials, 'CONFIG', `Not valid refresh token for revoke op: ${this.refreshToken}`); - } - let res = await fetch_1.fetch(IDP_REVOKE_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: JSON.stringify({ - "client_id": this.clientId, - "client_secret": this.clientSecret, - "token": this.refreshToken, - "token_type_hint": "refresh_token" - }) - }); - if (res.ok && res.size > 0) { - common_1.commonLogger.info(EmbeddedCredentials, 'Credentials(): Authorization token successfully revoked', 'IDENTITY'); - } - throw new error_1.PanCloudError(EmbeddedCredentials, 'IDENTITY', `HTTP Error from IDP refresh operation ${res.status} ${res.statusText}`); - } -} -OA2BaseCredentials.className = "OA2BaseCredentials"; -/** - * EmbeddedCredentials class keeps data and methods needed to maintain Application Framework access token alive - */ -class EmbeddedCredentials extends OA2BaseCredentials { - /** - * class constructor not exposed. You must use the static **EmbeddedCredentials.factory()** instead - */ - constructor(clientId, clientSecret, accessToken, refreshToken, idpTokenUrl, expiresIn) { - super(clientId, clientSecret, accessToken, refreshToken, idpTokenUrl, expiresIn); - } - /** - * Factory method to instantiate a new **Credentials** class based on the options provided - * @param opt object of **CredentialsOptions** class with instantiation options - * @returns a **Credentials** class instantiated with the provided `access_token` and - * `refresh_token` or fetching a fresh `access_token` using the provided `refresh_token` - */ - static async factory(opt) { - let idpTokenUrl = (opt.idpTokenUrl) ? opt.idpTokenUrl : IDP_TOKEN_URL; - if (opt.refreshToken && opt.accessToken) { - return new EmbeddedCredentials(opt.clientId, opt.clientSecret, opt.accessToken, opt.refreshToken, idpTokenUrl); - } - let tk; - let refreshToken = opt.refreshToken; - tk = await EmbeddedCredentials.refreshTokens(opt.clientId, opt.clientSecret, opt.refreshToken, idpTokenUrl); - if (tk.refresh_token) { - refreshToken = tk.refresh_token; - } - let exp_in = parseInt(tk.expires_in); - return new EmbeddedCredentials(opt.clientId, opt.clientSecret, tk.access_token, refreshToken, idpTokenUrl, exp_in); - } -} -EmbeddedCredentials.className = "EmbeddedCredentials"; -exports.EmbeddedCredentials = EmbeddedCredentials; -/** - * OA2CodeCredentials class keeps data and methods needed to maintain Application Framework access token alive - */ -class OA2CodeCredentials extends OA2BaseCredentials { - /** - * class constructor not exposed. You must use the static **OA2CodeCredentials.factory()** instead - */ - constructor(clientId, clientSecret, accessToken, refreshToken, idpTokenUrl, expiresIn) { - super(clientId, clientSecret, accessToken, refreshToken, idpTokenUrl, expiresIn); - } - /** - * Factory method to instantiate a new **Credentials** class based on the options provided - * @param opt object of **OA2CodeCredentialsOptions** class with instantiation options - * @returns a **Credentials** class instantiated with a new credential set of the OAUTH2 `code` is provided - */ - static async factory(opt) { - let idpTokenUrl = (opt.idpTokenUrl) ? opt.idpTokenUrl : IDP_TOKEN_URL; - let refreshToken; - let tk = await OA2CodeCredentials.fetchTokens(opt.clientId, opt.clientSecret, opt.code, idpTokenUrl, opt.redirectUri); - if (tk.refresh_token) { - refreshToken = tk.refresh_token; - } - else { - throw new error_1.PanCloudError(EmbeddedCredentials, 'IDENTITY', 'Missing refresh_token in the response'); - } - let exp_in = parseInt(tk.expires_in); - return new OA2CodeCredentials(opt.clientId, opt.clientSecret, tk.access_token, refreshToken, idpTokenUrl, exp_in); - } - /** - * Static class method to exchange a 60 seconds OAUTH2 code for valid credentials - * @param clientId OAUTH2 app `client_id` - * @param clientSecret OAUTH2 app `client_secret` - * @param code OAUTH2 app 60 seconds one time `code` - * @param idpTokenUrl OAUTH2 Identity Provider URL entry point - * @param redirectUri OAUTH2 app `redirect_uri` callback - * @returns a new set of tokens - */ - static async fetchTokens(clientId, clientSecret, code, idpTokenUrl, redirectUri) { - let res = await common_1.retrier(EmbeddedCredentials, undefined, undefined, fetch_1.fetch, idpTokenUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: JSON.stringify({ - "client_id": clientId, - "client_secret": clientSecret, - "redirect_uri": redirectUri, - "grant_type": "authorization_code", - "code": code - }) - }); - if (!res.ok) { - throw new error_1.PanCloudError(EmbeddedCredentials, 'IDENTITY', `HTTP Error from IDP fetch operation ${res.status} ${res.statusText}`); - } - let rJson; - try { - rJson = await res.json(); - } - catch (exception) { - throw new error_1.PanCloudError(EmbeddedCredentials, 'PARSER', `Invalid JSON fetch response: ${exception.message}`); - } - if (isIdpResponse(rJson)) { - common_1.commonLogger.info(EmbeddedCredentials, 'Authorization token successfully retrieved'); - return rJson; - } - if (isIdpErrorResponse(rJson)) { - throw new error_1.PanCloudError(EmbeddedCredentials, 'IDENTITY', rJson.error_description); - } - throw new error_1.PanCloudError(EmbeddedCredentials, 'PARSER', `Unparseable response received from IDP fetch operation: "${JSON.stringify(rJson)}"`); - } -} -OA2CodeCredentials.className = "OA2CodeCredentials"; -exports.OA2CodeCredentials = OA2CodeCredentials; -const ENV_CLIENT_ID = 'PAN_CLIENT_ID'; -const ENV_CLIENT_SECRET = 'PAN_CLIENT_SECRET'; -const ENV_REFRESH_TOKEN = 'PAN_REFRESH_TOKEN'; -/** - * EnvCredentials class keeps data and methods needed to maintain Application Framework access token alive - */ -class EnvCredentials extends OA2BaseCredentials { - /** - * Factory method to instantiate a new **Credentials** class based on the options provided - * @param opt object of **EnvCredentialsOptions** class with instantiation options - * @returns a **Credentials** class instantiated with the provided `client_id`, `client_secret`, - * `access_token` and `refresh_token` or fetching a fresh `access_token` getting values from - * environmental variables - */ - static async factory(opt) { - let clientIdEnv = (opt && opt.envClientId) ? opt.envClientId : ENV_CLIENT_ID; - let clientId = process_1.env[clientIdEnv]; - let clientSecretEnv = (opt && opt.envClientSecret) ? opt.envClientSecret : ENV_CLIENT_SECRET; - let clientSecret = process_1.env[clientSecretEnv]; - let refreshTokenEnv = (opt && opt.envRefreshToken) ? opt.envRefreshToken : ENV_REFRESH_TOKEN; - let refreshToken = process_1.env[refreshTokenEnv]; - if (clientId && clientSecret && refreshToken) - return EmbeddedCredentials.factory({ - clientId, clientSecret, refreshToken - }); - throw new error_1.PanCloudError(EnvCredentials, 'PARSER', `Enviromental variables (${clientIdEnv}, ${clientSecretEnv}, ${refreshTokenEnv}) not found`); - } -} -EnvCredentials.className = "EnvCredentials"; -exports.EnvCredentials = EnvCredentials; -function isCredentialsFileContent(obj) { - return obj.profiles && typeof obj.profiles == 'object' && - Object.values(obj.profiles).every(x => { - return x.client_id && typeof x.client_id == 'string' && - x.client_secret && typeof x.client_secret == 'string' && - x.client_secret && typeof x.client_secret == 'string' && - (!(x.access_token) || typeof x.access_token == 'string') && - (!(x.profile) || typeof x.profile == 'string'); - }); -} -const FILE_CREDENTIALS = 'credentials.json'; -const FILE_PROFILE = '1'; -const FILE_ENCODING = 'utf8'; -/** - * EnvCredentials class keeps data and methods needed to maintain Application Framework access token alive - */ -class FileCredentials extends OA2BaseCredentials { - /** - * Factory method to instantiate a new **Credentials** class based on the options provided - * @param opt object of **EnvCredentialsOptions** class with instantiation options - * @returns a **Credentials** class instantiated with the provided `client_id`, `client_secret`, - * `access_token` and `refresh_token` or fetching a fresh `access_token` getting values from - * a credentials file - */ - static async factory(opt) { - let fileName = (opt && opt.fileName) ? opt.fileName : FILE_CREDENTIALS; - let fileProfile = (opt && opt.profile) ? opt.profile : FILE_PROFILE; - let fileEncoding = (opt && opt.fileEncoding) ? opt.fileEncoding : FILE_ENCODING; - let fileContent; - try { - fileContent = fs_1.readFileSync(fileName, { encoding: fileEncoding }); - } - catch (e) { - throw new error_1.PanCloudError(FileCredentials, 'PARSER', `Error reading file ${fileName}`); - } - let fileContentJson; - try { - fileContentJson = JSON.parse(fileContent); - } - catch (e) { - throw new error_1.PanCloudError(FileCredentials, 'PARSER', `File ${fileName} is not a JSON document`); - } - if (isCredentialsFileContent(fileContentJson)) { - if (fileContentJson.profiles[fileProfile]) { - return EmbeddedCredentials.factory({ - clientId: fileContentJson.profiles[fileProfile].client_id, - clientSecret: fileContentJson.profiles[fileProfile].client_secret, - refreshToken: fileContentJson.profiles[fileProfile].refresh_token, - accessToken: fileContentJson.profiles[fileProfile].access_token - }); - } - throw new error_1.PanCloudError(EnvCredentials, 'PARSER', `Profile '${fileProfile}' not found in ${fileName}`); - } - throw new error_1.PanCloudError(EnvCredentials, 'PARSER', `Invalid JSON schema in ${fileName}`); - } -} -FileCredentials.className = "FileCredentials"; -exports.FileCredentials = FileCredentials; -class OA2AutoCredentials extends OA2BaseCredentials { - static async factory(opt) { - try { - return await EnvCredentials.factory(opt); - } - catch (_a) { - common_1.commonLogger.info(OA2AutoCredentials, 'Failed to instantiate EnvCredentials class'); - } - try { - return await FileCredentials.factory(opt); - } - catch (_b) { - common_1.commonLogger.info(OA2AutoCredentials, 'Failed to instantiate FileCredentials class'); - } - throw new error_1.PanCloudError(OA2AutoCredentials, 'PARSER', 'Unable to instantiate a Credentials class'); - } -} -OA2AutoCredentials.className = "OA2AutoCredentials"; -exports.OA2AutoCredentials = OA2AutoCredentials; diff --git a/lib/core.ts b/lib/core.ts index 60a3c79..6cc117d 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -15,6 +15,7 @@ * Implements the abstract coreClass that implements common methods for higher-end classes like Event Service * and Logging Service */ +import { URL } from 'url' import { fetch, FetchOptions, HttpMethod } from './fetch' import { Credentials } from './credentials' import { ApplicationFrameworkError, PanCloudError } from './error' @@ -61,6 +62,10 @@ export class CoreClass { * Credential object to be used by this instance */ protected cred: Credentials + /** + * Last known valid until value of the access token + */ + protected validUntil: Number /** * Master Application Framework API entry point */ @@ -96,7 +101,6 @@ export class CoreClass { } } - /** * Prepares the HTTP headers. Mainly used to keep the Autorization header (bearer access-token) */ @@ -110,14 +114,17 @@ export class CoreClass { /** * Triggers the credential object access-token refresh procedure and updates the HTTP headers + * DEPRECATED 190429 (rename it to `refresh` if needed) */ - protected async refresh(): Promise { + protected async _refresh(): Promise { await this.cred.retrieveAccessToken() await this.setFetchHeaders() } private async checkAutoRefresh(): Promise { - if (await this.cred.autoRefresh()) { + let currentValidUntil = await this.cred.autoRefresh() + if (this.validUntil != currentValidUntil) { + this.validUntil = currentValidUntil await this.setFetchHeaders() } } diff --git a/lib/credentialprovider.ts b/lib/credentialprovider.ts index 3fa6bb1..1306be1 100644 --- a/lib/credentialprovider.ts +++ b/lib/credentialprovider.ts @@ -103,9 +103,10 @@ export interface CredentialProviderOptions { /** * Abstract class to provide credentials for multiple datalakes. If you want to extend this class - * then you must implement its storage-related methods. + * then you must implement its storage-related methods. *T* describes the type of the optional + * metadata that can be attached to any datalake's credentials */ -export abstract class CortexCredentialProvider { +export abstract class CortexCredentialProvider { private clientId: string private clientSecret: string private idpTokenUrl: string @@ -117,15 +118,18 @@ export abstract class CortexCredentialProvider { private retrierAttempts?: number private retrierDelay?: number private accTokenGuardTime: number + protected tenantKey?: K static className = 'CortexCredentialProvider' /** * Class constructor * @param ops constructor options. Mandatory fields being OAUTH2 `clientId` and `clientSecret` + * @param tenantKey metadata feature, if used, mult solve at least the multi tenancy use case. That means that the metadata + * object of type `T` must include a property `K` that could be used for tenant membership identification */ protected constructor(ops: CredentialProviderOptions & { clientId: string, clientSecret: string, idpAuthUrl?: string - }) { + }, tenantKey?: K) { this.clientId = ops.clientId this.clientSecret = ops.clientSecret this.idpTokenUrl = (ops.idpTokenUrl) ? ops.idpTokenUrl : IDP_TOKEN_URL @@ -133,6 +137,7 @@ export abstract class CortexCredentialProvider { this.accTokenGuardTime = (ops.accTokenGuardTime) ? ops.accTokenGuardTime : ACCESS_GUARD this.retrierAttempts = ops.retrierAttempts this.retrierDelay = ops.retrierDelay + this.tenantKey = tenantKey if (this.accTokenGuardTime > 3300) { throw new PanCloudError(CortexCredentialProvider, 'CONFIG', `Property 'accTokenGuardTime' must be, at max 3300 seconds (${this.accTokenGuardTime})`) } @@ -232,7 +237,10 @@ export abstract class CortexCredentialProvider { * access to them to avoid the initial token refresh operation */ async issueWithRefreshToken(datalakeId: string, entryPoint: EntryPoint, - refreshToken: string, prefetch?: { accessToken: string, validUntil: number }): Promise { + refreshToken: string, prefetch?: { accessToken: string, validUntil: number }, metadata?: T): Promise { + if (metadata !== undefined && this.tenantKey === undefined) { + throw new PanCloudError(CortexCredentialProvider, 'CONFIG', 'Metadata provided without proper initialization of the tenantKey property. Review your subclass constructor.') + } if (!this.credentials) { await this.restoreState() } @@ -264,7 +272,7 @@ export abstract class CortexCredentialProvider { }) this.credentialsObject[datalakeId] = credentialsObject - await this.createCredentialsItem(datalakeId, credItem) + await this.createCredentialsItem(datalakeId, credItem, metadata) commonLogger.info(CortexCredentialProvider, `Issued new Credentials Object for datalake ID ${datalakeId}`) return credentialsObject } @@ -276,8 +284,10 @@ export abstract class CortexCredentialProvider { * @param entryPoint Cortex Datalake regional entry point * @param refreshToken OAUTH2 `refresh_token` value */ - async registerManualDatalake(datalakeId: string, entryPoint: EntryPoint, refreshToken: string): Promise { - return this.issueWithRefreshToken(datalakeId, entryPoint, refreshToken) + async registerManualDatalake( + datalakeId: string, entryPoint: EntryPoint, refreshToken: string, + prefetch?: { accessToken: string, validUntil: number }, metadata?: T): Promise { + return this.issueWithRefreshToken(datalakeId, entryPoint, refreshToken, prefetch, metadata) } /** @@ -285,7 +295,7 @@ export abstract class CortexCredentialProvider { * @param datalakeId ID of the datalake the Credentials object should be bound to */ async getCredentialsObject(datalakeId: string): Promise { - if (!this.credentials) { + if (this.credentials === undefined || this.credentials[datalakeId] === undefined) { await this.restoreState() } if (!this.credentialsObject[datalakeId]) { @@ -301,9 +311,13 @@ export abstract class CortexCredentialProvider { * @param datalakeId ID of the datalake to be removed */ async deleteDatalake(datalakeId: string): Promise { - if (!this.credentials) { + if (this.credentials === undefined || this.credentials[datalakeId] === undefined) { await this.restoreState() } + if (this.credentials[datalakeId] === undefined) { + commonLogger.info(CortexCredentialProvider, `Request to delete a non existant datalake ${datalakeId}. Ignoring it`) + return + } let param: FetchOptions = { method: 'POST', headers: { @@ -335,7 +349,7 @@ export abstract class CortexCredentialProvider { * @param datalakeId ID of the datalake to obtain `access_token` from */ async retrieveCortexAccessToken(datalakeId: string): Promise { - if (!this.credentials) { + if (this.credentials === undefined || this.credentials[datalakeId] === undefined) { await this.restoreState() } if (!(datalakeId in this.credentials)) { @@ -378,6 +392,14 @@ export abstract class CortexCredentialProvider { throw new PanCloudError(CortexCredentialProvider, 'PARSER', `Invalid response received by IDP provider`) } + /** + * Returns a basic `Credentials` subclass that just calls this provider's `retrieveCortexAccessToken` + * method when a new access_token is needed. + * @param datalakeId The datalake we want a credentials object for + * @param entryPoint The Cortex Datalake regional API entry point + * @param accTokenGuardTime Amount of seconds before expiration credentials object should use cached value + * @param prefetch Optinal prefetched access_token + */ protected async defaultCredentialsObjectFactory(datalakeId: string, entryPoint: EntryPoint, accTokenGuardTime: number, prefetch?: { accessToken: string, validUntil: number }): Promise { let credObject = new DefaultCredentials(datalakeId, entryPoint, accTokenGuardTime, this, prefetch) @@ -385,15 +407,56 @@ export abstract class CortexCredentialProvider { return credObject } - protected async abstract createCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem): Promise - protected async abstract updateCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem): Promise - protected async abstract deleteCredentialsItem(datalakeId: string): Promise - protected async abstract loadCredentialsDb(): Promise<{ [dlid: string]: CredentialsItem }> - protected async abstract credentialsObjectFactory(datalakeId: string, entryPoint: EntryPoint, accTokenGuardTime: number, + /** + * Implementation dependant. It is called by the abstract class each time a new set of credenentials have been + * created (either by manual refresh or by OAUTH2 code grant flow handled by a `CortexHubHelper` companion). + * The implementator is expected to store them somewhere + * @param datalakeId datalake identificator + * @param credentialsItem credential attributes + * @param metadata optional metadata (used by multitenant applications to attach tenant ID) + */ + protected abstract createCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem, metadata?: T): Promise + + /** + * Implementation dependant. It is called by the abstract class when a refresh token operation returns not + * only a new access_token but a new refresh_token as well. The implementator is expected to update the record + * @param datalakeId datalake identificator + * @param credentialsItem credential attributes + */ + protected abstract updateCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem): Promise + + /** + * Implementation dependant. It is called by the abstract class as a response to a successful revocation of + * the refresh_token. The implementator is expected to delete the record from the store + * @param datalakeId datalake identificator + */ + protected abstract deleteCredentialsItem(datalakeId: string): Promise + + /** + * Convenience method to allow a companion `CortexHubHelper` object retrieve metadata previously stored. + * Most implementations will return only these records matching the provided metadata (use case: to get + * all activated datalake ID's for a specific tenant) + */ + abstract selectDatalakeByTenant(tenantId: T[K]): Promise + + /** + * Implementation dependant. Expected to load all records from the store + */ + protected abstract loadCredentialsDb(): Promise<{ [dlid: string]: CredentialsItem }> + + /** + * Implementation dependant. Its purpose is to initialize and return a suitable credentials object from this + * credential provider for the specific datalake and attached attributes + * @param datalakeId The datalake we want a credentials object for + * @param entryPoint The Cortex Datalake regional API entry point + * @param accTokenGuardTime Amount of seconds before expiration credentials object should use cached value + * @param prefetch Optinal prefetched access_token + */ + protected abstract credentialsObjectFactory(datalakeId: string, entryPoint: EntryPoint, accTokenGuardTime: number, prefetch?: { accessToken: string, validUntil: number }): Promise } -class DefaultCredentialsProvider extends CortexCredentialProvider { +class DefaultCredentialsProvider extends CortexCredentialProvider { private sequence: number className = 'DefaultCredentialsProvider' @@ -414,6 +477,11 @@ class DefaultCredentialsProvider extends CortexCredentialProvider { commonLogger.info(this, 'Stateless credential provider. Discarding deleted item') } + selectDatalakeByTenant(tenantId: never): Promise { + commonLogger.info(this, 'Stateless credential provider. Do not support credentials metadata') + return Promise.resolve([]) + } + protected async loadCredentialsDb(): Promise<{ [dlid: string]: CredentialsItem }> { commonLogger.info(this, 'Stateless credential provider. Returning an empty item list to load() request') return {} @@ -425,11 +493,11 @@ class DefaultCredentialsProvider extends CortexCredentialProvider { } } -class DefaultCredentials extends Credentials { - accessTokenSupplier: CortexCredentialProvider +class DefaultCredentials extends Credentials { + accessTokenSupplier: CortexCredentialProvider datalakeId: string - constructor(datalakeId: string, entryPoint: EntryPoint, accTokenGuardTime: number, supplier: CortexCredentialProvider, + constructor(datalakeId: string, entryPoint: EntryPoint, accTokenGuardTime: number, supplier: CortexCredentialProvider, prefetch?: { accessToken: string, validUntil: number }) { super(entryPoint, accTokenGuardTime) this.datalakeId = datalakeId diff --git a/lib/credentials.ts b/lib/credentials.ts index 99ceb68..6e84bcd 100644 --- a/lib/credentials.ts +++ b/lib/credentials.ts @@ -68,7 +68,7 @@ export abstract class Credentials implements PancloudClass { * Checks the access token expiration time and automaticaly refreshes it if going to expire * inside the next 5 minutes */ - public async autoRefresh(): Promise { + public async autoRefresh(): Promise { if (!this.accessToken) { await this.retrieveAccessToken() } @@ -76,12 +76,12 @@ export abstract class Credentials implements PancloudClass { try { commonLogger.info(this, 'Cached access token about to expire. Requesting a new one.') await this.retrieveAccessToken() - return true + return this.validUntil } catch { commonLogger.info(this, 'Failed to get a new access token') } } - return false + return this.validUntil } /** diff --git a/lib/fscredentialprovider.ts b/lib/fscredentialprovider.ts index ba69431..17d37ee 100644 --- a/lib/fscredentialprovider.ts +++ b/lib/fscredentialprovider.ts @@ -19,34 +19,39 @@ import { CortexCredentialProvider, CredentialProviderOptions, CredentialsItem, i import { createCipheriv, createDecipheriv, createHash } from 'crypto' import * as fs from 'fs' -interface ConfigFile { - credentialItems: { [dlid: string]: CredentialsItem } +interface ConfigFile { + credentialItems: { [dlid: string]: CredentialsItem }, + metadata: { [datalakeId: string]: T } } -function isConfigFile(obj: any): obj is ConfigFile { +function isConfigFile(obj: any): obj is ConfigFile { return typeof obj == 'object' && + obj.metadata && obj.credentialItems && typeof obj.credentialItems == 'object' && Object.entries(obj.credentialItems).every(v => typeof v[0] == 'string' && isCredentialItem(v[1])) } -class FsCredProvider extends CortexCredentialProvider { +class FsCredProvider extends CortexCredentialProvider { private key: Buffer private iv: Buffer private configFileName: string + private metadata: { [datalakeId: string]: T } className = 'FsCredProvider' constructor(ops: CredentialProviderOptions & { clientId: string, clientSecret: string } & - { key: Buffer, iv: Buffer, configFileName: string }) { - super(ops) + { key: Buffer, iv: Buffer, configFileName: string }, tenantKey?: K) { + super(ops, tenantKey) this.key = ops.key this.iv = ops.iv this.configFileName = ops.configFileName + this.metadata = {} } private async fullSync(): Promise { - let configFile: ConfigFile = { - credentialItems: this.credentials + let configFile: ConfigFile = { + credentialItems: this.credentials, + metadata: this.metadata } Object.entries(this.credentials).forEach(v => { let aes = createCipheriv('aes128', this.key, this.iv) @@ -59,7 +64,10 @@ class FsCredProvider extends CortexCredentialProvider { await promifyFs(this, fs.writeFile, this.configFileName, JSON.stringify(configFile, undefined, ' ')) } - protected createCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem): Promise { + protected createCredentialsItem(datalakeId: string, credentialsItem: CredentialsItem, metadata?: T | undefined): Promise { + if (metadata) { + this.metadata[datalakeId] = metadata + } commonLogger.info(this, `Lazy implementation of CREATE credentials request for datalake ${datalakeId} with a full synch operation`) return this.fullSync() } @@ -74,6 +82,16 @@ class FsCredProvider extends CortexCredentialProvider { return this.fullSync() } + selectDatalakeByTenant(tenantId: T[K]): Promise { + let matchingDatalakes = Object.keys(this.metadata).filter(datalakeId => { + if (this.tenantKey === undefined) { + return false + } + return this.metadata[datalakeId][this.tenantKey] == tenantId + }) + return Promise.resolve(matchingDatalakes) + } + protected async loadCredentialsDb(): Promise<{ [dlid: string]: CredentialsItem }> { let jsonConfig: any try { @@ -82,9 +100,10 @@ class FsCredProvider extends CortexCredentialProvider { } catch (e) { throw PanCloudError.fromError(this, e) } - if (!isConfigFile(jsonConfig)) { + if (!isConfigFile(jsonConfig)) { throw new PanCloudError(this, 'PARSER', `Invalid configuration file format in ${this.configFileName}`) } + this.metadata = jsonConfig.metadata Object.entries(jsonConfig.credentialItems).forEach(v => { let aes = createDecipheriv('aes128', this.key, this.iv) let payload = Buffer.concat([aes.update(Buffer.from(v[1].refreshToken, 'base64')), aes.final()]).toString('utf8') @@ -117,8 +136,8 @@ const CONFIG_FILE = 'PANCLOUD_CONFIG.json' * @param ops.envClientSecret environmental variable that keeps the OAUTH2 `client_secret` value in * case it is not provided explicitly. Defaults to `{ops.envPrefix}_CLIENT_SECRET` */ -export async function fsCredentialsFactory(ops: CredentialProviderOptions & -{ envPrefix?: string, clientId?: string, clientSecret?: string, secret: string }): Promise { +export async function fsCredentialsFactory(ops: CredentialProviderOptions & +{ envPrefix?: string, clientId?: string, clientSecret?: string, secret: string }, tenantKey?: K): Promise> { let { key, iv } = passIvGenerator(ops.secret) let ePrefix = (ops && ops.envPrefix) ? ops.envPrefix : ENV_PREFIX let envClientId = `${ePrefix}_CLIENT_ID` @@ -140,8 +159,9 @@ export async function fsCredentialsFactory(ops: CredentialProviderOptions & await promifyFs(this, fs.stat, configFileName) } catch (e) { commonLogger.info({ className: 'fsCredProvider' }, `${configFileName} does not exist. Creating it`) - let blankConfig: ConfigFile = { - credentialItems: {} + let blankConfig: ConfigFile = { + credentialItems: {}, + metadata: {} } await promifyFs(this, fs.writeFile, configFileName, JSON.stringify(blankConfig)) } @@ -157,7 +177,7 @@ export async function fsCredentialsFactory(ops: CredentialProviderOptions & iv: iv, configFileName: configFileName, ...ops - }) + }, tenantKey) } function passIvGenerator(secret: string): { key: Buffer, iv: Buffer } { diff --git a/lib/hubhelper.ts b/lib/hubhelper.ts index 9d8e079..4592504 100644 --- a/lib/hubhelper.ts +++ b/lib/hubhelper.ts @@ -13,6 +13,7 @@ import { commonLogger, EntryPoint, region2EntryPoint, OAUTH2SCOPE } from './common' import { CortexCredentialProvider, AugmentedIdpResponse } from './credentialprovider' +import { Credentials } from './credentials' import { FetchOptions } from './fetch' import { PanCloudError } from './error' import { stringify as qsStringify, parse as qsParse } from 'querystring' @@ -29,6 +30,10 @@ export interface HubIdpCallback { * Would describe the error during the callback processing (if any) */ error?: string + /** + * Optional message in the response + */ + message?: string /** * Datalake ID if successfully processed and stored */ @@ -99,7 +104,7 @@ export function isCortexClientParams(obj: a * methods dealing with objects conforming to this interface. It describes an *authorization state* * (a pending authorization sent to IDP for user consent) */ -export interface HubIdpStateData { +export interface HubIdpStateData { /** * Requester Tenant ID */ @@ -107,7 +112,8 @@ export interface HubIdpStateData { /** * Requested datalakeID */ - datalakeId: string + datalakeId: string, + metadata: M } /** @@ -132,13 +138,16 @@ export interface CortexHelperOptions { * @param U interface used by the `req.user` object provided by a *PassportJS-like* enabled * application willing to use this class `authCallbackHandler` method. * @param K the string-like property in `U` containing the requester TenantID + * @param M interface describing the metadata that will be attached to datalakes in CortexCredentialProvider + * for multi-tenancy applications. CortexHubHelper will add/replace a property named `tenantId` in M so take this into + * consideration when defining the interface `M` */ -export abstract class CortexHubHelper { +export abstract class CortexHubHelper { private clientId: string private clientSecret: string private idpAuthUrl: string private callbackTenantValidation: boolean - private credProvider: CortexCredentialProvider + private credProvider: CortexCredentialProvider<{ tenantId: string } & M, 'tenantId'> private idpCallbackUrl: string private tenantKey?: K static className = 'CortexHubHelper' @@ -151,7 +160,7 @@ export abstract class CortexHubHelper, tenantKey?: K, ops?: CortexHelperOptions) { this.idpAuthUrl = (ops && ops.idpAuthUrl) ? ops.idpAuthUrl : IDP_AUTH_URL this.callbackTenantValidation = (ops && typeof ops.forceCallbackTenantValidation == 'boolean') ? ops.forceCallbackTenantValidation : false this.idpCallbackUrl = idpCallbackUrl @@ -190,12 +199,12 @@ export abstract class CortexHubHelper { + async idpAuthRequest(tenantId: string, datalakeId: string, scope: OAUTH2SCOPE[], metadata: M): Promise { let clientParams = await this.getDatalake(tenantId, datalakeId) if (!this.idpCallbackUrl) { throw new PanCloudError(CortexCredentialProvider, 'CONFIG', `idpCallbackUrl was not provided in the ops passed to the constructor. Can't request auth without it.`) } - let stateId = await this.requestAuthState({ tenantId: tenantId, datalakeId: datalakeId }) + let stateId = await this.requestAuthState({ tenantId: tenantId, datalakeId: datalakeId, metadata: metadata }) let qsParams: { [index: string]: string } = { response_type: 'code', client_id: this.clientId, @@ -234,8 +243,9 @@ export abstract class CortexHubHelper }[]> { + async listDatalake(tenantId: string): Promise<({ id: string } & CortexClientParams)[]> { let response = await this._listDatalake(tenantId) commonLogger.info(CortexHubHelper, `Successfully retrieved list of datalakes for tenant ${tenantId} from store`) return response @@ -405,11 +416,32 @@ export abstract class CortexHubHelper }[]> + /** + * Abstraction that allows the `CortexHubHelper` subclass implementation reach out its bound `CortexCredentialProvider` + * The typical use case if for the `CortexHubHelper` to ask the `CortexCredentialProvider` the list of datalake ID's + * it holds (activated) for a given tenant ID + * @param tenantId + */ + async datalakeActiveList(tenantId: string): Promise { + let activeList = await this.credProvider.selectDatalakeByTenant(tenantId as any) + commonLogger.info(CortexHubHelper, `Retrieved ${activeList} items from CredentialProvide for tenantid ${tenantId}`) + return activeList + } + + async getCredentialsObject(tenantId: string, datalakeId: string): Promise { + let activeList = await this.credProvider.selectDatalakeByTenant(tenantId as any) + if (!activeList.includes(datalakeId)) { + commonLogger.alert(CortexHubHelper, `Attempt request to access the datalake ${datalakeId} not present in ${tenantId} credentials store`) + throw new PanCloudError(CortexHubHelper, 'CONFIG', `datalake ${datalakeId} not found`) + } + return this.credProvider.getCredentialsObject(datalakeId) + } + + protected abstract _listDatalake(tenantId: string): Promise<({ id: string } & CortexClientParams)[]> protected abstract _getDatalake(tenantId: string, datalakeId: string): Promise> protected abstract _upsertDatalake(tenantId: string, datalakeId: string, clientParams: CortexClientParams): Promise protected abstract _deleteDatalake(tenantId: string, datalakeId: string): Promise - protected abstract requestAuthState(stateData: HubIdpStateData): Promise - protected abstract restoreAuthState(stateId: string): Promise + protected abstract requestAuthState(stateData: HubIdpStateData): Promise + protected abstract restoreAuthState(stateId: string): Promise> protected abstract deleteAuthState(stateId: string): Promise } diff --git a/lib/index.ts b/lib/index.ts index 635dd51..749f39d 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -14,11 +14,11 @@ export { Credentials, defaultCredentialsFactory } from './credentials' export { DevTokenCredentialsOptions, DevTokenCredentials } from './devtokencredentials' export { autoCredentials } from './autocredentials' -export { LoggingService, LsOptions, LsQueryCfg } from './loggingservice' +export { LoggingService, LsOptions, LsQueryCfg, LsControlMessage } from './loggingservice' export { EventService, EsOptions, EsFilterBuilderCfg, EsFilterCfg } from './eventservice' -export { DirectorySyncService, DssOptions, DssQueryFilter } from './directorysyncservice' +export { DirectorySyncService, DssOptions, DssQueryFilter, DssObjClass } from './directorysyncservice' export { EmitterInterface, L2correlation } from './emitter' -export { LogLevel, retrier, commonLogger, OAUTH2SCOPE, EntryPoint } from './common' +export { LogLevel, retrier, commonLogger, OAUTH2SCOPE, EntryPoint, setLogLevel, setLogger } from './common' export { isSdkError, PanCloudError } from './error' export { Util } from './util' export { diff --git a/lib/loggingservice.ts b/lib/loggingservice.ts index bf2600b..522bcd8 100644 --- a/lib/loggingservice.ts +++ b/lib/loggingservice.ts @@ -20,6 +20,7 @@ import { Emitter, EmitterOptions, EmitterInterface, EmitterStats, L2correlation import { PanCloudError, isSdkError, SdkErr } from './error' import { setTimeout } from 'timers'; import { Credentials } from './credentials'; +import { EventEmitter } from 'events'; /** * Default delay (in milliseconds) between successive polls (auto-poll feature). It can be overrided in the @@ -142,13 +143,19 @@ export interface JobResult { queryStatus: jobStatus, clientParameters: any result: { - esResult: null | { + esResult?: { hits: { hits: { _index: string, _type: string, _source: any - }[] + }[], + total?: number + }, + response?: { + result: { + aggregations?: any + } } } } @@ -185,6 +192,7 @@ interface JobEntry { reject: (reason: any) => void, maxWaitTime?: number clientParameters?: any + totalHits?: number } /** @@ -212,6 +220,12 @@ function isWriteResult(obj: any): obj is WriteResult { obj.uuids && typeof obj.uuids == 'object' && Array.isArray(obj.uuids) } +export interface LsControlMessage { + queryId: string, + lastKnownStatus: jobStatus, + totalHits?: number +} + /** * Options for the LoggingService class factory */ @@ -220,6 +234,7 @@ export interface LsOptions extends EmitterOptions { * Amount of milliseconds to wait between consecutive autopoll() attempts. Defaults to **200ms** */ autoPollSleep?: number + controlListener?: (message: LsControlMessage) => void } /** @@ -233,6 +248,7 @@ export class LoggingService extends Emitter { private jobQueue: { [i: string]: JobEntry } private lastProcElement: number private pendingQueries: string[] + private controlEmitter?: EventEmitter protected stats: LsStats /** @@ -246,6 +262,10 @@ export class LoggingService extends Emitter { this.jobQueue = {} this.lastProcElement = 0 this.pendingQueries = [] + if (ops && ops.controlListener) { + this.controlEmitter = new EventEmitter() + this.controlEmitter.addListener('on', ops.controlListener) + } this.stats = { records: 0, deletes: 0, @@ -323,6 +343,16 @@ export class LoggingService extends Emitter { }) this.pendingQueries = Object.keys(this.jobQueue) this.eventEmitter(rJson) + if (rJson.result.esResult) { + if (this.controlEmitter) { + let ctrlMessage: LsControlMessage = { + lastKnownStatus: rJson.queryStatus, + queryId: rJson.queryId, + totalHits: rJson.result.esResult.hits.total + } + this.controlEmitter.emit('on', ctrlMessage) + } + } if (rJson.queryStatus == "JOB_FINISHED") { let jobResolver = this.jobQueue[rJson.queryId].resolve this.emitterCleanup(rJson) @@ -387,7 +417,7 @@ export class LoggingService extends Emitter { let jobR: JobResult = { queryId: "", queryStatus: "RUNNING", - result: { esResult: null }, + result: {}, sequenceNo: 0, clientParameters: {} } @@ -398,6 +428,16 @@ export class LoggingService extends Emitter { await ls.cancelPoll(currentQid, new PanCloudError(ls, "UNKNOWN", "JOB_FAILED")) } else { ls.eventEmitter(jobR) + if (jobR.result.esResult) { + if (ls.controlEmitter) { + let ctrlMessage: LsControlMessage = { + lastKnownStatus: jobR.queryStatus, + queryId: jobR.queryId, + totalHits: jobR.result.esResult.hits.total + } + ls.controlEmitter.emit('on', ctrlMessage) + } + } if (jobR.queryStatus == "FINISHED") { currentJob.sequenceNo++ } diff --git a/package.json b/package.json index 508ca08..901fce0 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "pancloud-nodejs", - "version": "1.0.0", + "name": "pancloud", + "version": "1.0.1", "description": "Palo Alto Networks Application Framework NodeJS SDK", - "main": "index.js", - "types": "lib/index.ts", + "main": "dist/index.js", "directories": { "lib": "lib" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build:library": "mkdir -p dist; rm -rf dist; tsc -p tsconfig.json", + "build:test": "cd test;rm *js;tsc" }, "repository": { "type": "git", diff --git a/test/credential.js b/test/credential.js new file mode 100644 index 0000000..c46d270 --- /dev/null +++ b/test/credential.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const cred_basic = require("./credential_basic"); +const cred_fs = require("./credential_fs"); +const examples = { + "BASIC": cred_basic.main, + "FS": cred_fs.main +}; +if (process.argv.length < 3 || !Object.keys(examples).includes(process.argv[2])) { + console.log("Usage: 'node example/credential ' where example is one of the following keywords"); + Object.keys(examples).forEach(e => { + console.log(`- ${e}`); + }); + process.exit(); +} +examples[process.argv[2]]().then().catch(e => { + console.log(`General Error\n${e.stack}`); +}); diff --git a/example/credential.ts b/test/credential.ts similarity index 100% rename from example/credential.ts rename to test/credential.ts diff --git a/test/credential_basic.js b/test/credential_basic.js new file mode 100644 index 0000000..3369f11 --- /dev/null +++ b/test/credential_basic.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const process_1 = require("process"); +async function main() { + let accessToken = process_1.env['PAN_ACCESS_TOKEN']; + if (!accessToken) { + throw new Error(`environmental variable PAN_ACCESS_TOKEN does not exist is null`); + } + let c = await pancloud_nodejs_1.defaultCredentialsFactory('https://api.us.paloaltonetworks.com', accessToken); + let d = new Date(await c.getExpiration() * 1000); + console.log(`Access Token: ${await c.getAccessToken()}\nValid until: ${d.toISOString()}`); +} +exports.main = main; diff --git a/example/credential_basic.ts b/test/credential_basic.ts similarity index 100% rename from example/credential_basic.ts rename to test/credential_basic.ts diff --git a/test/credential_fs.js b/test/credential_fs.js new file mode 100644 index 0000000..a3ed8c6 --- /dev/null +++ b/test/credential_fs.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const process_1 = require("process"); +async function main() { + let refreshToken = process_1.env['PAN_REFRESH_TOKEN']; + if (!refreshToken) { + throw new Error('Provide a valid refresh token in the PAN_REFRESH_TOKEN environment variable'); + } + let credProv = await pancloud_nodejs_1.fsCredentialsFactory({ secret: 'mysecret' }); + let c = await credProv.registerManualDatalake('hello', 'https://api.us.paloaltonetworks.com', refreshToken); + let d = new Date(await c.getExpiration() * 1000); + console.log(`Access Token: ${await c.getAccessToken()}\nValid until: ${d.toISOString()}`); +} +exports.main = main; diff --git a/example/credential_fs.ts b/test/credential_fs.ts similarity index 100% rename from example/credential_fs.ts rename to test/credential_fs.ts diff --git a/test/dnsdecode.js b/test/dnsdecode.js new file mode 100644 index 0000000..14fd9d4 --- /dev/null +++ b/test/dnsdecode.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let testEntries = [ + { "receive_time": 1547040279, "sessionid": 43930, "time_generated": 1547040231, "dns-rsp-reply-code": 0, "type": "DPI", "dns-rsp-transaction-id": 165, "content_ver": "8111-5239", "txn_start": 1547040231, "txn_id": 166, "dns-req-query-items": [{ "dns-req-query-name": { "value": "BmVtc3NhcwxiYW5jc2FiYWRlbGwDY29tAA==", "seqno": 12 }, "dns-req-query-type": 1 }], "dns-rsp-query-items": [{ "dns-rsp-query-name": { "value": "BmVtc3NhcwxiYW5jc2FiYWRlbGwDY29tAA==", "seqno": 12 }, "dns-rsp-query-type": 1 }], "dns-rsp-resource-record-items": [{ "dns-rsp-rr-name": { "value": "wAw=", "seqno": 41 }, "dns-rsp-rr-type": 5, "dns-rsp-rr-value": { "value": "CWVtc3Nhcy1pYQdhaW1hdGNowCA=", "seqno": 53 } }, { "dns-rsp-rr-name": { "value": "wDU=", "seqno": 73 }, "dns-rsp-rr-type": 5, "dns-rsp-rr-value": { "value": "CWVtc3Nhcy1pYQdhaW1hdGNoA25ldAA=", "seqno": 85 } }, { "dns-rsp-rr-name": { "value": "wFU=", "seqno": 108 }, "dns-rsp-rr-type": 5, "dns-rsp-rr-value": { "value": "CnRpZXIxLWV1dzEHaXJlbGFuZAhkZWxpdmVyecBf", "seqno": 120 } }, { "dns-rsp-rr-name": { "value": "wHg=", "seqno": 150 }, "dns-rsp-rr-type": 1, "dns-rsp-rr-value": { "value": "Nkg8Sg==", "seqno": 162 } }, { "dns-rsp-rr-name": { "value": "wHg=", "seqno": 166 }, "dns-rsp-rr-type": 1, "dns-rsp-rr-value": { "value": "NDDE8A==", "seqno": 178 } }, { "dns-rsp-rr-name": { "value": "wHg=", "seqno": 182 }, "dns-rsp-rr-type": 1, "dns-rsp-rr-value": { "value": "IvgzGw==", "seqno": 194 } }], "customer-id": "........", "serial": "", "receptor_txn_start": 1547040239, "subtype": "dns", "dns-req-transaction-id": 165, "dns-rsp-is-over-tcp": 0, "dns-req-is-over-tcp": 0, "client_sw": "8.1.4", "recsize": 1537 }, + { "receive_time": 1547039555, "sessionid": 506, "time_generated": 1547039512, "dns-rsp-reply-code": 0, "type": "DPI", "dns-rsp-transaction-id": 26297, "content_ver": "8111-5239", "txn_start": 1547039512, "dns-rsp-query-items": [{ "dns-rsp-query-name": { "value": "BHdzMTIDZ3RpBm1jYWZlZQNjb20A", "seqno": 12 }, "dns-rsp-query-type": 28 }], "txn_id": 26298, "dns-req-query-items": [{ "dns-req-query-name": { "value": "BHdzMTIDZ3RpBm1jYWZlZQNjb20A", "seqno": 12 }, "dns-req-query-type": 28 }], "dns-rsp-resource-record-items": [{ "dns-rsp-rr-name": { "value": "wAw=", "seqno": 37 }, "dns-rsp-rr-type": 5, "dns-rsp-rr-value": { "value": "BHdzMTIDZ3RpBm1jYWZlZQZha2FkbnMDbmV0AA==", "seqno": 49 } }, { "dns-rsp-rr-name": { "value": "wEE=", "seqno": 77 }, "dns-rsp-rr-type": 6, "dns-rsp-soa-primary-name-server": { "value": "CGludGVybmFswEE=", "seqno": 89 } }], "customer-id": ".........", "serial": "", "receptor_txn_start": 1547039517, "subtype": "dns", "dns-req-transaction-id": 26297, "dns-rsp-is-over-tcp": 0, "dns-req-is-over-tcp": 0, "client_sw": "8.1.4", "recsize": 1006 }, + { "receive_time": 1547040972, "sessionid": 43196, "time_generated": 1547040931, "dns-rsp-reply-code": 3, "type": "DPI", "dns-rsp-transaction-id": 60122, "content_ver": "8111-5239", "txn_start": 1547040931, "dns-rsp-query-items": [{ "dns-rsp-query-name": { "value": "DV9hdXRvZGlzY292ZXIEX3RjcAtzbWEtaWJlcmljYQNjb20A", "seqno": 12 }, "dns-rsp-query-type": 33 }], "txn_id": 60123, "dns-req-query-items": [{ "dns-req-query-name": { "value": "DV9hdXRvZGlzY292ZXIEX3RjcAtzbWEtaWJlcmljYQNjb20A", "seqno": 12 }, "dns-req-query-type": 33 }], "dns-rsp-resource-record-items": [{ "dns-rsp-rr-name": { "value": "wB8=", "seqno": 52 }, "dns-rsp-rr-type": 6, "dns-rsp-soa-primary-name-server": { "value": "A25zMQdldXJvZG5zwCs=", "seqno": 64 } }, { "dns-rsp-rr-name": { "value": "AA==", "seqno": 111 }, "dns-rsp-rr-type": 41, "dns-rsp-rr-value": { "value": "", "seqno": 122 } }], "customer-id": ".........", "serial": "", "receptor_txn_start": 1547040935, "subtype": "dns", "dns-req-transaction-id": 60122, "dns-rsp-is-over-tcp": 0, "dns-req-is-over-tcp": 0, "client_sw": "8.1.4", "recsize": 1015 }, + { "receive_time": 1547042089, "sessionid": 39763, "time_generated": 1547042065, "dns-rsp-reply-code": 0, "type": "DPI", "dns-rsp-transaction-id": 25653, "content_ver": "8111-5239", "txn_start": 1547042065, "dns-rsp-query-items": [{ "dns-rsp-query-name": { "value": "BHBsYXkGZ29vZ2xlA2NvbQA=", "seqno": 12 }, "dns-rsp-query-type": 28 }], "txn_id": 25654, "dns-req-query-items": [{ "dns-req-query-name": { "value": "BHBsYXkGZ29vZ2xlA2NvbQA=", "seqno": 12 }, "dns-req-query-type": 28 }], "dns-rsp-resource-record-items": [{ "dns-rsp-rr-name": { "value": "wAw=", "seqno": 33 }, "dns-rsp-rr-type": 28, "dns-rsp-rr-value": { "value": "KgAUUEADCAIAAAAAAAAgDg==", "seqno": 45 } }], "customer-id": ".........", "serial": "", "receptor_txn_start": 1547042071, "subtype": "dns", "dns-req-transaction-id": 25653, "dns-rsp-is-over-tcp": 0, "dns-req-is-over-tcp": 0, "client_sw": "8.1.4", "recsize": 839 }, + { "receive_time": 1547039525, "sessionid": 23229, "time_generated": 1547039489, "dns-rsp-reply-code": 0, "type": "DPI", "dns-rsp-transaction-id": 5792, "content_ver": "8111-5239", "txn_start": 1547039489, "dns-rsp-query-items": [{ "dns-rsp-query-name": { "value": "A2NybANwa2kEZ29vZwA=", "seqno": 12 }, "dns-rsp-query-type": 1 }], "txn_id": 5793, "dns-req-query-items": [{ "dns-req-query-name": { "value": "A2NybANwa2kEZ29vZwA=", "seqno": 12 }, "dns-req-query-type": 1 }], "dns-rsp-resource-record-items": [{ "dns-rsp-rr-name": { "value": "wBA=", "seqno": 30 }, "dns-rsp-rr-type": 2, "dns-rsp-rr-value": { "value": "A25zMQR6ZG5zBmdvb2dsZQA=", "seqno": 42 } }, { "dns-rsp-rr-name": { "value": "wBA=", "seqno": 59 }, "dns-rsp-rr-type": 2, "dns-rsp-rr-value": { "value": "A25zMsAu", "seqno": 71 } }, { "dns-rsp-rr-name": { "value": "wBA=", "seqno": 77 }, "dns-rsp-rr-type": 2, "dns-rsp-rr-value": { "value": "A25zM8Au", "seqno": 89 } }, { "dns-rsp-rr-name": { "value": "wBA=", "seqno": 95 }, "dns-rsp-rr-type": 2, "dns-rsp-rr-value": { "value": "A25zNMAu", "seqno": 107 } }, { "dns-rsp-rr-name": { "value": "wBA=", "seqno": 113 }, "dns-rsp-rr-type": 43, "dns-rsp-rr-value": { "value": "TVkIAgPwujbHqFU59MoVSdwGK9HLwhe0mQI9SSJY7CwXU/mN", "seqno": 125 } }, { "dns-rsp-rr-name": { "value": "wBA=", "seqno": 161 }, "dns-rsp-rr-type": 46, "dns-rsp-rr-value": { "value": "ACsIAgAAALRcTZtyXDCacilABGdvb2cALTX557t50dGbe1SxXxxFzXoqb3DH5MC2r72xp3T77XLTfooN1BbVw+Q8rNhrkDPJKK3sywpz9yQXoq1IzvuBGMTD9rfiIw/gAS0dijIRcPGvgMlc4PP86b/htNvHqsyO/4HaIwo07ZTbyW2zCRI6ESyjCFHe3qm9o12H0BIre6o=", "seqno": 173 } }, { "dns-rsp-rr-name": { "value": "AA==", "seqno": 325 }, "dns-rsp-rr-type": 41, "dns-rsp-rr-value": { "value": "", "seqno": 336 } }], "customer-id": ".........", "serial": "", "receptor_txn_start": 1547039494, "subtype": "dns", "dns-req-transaction-id": 5792, "dns-rsp-is-over-tcp": 0, "dns-req-is-over-tcp": 0, "client_sw": "8.1.4", "recsize": 1801 } +]; +testEntries.forEach(x => { + pancloud_nodejs_1.Util.dnsDecode(x); + console.log('___'); + console.log(JSON.stringify(x, undefined, ' ')); +}); diff --git a/example/dnsdecode.ts b/test/dnsdecode.ts similarity index 100% rename from example/dnsdecode.ts rename to test/dnsdecode.ts diff --git a/test/dss.js b/test/dss.js new file mode 100644 index 0000000..49cfe3d --- /dev/null +++ b/test/dss.js @@ -0,0 +1,41 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const dss_attributes = require("./dss_attributes"); +const dss_domains = require("./dss_domains"); +const dss_count = require("./dss_count"); +const dss_query_users = require("./dss_query_users"); +const dss_query_computers = require("./dss_query_computers"); +const dss_query_ous = require("./dss_query_ous"); +const dss_query_groups = require("./dss_query_groups"); +const dss_query_containers = require("./dss_query_containers"); +const dss_query_sublist_users = require("./dss_query_sublist_users"); +const dss_query_filter_users = require("./dss_query_filter_users"); +const examples = { + "ATTR": dss_attributes.main, + "DOMAINS": dss_domains.main, + "COUNT": dss_count.main, + "QUERY_USERS": dss_query_users.main, + "QUERY_COMPUTERS": dss_query_computers.main, + "QUERY_OUS": dss_query_ous.main, + "QUERY_GROUPS": dss_query_groups.main, + "QUERY_CONTAINERS": dss_query_containers.main, + "QUERY_SUBLIST_USERS": dss_query_sublist_users.main, + "QUERY_FILTER_USERS": dss_query_filter_users.main +}; +if (process.argv.length < 3 || !Object.keys(examples).includes(process.argv[2])) { + console.log("Usage: 'node example/dss ' where example is one of the following keywords"); + Object.keys(examples).forEach(e => { + console.log(`- ${e}`); + }); + process.exit(); +} +examples[process.argv[2]]().then().catch(e => { + if (pancloud_nodejs_1.isSdkError(e)) { + let aferr = e; + console.log(`Application Framework Error fields: code = ${aferr.getErrorCode()}, message = ${aferr.getErrorMessage()}`); + } + else { + console.log(`General Error\n${e.stack}`); + } +}); diff --git a/example/dss.ts b/test/dss.ts similarity index 100% rename from example/dss.ts rename to test/dss.ts diff --git a/test/dss_attributes.js b/test/dss_attributes.js new file mode 100644 index 0000000..dbe0059 --- /dev/null +++ b/test/dss_attributes.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let attr = await dss.attributes(); + console.log("Sucessfully Received Attributes"); + console.log(JSON.stringify(attr, undefined, ' ')); +} +exports.main = main; diff --git a/example/dss_attributes.ts b/test/dss_attributes.ts similarity index 100% rename from example/dss_attributes.ts rename to test/dss_attributes.ts diff --git a/test/dss_count.js b/test/dss_count.js new file mode 100644 index 0000000..a66c495 --- /dev/null +++ b/test/dss_count.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + console.log("Retrieving count per object classes"); + for (let i of ["computers", "containers", "groups", "users"]) { + let count = await dss.count("panwdomain", i); + console.log(`${i}: ${count}`); + } +} +exports.main = main; diff --git a/example/dss_count.ts b/test/dss_count.ts similarity index 70% rename from example/dss_count.ts rename to test/dss_count.ts index 7a478d7..8df27d3 100644 --- a/example/dss_count.ts +++ b/test/dss_count.ts @@ -1,5 +1,4 @@ -import { autoCredentials, DirectorySyncService, LogLevel } from 'pancloud-nodejs' -import { DssObjClass } from 'pancloud-nodejs/lib/directorysyncservice'; +import { autoCredentials, DirectorySyncService, LogLevel, DssObjClass } from 'pancloud-nodejs' export async function main(): Promise { let c = await autoCredentials() diff --git a/test/dss_domains.js b/test/dss_domains.js new file mode 100644 index 0000000..e032952 --- /dev/null +++ b/test/dss_domains.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const entryPoint = "https://api.us.paloaltonetworks.com"; +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let attr = await dss.domains(); + console.log("Sucessfully Received Domains"); + attr.forEach((v, i) => { + console.log(`${i}: ${JSON.stringify(v)}`); + }); +} +exports.main = main; diff --git a/example/dss_domains.ts b/test/dss_domains.ts similarity index 100% rename from example/dss_domains.ts rename to test/dss_domains.ts diff --git a/test/dss_query_computers.js b/test/dss_query_computers.js new file mode 100644 index 0000000..f4a2715 --- /dev/null +++ b/test/dss_query_computers.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const entryPoint = "https://api.us.paloaltonetworks.com"; +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let computers = await dss.query('computers'); + console.log(`Sucessfully Received ${computers.count} computer objects`); + computers.result.forEach(x => { + console.log(`Domain: ${x.domainName}\n---`); + console.log(JSON.stringify(x, undefined, ' ')); + }); + console.log(`Page Number: ${computers.pageNumber}`); + console.log(`Page Size: ${computers.pageSize}`); + if (computers.unreadResults) { + console.log(`Unread Results: ${computers.unreadResults}`); + } +} +exports.main = main; diff --git a/example/dss_query_computers.ts b/test/dss_query_computers.ts similarity index 100% rename from example/dss_query_computers.ts rename to test/dss_query_computers.ts diff --git a/test/dss_query_containers.js b/test/dss_query_containers.js new file mode 100644 index 0000000..9c6c81a --- /dev/null +++ b/test/dss_query_containers.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const entryPoint = "https://api.us.paloaltonetworks.com"; +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let containers = await dss.query('containers'); + console.log(`Sucessfully Received ${containers.count} container objects`); + containers.result.forEach(x => { + console.log(`Domain: ${x.domainName}\n---`); + console.log(JSON.stringify(x, undefined, ' ')); + }); + console.log(`Page Number: ${containers.pageNumber}`); + console.log(`Page Size: ${containers.pageSize}`); + if (containers.unreadResults) { + console.log(`Unread Results: ${containers.unreadResults}`); + } +} +exports.main = main; diff --git a/example/dss_query_containers.ts b/test/dss_query_containers.ts similarity index 100% rename from example/dss_query_containers.ts rename to test/dss_query_containers.ts diff --git a/test/dss_query_filter_users.js b/test/dss_query_filter_users.js new file mode 100644 index 0000000..0e4c1f6 --- /dev/null +++ b/test/dss_query_filter_users.js @@ -0,0 +1,31 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const entryPoint = "https://api.us.paloaltonetworks.com"; +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let users = await dss.query('users', { + domain: "panwdomain", + filter: { + level: 'immediate', + type: 'group', + name: { + attributeName: 'Common-Name', + attributeValue: 'Adm', + matchCriteria: 'startWith' + } + } + }); + console.log(`Sucessfully Received ${users.count} user objects`); + users.result.forEach(x => { + console.log(`Domain: ${x.domainName}\n---`); + console.log(JSON.stringify(x, undefined, ' ')); + }); + console.log(`Page Number: ${users.pageNumber}`); + console.log(`Page Size: ${users.pageSize}`); + if (users.unreadResults) { + console.log(`Unread Results: ${users.unreadResults}`); + } +} +exports.main = main; diff --git a/example/dss_query_filter_users.ts b/test/dss_query_filter_users.ts similarity index 100% rename from example/dss_query_filter_users.ts rename to test/dss_query_filter_users.ts diff --git a/test/dss_query_groups.js b/test/dss_query_groups.js new file mode 100644 index 0000000..dc43ac7 --- /dev/null +++ b/test/dss_query_groups.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const entryPoint = "https://api.us.paloaltonetworks.com"; +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let groups = await dss.query('groups'); + console.log(`Sucessfully Received ${groups.count} group objects`); + groups.result.forEach(x => { + console.log(`Domain: ${x.domainName}\n---`); + console.log(JSON.stringify(x, undefined, ' ')); + }); + console.log(`Page Number: ${groups.pageNumber}`); + console.log(`Page Size: ${groups.pageSize}`); + if (groups.unreadResults) { + console.log(`Unread Results: ${groups.unreadResults}`); + } +} +exports.main = main; diff --git a/example/dss_query_groups.ts b/test/dss_query_groups.ts similarity index 100% rename from example/dss_query_groups.ts rename to test/dss_query_groups.ts diff --git a/test/dss_query_ous.js b/test/dss_query_ous.js new file mode 100644 index 0000000..bdd3669 --- /dev/null +++ b/test/dss_query_ous.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const entryPoint = "https://api.us.paloaltonetworks.com"; +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let ous = await dss.query('ous'); + console.log(`Sucessfully Received ${ous.count} ou objects`); + ous.result.forEach(x => { + console.log(`Domain: ${x.domainName}\n---`); + console.log(JSON.stringify(x, undefined, ' ')); + }); + console.log(`Page Number: ${ous.pageNumber}`); + console.log(`Page Size: ${ous.pageSize}`); + if (ous.unreadResults) { + console.log(`Unread Results: ${ous.unreadResults}`); + } +} +exports.main = main; diff --git a/example/dss_query_ous.ts b/test/dss_query_ous.ts similarity index 100% rename from example/dss_query_ous.ts rename to test/dss_query_ous.ts diff --git a/test/dss_query_sublist_users.js b/test/dss_query_sublist_users.js new file mode 100644 index 0000000..594f76b --- /dev/null +++ b/test/dss_query_sublist_users.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const entryPoint = "https://api.us.paloaltonetworks.com"; +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let users = await dss.query('users', { + domain: "panwdomain", + name: { + attributeName: 'Common-Name', + attributeValue: 'Adm', + matchCriteria: 'startWith' + } + }); + console.log(`Sucessfully Received ${users.count} user objects`); + users.result.forEach(x => { + console.log(`Domain: ${x.domainName}\n---`); + console.log(JSON.stringify(x, undefined, ' ')); + }); + console.log(`Page Number: ${users.pageNumber}`); + console.log(`Page Size: ${users.pageSize}`); + if (users.unreadResults) { + console.log(`Unread Results: ${users.unreadResults}`); + } +} +exports.main = main; diff --git a/example/dss_query_sublist_users.ts b/test/dss_query_sublist_users.ts similarity index 100% rename from example/dss_query_sublist_users.ts rename to test/dss_query_sublist_users.ts diff --git a/test/dss_query_users.js b/test/dss_query_users.js new file mode 100644 index 0000000..80ba7d4 --- /dev/null +++ b/test/dss_query_users.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const entryPoint = "https://api.us.paloaltonetworks.com"; +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let dss = await pancloud_nodejs_1.DirectorySyncService.factory(c); + let users = await dss.query('users'); + console.log(`Sucessfully Received ${users.count} user objects`); + users.result.forEach(x => { + console.log(`Domain: ${x.domainName}\n---`); + console.log(JSON.stringify(x, undefined, ' ')); + }); + console.log(`Page Number: ${users.pageNumber}`); + console.log(`Page Size: ${users.pageSize}`); + if (users.unreadResults) { + console.log(`Unread Results: ${users.unreadResults}`); + } +} +exports.main = main; diff --git a/example/dss_query_users.ts b/test/dss_query_users.ts similarity index 100% rename from example/dss_query_users.ts rename to test/dss_query_users.ts diff --git a/test/eventservice.js b/test/eventservice.js new file mode 100644 index 0000000..1656523 --- /dev/null +++ b/test/eventservice.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const eventservice_ack = require("./eventservice_ack"); +const eventservice_nack = require("./eventservice_nack"); +const eventservice_flush = require("./eventservice_flush"); +const eventservice_setfilter = require("./eventservice_setfilter"); +const eventservice_getfilter = require("./eventservice_getfilter"); +const eventservice_poll = require("./eventservice_poll"); +const eventservice_generator = require("./eventservice_generator"); +const eventservice_async_poll = require("./eventservice_async_poll"); +const eventservice_clearfilter = require("./eventservice_clearfilter"); +const eventservice_pcap = require("./eventservice_async_pcap"); +const eventservice_corr = require("./eventservice_correlation"); +const examples = { + "ACK": eventservice_ack.main, + "NACK": eventservice_nack.main, + "FLUSH": eventservice_flush.main, + "SET_FILTER": eventservice_setfilter.main, + "GET_FILTER": eventservice_getfilter.main, + "POLL": eventservice_poll.main, + "GENERATOR_POLL": eventservice_generator.main, + "ASYNC_POLL": eventservice_async_poll.main, + "ASYNC_PCAP": eventservice_pcap.main, + "L2CORRELATION": eventservice_corr.main, + "CLEAR_FILTER": eventservice_clearfilter.main, +}; +if (process.argv.length < 3 || !Object.keys(examples).includes(process.argv[2])) { + console.log("Usage: 'node example/credential ' where example is one of the following keywords"); + Object.keys(examples).forEach(e => { + console.log(`- ${e}`); + }); + process.exit(); +} +examples[process.argv[2]]().then().catch(e => { + if (pancloud_nodejs_1.isSdkError(e)) { + let aferr = e; + console.log(`Application Framework Error fields: code = ${aferr.getErrorCode()}, message = ${aferr.getErrorMessage()}`); + } + else { + console.log(`General Error\n${e.stack}`); + } +}); diff --git a/example/eventservice.ts b/test/eventservice.ts similarity index 100% rename from example/eventservice.ts rename to test/eventservice.ts diff --git a/test/eventservice_ack.js b/test/eventservice_ack.js new file mode 100644 index 0000000..f692a31 --- /dev/null +++ b/test/eventservice_ack.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.ack(); + console.log("Sucessfully ack'ed the channel"); +} +exports.main = main; diff --git a/example/eventservice_ack.ts b/test/eventservice_ack.ts similarity index 100% rename from example/eventservice_ack.ts rename to test/eventservice_ack.ts diff --git a/test/eventservice_async_pcap.js b/test/eventservice_async_pcap.js new file mode 100644 index 0000000..777454f --- /dev/null +++ b/test/eventservice_async_pcap.js @@ -0,0 +1,50 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const fs_1 = require("fs"); +let builderCfg = { + filter: [ + { table: "panw.threat", timeout: 1000 } + ], + filterOptions: { + callBack: { + pcap: receiver + }, + poolOptions: { + ack: true, + pollTimeout: 1000 + } + }, + flush: true +}; +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.filterBuilder(builderCfg); + console.log("Set the filter and registered the async pcap receiver"); + await new Promise(resolve => { + setTimeout(() => { + console.log('\n1 minute timer expired. Pausing the poller'); + es.pause(); + resolve(); + }, 60000); + }); + await es.clearFilter(true); + console.log("Cleared the filter and flushed the channel"); + console.log("Event Service stats"); + console.log(JSON.stringify(es.getEsStats(), undefined, " ")); +} +exports.main = main; +let pcapCounter = 0; +function receiver(e) { + if (e.message) { + fs_1.writeFileSync("pcap" + ("00" + pcapCounter++).substr(-3) + ".pcap", e.message); + console.log(`Received PCAP body of ${e.message.length} bytes`); + } + else { + console.log(`Received null event from ${e.source}. Ending process`); + } +} diff --git a/example/eventservice_async_pcap.ts b/test/eventservice_async_pcap.ts similarity index 100% rename from example/eventservice_async_pcap.ts rename to test/eventservice_async_pcap.ts diff --git a/test/eventservice_async_poll.js b/test/eventservice_async_poll.js new file mode 100644 index 0000000..21d5346 --- /dev/null +++ b/test/eventservice_async_poll.js @@ -0,0 +1,52 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let builderCfg = { + filter: [ + { table: "panw.traffic", timeout: 1000 }, + { table: "panw.dpi", timeout: 1000 }, + { table: "panw.threat", where: 'where risk-of-app > 3' } + ], + filterOptions: { + callBack: { + event: receiver + }, + poolOptions: { + ack: true, + pollTimeout: 1000 + } + } +}; +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.filterBuilder(builderCfg); + console.log("Set the filter and registered the async event receiver"); + await new Promise(resolve => { + setTimeout(() => { + console.log('\n1 minute timer expired. Pausing the poller'); + es.pause(); + resolve(); + }, 60000); + }); + await es.clearFilter(true); + console.log("Cleared the filter and flushed the channel"); + console.log("Event Service stats"); + console.log(JSON.stringify(es.getEsStats(), undefined, " ")); +} +exports.main = main; +let lType = ""; +let eventCounter = 0; +function receiver(e) { + if (e.logType && e.logType != lType) { + lType = e.logType; + console.log(`\nReceiving: Event Type: ${lType} from ${e.source}`); + } + if (e.message) { + eventCounter += e.message.length; + } + process.stdout.write(`${eventCounter}...`); +} diff --git a/example/eventservice_async_poll.ts b/test/eventservice_async_poll.ts similarity index 100% rename from example/eventservice_async_poll.ts rename to test/eventservice_async_poll.ts diff --git a/test/eventservice_clearfilter.js b/test/eventservice_clearfilter.js new file mode 100644 index 0000000..67eb83e --- /dev/null +++ b/test/eventservice_clearfilter.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.clearFilter(); + console.log('Successfully cleared the filter'); +} +exports.main = main; diff --git a/example/eventservice_clearfilter.ts b/test/eventservice_clearfilter.ts similarity index 100% rename from example/eventservice_clearfilter.ts rename to test/eventservice_clearfilter.ts diff --git a/test/eventservice_correlation.js b/test/eventservice_correlation.js new file mode 100644 index 0000000..e19bdea --- /dev/null +++ b/test/eventservice_correlation.js @@ -0,0 +1,63 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let builderCfg = { + filter: [ + { table: "panw.traffic", timeout: 1000 }, + { table: "panw.dpi", timeout: 1000 } + ], + filterOptions: { + callBack: { + corr: corrReceicer + }, + poolOptions: { + ack: true, + pollTimeout: 1000 + } + } +}; +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.filterBuilder(builderCfg); + console.log("Set the filter and registered the async event receiver"); + await new Promise(resolve => { + setTimeout(() => { + console.log('\n1 minute timer expired. Pausing the poller'); + es.pause(); + resolve(); + }, 120000); + }); + await es.clearFilter(true); + console.log("Cleared the filter and flushed the channel"); + console.log("Event Service stats"); + console.log(JSON.stringify(es.getEsStats(), undefined, " ")); +} +exports.main = main; +let l2l3map = { + l2dst: {}, l2src: {} +}; +let corrEventCounter = 0; +function corrReceicer(e) { + if (e.message) { + corrEventCounter += e.message.length; + console.log(`${corrEventCounter} correlation events received so far`); + e.message.forEach(x => { + if (x["extended-traffic-log-mac"] in l2l3map.l2src) { + l2l3map.l2src[x["extended-traffic-log-mac"]][x.src] = true; + } + else { + l2l3map.l2src[x["extended-traffic-log-mac"]] = { [x.src]: true }; + } + if (x["extended-traffic-log-mac-stc"] in l2l3map.l2dst) { + l2l3map.l2dst[x["extended-traffic-log-mac-stc"]][x.dst] = true; + } + else { + l2l3map.l2dst[x["extended-traffic-log-mac-stc"]] = { [x.dst]: true }; + } + }); + } +} diff --git a/example/eventservice_correlation.ts b/test/eventservice_correlation.ts similarity index 100% rename from example/eventservice_correlation.ts rename to test/eventservice_correlation.ts diff --git a/test/eventservice_flush.js b/test/eventservice_flush.js new file mode 100644 index 0000000..e39959e --- /dev/null +++ b/test/eventservice_flush.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.flush(); + console.log("Successfully flushed the channel"); +} +exports.main = main; diff --git a/example/eventservice_flush.ts b/test/eventservice_flush.ts similarity index 100% rename from example/eventservice_flush.ts rename to test/eventservice_flush.ts diff --git a/test/eventservice_generator.js b/test/eventservice_generator.js new file mode 100644 index 0000000..63e75e3 --- /dev/null +++ b/test/eventservice_generator.js @@ -0,0 +1,34 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let builderCfg = { + filter: [ + { table: "panw.traffic", timeout: 1000, batchSize: 8000 }, + { table: "panw.dpi", timeout: 1000, batchSize: 8000 }, + { table: "panw.threat", where: 'where risk-of-app > 3' } + ], + flush: false, + filterOptions: {} +}; +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.filterBuilder(builderCfg); + let iterations = 10; + for (let prom of es) { + if (iterations-- == 0) + break; + let response = await prom; + console.log(`Processed iteration ${iterations}`); + response.forEach(e => { + console.log(`${e.event.length} ${e.logType} events`); + }); + } + await es.clearFilter(); + console.log("Event Service stats"); + console.log(JSON.stringify(es.getEsStats(), undefined, " ")); +} +exports.main = main; diff --git a/example/eventservice_generator.ts b/test/eventservice_generator.ts similarity index 100% rename from example/eventservice_generator.ts rename to test/eventservice_generator.ts diff --git a/test/eventservice_getfilter.js b/test/eventservice_getfilter.js new file mode 100644 index 0000000..e87a952 --- /dev/null +++ b/test/eventservice_getfilter.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + let f = await es.getFilters(); + console.log(`Current Filter Entries (flush: ${f.flush})`); + f.filters.forEach(o => { + Object.entries(o).forEach(e => { + console.log(`- Table: ${e[0]} - filter: ${e[1].filter} / batchSize: ${e[1].batchSize} / timeout: ${e[1].timeout}`); + }); + }); +} +exports.main = main; diff --git a/example/eventservice_getfilter.ts b/test/eventservice_getfilter.ts similarity index 100% rename from example/eventservice_getfilter.ts rename to test/eventservice_getfilter.ts diff --git a/test/eventservice_nack.js b/test/eventservice_nack.js new file mode 100644 index 0000000..9831ff1 --- /dev/null +++ b/test/eventservice_nack.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.nack(); + console.log("Sucessfully nack'ed the channel"); +} +exports.main = main; diff --git a/example/eventservice_nack.ts b/test/eventservice_nack.ts similarity index 100% rename from example/eventservice_nack.ts rename to test/eventservice_nack.ts diff --git a/test/eventservice_poll.js b/test/eventservice_poll.js new file mode 100644 index 0000000..b7a2b53 --- /dev/null +++ b/test/eventservice_poll.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + let t = await es.poll(); + t.forEach(e => { + console.log(`Event Type: ${e.logType}, Record Count: ${e.event.length}`); + console.log(`First Event\n${JSON.stringify(e.event[0])}`); + }); +} +exports.main = main; diff --git a/example/eventservice_poll.ts b/test/eventservice_poll.ts similarity index 100% rename from example/eventservice_poll.ts rename to test/eventservice_poll.ts diff --git a/test/eventservice_setfilter.js b/test/eventservice_setfilter.js new file mode 100644 index 0000000..a0529f7 --- /dev/null +++ b/test/eventservice_setfilter.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let builderCfg = { + filter: [ + { table: "panw.traffic", timeout: 1000, batchSize: 8000 }, + { table: "panw.dpi", timeout: 1000, batchSize: 8000 }, + { table: "panw.threat", where: 'where risk-of-app > 3' } + ], + flush: false, + filterOptions: {} +}; +/** + * Use the enventservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let es = await pancloud_nodejs_1.EventService.factory(c); + await es.filterBuilder(builderCfg); + console.log('Successfully set a new filter'); +} +exports.main = main; diff --git a/example/eventservice_setfilter.ts b/test/eventservice_setfilter.ts similarity index 100% rename from example/eventservice_setfilter.ts rename to test/eventservice_setfilter.ts diff --git a/test/loggingservice.js b/test/loggingservice.js new file mode 100644 index 0000000..96bedc8 --- /dev/null +++ b/test/loggingservice.js @@ -0,0 +1,35 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const loggingservice_basic = require("./loggingservice_basic"); +const loggingservice_poll = require("./loggingservice_poll"); +const loggingservice_async_poll = require("./loggingservice_async_poll"); +const loggingservice_complex_async_poll = require("./loggingservice_complex_async_poll"); +const loggingservice_async_pcap = require("./loggingservice_async_pcap"); +const loggingservice_async_dns_poll = require("./loggingservice_async_dns_poll"); +const loggingservice_cancel_async_poll = require("./loggingservice_cancel_async_poll"); +const examples = { + "BASIC": loggingservice_basic.main, + "POLL": loggingservice_poll.main, + "ASYNC_POLL": loggingservice_async_poll.main, + "ASYNC_CANCEL": loggingservice_cancel_async_poll.main, + "ASYNC_DNS_POLL": loggingservice_async_dns_poll.main, + "ASYNC_PCAP": loggingservice_async_pcap.main, + "COMPLEX_ASYNC_POLL": loggingservice_complex_async_poll.main +}; +if (process.argv.length < 3 || !Object.keys(examples).includes(process.argv[2])) { + console.log("Usage: 'node example/credential ' where example is one of the following keywords"); + Object.keys(examples).forEach(e => { + console.log(`- ${e}`); + }); + process.exit(); +} +examples[process.argv[2]]().then().catch(e => { + if (pancloud_nodejs_1.isSdkError(e)) { + let aferr = e; + console.log(`Application Framework Error fields: code = ${aferr.getErrorCode()}, message = ${aferr.getErrorMessage()}`); + } + else { + console.log(`General Error\n${e.stack}`); + } +}); diff --git a/example/loggingservice.ts b/test/loggingservice.ts similarity index 100% rename from example/loggingservice.ts rename to test/loggingservice.ts diff --git a/test/loggingservice_async_dns_poll.js b/test/loggingservice_async_dns_poll.js new file mode 100644 index 0000000..fe88dfe --- /dev/null +++ b/test/loggingservice_async_dns_poll.js @@ -0,0 +1,36 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let now = Math.floor(Date.now() / 1000); +let query = { + query: "select * from panw.dpi where subtype='dns' limit 40", + startTime: now - 3600, + endTime: now, + maxWaitTime: 1000, + callBack: { + event: receiver + } +}; +let decodingErrors = 0; +/** + * Use the loggingservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let ls = await pancloud_nodejs_1.LoggingService.factory(c, { fetchTimeout: 45000 }); + await ls.query(query); // Schedule query 1 and register the receiver + console.log("Logging Service stats"); + console.log(JSON.stringify(ls.getLsStats(), undefined, " ")); + console.log(`DNS Decoding Errorr: ${decodingErrors}`); +} +exports.main = main; +function receiver(e) { + if (e.message) { + e.message.forEach(x => { + if (!pancloud_nodejs_1.Util.dnsDecode(x)) { + decodingErrors++; + } + }); + console.log(JSON.stringify(e, undefined, ' ')); + } +} diff --git a/example/loggingservice_async_dns_poll.ts b/test/loggingservice_async_dns_poll.ts similarity index 100% rename from example/loggingservice_async_dns_poll.ts rename to test/loggingservice_async_dns_poll.ts diff --git a/test/loggingservice_async_pcap.js b/test/loggingservice_async_pcap.js new file mode 100644 index 0000000..4f6f2d3 --- /dev/null +++ b/test/loggingservice_async_pcap.js @@ -0,0 +1,32 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const fs_1 = require("fs"); +let now = Math.floor(Date.now() / 1000); +let query = { + query: 'select * from panw.threat limit 40', + startTime: now - 3600, + endTime: now, + maxWaitTime: 1000, + callBack: { + pcap: receiver + } +}; +/** + * Use the loggingservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let ls = await pancloud_nodejs_1.LoggingService.factory(c, { fetchTimeout: 45000 }); + await ls.query(query); // Schedule query 1 and register the receiver + console.log("Logging Service stats"); + console.log(JSON.stringify(ls.getLsStats(), undefined, " ")); +} +exports.main = main; +let pcapCounter = 0; +function receiver(e) { + if (e.message) { + fs_1.writeFileSync("pcap" + ("00" + pcapCounter++).substr(-3) + ".pcap", e.message); + console.log(`Received PCAP body of ${e.message.length} bytes`); + } +} diff --git a/example/loggingservice_async_pcap.ts b/test/loggingservice_async_pcap.ts similarity index 100% rename from example/loggingservice_async_pcap.ts rename to test/loggingservice_async_pcap.ts diff --git a/test/loggingservice_async_poll.js b/test/loggingservice_async_poll.js new file mode 100644 index 0000000..b457ba5 --- /dev/null +++ b/test/loggingservice_async_poll.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let now = Math.floor(Date.now() / 1000); +let query = { + query: 'select * from panw.traffic limit 4', + startTime: now - 3600, + endTime: now, + maxWaitTime: 1000, + callBack: { + event: receiver + } +}; +/** + * Use the loggingservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let ls = await pancloud_nodejs_1.LoggingService.factory(c, { fetchTimeout: 45000 }); + try { + let result = await ls.query(query); + console.log(`Job ${result.queryId} completed with status ${result.queryStatus}`); + } + catch (e) { + console.log(`Something went wrong with a LS query ${e}`); + } + console.log("Logging Service stats"); + console.log(JSON.stringify(ls.getLsStats(), undefined, " ")); +} +exports.main = main; +let lQid = ""; +let eventCounter = 0; +function receiver(e) { + if (e.source != lQid) { + lQid = e.source; + console.log(`\nReceiving: Event Type: ${e.logType} from ${e.source}`); + } + if (e.message) { + eventCounter += e.message.length; + console.log(`${eventCounter} events received so far`); + } +} diff --git a/example/loggingservice_async_poll.ts b/test/loggingservice_async_poll.ts similarity index 100% rename from example/loggingservice_async_poll.ts rename to test/loggingservice_async_poll.ts diff --git a/test/loggingservice_basic.js b/test/loggingservice_basic.js new file mode 100644 index 0000000..e18bee7 --- /dev/null +++ b/test/loggingservice_basic.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let now = Math.floor(Date.now() / 1000); +let query = { + query: 'select * from panw.traffic limit 10', + startTime: now - 36000, + endTime: now, + maxWaitTime: 20000 +}; +/** + * Use the loggingservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let ls = await pancloud_nodejs_1.LoggingService.factory(c, { fetchTimeout: 45000 }); + let job = await ls.query(query); + console.log(`Successfully scheduled the query id: ${job.queryId} with status: ${job.queryStatus}`); + if (job.result.esResult) { + console.log(`... containing ${job.result.esResult.hits.hits.length} events`); + } +} +exports.main = main; diff --git a/example/loggingservice_basic.ts b/test/loggingservice_basic.ts similarity index 100% rename from example/loggingservice_basic.ts rename to test/loggingservice_basic.ts diff --git a/test/loggingservice_cancel_async_poll.js b/test/loggingservice_cancel_async_poll.js new file mode 100644 index 0000000..d3c34b7 --- /dev/null +++ b/test/loggingservice_cancel_async_poll.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let now = Math.floor(Date.now() / 1000); +let query = { + query: 'select * from panw.traffic limit 40000', + startTime: now - 3600, + endTime: now, + maxWaitTime: 1000, + callBack: { + event: receiver + } +}; +let ls; +/** + * Use the loggingservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let ls = await pancloud_nodejs_1.LoggingService.factory(c, { fetchTimeout: 45000 }); + try { + let result = await ls.query(query); + console.log(`Job ${result.queryId} completed with status ${result.queryStatus}`); + } + catch (e) { + console.log(`Something went wrong with a LS query ${e}`); + } + console.log("Logging Service stats"); + console.log(JSON.stringify(ls.getLsStats(), undefined, " ")); +} +exports.main = main; +let lQid = ""; +let eventCounter = 0; +function receiver(e) { + if (e.source != lQid) { + lQid = e.source; + console.log(`\nReceiving: Event Type: ${e.logType} from ${e.source}`); + } + if (e.message) { + eventCounter += e.message.length; + console.log(`${eventCounter} events received so far`); + } + console.log("Let's assume something went wrong in the receiver and that we have to cancell the query"); + ls.cancelPoll(e.source); +} diff --git a/example/loggingservice_cancel_async_poll.ts b/test/loggingservice_cancel_async_poll.ts similarity index 100% rename from example/loggingservice_cancel_async_poll.ts rename to test/loggingservice_cancel_async_poll.ts diff --git a/test/loggingservice_complex_async_poll.js b/test/loggingservice_complex_async_poll.js new file mode 100644 index 0000000..b19d5b0 --- /dev/null +++ b/test/loggingservice_complex_async_poll.js @@ -0,0 +1,84 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let now = Math.floor(Date.now() / 1000); +let es; +let query1 = { + query: 'select * from panw.traffic limit 40000', + startTime: now - 36000, + endTime: now, + maxWaitTime: 1000, + callBack: { + event: receiver + } +}; +let query2 = { + query: 'select * from panw.threat limit 30000', + startTime: now - 36000, + endTime: now, + maxWaitTime: 1000, + callBack: {} +}; +let builderCfg = { + filter: [ + { table: "panw.traffic", timeout: 1000 }, + { table: "panw.dpi", timeout: 1000 }, + { table: "panw.threat", where: 'where risk-of-app > 3' } + ], + filterOptions: { + callBack: { + event: receiver + }, + poolOptions: { + ack: true, + pollTimeout: 1000 + } + } +}; +/** + * Use the loggingservice.js launcher to call this main() function + */ +async function main() { + /* let c = await EmbeddedCredentials.factory({ + clientId: c_id, + clientSecret: c_secret, + refreshToken: r_token, + accessToken: a_token + }) + */ + let c = await pancloud_nodejs_1.autoCredentials(); + es = await pancloud_nodejs_1.EventService.factory(c, { fetchTimeout: 45000 }); + await es.filterBuilder(builderCfg); + console.log("Successfully started the Event Service notifier"); + let ls = await pancloud_nodejs_1.LoggingService.factory(c, { fetchTimeout: 45000 }); + let job1 = ls.query(query1); // Schedule query 1 and register the receiver + let job2 = ls.query(query2); // Schedule query 2 with no additional registration + try { + let results = await Promise.all([job1, job2]); + results.forEach(j => { + console.log(`Job ${j.queryId} completed with status ${j.queryStatus}`); + }); + } + catch (e) { + console.log(`Something went wrong with a LS query ${e}`); + } + es.pause(); + await es.clearFilter(); + console.log("Logging Service stats"); + console.log(JSON.stringify(ls.getLsStats(), undefined, " ")); + console.log("Event Service stats"); + console.log(JSON.stringify(es.getEsStats(), undefined, " ")); +} +exports.main = main; +let lQid = ""; +let eventCounter = 0; +function receiver(e) { + if (e.source != lQid) { + lQid = e.source; + console.log(`\nReceiving: Event Type: ${e.logType} from ${e.source}`); + } + if (e.message) { + eventCounter += e.message.length; + console.log(`${eventCounter} events received so far`); + } +} diff --git a/example/loggingservice_complex_async_poll.ts b/test/loggingservice_complex_async_poll.ts similarity index 100% rename from example/loggingservice_complex_async_poll.ts rename to test/loggingservice_complex_async_poll.ts diff --git a/test/loggingservice_poll.js b/test/loggingservice_poll.js new file mode 100644 index 0000000..7dafb26 --- /dev/null +++ b/test/loggingservice_poll.js @@ -0,0 +1,70 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +let ls; +let now = Math.floor(Date.now() / 1000); +let query = { + query: 'select * from panw.traffic limit 40000', + startTime: now - 36000, + endTime: now, + maxWaitTime: 1000 +}; +/** + * Use the loggingservice.js launcher to call this main() function + */ +async function main() { + let c = await pancloud_nodejs_1.autoCredentials(); + let ls = await pancloud_nodejs_1.LoggingService.factory(c, { + fetchTimeout: 45000, controlListener: x => { + console.log('Received control message\n', JSON.stringify(x, undefined, ' ')); + } + }); + let job = await ls.query(query); + let seq = job.sequenceNo; + if (job.queryStatus == "FINISHED") { + seq = job.sequenceNo + 1; + } + let loopException; + while (job.queryStatus != "JOB_FINISHED" && !loopException) { + console.log(`Successfully checked the query id: ${job.queryId} with status: ${job.queryStatus} and sequence: ${job.sequenceNo}`); + if (job.result.esResult) { + console.log(` ... and contains ${job.result.esResult.hits.hits.length} records`); + } + try { + job = await delayedFunc(1000, ls.poll.bind(ls), job.queryId, seq); + } + catch (loopException) { } + if (job.queryStatus == "FINISHED") { + seq = job.sequenceNo + 1; + } + if (job.queryStatus == "JOB_FAILED") { + throw new Error("JOB Failed"); + } + } + try { + await ls.deleteQuery(job.queryId); + } + catch (loopException) { } + if (loopException) { + throw loopException; + } + console.log(`Successfully checked the query id: ${job.queryId} with status: ${job.queryStatus} and sequence: ${job.sequenceNo}`); + if (job.result.esResult) { + console.log(` ... and contains ${job.result.esResult.hits.hits.length} records`); + } + console.log(`Job also has been deleted`); +} +exports.main = main; +function delayedFunc(delay, f, ...args) { + return new Promise((ready, notReady) => { + let task = f(...args); + setTimeout(async () => { + try { + ready(await task); + } + catch (e) { + notReady(e); + } + }, delay); + }); +} diff --git a/example/loggingservice_poll.ts b/test/loggingservice_poll.ts similarity index 91% rename from example/loggingservice_poll.ts rename to test/loggingservice_poll.ts index da99c02..e7dab35 100644 --- a/example/loggingservice_poll.ts +++ b/test/loggingservice_poll.ts @@ -15,7 +15,11 @@ let query: LsQueryCfg = { */ export async function main(): Promise { let c = await autoCredentials() - let ls = await LoggingService.factory(c, { fetchTimeout: 45000 }) + let ls = await LoggingService.factory(c, { + fetchTimeout: 45000, controlListener: x => { + console.log('Received control message\n', JSON.stringify(x, undefined, ' ')) + } + }) let job = await ls.query(query) let seq = job.sequenceNo if (job.queryStatus == "FINISHED") { diff --git a/test/pcap.js b/test/pcap.js new file mode 100644 index 0000000..f9f149b --- /dev/null +++ b/test/pcap.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const pancloud_nodejs_1 = require("pancloud-nodejs"); +const fs_1 = require("fs"); +let example = { "container": 0, "risk-of-app": "4", "logset": "lcaas", "natsport": 0, "sessionid": 27478, "url_denied": 0, "type": "threat", "url_idx": 2, "parent_start_time": 0, "characteristic-of-app": ["able-to-transfer-file", "has-known-vulnerability", "tunnel-other-application", "prone-to-misuse", "is-saas"], "dg_hier_level_4": 0, "http_method": "unknown", "dg_hier_level_1": 0, "dg_hier_level_3": 0, "dg_hier_level_2": 0, "action": "alert", "recsize": 3421, "from": "TAPZONE", "parent_session_id": 0, "repeatcnt": 1, "app": "web-browsing", "vsys": "vsys1", "nat": 0, "technology-of-app": "browser-based", "pbf_s2c": 0, "pbf_c2s": 0, "receive_time": 1548759463, "non-standard-dport": 0, "subcategory-of-app": "internet-utility", "pcap_id": 1204889238391226400, "users": "10.10.108.11", "ppid": -1, "captive_portal": 0, "is_gpaas": 0, "proxy": 0, "fwd": 1, "log_feat_bit1": 1, "config_ver": 2049, "cloud_hostname": "PA-VM", "is_fwaas": 0, "customer-id": "........", "is_dup_log": 0, "proto": "tcp", "non_std_dport": 0, "tunneled-app": "tunneled-app", "recon_excluded": 0, "is-saas-of-app": 0, "sig_flags": 0, "natdport": 0, "flag": 0, "dst": "195.208.1.108", "reportid": 0, "natdst": "0.0.0.0", "flags": -2147475456, "rule": "MonitorAll", "decrypt_mirror": 0, "contentver": 531829896, "dport": 80, "sanctioned-state-of-app": 0, "category-of-threatid": "unknown", "inbound_if": "ethernet1/1", "device_name": "PA-VM", "mptcp_on": 0, "ui-contentver": "AppThreat-8115-5256", "subtype": "vulnerability", "time_received": 1548759452, "actionflags": -6917529027641082000, "tunnelid_imsi": 0, "pcap": "AQADsgAABCAQuKBg6AAAYlxQMZ0AIwAjAA4AIgN8BZoAFAABAAmXLHoAACFZJ30MCABFAAWMZsBAADMGoFrD0AFsCgpsCwBQyZtmSziT4tS9y1AQAB+rmQAASFRUUC8xLjEgMjAwIE9LDQpTZXJ2ZXI6IG5naW54LzEuMTIuMg0KRGF0ZTogVHVlLCAyOSBKYW4gMjAxOSAxMDo1NzozMyBHTVQNCkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vamF2YXNjcmlwdA0KQ29udGVudC1MZW5ndGg6IDE4OTQ3DQpDb25uZWN0aW9uOiBrZWVwLWFsaXZlDQpMYXN0LU1vZGlmaWVkOiBUdWUsIDExIEp1bCAyMDE3IDIxOjI3OjM4IEdNVA0KRVRhZzogIjRhMDMtNTU0MTE1ZTZhODIwMCINCkFjY2VwdC1SYW5nZXM6IGJ5dGVzDQoNCgovKgogKiBqUXVlcnkgVG9vbHMgMS4yLjUgLSBUaGUgbWlzc2luZyBVSSBsaWJyYXJ5IGZvciB0aGUgV2ViCiAqCiAqIFtkYXRlaW5wdXQsIHJhbmdlaW5wdXQsIHZhbGlkYXRvcl0KICoKICogTk8gQ09QWVJJR0hUUyBPUiBMSUNFTlNFUy4gRE8gV0hBVCBZT1UgTElLRS4KICoKICogaHR0cDovL2Zsb3dwbGF5ZXIub3JnL3Rvb2xzLwogKgogKiBGaWxlIGdlbmVyYXRlZDogV2VkIEphbiAxMiAyMzoxMjoyMiBHTVQgMjAxMQogKi8KKGZ1bmN0aW9uKGQpe2Z1bmN0aW9uIFIoYSxjKXtyZXR1cm4gMzItKG5ldyBEYXRlKGEsYywzMikpLmdldERhdGUoKX1mdW5jdGlvbiBTKGEsYyl7YT0iIithO2ZvcihjPWN8fDI7YS5sZW5ndGg8YzspYT0iMCIrYTtyZXR1cm4gYX1mdW5jdGlvbiBUKGEsYyxqKXt2YXIgcT1hLmdldERhdGUoKSxoPWEuZ2V0RGF5KCkscj1hLmdldE1vbnRoKCk7YT1hLmdldEZ1bGxZZWFyKCk7dmFyIGY9e2Q6cSxkZDpTKHEpLGRkZDpCW2pdLnNob3J0RGF5c1toXSxkZGRkOkJbal0uZGF5c1toXSxtOnIrMSxtbTpTKHIrMSksbW1tOkJbal0uc2hvcnRNb250aHNbcl0sbW1tbTpCW2pdLm1vbnRoc1tyXSx5eTpTdHJpbmcoYSkuc2xpY2UoMikseXl5eTphfTtjPWMucmVwbGFjZShYLGZ1bmN0aW9uKHMpe2E3ZWEzOTRiNjgyY2E3YjVjZDM5MDgzZDc3OWFiZDINCg0KYWZ0ZXJMb2FkKCk7DQoJCQl9LCAxKTsNCgkJfSk7DQoJfTtLf+f6", "sym_return": 0, "name-of-threatid": "JavaScriptObfuscationDetected", "direction": "server-to-client", "misc": "elka-barrier.ru/custom/themes/default/jquery.tools.js", "threatid": 54261, "severity": "informational", "exported": 0, "natsrc": "0.0.0.0", "seqno": 20453797, "src": "10.10.108.11", "time_generated": 1548759452, "outbound_if": "ethernet1/1", "category-of-app": "general-internet", "srcloc": "10.0.0.0-10.255.255.255", "dstloc": "RU", "tunnel_inspected": 0, "serial": "", "vsys_id": 1, "ui-srcloc": "10.0.0.0-10.255.255.255", "to": "TAPZONE", "category": "business-and-economy", "sport": 51611, "packet_capture": 1, "tunnel": 0, "ui-dstloc": "RussianFederation", "transaction": 0, "is_phishing": 0 }; +let pcapBody = pancloud_nodejs_1.Util.pcaptize(example); +if (pcapBody) { + fs_1.writeFileSync('pcap.pcap', pcapBody); + console.log("PCAP data saved as 'pcap.pcap'"); +} +else { + console.log("Event without PCAP data"); +} diff --git a/example/pcap.ts b/test/pcap.ts similarity index 100% rename from example/pcap.ts rename to test/pcap.ts diff --git a/example/tsconfig.json b/test/tsconfig.json similarity index 66% rename from example/tsconfig.json rename to test/tsconfig.json index ee502c6..ca6d7aa 100644 --- a/example/tsconfig.json +++ b/test/tsconfig.json @@ -4,7 +4,12 @@ "strictNullChecks": true, "target": "es2017", "module": "commonjs", - "baseUrl": "../.." + "baseUrl": ".", + "paths": { + "pancloud-nodejs": [ + "../dist" + ] + } }, "include": [ "*.ts" diff --git a/tsconfig.json b/tsconfig.json index b22b581..dfb4b03 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "declaration": true, "target": "es2017", "module": "commonjs", + "moduleResolution": "node", "baseUrl": ".", "outDir": "dist" },