commit f3587ac821ef451ad6bb395f5adb4e5889397b7d Author: frikky Date: Mon May 11 19:17:35 2020 +0200 Initial open source commit diff --git a/.env b/.env new file mode 100644 index 00000000..cf00a013 --- /dev/null +++ b/.env @@ -0,0 +1,8 @@ +# Default execution environment for workers +ORG_ID=Shuffle +ENVIRONMENT_NAME=Shuffle + +# Other configs +BACKEND_HOSTNAME=shuffle-backend +BACKEND_PORT=5010 +OUTER_HOSTNAME=192.168.3.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f637768c --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*node_modules/ +*build/ +*.lock +*.swo +*.swp +*.swn +*__pycache__* + +Shuffle-*.json + +functions/generated_apps +*.zip + +*.png +*.jpeg +*.jpg + +backend/onprem/app_sdk/apps +*test.py diff --git a/README.md b/README.md new file mode 100644 index 00000000..84d99b83 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# Shuffle +[Shuffler](https://shuffler.io) is an automation platform for your security stack. It leverages docker for scaling and OpenAPI for integrations. It has the possibility to run across multiple isolated environments, and gives you powerful tools to track progress. + +## Try it +Check out the [installation guide](https://github.com/frikky/shuffle/blob/master/install-guide.md) + +## Documentation +Documentation can be found on https://shuffler.io/docs/about or in your own instance. Currently lacking: +* API documentation +* Updates after migrating from SaaS to open source + +## Features +* Premade workflows for Email, TheHive, C rtex MISP +* Premade apps for a number of security tools +* Simple workflow editor +* App creator for [OpenAPI](https://github.com/frikky/OpenAPI-security-definitions) +* Easy to learn Python library for custom apps + +## License +Shuffle is an open source and free software released under the AGPL (Affero General Public License). + +### Setup - Local +Frontend - requires [npm](https://nodejs.org/en/download/)/[yarn](https://yarnpkg.com/lang/en/docs/install/#debian-stable)/your preferred manager. Runs independently from backend - edit frontend/src/App.yaml to change from localhost to prod setting. +```bash +cd frontend +npm i +npm start +``` + +Backend - API calls - requires [>=go1.13](https://golang.org/dl/) and [gcloud](https://cloud.google.com/sdk/install) +```bash +cd backend/go-app +go build +sudo apt -y update && sudo apt -y upgrade && sudo apt install -y google-cloud-sdk-app-engine-python google-cloud-sdk-app-engine-python google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-go +go run *.go +``` + +### Project overview +Below is the folder structure with a short explanation +```bash +├── README.md # :) +├── deploy.sh # Simple oneliner script to build and deploy the code to gcloud +├── backend # Contains directly backend related code. Go with sh tests +├── frontend # Contains frontend code. ReactJS and cytoscape. Horrible code :) +├── app_gen # Contains code generation projects for OpenAPI or PythonLib -> Shuffler app +├── functions # Contains google cloud function code mainly. +│   ├── apps # Some of the existing apps, manually made mostly +│   ├── generated_apps # Some of the autogenerated apps +│   ├── newworker # The worker that handles a workflow as a google cloud function +│   ├── static_baseline.py # Static code used by stitcher.go to generate code +│   ├── stitcher.go # Attempts to stitch together an app and deploy it to cloud functions and (TBD: Docker hub) +│   └── triggers # Custom triggers used in https://shuffler.io/workflows +│   ├── onprem # All code for onprem solutions (https://shuffler.io/docs/hybrid for short doc) _mostly_ reflects google cloud. Should be deprecated somehow and use the same code. +├── openintegrationhub # Here to remind me that openintegrationhub is a thing +├── legacy # Legacy README. Contains A LOT of useful information about what I found with WALKOFF +└── tmp # Some legacy code, not yet ready to be removed +``` + +# Architecture +A basic image of how everything fits together, including legacy (left side) +![](architecture.png) + +# Technology +GCP was chosen because why not use the best thingies. "Serverless" \o/ +```bash +├── languages +│   ├── Go # I like go, which is why go. +│   ├── Python3.7 # 3.7 specifically because of f-strings and 2.7 deprecation in 2020 +│   ├── Javascript # Frontend stuff. Uses ReactJS + Cytoscape for visualization +│   ├── sh/Bash # Basic testing and some deployment stuff +├── gcloud +│   ├── appengine # Hosting frontend and backend. Currently on a free plan which is nice :) +│   ├── cloud functions # Runs the "apps", "triggers" and other things +│   ├── datastore # Database - TODO before live: Move to firebase +│   ├── storage # Save datablobs and information before deployment +│   ├── pubsub # Used to instantly run cloud functions +│   ├── scheduler # Schedules can be triggers +├── onprem +│   ├── Docker # Runs the same cloud functions. I didn't like the thought of proxies +``` + +# Current focus(es) AKA todo +1. Make workflows work 99%+ of the time. This is a challenge with onprem + cloud stuff. Cloud sometimes breaks currently because of workers +2. Add user run statistics (e.g. how many runs of each workflow, how many failures etc.) +3. X - Fix OpenAPI app generator +4. Fix error overview in workflows +5. X - Better GUI (improved, but not good) +6. Have default workflows + +# How to Add a trigger / custom thing +1. Add it to TriggersView in AngularWorkflow.js +2. Add it to RightSideBar for trigger + +# Migration +There will be a major overhaul to the backend specifically. I'm currently moving and updating the following: +- Create dockerfiles and a single runscript +- * App creator - (Cloud function -> Docker) +- * Workflows - Run workflows locally +- * App list - IMPORT EXISTING APPS +- * Dockerfiles - Load the ones that are in workflows with a new version +- * Docker-compose- Frontend, backend, db & orborus +- * Configuration - Write setup documentation - Did for docker +- Workflows - IMPORT DEFAULT WORKFLOWS - Create some towards e.g. TheHive & MISP. +- Documentation - General documentation /docs rewrite +- API doc - 1. In Shuffle. 2. In e.g. python +- Remove orborus? Can deploy straight, but that would be weird. +- Add secret to orborus +- Change workflow name +- Remove registration and add user screen +- Add external and internal hostname for orborus & worker + +``` +# 1. export DATASTORE_EMULATOR_HOST=0.0.0.0:8000 +# 2. docker run -p 8000:8000 google/cloud-sdk gcloud beta emulators datastore start --project=shuffle --host-port 0.0.0.0:8000 --no-store-on-disk +``` +* Mail: Use appengine and connect to sendmail + +### Migration issues: +* Some workflows where items have multiple parents don't work. +* Fix dummy.json (GCP config) - bypass this somehow. diff --git a/app_gen/openapi-parsers/generated/carbon_black_response.yaml b/app_gen/openapi-parsers/generated/carbon_black_response.yaml new file mode 100644 index 00000000..124f2968 --- /dev/null +++ b/app_gen/openapi-parsers/generated/carbon_black_response.yaml @@ -0,0 +1,235 @@ +components: + schemas: + tmp0: + properties: + cb_version: + type: string + company_name: + type: string + copied_mod_len: + type: string + digsig_issuer: + type: string + digsig_prog_name: + type: string + digsig_publisher: + type: string + digsig_result: + type: string + digsig_result_code: + type: string + digsig_sign_time: + type: string + digsig_subject: + type: string + endpoint: + type: string + event_partition_id: + type: string + facet_id: + type: string + file_desc: + type: string + file_version: + type: string + group: + type: string + host_count: + type: string + internal_name: + type: string + is_64bit: + type: string + is_executable_image: + type: string + last_seen: + type: string + legal_copyright: + type: string + md5: + type: string + observed_filename: + type: string + orig_mod_len: + type: string + original_filename: + type: string + os_type: + type: string + product_name: + type: string + product_version: + type: string + server_added_timestamp: + type: string + signed: + type: string + timestamp: + type: string + watchlists: + type: string + type: object + tmp1: + properties: + message: + type: string + type: object + tmp2: + properties: + childproc_count: + type: string + cmdline: + type: string + comms_ip: + type: string + crossproc_count: + type: string + emet_config: + type: string + emet_count: + type: string + filemod_count: + type: string + filtering_known_dlls: + type: string + group: + type: string + host_type: + type: string + hostname: + type: string + id: + type: string + interface_ip: + type: string + last_server_update: + type: string + last_update: + type: string + modload_count: + type: string + netconn_count: + type: string + os_type: + type: string + parent_id: + type: string + parent_name: + type: string + parent_pid: + type: string + parent_unique_id: + type: string + path: + type: string + process_md5: + type: string + process_name: + type: string + process_pid: + type: string + processblock_count: + type: string + regmod_count: + type: string + segment_id: + type: string + sensor_id: + type: string + start: + type: string + terminated: + type: string + unique_id: + type: string + username: + type: string + type: object + tmp3: + properties: + message: + type: string + type: object + securitySchemes: {} +info: + contact: + email: frikky@shuffler.io + name: '@frikkylikeme' + url: https://twitter.com/frikkylikeme + description: Automated generation of Carbon Black Response + title: Carbon Black Response + version: 1.0.0 +openapi: 3.0.2 +paths: + tmp0: + post: + description: Carbon Black Response Binary Search + parameters: + - description: Query + in: query + name: Query + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp0' + description: Successful request + summary: Carbon Black Response Binary Search + tmp1: + post: + description: Carbon Black Response Isolate Sensor + parameters: + - description: Hostname of a sensor to isolate. + in: query + name: Hostname + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp1' + description: Successful request + summary: Carbon Black Response Isolate Sensor + tmp2: + post: + description: Carbon Black Response Process Search + parameters: + - description: Query + in: query + name: Query + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp2' + description: Successful request + summary: Carbon Black Response Process Search + tmp3: + post: + description: Carbon Black Response Unisolate Sensor + parameters: + - description: Hostname of a sensor to unisolate. + in: query + name: Hostname + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp3' + description: Successful request + summary: Carbon Black Response Unisolate Sensor diff --git a/app_gen/openapi-parsers/generated/cyberreason.yaml b/app_gen/openapi-parsers/generated/cyberreason.yaml new file mode 100644 index 00000000..70e10109 --- /dev/null +++ b/app_gen/openapi-parsers/generated/cyberreason.yaml @@ -0,0 +1,559 @@ +components: + schemas: + tmp0: + properties: + outcome: + type: string + type: object + tmp1: + properties: + allRansomwareProcessesSuspended: + type: string + closeTime: + type: string + closerName: + type: string + creationTime: + type: string + customClassification: + type: string + decisionFeature: + type: string + detectionType: + type: string + elementDisplayName: + type: string + hasRansomwareSuspendedProcesses: + type: string + isBlocked: + type: string + malopActivityTypes: + type: string + malopLastUpdateTime: + type: string + malopStartTime: + type: string + managementStatus: + type: string + rootCauseElementNames: + type: string + rootCauseElementTypes: + type: string + type: object + tmp2: + properties: + blocking: + type: string + direction: + type: string + domain: + type: string + ipAddress: + type: string + ipAddressString: + type: string + lastUpdated: + type: string + port: + type: string + ruleId: + type: string + type: object + tmp3: + properties: + blacklistClassification: + type: string + classificationLink: + type: string + containsTorDomain: + type: string + domainClassificationSuspicion: + type: string + domainNameUniqueId: + type: string + elementDisplayName: + type: string + everResolvedDomain: + type: string + everResolvedSecondLevelDomain: + type: string + extendedDisplayId: + type: string + federationSegments: + type: string + getIpDiscoveryDomainList: + type: string + hasMalops: + type: string + hasResolvedClassificationEvidence: + type: string + hasSuspicions: + type: string + indifferentClassification: + type: string + isDomainMalicious: + type: string + isInIpDiscoveryDomainList: + type: string + isInternalDomain: + type: string + isInternalDomainByName: + type: string + isReverseLookup: + type: string + isTorrentDomain: + type: string + lookupDomainClassification: + type: string + maliciousClassification: + type: string + maliciousClassificationString: + type: string + maliciousClassificationType: + type: string + malwareClassification: + type: string + name: + type: string + relatedToMalop: + type: string + rootCauseKey: + type: string + secondLevelDomain: + type: string + sinkholedClassification: + type: string + sinkholedClassificationEvidence: + type: string + topLevelDomain: + type: string + unknownClassification: + type: string + unresolvedClassification: + type: string + unwantedClassification: + type: string + whitelistClassification: + type: string + type: object + tmp4: + properties: + attemptExecutionProcessSuspicion: + type: string + attributes: + type: string + blackListClassification: + type: string + canonizedPath: + type: string + classificationLink: + type: string + correctedPath: + type: string + createdTime: + type: string + detectionEventData: + type: string + dualExtensionName: + type: string + elementDisplayName: + type: string + extendedDisplayId: + type: string + extension: + type: string + extensionRecord: + type: string + extensionType: + type: string + externalProductClassification: + type: string + externalProductClassificationIsSigned: + type: string + externalProductClassificationSignatureVerificationStatus: + type: string + externalProductClassificationSignatureVerified: + type: string + externalProductClassificationSignatureVerifiedByVerificationStatus: + type: string + externalProductClassificationType: + type: string + federationSegments: + type: string + fileHasSystem32SubDirectories: + type: string + fileHasSystemSubDirectories: + type: string + fileHashUniqueId: + type: string + fileInSystemPath: + type: string + fileReputationSuspicion: + type: string + fileReputationSuspicionDecision: + type: string + fileVersionSuspicionDecision: + type: string + getNetworkScannersNames: + type: string + getToolsForUnusualNetworkEvidence: + type: string + hackingToolClassification: + type: string + hasAutorun: + type: string + hasClassification: + type: string + hasInternalName: + type: string + hasLegitClassification: + type: string + hasMalops: + type: string + hasNonLegitClassification: + type: string + hasNonLegitClassificationEvidence: + type: string + hasProductClassification: + type: string + hasRansomwareClassificationSubType: + type: string + hasSuspicions: + type: string + identifiedProduct: + type: string + indifferentClassification: + type: string + isDocument: + type: string + isExecutable: + type: string + isFromRemovableDevice: + type: string + isFromTemp: + type: string + isNoTypeFoundClassification: + type: string + isPEFile: + type: string + isProcessImageFile: + type: string + isScreenSaver: + type: string + isSigned: + type: string + isSuspicious: + type: string + lastDetectionEventData: + type: string + maliciousClassification: + type: string + maliciousClassificationString: + type: string + maliciousClassificationType: + type: string + maliciousToolClassification: + type: string + malwareClassification: + type: string + malwareClassificationEvidence: + type: string + malwareType: + type: string + md5: + type: string + md5String: + type: string + missingInterperterSectionValue: + type: string + modifiedTime: + type: string + name: + type: string + nameWithoutExtension: + type: string + path: + type: string + peSignedAndVerified: + type: string + productClassificationType: + type: string + productType: + type: string + profileId: + type: string + ransomwareClassification: + type: string + reasonSignatureVerificationStatus: + type: string + relatedToMalop: + type: string + reportedByAntiMalwareEvidence: + type: string + reportedByAntiMalwareSuspicion: + type: string + rootCauseKey: + type: string + sha1: + type: string + sha1String: + type: string + signatureVerified: + type: string + signatureVerifiedByVerificationStatus: + type: string + signatureVerifiedInternalOrExternal: + type: string + signedByApple: + type: string + signedByLinux: + type: string + signedByMicrosoft: + type: string + signedByOperatingSystem: + type: string + signedInternalOrExternal: + type: string + size: + type: string + suspiciousClassification: + type: string + suspiciousScreenSaverCondition: + type: string + unknownClassification: + type: string + unsignedPeFileEvidence: + type: string + unwantedClassification: + type: string + whitelistClassification: + type: string + type: object + tmp5: + properties: + address: + type: string + addressInternalExternalLocal: + type: string + addressString: + type: string + blackListClassification: + type: string + countryCode: + type: string + countryName: + type: string + countryNameOrNotExternalType: + type: string + elementDisplayName: + type: string + extendedDisplayId: + type: string + federationSegments: + type: string + geolocationLookup: + type: string + hasMalops: + type: string + hasSuspicions: + type: string + isDynamicConfiguration: + type: string + isExternalAddress: + type: string + isInternalAddress: + type: string + isLocalAddress: + type: string + latitude: + type: string + longitude: + type: string + lookupIpClassification: + type: string + maliciousClassification: + type: string + maliciousClassificationType: + type: string + rootCauseKey: + type: string + uniqueIpAddressHash: + type: string + version: + type: string + whiteListClassification: + type: string + type: object + securitySchemes: {} +info: + contact: + email: frikky@shuffler.io + name: '@frikkylikeme' + url: https://twitter.com/frikkylikeme + description: Automated generation of Cyberreason + title: Cyberreason + version: 1.0.0 +openapi: 3.0.2 +paths: + tmp0: + post: + description: CyberReason Block or Unblock an Item + parameters: + - description: Either 'blacklist' or 'whitelist' + in: query + name: Action + required: true + schema: + type: string + - description: IP, Domain, or Hash CSV values to blacklist or whitelist + in: query + name: Values + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp0' + description: Successful request + summary: CyberReason Block Item + tmp1: + post: + description: CyberReason get alerts from MalOps + parameters: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp1' + description: Successful request + summary: CyberReason Get MalOps Alerts + tmp2: + post: + description: CyberReason isolate a host by port + parameters: + - description: IP to block + in: query + name: IP + required: true + schema: + type: string + - description: Direction to block traffic, one of 'ALL', 'INCOMING', 'OUTGOING' + in: query + name: Direction to block + required: true + schema: + type: string + - description: Port to block + in: query + name: Port + required: true + schema: + type: string + - description: If true will isolate, false will remove from isolation + in: query + name: Block + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp2' + description: Successful request + summary: CyberReason Isolate Host + tmp3: + post: + description: Check CyberReason for a domain + parameters: + - description: Limit of results to return + in: body + name: Limit + required: false + schema: + type: string + - description: Timeout + in: body + name: Timeout (ms) + required: false + schema: + type: string + - description: Domain to search for + in: query + name: Domain + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp3' + description: Successful request + summary: CyberReason Query for Domain + tmp4: + post: + description: Check CyberReason for a hash + parameters: + - description: Limit of results to return + in: body + name: Limit + required: false + schema: + type: string + - description: Timeout + in: body + name: Timeout (ms) + required: false + schema: + type: string + - description: Hash to search for + in: query + name: Hash + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp4' + description: Successful request + summary: CyberReason Query for Hash + tmp5: + post: + description: Check CyberReason for a IP + parameters: + - description: Limit of results to return + in: body + name: Limit + required: false + schema: + type: string + - description: Timeout + in: body + name: Timeout (ms) + required: false + schema: + type: string + - description: IP to search for + in: query + name: IP + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp5' + description: Successful request + summary: CyberReason Query for IP diff --git a/app_gen/openapi-parsers/generated/recorded_future.yaml b/app_gen/openapi-parsers/generated/recorded_future.yaml new file mode 100644 index 00000000..2c3bc20e --- /dev/null +++ b/app_gen/openapi-parsers/generated/recorded_future.yaml @@ -0,0 +1,1383 @@ +components: + schemas: + tmp0: + properties: + counts_returned: + type: string + counts_total: + type: string + data_results: + type: string + type: object + tmp1: + properties: + data_entity_id: + type: string + data_entity_name: + type: string + data_entity_type: + type: string + data_timestamps_firstSeen: + type: string + data_timestamps_lastSeen: + type: string + error_message: + type: string + type: object + tmp10: + properties: + analystNotes: + type: string + counts_count: + type: string + counts_date: + type: string + entity_id: + type: string + entity_name: + type: string + entity_type: + type: string + error_message: + type: string + intelCard: + type: string + location_asn: + type: string + location_cidr_id: + type: string + location_cidr_name: + type: string + location_cidr_type: + type: string + location_location_city: + type: string + location_location_continent: + type: string + location_location_country: + type: string + location_organization: + type: string + metrics_type: + type: string + metrics_value: + type: string + relatedEntities_entities_count: + type: string + relatedEntities_entities_entity_id: + type: string + relatedEntities_entities_entity_name: + type: string + relatedEntities_entities_entity_type: + type: string + relatedEntities_type: + type: string + risk_criticality: + type: string + risk_criticalityLabel: + type: string + risk_evidenceDetails_criticality: + type: string + risk_evidenceDetails_criticalityLabel: + type: string + risk_evidenceDetails_evidenceString: + type: string + risk_evidenceDetails_mitigationString: + type: string + risk_evidenceDetails_rule: + type: string + risk_evidenceDetails_timestamp: + type: string + risk_riskString: + type: string + risk_riskSummary: + type: string + risk_rules: + type: string + risk_score: + type: string + riskyCIDRIPs_ip_id: + type: string + riskyCIDRIPs_ip_name: + type: string + riskyCIDRIPs_ip_type: + type: string + riskyCIDRIPs_score: + type: string + sightings_fragment: + type: string + sightings_published: + type: string + sightings_source: + type: string + sightings_title: + type: string + sightings_type: + type: string + sightings_url: + type: string + threatLists_description: + type: string + threatLists_id: + type: string + threatLists_name: + type: string + threatLists_type: + type: string + timestamps_firstSeen: + type: string + timestamps_lastSeen: + type: string + type: object + tmp11: + properties: + analystNotes_attributes_context_entities_id: + type: string + analystNotes_attributes_context_entities_name: + type: string + analystNotes_attributes_context_entities_type: + type: string + analystNotes_attributes_note_entities_id: + type: string + analystNotes_attributes_note_entities_name: + type: string + analystNotes_attributes_note_entities_type: + type: string + analystNotes_attributes_published: + type: string + analystNotes_attributes_text: + type: string + analystNotes_attributes_title: + type: string + analystNotes_attributes_topic_id: + type: string + analystNotes_attributes_topic_name: + type: string + analystNotes_attributes_topic_type: + type: string + analystNotes_attributes_validated_on: + type: string + analystNotes_attributes_validation_urls_id: + type: string + analystNotes_attributes_validation_urls_name: + type: string + analystNotes_attributes_validation_urls_type: + type: string + analystNotes_id: + type: string + analystNotes_source_id: + type: string + analystNotes_source_name: + type: string + analystNotes_source_type: + type: string + counts_count: + type: string + counts_date: + type: string + entity_id: + type: string + entity_name: + type: string + entity_type: + type: string + error_message: + type: string + intelCard: + type: string + metrics_type: + type: string + metrics_value: + type: string + relatedEntities_entities_count: + type: string + relatedEntities_entities_entity_id: + type: string + relatedEntities_entities_entity_name: + type: string + relatedEntities_entities_entity_type: + type: string + relatedEntities_type: + type: string + sightings_fragment: + type: string + sightings_published: + type: string + sightings_source: + type: string + sightings_title: + type: string + sightings_type: + type: string + sightings_url: + type: string + timestamps_firstSeen: + type: string + timestamps_lastSeen: + type: string + type: object + tmp12: + properties: + Criticality: + type: string + CriticalityLabel: + type: string + EvidenceString: + type: string + MitigationString: + type: string + Name: + type: string + Risk: + type: string + RiskString: + type: string + Rule: + type: string + Timestamp: + type: string + type: object + tmp13: + properties: + count: + type: string + criticality: + type: string + criticalityLabel: + type: string + description: + type: string + name: + type: string + type: object + tmp14: + properties: + analystNotes: + type: string + counts_count: + type: string + counts_date: + type: string + entity_id: + type: string + entity_name: + type: string + entity_type: + type: string + error_message: + type: string + metrics_type: + type: string + metrics_value: + type: string + relatedEntities: + type: string + risk_criticality: + type: string + risk_criticalityLabel: + type: string + risk_evidenceDetails_criticality: + type: string + risk_evidenceDetails_criticalityLabel: + type: string + risk_evidenceDetails_evidenceString: + type: string + risk_evidenceDetails_mitigationString: + type: string + risk_evidenceDetails_rule: + type: string + risk_evidenceDetails_timestamp: + type: string + risk_riskString: + type: string + risk_riskSummary: + type: string + risk_rules: + type: string + risk_score: + type: string + sightings: + type: string + timestamps_firstSeen: + type: string + timestamps_lastSeen: + type: string + type: object + tmp15: + properties: + Criticality: + type: string + CriticalityLabel: + type: string + EvidenceString: + type: string + MitigationString: + type: string + Name: + type: string + Risk: + type: string + RiskString: + type: string + Rule: + type: string + Timestamp: + type: string + type: object + tmp16: + properties: + count: + type: string + criticality: + type: string + criticalityLabel: + type: string + description: + type: string + name: + type: string + type: object + tmp17: + properties: + analystNotes: + type: string + commonNames: + type: string + counts_count: + type: string + counts_date: + type: string + cpe: + type: string + cpe22uri: + type: string + entity_description: + type: string + entity_id: + type: string + entity_name: + type: string + entity_type: + type: string + error_message: + type: string + intelCard: + type: string + metrics_type: + type: string + metrics_value: + type: string + nvdDescription: + type: string + rawrisk_rule: + type: string + rawrisk_timestamp: + type: string + relatedEntities_entities_count: + type: string + relatedEntities_entities_entity_description: + type: string + relatedEntities_entities_entity_id: + type: string + relatedEntities_entities_entity_name: + type: string + relatedEntities_entities_entity_type: + type: string + relatedEntities_type: + type: string + relatedLinks: + type: string + risk_criticality: + type: string + risk_criticalityLabel: + type: string + risk_evidenceDetails_criticality: + type: string + risk_evidenceDetails_criticalityLabel: + type: string + risk_evidenceDetails_evidenceString: + type: string + risk_evidenceDetails_mitigationString: + type: string + risk_evidenceDetails_rule: + type: string + risk_evidenceDetails_timestamp: + type: string + risk_riskString: + type: string + risk_riskSummary: + type: string + risk_rules: + type: string + risk_score: + type: string + sightings_fragment: + type: string + sightings_published: + type: string + sightings_source: + type: string + sightings_title: + type: string + sightings_type: + type: string + sightings_url: + type: string + threatLists: + type: string + timestamps_firstSeen: + type: string + timestamps_lastSeen: + type: string + type: object + tmp2: + properties: + Criticality: + type: string + CriticalityLabel: + type: string + EvidenceString: + type: string + MitigationString: + type: string + Name: + type: string + Risk: + type: string + RiskString: + type: string + Rule: + type: string + Timestamp: + type: string + type: object + tmp3: + properties: + count: + type: string + criticality: + type: string + criticalityLabel: + type: string + description: + type: string + name: + type: string + type: object + tmp4: + properties: + analystNotes: + type: string + counts_count: + type: string + counts_date: + type: string + entity_id: + type: string + entity_name: + type: string + entity_type: + type: string + error_message: + type: string + intelCard: + type: string + metrics_type: + type: string + metrics_value: + type: string + relatedEntities_entities_count: + type: string + relatedEntities_entities_entity_id: + type: string + relatedEntities_entities_entity_name: + type: string + relatedEntities_entities_entity_type: + type: string + relatedEntities_type: + type: string + risk_criticality: + type: string + risk_criticalityLabel: + type: string + risk_evidenceDetails_criticality: + type: string + risk_evidenceDetails_criticalityLabel: + type: string + risk_evidenceDetails_evidenceString: + type: string + risk_evidenceDetails_mitigationString: + type: string + risk_evidenceDetails_rule: + type: string + risk_evidenceDetails_timestamp: + type: string + risk_riskString: + type: string + risk_riskSummary: + type: string + risk_rules: + type: string + risk_score: + type: string + sightings_fragment: + type: string + sightings_published: + type: string + sightings_source: + type: string + sightings_title: + type: string + sightings_type: + type: string + sightings_url: + type: string + threatLists: + type: string + timestamps_firstSeen: + type: string + timestamps_lastSeen: + type: string + type: object + tmp5: + properties: + Criticality: + type: string + CriticalityLabel: + type: string + EvidenceString: + type: string + MitigationString: + type: string + Name: + type: string + Risk: + type: string + RiskString: + type: string + Rule: + type: string + Timestamp: + type: string + type: object + tmp6: + properties: + count: + type: string + criticality: + type: string + criticalityLabel: + type: string + description: + type: string + name: + type: string + type: object + tmp7: + properties: + analystNotes: + type: string + counts_count: + type: string + counts_date: + type: string + entity_id: + type: string + entity_name: + type: string + entity_type: + type: string + error_message: + type: string + hashAlgorithm: + type: string + intelCard: + type: string + metrics_type: + type: string + metrics_value: + type: string + relatedEntities_entities_count: + type: string + relatedEntities_entities_entity_id: + type: string + relatedEntities_entities_entity_name: + type: string + relatedEntities_entities_entity_type: + type: string + relatedEntities_type: + type: string + risk_criticality: + type: string + risk_criticalityLabel: + type: string + risk_evidenceDetails: + type: string + risk_riskString: + type: string + risk_riskSummary: + type: string + risk_rules: + type: string + risk_score: + type: string + sightings_fragment: + type: string + sightings_published: + type: string + sightings_source: + type: string + sightings_title: + type: string + sightings_type: + type: string + sightings_url: + type: string + threatLists: + type: string + timestamps_firstSeen: + type: string + timestamps_lastSeen: + type: string + type: object + tmp8: + properties: + Criticality: + type: string + CriticalityLabel: + type: string + EvidenceString: + type: string + MitigationString: + type: string + Name: + type: string + Risk: + type: string + RiskString: + type: string + Rule: + type: string + Timestamp: + type: string + type: object + tmp9: + properties: + count: + type: string + criticality: + type: string + criticalityLabel: + type: string + description: + type: string + name: + type: string + type: object + securitySchemes: {} +info: + contact: + email: frikky@shuffler.io + name: '@frikkylikeme' + url: https://twitter.com/frikkylikeme + description: Automated generation of Recorded Future + title: Recorded Future + version: 1.0.0 +openapi: 3.0.2 +paths: + tmp0: + post: + description: Search Alert Rules + parameters: + - description: Maximum number of records. + in: body + name: Limit + required: false + schema: + type: string + - description: Freetext search for an alert. + in: query + name: Freetext Search + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp0' + description: Successful request + summary: Recorded Future Search Alert Rules + tmp1: + post: + description: Lookup Alert Notification + parameters: + - description: Alert ID + in: query + name: Alert ID + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp1' + description: Successful request + summary: Recorded Future Lookup Alert Notification + tmp10: + post: + description: Lookup IP Address + parameters: + - description: Whether to include threat lists fields in output + in: query + name: Threat Lists + required: true + schema: + type: boolean + - description: Whether to include risk fields in output + in: query + name: Risk + required: true + schema: + type: boolean + - description: Whether to include risky CIDR IPs fields in output + in: query + name: Risky CIDR IPs + required: true + schema: + type: boolean + - description: IP Address to lookup. + in: query + name: IP Address + required: true + schema: + type: string + - description: Whether to include sightings fields in output + in: query + name: Sightings + required: true + schema: + type: boolean + - description: Whether to include entity fields in output + in: query + name: Entity + required: true + schema: + type: boolean + - description: Whether to include metrics fields in output + in: query + name: Metrics + required: true + schema: + type: boolean + - description: Whether to include intel card fields in output + in: query + name: Intel Card + required: true + schema: + type: boolean + - description: Whether to include location in output + in: query + name: Location + required: true + schema: + type: boolean + - description: Whether to include timestamps fields in output + in: query + name: Timestamps + required: true + schema: + type: boolean + - description: Whether to include counts fields in output + in: query + name: Counts + required: true + schema: + type: boolean + - description: Whether to include related entities fields in output + in: query + name: Related Entities + required: true + schema: + type: boolean + - description: Whether to include analyst notes fields in output + in: query + name: Analyst Notes + required: true + schema: + type: boolean + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp10' + description: Successful request + summary: Recorded Future Lookup IP Address + tmp11: + post: + description: Lookup Malware + parameters: + - description: Malware name or ID to lookup + in: query + name: Malware + required: true + schema: + type: string + - description: Whether to include sightings fields in output + in: query + name: Sightings + required: true + schema: + type: boolean + - description: Whether to include entity fields in output + in: query + name: Entity + required: true + schema: + type: boolean + - description: Whether to include metrics fields in output + in: query + name: Metrics + required: true + schema: + type: boolean + - description: Whether to include intel card fields in output + in: query + name: Intel Card + required: true + schema: + type: boolean + - description: Whether to include analyst notes fields in output + in: query + name: Analyst Notes + required: true + schema: + type: boolean + - description: Whether to include timestamps fields in output + in: query + name: Timestamps + required: true + schema: + type: boolean + - description: Whether to include counts fields in output + in: query + name: Counts + required: true + schema: + type: boolean + - description: Whether to include related entities fields in output + in: query + name: Related Entities + required: true + schema: + type: boolean + - description: Whether to include categories fields in output + in: query + name: Categories + required: true + schema: + type: boolean + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp11' + description: Successful request + summary: Recorded Future Lookup Malware + tmp12: + post: + description: Get URL Risk List + parameters: + - description: Limit content to entities matching a category/rule. + in: query + name: Category + required: false + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp12' + description: Successful request + summary: Recorded Future Get URL Risk List + tmp13: + post: + description: URL Risk Rules + parameters: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp13' + description: Successful request + summary: Recorded Future List URL Risk Rules + tmp14: + post: + description: Recorded Future Lookup URL + parameters: + - description: Whether to include risk fields in output + in: query + name: Risk + required: true + schema: + type: boolean + - description: URL to lookup + in: query + name: URL + required: true + schema: + type: string + - description: Whether to include sightings fields in output + in: query + name: Sightings + required: true + schema: + type: boolean + - description: Whether to include related entities fields in output + in: query + name: Related Entities + required: true + schema: + type: boolean + - description: Whether to include metrics fields in output + in: query + name: Metrics + required: true + schema: + type: boolean + - description: Whether to include analyst notes fields in output + in: query + name: Analyst Notes + required: true + schema: + type: boolean + - description: Whether ot include timestamps fields in output + in: query + name: Timestamps + required: true + schema: + type: boolean + - description: Whether to include counts fields in output + in: query + name: Counts + required: true + schema: + type: boolean + - description: Whether to include entity fields in output + in: query + name: Entity + required: true + schema: + type: boolean + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp14' + description: Successful request + summary: Recorded Future Lookup URL + tmp15: + post: + description: Get Vulnerability Risk List + parameters: + - description: Limit content to entities matching a category/rule. + in: query + name: Category + required: false + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp15' + description: Successful request + summary: Recorded Future Get Vulnerability Risk List + tmp16: + post: + description: List Vulnerability Risk Rules + parameters: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp16' + description: Successful request + summary: Recorded Future List Vulnerability Risk Rules + tmp17: + post: + description: Lookup Vulnerability + parameters: + - description: Whether to include related links in the output + in: query + name: Related Links + required: true + schema: + type: boolean + - description: Whether to include NVD description fields in output + in: query + name: NVD Description + required: true + schema: + type: boolean + - description: Whether to include risk fields in output + in: query + name: Risk + required: true + schema: + type: boolean + - description: Whether to include CPE 2.2 URI fields in output + in: query + name: CPE 2.2 URI + required: true + schema: + type: boolean + - description: Whether to include common names fields in output + in: query + name: Common Names + required: true + schema: + type: boolean + - description: Vulnerability to lookup + in: query + name: Vulnerability + required: true + schema: + type: string + - description: Whether to include CPE fields in output + in: query + name: CPE + required: true + schema: + type: boolean + - description: Whether to include threat lists fields in output + in: query + name: Threat Lists + required: true + schema: + type: boolean + - description: Whether to include entity fields in output + in: query + name: Entity + required: true + schema: + type: boolean + - description: Whether to include metrics fields in output + in: query + name: Metrics + required: true + schema: + type: boolean + - description: Whether to include intel card fields in output + in: query + name: Intel Card + required: true + schema: + type: boolean + - description: Whether to include analyst notes fields in output + in: query + name: Analyst Notes + required: true + schema: + type: boolean + - description: Whether to include raw risk fields in output + in: query + name: Raw Risk + required: true + schema: + type: boolean + - description: Whether to include timestamps fields in output + in: query + name: Timestamps + required: true + schema: + type: boolean + - description: Whether to include counts fields in output + in: query + name: Counts + required: true + schema: + type: boolean + - description: Whether to include related entities fields in output + in: query + name: Related Entities + required: true + schema: + type: boolean + - description: Whether to include sightings fields in output + in: query + name: Sightings + required: true + schema: + type: boolean + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp17' + description: Successful request + summary: Recorded Future Lookup Vulnerability + tmp2: + post: + description: Get Domain Risk List + parameters: + - description: Limit content to entities matching a category/rule. + in: query + name: Category + required: false + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp2' + description: Successful request + summary: Recorded Future Get Domain Risk List + tmp3: + post: + description: List Domain Risk Rules + parameters: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp3' + description: Successful request + summary: Recorded Future List Domain Risk Rules + tmp4: + post: + description: Lookup Domain + parameters: + - description: Whether to include threat lists fields in output + in: query + name: Threat Lists + required: true + schema: + type: boolean + - description: Domain name to lookup. + in: query + name: Domain + required: true + schema: + type: string + - description: Whether to include risk fields in output + in: query + name: Risk + required: true + schema: + type: boolean + - description: Whether to include sightings fields in output + in: query + name: Sightings + required: true + schema: + type: boolean + - description: Whether to include entity fields in output + in: query + name: Entity + required: true + schema: + type: boolean + - description: Whether to include metrics fields in output + in: query + name: Metrics + required: true + schema: + type: boolean + - description: Whether to include intel card fields in output + in: query + name: Intel Card + required: true + schema: + type: boolean + - description: Whether to include analyst notes fields in output + in: query + name: Analyst Notes + required: true + schema: + type: boolean + - description: Whether to include timestamps fields in output + in: query + name: Timestamps + required: true + schema: + type: boolean + - description: Whether to include counts fields in output + in: query + name: Counts + required: true + schema: + type: boolean + - description: Whether to include related entities fields in output + in: query + name: Related Entities + required: true + schema: + type: boolean + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp4' + description: Successful request + summary: Recorded Future Lookup Domain + tmp5: + post: + description: Get Hash Risk List + parameters: + - description: Limit content to entities matching a category/rule. + in: query + name: Category + required: false + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp5' + description: Successful request + summary: Recorded Future Get Hash Risk List + tmp6: + post: + description: List Hash Risk Rules + parameters: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp6' + description: Successful request + summary: Recorded Future List Hash Risk Rules + tmp7: + post: + description: Lookup Hash + parameters: + - description: Whether to include threat lists fields in output + in: query + name: Threat Lists + required: true + schema: + type: boolean + - description: Whether to include hash algorithm fields in output + in: query + name: Hash Algorithm + required: true + schema: + type: boolean + - description: Hash to lookup + in: query + name: Hash + required: true + schema: + type: string + - description: Whether to include risk fields in output + in: query + name: Risk + required: true + schema: + type: boolean + - description: Whether to include sightings fields in output + in: query + name: Sightings + required: true + schema: + type: boolean + - description: Whether to include entity fields in output + in: query + name: Entity + required: true + schema: + type: boolean + - description: Whether to include metrics fields in output + in: query + name: Metrics + required: true + schema: + type: boolean + - description: Whether to include intel card fields in output + in: query + name: Intel Card + required: true + schema: + type: boolean + - description: Whether to include analyst notes fields in output + in: query + name: Analyst Notes + required: true + schema: + type: boolean + - description: Whether to include timestamps fields in output + in: query + name: Timestamps + required: true + schema: + type: boolean + - description: Whether to include counts fields in output + in: query + name: Counts + required: true + schema: + type: boolean + - description: Whether to include related entities fields in output + in: query + name: Related Entities + required: true + schema: + type: boolean + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp7' + description: Successful request + summary: Recorded Future Lookup Hash + tmp8: + post: + description: Get IP Risk List + parameters: + - description: Limit content to entities matching a category/rule. + in: query + name: Category + required: false + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp8' + description: Successful request + summary: Recorded Future Get IP Risk List + tmp9: + post: + description: List IP Risk Rules + parameters: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp9' + description: Successful request + summary: Recorded Future List IP Risk Rules diff --git a/app_gen/openapi-parsers/generated/shodan.yaml b/app_gen/openapi-parsers/generated/shodan.yaml new file mode 100644 index 00000000..8004d442 --- /dev/null +++ b/app_gen/openapi-parsers/generated/shodan.yaml @@ -0,0 +1,166 @@ +components: + schemas: + tmp0: + properties: + bid: + type: string + cve: + type: string + description: + type: string + msb: + type: string + osvdb: + type: string + source: + type: string + type: object + tmp1: + properties: + data: + type: string + domains: + type: string + hostnames: + type: string + ip: + type: string + location.area_code: + type: string + location.city: + type: string + location.country_code: + type: string + location.country_name: + type: string + location.dma_code: + type: string + location.latitude: + type: string + location.longitude: + type: string + location.postal_code: + type: string + location.region_code: + type: string + org: + type: string + os: + type: string + port: + type: string + transport: + type: string + type: object + tmp2: + properties: + devicetype: + type: string + domains: + type: string + hostnames: + type: string + ip_str: + type: string + isp: + type: string + location.area_code: + type: string + location.city: + type: string + location.country_code: + type: string + location.country_name: + type: string + location.dma_code: + type: string + location.latitude: + type: string + location.longitude: + type: string + location.postal_code: + type: string + org: + type: string + os: + type: string + port: + type: string + product: + type: string + timestamp: + type: string + title: + type: string + type: object + securitySchemes: {} +info: + contact: + email: frikky@shuffler.io + name: '@frikkylikeme' + url: https://twitter.com/frikkylikeme + description: Automated generation of Shodan + title: Shodan + version: 1.0.0 +openapi: 3.0.2 +paths: + tmp0: + post: + description: Search across a variety of data sources for exploits + parameters: + - description: Search query used to search the database of known exploits + in: query + name: Query + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp0' + description: Successful request + summary: Shodan Exploit Search + tmp1: + post: + description: Search all services that have been found on the given host IP + parameters: + - description: Host IP address + in: query + name: IP + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp1' + description: Successful request + summary: Shodan Host + tmp2: + post: + description: Search the SHODAN database + parameters: + - description: Keyword to search Shodan for + in: query + name: Query + required: true + schema: + type: string + - description: 'Max number of results to return. Default: ''Infinite''' + in: body + name: Limit + required: false + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/tmp2' + description: Successful request + summary: Shodan Search diff --git a/app_gen/openapi-parsers/generated/tenable_tenable.io.yaml b/app_gen/openapi-parsers/generated/tenable_tenable.io.yaml new file mode 100644 index 00000000..3bac77a9 --- /dev/null +++ b/app_gen/openapi-parsers/generated/tenable_tenable.io.yaml @@ -0,0 +1,149 @@ +components: + schemas: + tmp0: + properties: + bios_uuid: + type: string + fqdn: + type: string + hostname: + type: string + id: + type: string + ipv4: + type: string + ipv6: + type: string + mac_address: + type: string + netbios_name: + type: string + operating_system: + type: string + ssh_fingerprint: + type: string + system_type: + type: string + type: object + tmp1: + properties: + agent_name: + type: string + fqdn: + type: string + id: + type: string + ipv4: + type: string + ipv6: + type: string + last_seen: + type: string + mac_address: + type: string + operating_system: + type: string + type: object + tmp2: + properties: + hostcount: + type: string + name: + type: string + owner: + type: string + policy: + type: string + scan_end: + type: string + scan_start: + type: string + status: + type: string + targets: + type: string + uuid: + type: string + type: object + tmp4: + properties: + count: + type: string + plugin_family: + type: string + plugin_name: + type: string + severity: + type: string + vulnerability_state: + type: string + type: object + securitySchemes: {} +info: + contact: + email: frikky@shuffler.io + name: '@frikkylikeme' + url: https://twitter.com/frikkylikeme + description: Automated generation of Tenable Tenable.io + title: Tenable Tenable.io + version: 1.0.0 +openapi: 3.0.2 +paths: + tmp0: + post: + description: Returns information about the specified asset. + parameters: + - description: The UUID of the asset. + in: query + name: Asset UUID + required: true + schema: + type: string + responses: + '200': + description: Successful request + summary: Tenable.io Asset Info + tmp1: + post: + description: Returns a list of up to 5000 assets. + parameters: [] + responses: + '200': + description: Successful request + summary: Tenable.io List Assets + tmp2: + post: + description: Returns details for the given scan. + parameters: + - description: The ID of the scan. + in: query + name: Scan ID + required: true + schema: + type: string + responses: + '200': + description: Successful request + summary: Tenable.io Scan Details + tmp3: + post: + description: Launches a scan. + parameters: + - description: The ID of the scan. + in: body + name: Scan ID + required: true + schema: + type: string + responses: + '200': + description: Successful request + summary: Tenable.io Scan Launch + tmp4: + post: + description: Retrieves a list of recorded vulnerabilities. + parameters: [] + responses: + '200': + description: Successful request + summary: Tenable.io Vulnerabilities diff --git a/app_gen/openapi-parsers/misp.py b/app_gen/openapi-parsers/misp.py new file mode 100644 index 00000000..66e06c60 --- /dev/null +++ b/app_gen/openapi-parsers/misp.py @@ -0,0 +1,109 @@ +import json +import yaml + +items = [] + +openapi = { + "openapi": "3.0.2", + "info": { + "title": "MISP", + "description": "MISP API generated from the misp book: https://github.com/MISP/misp-book/blob/master/automation/README.md", + "version": "1.0.0", + "contact": { + "name": "@frikkylikeme", + "url": "https://twitter.com/frikkylikeme", + "email": "frikky@shuffler.io" + } + }, + "paths": {}, + "components": { + "schemas": {}, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apikey", + "in": "header", + "name": "Authorization", + } + }, + } +} + +with open("misp.txt", "r") as tmp: + newitem = {} + recorditem = False + + counter = 0 + itemsplit = tmp.read().split("\n") + for item in itemsplit: + counter += 1 + if item.startswith("### ") and "/" in item: + try: + path = item.split(" ")[2] + method = item.split(" ")[1].lower() + newitem = { + "path": path, + "method": method, + } + + try: + openapi["paths"][path][method] = {} + except KeyError: + openapi["paths"][path] = {} + openapi["paths"][path][method] = {} + + except IndexError: + newitem = {} + continue + + recorditem = True + #print(newitem) + + if not recorditem: + continue + + if "Description" in item: + openapi["paths"][newitem["path"]][newitem["method"]]["description"] = itemsplit[counter+1] + elif "URL Arguments" in item: + parameters = [] + innercnt = 0 + + openapi["paths"][newitem["path"]][newitem["method"]]["parameters"] = [] + while True: + curline = itemsplit[counter+1+innercnt] + if "#" in curline: + break + + innercnt += 1 + if not curline: + continue + + print(curline) + parameters.append({ + "description": curline.split(" ")[1], + "in": "query", + "name": curline.split(" ")[1], + "required": True, + "schema": {"type": "string"}, + }) + + openapi["paths"][newitem["path"]][newitem["method"]]["parameters"] = parameters + elif "Output" in item: + # FIXME + innercnt = 0 + while True: + curline = itemsplit[counter+1+innercnt] + + if "#" in curline: + break + + innercnt += 1 + if "json" in curline: + continue + #print(curline) + +print(json.dumps(openapi, indent=4)) + + +generatedfile = "generated/misp.yaml" +with open(generatedfile, "w+") as tmp: + tmp.write(yaml.dump(openapi)) diff --git a/app_gen/openapi-parsers/other/TIO-API-Container-Security-v1.json b/app_gen/openapi-parsers/other/TIO-API-Container-Security-v1.json new file mode 100644 index 00000000..1b6783ad --- /dev/null +++ b/app_gen/openapi-parsers/other/TIO-API-Container-Security-v1.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"Container Security v1","version":"1.0.0"},"security":[{"cloud":[]}],"servers":[{"url":"https://cloud.tenable.com"}],"components":{"securitySchemes":{"cloud":{"type":"apiKey","in":"header","name":"X-ApiKeys","description":"Format - accessKey=ACCESS_KEY;secretKey=SECRET_KEY"}}},"x-samples-languages":["python","curl","node","powershell","ruby","javascript","objectivec","java","php","csharp","go","swift","kotlin"],"paths":{"/container-security/api/v1/container/list":{"get":{"summary":"List containers","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. Use the [GET /container-security/api/v2/images](/reference#list-images) endpoint instead.\nLists all containers.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-containers-list-containers","deprecated":true,"tags":["Containers"],"responses":{"200":{"description":"Returns an array of containers.","content":{"application/json":{"schema":{},"examples":{"response":{"value":[{"number_of_vulnerabilities":"string","name":"string","size":"string","digest":"string","repo_name":"string","score":"string","id":"string","status":"string","created_at":"string","repo_id":"string","platform":"string","updated_at":"string"}]}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/container/{imageID}/status":{"get":{"summary":"Get image inventory","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. Use the [GET /container-security/api/v2/images](/reference#container-security-v2-list-images) endpoint instead.\nReturns an inventory of an image by ID.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-containers-image-inventory","deprecated":true,"tags":["Containers"],"parameters":[{"description":"The ID of the image that you want to inventory.","required":true,"name":"imageID","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the inventory of the image you specified.","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"object"}},"packages":{"type":"array","items":{"type":"object"}},"id":{"type":"string"}}},"examples":{"response":{"value":{"files":[{"path":"string","md5":"string","sha256":"string","fileType":"string","isCritical":true}],"packages":[{"name":"string","version":"string","release":"string","epoch":"string","rawString":"string"}],"id":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/import":{"post":{"summary":"Create import","deprecated":true,"description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. For images import, use Tenable.io connectors. For more information, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/ConfigureConnectors.htm).\nCreates an import.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"container-security-import-import","tags":["Import"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"host":{"type":"string","description":""},"port":{"type":"integer","description":"","format":"int32"},"username":{"type":"string","description":""},"password":{"type":"string","description":""},"provider":{"type":"string","description":""},"active":{"type":"boolean","description":""},"ssl":{"type":"boolean","description":""}},"required":["host","port","username","password","provider","ssl"]}}}},"responses":{"200":{"description":"Returns the ID you specified in the request.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"id":{"type":"string"}}},"examples":{"response":{"value":{"status":"string","id":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/import/list":{"get":{"summary":"List imports","deprecated":true,"description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. For images import, use Tenable.io connectors. For more information, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/ConfigureConnectors.htm).\nReturns a list of all imports.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"container-security-import-list-imports","tags":["Import"],"responses":{"200":{"description":"Returns an array of information about each import that has been performed.","content":{"application/json":{"schema":{},"examples":{"response":{"value":[{"org_id":"integer","user_uuid":"string","host":"string","port":"integer","username":"string","password":"string","provider":"string","active":"boolean","ssl":"boolean","hourBetween":"integer","id":"integer","created_at":"string","updated_at":"string","started_at":"string","finished_at":"string"}]}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/import/{id}":{"post":{"summary":"Update import","deprecated":true,"description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. For images import, use Tenable.io connectors. For more information, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/ConfigureConnectors.htm).\nUpdates an import by ID.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"container-security-import-update-import-by-id","tags":["Import"],"parameters":[{"description":"The ID of the import that you want to update.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"host":{"type":"string","description":""},"port":{"type":"integer","description":"","format":"int32"},"username":{"type":"string","description":""},"password":{"type":"string","description":""},"provider":{"type":"string","description":""},"active":{"type":"boolean","description":""},"ssl":{"type":"boolean","description":""}},"required":["host","port","username","password","provider","ssl"]}}}},"responses":{"200":{"description":"Returns the ID you specified in the request.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"id":{"type":"string"}}},"examples":{"response":{"value":{"status":"string","id":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete import","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. For images import, use Tenable.io connectors. For more information, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/ConfigureConnectors.htm).\nDeletes an import by ID.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"container-security-import-delete-import-by-id","deprecated":true,"tags":["Import"],"parameters":[{"description":"The ID of the import that you want to delete.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the ID of the deleted import.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"id":{"type":"string"}}},"examples":{"response":{"value":{"status":"string","id":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/import/{id}/run":{"post":{"summary":"Run import ","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. For images import, use Tenable.io connectors. For more information, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/ConfigureConnectors.htm).\nRuns an import by ID.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"container-security-import-run-import-by-id","deprecated":true,"tags":["Import"],"parameters":[{"description":"The ID of the import that you want to run.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the ID of the import you want to run.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"id":{"type":"string"}}},"examples":{"response":{"value":{"status":"string","id":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/import/{id}/test":{"post":{"summary":"Test connection to Tenable.io","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. For images import, use Tenable.io connectors. For more information, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/ConfigureConnectors.htm).\nTests your connection to Tenable.io Container Security.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"container-security-import-test-connection","deprecated":true,"tags":["Import"],"parameters":[{"description":"A test ID. You can specify any integer as the ID. Tenable.io Container Security uses this value for the test only.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the ID you specified in the request.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"id":{"type":"string"}}},"examples":{"response":{"value":{"status":"string","id":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/jobs/list":{"get":{"summary":"List active jobs","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. To determine the progress of an image analysis, use the [GET /container-security/api/v2/images/{repository}/{image}/{tag}](/reference#container-security-v2-get-image-report) endpoint in Tenable.io Container Security API v2.\nReturns a list of active jobs.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-jobs-list-jobs","deprecated":true,"tags":["Jobs"],"responses":{"200":{"description":"Returns an array of the statuses of all active jobs.","content":{"application/json":{"schema":{},"examples":{"response":{"value":[{"container_id":"string","job_id":"string","error":"string","job_status":"string","created_at":"string","updated_at":"string"}]}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/jobs/status":{"get":{"summary":"Get job status by ID","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. To determine the progress of an image analysis, use the [GET /container-security/api/v2/images/{repository}/{image}/{tag}](/reference#container-security-v2-get-image-report) endpoint in Tenable.io Container Security API v2.\nReturns the status of a job that you specify by ID to determine if the job is still queued, in progress, or has completed.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-jobs-job-status","deprecated":true,"tags":["Jobs"],"parameters":[{"description":"The ID of the job for which you want the status.","required":true,"name":"job_id","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the status of the job you specified.","content":{"application/json":{"schema":{"type":"object","properties":{"container_id":{"type":"string"},"job_id":{"type":"string"},"error":{"type":"string"},"job_status":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"examples":{"response":{"value":{"container_id":"string","job_id":"string","error":"string","job_status":"string","created_at":"string","updated_at":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/jobs/image_status":{"get":{"summary":"Get job status by image ID","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. To determine the progress of an image analysis, use the [GET /container-security/api/v2/images/{repository}/{image}/{tag}](/reference#container-security-v2-get-image-report) endpoint in Tenable.io Container Security API v2.\nReturns the status of a job by specifying an image ID to determine if the job is still queued, in progress, or has completed.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-jobs-job-status-by-image-id","deprecated":true,"tags":["Jobs"],"parameters":[{"description":"The ID of the image for which you want the status.","required":true,"name":"image_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the status of the job you specified.","content":{"application/json":{"schema":{"type":"object","properties":{"container_id":{"type":"string"},"job_id":{"type":"string"},"error":{"type":"string"},"job_status":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"examples":{"response":{"value":{"container_id":"string","job_id":"string","error":"string","job_status":"string","created_at":"string","updated_at":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/jobs/image_status_digest":{"get":{"summary":"Get job status by image digest","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. To determine the progress of an image analysis, use the [GET /container-security/api/v2/images/{repository}/{image}/{tag}](/reference#container-security-v2-get-image-report) endpoint in Tenable.io Container Security API v2.\nReturns the status of a job by specifying an image digest to determine if the job is still queued, in progress, or has completed.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-jobs-job-status-by-image-digest","deprecated":true,"tags":["Jobs"],"parameters":[{"description":"The image digest of the job for which you want the status.","required":true,"name":"image_digest","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the status of the job you specified.","content":{"application/json":{"schema":{"type":"object","properties":{"container_id":{"type":"string"},"job_id":{"type":"string"},"error":{"type":"string"},"job_status":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"examples":{"response":{"value":{"container_id":"string","job_id":"string","error":"string","job_status":"string","created_at":"string","updated_at":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/policycompliance":{"get":{"summary":"Get compliance status by ID","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated.\nChecks the compliance of an image that you specify by ID against your policies.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-policy-policy-compliance-by-id","deprecated":true,"tags":["Policy"],"parameters":[{"description":"The ID of the image that you want to check for policy compliance.","required":true,"name":"image_id","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns an array of compliance results.","content":{"application/json":{"schema":{},"examples":{"response":{"value":[{"status":"string","message":"string","reason":"string"}]}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/compliancebyname":{"get":{"summary":"Get compliance status by name","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated.\nChecks the compliance of an image that you specify by name against your policies.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-policy-policy-compliance-by-name","deprecated":true,"tags":["Policy"],"parameters":[{"description":"The name of the image for which you want the job status.","required":true,"name":"image","in":"query","schema":{"type":"string"}},{"description":"The name of the repository that hosts the image. By default, this value is library.","required":false,"name":"repo","in":"query","schema":{"type":"string"}},{"description":"The tag for the image that you want to check for policy compliance.","required":false,"name":"tag","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns an array compliance results.","content":{"application/json":{"schema":{},"examples":{"response":{"value":[{"status":"string","message":"string","reason":"string"}]}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/reports/show":{"get":{"summary":"Get container report","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. Use the [GET /container-security/api/v2/images/{repository}/{image}/{tag}](/reference#container-security-v2-get-image-report) endpoint instead.\nReturns a report in JSON format for a container that you specify by ID. Note: If you do not have the container_id, you can call the list-containers endpoint.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-reports-report-by-container-id","deprecated":true,"tags":["Reports"],"parameters":[{"description":"The ID of the container for which you want a report.","required":true,"name":"container_id","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the report for the container you specified.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"image_name":{"type":"string"},"docker_image_id":{"type":"string"},"tag":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"platform":{"type":"string"},"findings":{"type":"array","items":{"type":"object"}},"malware":{"type":"array","items":{"type":"object"}},"potentially_unwanted_programs":{"type":"array","items":{"type":"object"}},"sha256":{"type":"string"},"os":{"type":"string"},"os_version":{"type":"string"},"os_architecture":{"type":"string"},"os_release_name":{"type":"string"},"installed_packages":{"type":"array","items":{"type":"object"}},"risk_score":{"type":"integer","format":"int32"},"digest":{"type":"string"}}},"examples":{"response":{"value":{"id":"string","image_name":"string","docker_image_id":"string","tag":"string","created_at":"string","updated_at":"string","platform":"string","findings":[{"nvdFinding":{"reference_id":"string","cve":"string","published_date":"string","modified_date":"string","description":"string","cvss_score":"string","access_vector":"string","access_complexity":"string","auth":"string","availability_impact":"string","confidentiality_impact":"string","integrity_impact":"string","cwe":"string","cpe":["string"],"remediation":"string","references":["string"]},"packages":[{"name":"string","version":"string","release":"string","epoch":"string","rawString":"string"}]}],"malware":[{"infectedFile":"string","fileTypeDescriptor":"string","md5":"string","sha256":"string"}],"potentially_unwanted_programs":[{"file":"string","md5":"string","sha256":"string"}],"sha256":"string","os":"string","os_version":"string","os_architecture":"string","os_release_name":"string","installed_packages":[{"name":"string","version":"string","release":"string","epoch":"string","rawString":"string"}],"risk_score":0,"digest":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/reports/by_image":{"get":{"summary":"Get image report","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. Use the [GET /container-security/api/v2/images/{repository}/{image}/{tag}](/reference#container-security-v2-get-image-report) endpoint instead.\nReturns a report in JSON format for an image that you specify by ID. Note: If you do not have the image_id, you can call the list-images endpoint.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-reports-report-by-image-id","deprecated":true,"tags":["Reports"],"parameters":[{"description":"The ID of the image for which you want a report.","required":true,"name":"image_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the report for the image you specified.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"image_name":{"type":"string"},"docker_image_id":{"type":"string"},"tag":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"platform":{"type":"string"},"findings":{"type":"array","items":{"type":"object"}},"malware":{"type":"array","items":{"type":"object"}},"potentially_unwanted_programs":{"type":"array","items":{"type":"object"}},"sha256":{"type":"string"},"os":{"type":"string"},"os_version":{"type":"string"},"os_architecture":{"type":"string"},"os_release_name":{"type":"string"},"installed_packages":{"type":"array","items":{"type":"object"}},"risk_score":{"type":"integer","format":"int32"},"digest":{"type":"string"}}},"examples":{"response":{"value":{"id":"string","image_name":"string","docker_image_id":"string","tag":"string","created_at":"string","updated_at":"string","platform":"string","findings":[{"nvdFinding":{"cve":"string","published_date":"string","modified_date":"string","description":"string","cvss_score":"string","access_vector":"string","access_complexity":"string","auth":"string","availability_impact":"string","confidentiality_impact":"string","integrity_impact":"string","cwe":"string","cpe":["string"],"remediation":"string","references":["string"]},"packages":[{"name":"string","version":"string","release":"string","epoch":"string","rawString":"string"}]}],"malware":[{"infectedFile":"string","fileTypeDescriptor":"string","md5":"string","sha256":"string"}],"potentially_unwanted_programs":[{"file":"string","md5":"string","sha256":"string"}],"sha256":"string","os":"string","os_version":"string","os_architecture":"string","os_release_name":"string","installed_packages":[{"name":"string","version":"string","type":"string","license":"string"}],"risk_score":0,"digest":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/reports/by_image_digest":{"get":{"summary":"Get image digest report","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. Use the [GET /container-security/api/v2/images/{repository}/{image}/{tag}](/reference#container-security-v2-get-image-report) endpoint instead.\nReturns a report in JSON format for an image digest.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-reports-report-by-image-digest","deprecated":true,"tags":["Reports"],"parameters":[{"description":"The image digest of the image for which you want a report.","required":true,"name":"image_digest","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the report for the image you specified.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"image_name":{"type":"string"},"docker_image_id":{"type":"string"},"tag":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"platform":{"type":"string"},"findings":{"type":"array","items":{"type":"object"}},"malware":{"type":"array","items":{"type":"object"}},"potentially_unwanted_programs":{"type":"array","items":{"type":"object"}},"sha256":{"type":"string"},"os":{"type":"string"},"os_version":{"type":"string"},"os_architecture":{"type":"string"},"os_release_name":{"type":"string"},"installed_packages":{"type":"array","items":{"type":"object"}},"risk_score":{"type":"integer","format":"int32"},"digest":{"type":"string"}}},"examples":{"response":{"value":{"id":"string","image_name":"string","docker_image_id":"string","tag":"string","created_at":"string","updated_at":"string","platform":"string","findings":[{"nvdFinding":{"reference_id":"string","cve":"string","published_date":"string","modified_date":"string","description":"string","cvss_score":"string","access_vector":"string","access_complexity":"string","auth":"string","availability_impact":"string","confidentiality_impact":"string","integrity_impact":"string","cwe":"string","cpe":["string"],"remediation":"string","references":["string"]},"packages":[{"name":"string","version":"string","release":"string","epoch":"string","rawString":"string"}]}],"malware":[{"infectedFile":"string","fileTypeDescriptor":"string","md5":"string","sha256":"string"}],"potentially_unwanted_programs":[{"file":"string","md5":"string","sha256":"string"}],"sha256":"string","os":"string","os_version":"string","os_architecture":"string","os_release_name":"string","installed_packages":[{"name":"string","version":"string","release":"string","epoch":"string","rawString":"string"}],"risk_score":0,"digest":"string"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/reports/nessus/show":{"get":{"summary":"Get Nessus report for container","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. Use the [GET /container-security/api/v2/images/{repository}/{image}/{tag}](/reference#container-security-v2-get-image-report) endpoint instead.\nReturns a Nessus report for a container that you specify by ID. Note: If you do not have the container_id, you can call the list-containers endpoint.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-reports-nessus-report-by-container-id","deprecated":true,"tags":["Reports"],"parameters":[{"description":"The ID of the container for which you want a report.","required":true,"name":"id","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the Nessus report for the container you specified.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"To do":"Add response sample here"}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/repositories":{"get":{"summary":"List repositories","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. Use the [GET /container-security/api/v2/repositories](/reference#container-security-v2-list-repositories) endpoint instead.\nReturns a list of repositories.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-repositories-list-repositories","deprecated":true,"tags":["Repositories"],"parameters":[{"description":"The number of items Tenable.io Container Security skips before starting to collect the result set.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The maximum number of items to return.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns an array of repositories.","content":{"application/json":{"schema":{"type":"object","properties":{"offset":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"type":"object"}}}},"examples":{"response":{"value":{"offset":"integer","limit":"integer","total":"integer","items":[{"name":"string","description":"string","pullCount":"integer","pushCount":"integer"}]}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/container-security/api/v1/repositories/{id}/images":{"get":{"summary":"List images in repository","description":"**Deprecated!** Tenable.io Container Security API v1 is deprecated. Use the [GET /container-security/api/v2/images](/reference#container-security-v2-list-images) endpoint with the repository filter instead.\nReturns a list of images inside a specific repository.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"container-security-repositories-list-images","deprecated":true,"tags":["Repositories"],"parameters":[{"description":"The ID of the relevant repository.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The number of items to skip before Tenable.io Container Security starts to collect the result set.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The maximum number of items to return.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns an array of images.","content":{"application/json":{"schema":{"type":"object","properties":{"offset":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"type":"object"}}}},"examples":{"response":{"value":{"offset":"integer","limit":"integer","total":"integer","items":[{"id":"string","repoId":"string","name":"string","tag":"string","digest":"string","hasReport":"boolean","hasInventory":"boolean","status":"string","score":"integer","numberOfVulns":"integer","numberOfMalware":"integer","pullCount":"string","pushCount":"string","source":"string","createdAt":"string","updatedAt":"string","finishedAt":"string","imageHash":"string","size":"string","layers":["string"]}]}}}}}},"401":{"description":"Returns an error message if the request is not authorized."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}}},"x-explorer-enabled":true,"x-proxy-enabled":true,"x-samples-enabled":true} \ No newline at end of file diff --git a/app_gen/openapi-parsers/other/TIO-API-Container-Security-v2.json b/app_gen/openapi-parsers/other/TIO-API-Container-Security-v2.json new file mode 100644 index 00000000..dfb515ae --- /dev/null +++ b/app_gen/openapi-parsers/other/TIO-API-Container-Security-v2.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"Container Security v2","description":"Container Security API provides the endpoints for securing container images, for example, Docker. Using the API, you can you seamlessly and securely enable DevOps processes by providing visibility into the security of container images – including vulnerabilities, malware and policy violations – through integration with the build process.\n\nFor background information about managing container security, see the [documentation site](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/Welcome.htm).","version":"1.0.0"},"security":[{"cloud":[]}],"tags":[{"name":"Images","description":"With the Tenable.io Container Security images API, you can get a filtered list of available images, as well as the details of an individual image.\n\nFor background information, see [Tenable.io Container Security User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/Welcome.htm)."},{"name":"Repositories","description":"With the Tenable.io Container Security repositories API, you can get a filtered list of available image repositories, as well as the details of an individual repository.\n\nFor background information, see [Tenable.io Container Security User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/ManageImageRepositories.htm)."},{"name":"Reports","description":"With the Tenable.io Container security reports API, you can get a detailed vulnerability scan report for an image.\n\nFor background information, see [Tenable.io Container Security User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/ViewScanResults.htm)."}],"servers":[{"url":"https://cloud.tenable.com/container-security/api/v2/"}],"paths":{"/images":{"get":{"tags":["Images"],"operationId":"container-security-v2-list-images","summary":"List images","description":"Returns a paginated list of images. Use URL query parameters to filter the list.

Requires BASIC [16] user permissions. See Permissions.

","parameters":[{"in":"query","name":"offset","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"The number of skipped records in the returned result set. Must be in the int32 format."},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":50},"description":"The number of records to include in the result set. Must be in the int32 format. Default is 50. The maximum limit is 1,000."},{"in":"query","name":"name","schema":{"type":"string"},"description":"The image name to filter on. Tenable.io returns only the images with names that exactly match the parameter value. The value is case-sensitive."},{"in":"query","name":"repo","schema":{"type":"string"},"description":"The repository name to filter on. Tenable.io returns only the images from repositories that exactly match the parameter value. The value is case-sensitive."},{"in":"query","name":"tag","schema":{"type":"string"},"description":"The tag to filter on. Tenable.io returns only the images with tags that exactly match the parameter value. The value is case-sensitive."},{"in":"query","name":"hasMalware","schema":{"type":"boolean"},"description":"Specifies whether to return only the images with associated malware (images with the `numberOfMalware` attribute greater than 0)."},{"in":"query","name":"score","schema":{"type":"integer"},"description":"The score to filter on. Tenable.io limits results based on the parameter value and the comparison operator specified by the `scoreOperator` parameter. For more information about the risk score metric, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/RiskMetrics.htm)."},{"in":"query","name":"scoreOperator","description":"The comparison operator for the value specified by the `score` parameter. Operators include:\n - EQ—equals\n - GT—greater than\n - EQ—less than","schema":{"type":"string","enum":["EQ","LT","GT"]}},{"in":"query","name":"os","schema":{"type":"string"},"description":"The operating system to filter on. Tenable.io returns only the images with an operating system that exactly matches the parameter value."}],"responses":{"200":{"description":"Returns a paginated list of images in your Tenable.io Container Security instance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/imageListResponse"},"examples":{"response":{"value":{"items":[{"repoId":"2491620318530539587","repoName":"dmjb","name":"jboss","tag":"latest","digest":"sha256:f75748b2bbd5a386c8d876770ff09a65c42335fb1f538025b928c794ffa8123f","hasReport":false,"hasInventory":false,"status":"scan_failed","lastJobStatus":"failed","pullCount":"0","pushCount":"1","source":"pushed","createdAt":"2019-04-19T11:31:11.283Z","updatedAt":"2019-04-19T12:33:29.903Z","finishedAt":"2019-04-19T12:33:29.903Z","imageHash":"859c02589af7","size":"4471","layers":[{"size":133212385,"digest":"sha256:01e684a89bbea67a11fcc96caed8e8b3320c44e29c2ab2a85016f48a69870bc8"},{"size":32,"digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"},{"size":32,"digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"},{"size":32,"digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"},{"size":32,"digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"},{"size":3338,"digest":"sha256:ebdaef68b4f08d23189ae32fd90bd68261385549f46360d9c3d163b988d91a45"},{"size":250,"digest":"sha256:cc7ec6c68bd72324ab932dc00474d0713044542c2adf6bff6f4e4d1cacc70737"},{"size":510,"digest":"sha256:01bb3ac59edc6a05e14a5151cdc39eae261ff37a933118063d1f91bb14aaceb4"},{"size":7863072,"digest":"sha256:a248b0871c3cac9ce2b2a956e118ce03e49027d5d4c4da74df00ae399fff17c3"},{"size":681,"digest":"sha256:c9f371853f28eb40b76f309e27f671b57bffb8d80df4ca8e7970885ae532e172"},{"size":86733990,"digest":"sha256:2fe0df338fc0cc7d9ec4428ba7538f165f901f3a7760c7fecbdda11a435c5eee"},{"size":71511,"digest":"sha256:aa2f8df214335759614f9aceea51f570354944f59c36cc8399415bbfab91839e"},{"size":67494686,"digest":"sha256:23efb549476f5f10a40b3784758a807c0194d87a0b18c9a5a3436e67611e971b"},{"size":421,"digest":"sha256:1049dfc2ba444c2f74f3eb77a07d0fd5fe304d79ea7178d5a0885788696d63aa"},{"size":374,"digest":"sha256:ef072d3c9b418ba3ce624ce456d311bb81c9ae5d4d5bc682da5edadde408fce7"}],"os":"Unknown","osVersion":"Unknown"},{"repoId":"7185635748924628551","repoName":"elastic","name":"elasticsearch","tag":"5","digest":"sha256:0278ed727ad6dd0bef0be279b3112755a110980c31f07e7f4a54e19b9ca2e24a","hasReport":true,"hasInventory":false,"status":"scanned","lastJobStatus":"completed","score":10,"numberOfVulns":54,"numberOfMalware":0,"pullCount":"0","pushCount":"1","source":"on_prem_import","createdAt":"2018-12-13T17:51:03.295Z","updatedAt":"2019-05-14T10:48:02.857Z","finishedAt":"2019-05-14T10:48:02.857Z","imageHash":"5e9d896dc62c","size":"3460","layers":[],"os":"Debian","osVersion":"9.5"},{"repoId":"7185635748924628551","repoName":"postgres_db","name":"postgres","tag":"latest","digest":"sha256:0dec082064d1203a3ead704057de56823d2c8ae11818da0fd3065dee9ec1b92e","hasReport":true,"hasInventory":false,"status":"scanned","lastJobStatus":"completed","score":10,"numberOfVulns":46,"numberOfMalware":0,"pullCount":"0","pushCount":"1","source":"on_prem_import","createdAt":"2018-12-13T17:51:43.861Z","updatedAt":"2019-05-14T16:46:10.739Z","finishedAt":"2019-05-14T16:46:10.739Z","imageHash":"c230b2f564da","size":"3244","layers":[],"os":"Debian","osVersion":"9.6"}],"pagination":{"offset":0,"limit":1000,"total":138,"sort":[]}}}}}}},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."}}}},"/images/{repository}/{image}/{tag}":{"get":{"tags":["Images"],"summary":"Get image details","operationId":"container-security-v2-get-image-details","description":"Returns the details for an image specified by repository, name, and tag.

Requires BASIC [16] user permissions. See Permissions.

","parameters":[{"name":"repository","in":"path","description":"The name of the Tenable.io Container Security repository where the image is stored. The value is case-sensitive.","required":true,"schema":{"type":"string"}},{"name":"image","in":"path","required":true,"schema":{"type":"string"},"description":"The name of the image. The value is case-sensitive."},{"name":"tag","in":"path","required":true,"description":"The tag identifying the image version. The value is case-sensitive.\n**Note**: Image tags are not equivalent to Tenable.io [asset tags](/reference#tags).","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the image details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/imageDetails"},"examples":{"response":{"value":{"name":"jboss","repository":"dmjb","tag":"latest","digest":"sha256:f75748b2bbd5a386c8d876770ff09a65c42335fb1f538025b928c794ffa8123f","uploadedAt":"2019-04-19T11:31:11.283Z","lastScanned":"2019-04-19T12:33:29.903Z","status":"scan_failed","size":"4471","layers":[{"size":133212385,"digest":"sha256:01e684a89bbea67a11fcc96caed8e8b3320c44e29c2ab2a85016f48a69870bc8"},{"size":32,"digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"},{"size":32,"digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"},{"size":32,"digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"},{"size":32,"digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"},{"size":3338,"digest":"sha256:ebdaef68b4f08d23189ae32fd90bd68261385549f46360d9c3d163b988d91a45"},{"size":250,"digest":"sha256:cc7ec6c68bd72324ab932dc00474d0713044542c2adf6bff6f4e4d1cacc70737"},{"size":510,"digest":"sha256:01bb3ac59edc6a05e14a5151cdc39eae261ff37a933118063d1f91bb14aaceb4"},{"size":7863072,"digest":"sha256:a248b0871c3cac9ce2b2a956e118ce03e49027d5d4c4da74df00ae399fff17c3"},{"size":681,"digest":"sha256:c9f371853f28eb40b76f309e27f671b57bffb8d80df4ca8e7970885ae532e172"},{"size":86733990,"digest":"sha256:2fe0df338fc0cc7d9ec4428ba7538f165f901f3a7760c7fecbdda11a435c5eee"},{"size":71511,"digest":"sha256:aa2f8df214335759614f9aceea51f570354944f59c36cc8399415bbfab91839e"},{"size":67494686,"digest":"sha256:23efb549476f5f10a40b3784758a807c0194d87a0b18c9a5a3436e67611e971b"},{"size":421,"digest":"sha256:1049dfc2ba444c2f74f3eb77a07d0fd5fe304d79ea7178d5a0885788696d63aa"},{"size":374,"digest":"sha256:ef072d3c9b418ba3ce624ce456d311bb81c9ae5d4d5bc682da5edadde408fce7"}]}}}}}},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"404":{"description":"Returned if Tenable.io cannot find the specified image."}}},"delete":{"tags":["Images"],"summary":"Delete image","description":"Deletes an image specified by repository, name, and tag.

Requires SCAN OPERATOR [24] user permissions. See Permissions.

","operationId":"container-security-v2-delete-image","parameters":[{"name":"repository","in":"path","description":"The name of the Tenable.io Container Security repository where the image is stored. The value is case-sensitive.","required":true,"schema":{"type":"string"}},{"name":"image","in":"path","required":true,"schema":{"type":"string"},"description":"The name of the image. The value is case-sensitive."},{"name":"tag","in":"path","required":true,"description":"The tag identifying the image version. The value is case-sensitive.\n**Note**: Image tags are not equivalent to Tenable.io [asset tags](/reference#tags).","schema":{"type":"string"}}],"responses":{"204":{"description":"Returned if Tenable.io successfully deletes the specified image."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"404":{"description":"Returned if Tenable.io cannot find the specified image."}}}},"/repositories":{"get":{"tags":["Repositories"],"operationId":"container-security-v2-list-repositories","summary":"List repositories","description":"Returns a list of image repositories in your Tenable.io Container Security instance. Use the query parameters to filter the list.

Requires BASIC [16] user permissions. See Permissions.

","parameters":[{"name":"imageName","in":"query","required":false,"schema":{"type":"string"},"description":"The repository name to filter on. Tenable.io Container Security returns only the repositories with names that exactly match the parameter value. The value is case-sensitive."},{"name":"nameContains","in":"query","required":false,"schema":{"type":"string"},"description":"The partial repository name to filter on. Tenable.io Container Security returns the images with names that contain the parameter value. The value is case-sensitive."},{"in":"query","name":"offset","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"The number of skipped records in the returned result set. Must be in the int32 format."},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":50},"description":"The number of records to include in the result set. Must be in the int32 format. Default is 50. The maximum limit is 1,000."}],"responses":{"200":{"description":"success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/repositoryListResponse"},"examples":{"response":{"value":{"items":[{"name":"dmjb","imagesCount":6,"labelsCount":6,"vulnerabilitiesCount":792,"malwareCount":0,"pullCount":0,"pushCount":12,"totalBytes":1766950068},{"name":"air-gap","imagesCount":7,"labelsCount":7,"vulnerabilitiesCount":514,"malwareCount":0,"pullCount":0,"pushCount":0,"totalBytes":0},{"name":"imiell","imagesCount":1,"labelsCount":1,"vulnerabilitiesCount":291,"malwareCount":0,"pullCount":0,"pushCount":2,"totalBytes":403346467}],"pagination":{"offset":0,"limit":10,"total":3,"sort":[]}}}}}}},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."}}}},"/repositories/{name}":{"get":{"tags":["Repositories"],"operationId":"container-security-v2-get-repository-details","summary":"Get repository details","description":"Returns details for a Tenable.io Container Security repository.

Requires BASIC [16] user permissions. See Permissions.

","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the repository details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/repositoryDetails"},"examples":{"response":{"value":{"name":"dmjb","imagesCount":6,"labelsCount":6,"vulnerabilitiesCount":842,"malwareCount":0,"pullCount":0,"pushCount":12,"totalBytes":1766950068}}}}}},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"404":{"description":"Returned if Tenable.io cannot find the specified repository."}}},"delete":{"tags":["Repositories"],"summary":"Delete repository","description":"Deletes a Tenable.io Container Security repository.

Requires SCAN OPERATOR [24] user permissions. See Permissions.

","operationId":"container-security-v2-delete-repository","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"The name of the repository to delete."}],"responses":{"204":{"description":"Returned if Tenable.io successfully deletes the specified repository."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"404":{"description":"Returned if Tenable.io cannot find the specified repository."}}}},"/reports/{repository}/{image}/{tag}":{"get":{"tags":["Reports"],"operationId":"container-security-v2-get-image-report","summary":"Get image report","description":"Returns a vulnerability report for the specified image.

Requires BASIC [16] user permissions. See Permissions.

","parameters":[{"name":"repository","in":"path","description":"The name of the Tenable.io Container Security repository where the image is stored. The value is case-sensitive.","required":true,"schema":{"type":"string"}},{"name":"image","in":"path","required":true,"schema":{"type":"string"},"description":"The name of the image. The value is case-sensitive."},{"name":"tag","in":"path","required":true,"description":"The tag identifying the image version. The value is case-sensitive.\n**Note**: Image tags are not equivalent to Tenable.io [asset tags](/reference#tags).","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a vulnerability scan report for the image.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/imageReport"},"examples":{"response":{"value":{"os_release_name":"16.04.2 LTS (Xenial Xerus)","malware":[{"file":"/20131116130541_http___198_2_192_204_22_disknyp","type":"ELF32","md5":"c92129fc230bacd113530fee254fc2b6","sha256":"sha256:60e24cb19a3cfdc88712f3511adfde242abff3c1915b34eeb19dd7cc72380df2"},{"file":"/20131103183232_http___61_132_227_111_8080_meimei","type":"ELF32","md5":"27072fd3a3cedaeed8cfebf29b9ed73f","sha256":"sha256:a8cd37210dea08880122c360cd096eda872f443c3dd39e498b2695955a3e0ad7"},{"file":"/20131116163507_http___198_2_192_204_22_disknyp","type":"ELF32","md5":"c92129fc230bacd113530fee254fc2b6","sha256":"sha256:60e24cb19a3cfdc88712f3511adfde242abff3c1915b34eeb19dd7cc72380df2"}],"sha256":"sha256:f708f91abdec052d05a46213815540616d24627b6af9cb3668484efb017969bf","os":"LINUX_UBUNTU","risk_score":10,"findings":[{"nvdFinding":{"cve":"CVE-2018-0494","description":"2018/05/09","published_date":"2018/05/09","modified_date":"It was discovered that Wget incorrectly handled certain inputs. An\nattacker could possibly use this to inject arbitrary cookie values.\n\nNote that Tenable Network Security has extracted the preceding\ndescription block directly from the Ubuntu security advisory. Tenable\nhas attempted to automatically clean and format it as much as possible\nwithout introducing additional issues.","cvss_score":"4.3","access_vector":"Network","access_complexity":"Medium","auth":"None required","availability_impact":"None","confidentiality_impact":"None","integrity_impact":"Partial","cwe":"CWE-20","cpe":["p-cpe:/a:canonical:ubuntu_linux:wget"],"remediation":"Update the affected wget package.","references":["USN:3643-1"]},"packages":[{"name":"wget","version":"1.17.1-1ubuntu1.2","type":"linux"}]},{"nvdFinding":{"cve":"CVE-2017-15670","description":"2018/01/17","published_date":"2018/01/17","modified_date":"It was discovered that the GNU C library did not properly handle all\nof the possible return values from the kernel getcwd(2) syscall. A\nlocal attacker could potentially exploit this to execute arbitrary\ncode in setuid programs and gain administrative privileges.\n(CVE-2018-1000001)\n\nA memory leak was discovered in the _dl_init_paths() function in the\nGNU C library dynamic loader. A local attacker could potentially\nexploit this with a specially crafted value in the LD_HWCAP_MASK\nenvironment variable, in combination with CVE-2017-1000409 and another\nvulnerability on a system with hardlink protections disabled, in order\nto gain administrative privileges. (CVE-2017-1000408)\n\nA heap-based buffer overflow was discovered in the _dl_init_paths()\nfunction in the GNU C library dynamic loader. A local attacker could\npotentially exploit this with a specially crafted value in the\nLD_LIBRARY_PATH environment variable, in combination with\nCVE-2017-1000408 and another vulnerability on a system with hardlink\nprotections disabled, in order to gain administrative privileges.\n(CVE-2017-1000409)\n\nAn off-by-one error leading to a heap-based buffer overflow was\ndiscovered in the GNU C library glob() implementation. An attacker\ncould potentially exploit this to cause a denial of service or execute\narbitrary code via a maliciously crafted pattern. (CVE-2017-15670)\n\nA heap-based buffer overflow was discovered during unescaping of user\nnames with the ~ operator in the GNU C library glob() implementation.\nAn attacker could potentially exploit this to cause a denial of\nservice or execute arbitrary code via a maliciously crafted pattern.\n(CVE-2017-15804)\n\nIt was discovered that the GNU C library dynamic loader mishandles\nRPATH and RUNPATH containing $ORIGIN for privileged (setuid or\nAT_SECURE) programs. A local attacker could potentially exploit this\nby providing a specially crafted library in the current working\ndirectory in order to gain administrative privileges. (CVE-2017-16997)\n\nIt was discovered that the GNU C library malloc() implementation could\nreturn a memory block that is too small if an attempt is made to\nallocate an object whose size is close to SIZE_MAX, resulting in a\nheap-based overflow. An attacker could potentially exploit this to\ncause a denial of service or execute arbitrary code. This issue only\naffected Ubuntu 17.10. (CVE-2017-17426).\n\nNote that Tenable Network Security has extracted the preceding\ndescription block directly from the Ubuntu security advisory. Tenable\nhas attempted to automatically clean and format it as much as possible\nwithout introducing additional issues.","cvss_score":"7.5","access_vector":"Network","access_complexity":"Medium","auth":"None required","availability_impact":"Complete","confidentiality_impact":"Complete","integrity_impact":"Complete","cwe":"CWE-119","cpe":["p-cpe:/a:canonical:ubuntu_linux:libc6"],"remediation":"Update the affected libc6 package.","references":["USN:3534-1"]},"packages":[{"name":"libc6","version":"2.23-0ubuntu9","type":"linux"}]},{"nvdFinding":{"cve":"CVE-2017-13089","description":"2017/10/26","published_date":"2017/10/26","modified_date":"Antti Levomäki, Christian Jalio, and Joonas Pihlaja discovered that\nWget incorrectly handled certain HTTP responses. A remote attacker\ncould use this issue to cause Wget to crash, resulting in a denial of\nservice, or possibly execute arbitrary code. (CVE-2017-13089,\nCVE-2017-13090)\n\nDawid Golunski discovered that Wget incorrectly handled recursive or\nmirroring mode. A remote attacker could possibly use this issue to\nbypass intended access list restrictions. (CVE-2016-7098)\n\nOrange Tsai discovered that Wget incorrectly handled CRLF sequences in\nHTTP headers. A remote attacker could possibly use this issue to\ninject arbitrary HTTP headers. (CVE-2017-6508).\n\nNote that Tenable Network Security has extracted the preceding\ndescription block directly from the Ubuntu security advisory. Tenable\nhas attempted to automatically clean and format it as much as possible\nwithout introducing additional issues.","cvss_score":"9.3","access_vector":"Network","access_complexity":"Medium","auth":"None required","availability_impact":"Complete","confidentiality_impact":"Complete","integrity_impact":"Complete","cwe":"CWE-119","cpe":["p-cpe:/a:canonical:ubuntu_linux:wget"],"remediation":"Update the affected wget package.","references":["USN:3464-1"]},"packages":[{"name":"wget","version":"1.17.1-1ubuntu1.2","type":"linux"}]}],"os_version":"16.04","created_at":"2018-09-17T17:07:34.556Z","installed_packages":[{"name":"dpkg","version":"1.18.4ubuntu1.2","type":"linux"},{"name":"ubuntu-keyring","version":"2012.05.19","type":"linux"},{"name":"libssl1.0.0","version":"1.0.2g-1ubuntu4.8","type":"linux"},{"name":"libcap2-bin","version":"1:2.24-12","type":"linux"},{"name":"liblz4-1","version":"0.0~r131-2ubuntu2","type":"linux"}],"platform":"docker","image_name":"ubuntu","updated_at":"2019-05-16T11:04:20.301Z","digest":"f708f91abdec052d05a46213815540616d24627b6af9cb3668484efb017969bf","tag":"infected","potentially_unwanted_programs":[],"docker_image_id":"4013750e4cd5","os_architecture":"AMD64"}}}}}},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"404":{"description":"Returned if Tenable.io cannot find the specified image or the report for the image is not ready."}}}}},"components":{"securitySchemes":{"cloud":{"type":"apiKey","in":"header","name":"X-ApiKeys","description":"Format - accessKey=ACCESS_KEY;secretKey=SECRET_KEY"}},"schemas":{"imageListResponse":{"type":"object","description":"A list of images with pagination information.","properties":{"pagination":{"$ref":"#/components/schemas/pagination"},"items":{"type":"array","items":{"$ref":"#/components/schemas/imageDetails"}}}},"imageDetails":{"type":"object","description":"The image details.","properties":{"name":{"type":"string","description":"The name of the image."},"repository":{"type":"string","description":"The name of the Tenable.io Container Security repository where the image is stored."},"tag":{"type":"string","description":"The tag identifying the image version.\n**Note**: Image tags are not equivalent to Tenable.io [asset tags](/reference#tags)."},"digest":{"type":"string","description":"A content-addressable image identifier."},"status":{"type":"string","description":"The image analysis status. Status values can include:\n - never_scanned\n - scanned\n - scan_failed","enum":["never_scanned","scanned","scan_failed"]},"score":{"type":"number","format":"double","description":"The image risk score. For more information about the risk score metric, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/RiskMetrics.htm)."},"numberOfVulns":{"type":"integer","format":"int32","description":"The number of known vulnerabilities for the image."},"numberOfMalware":{"type":"integer","format":"int32","description":"The number of known malware exploits for image."},"uploadedAt":{"type":"string","description":"An ISO timestamp indicating the date and time when the image was uploaded to Tenable.io Container Security, for example, `2018-12-31T13:51:17.243Z`."},"lastScanned":{"type":"string","description":"An ISO timestamp indicating the date and time when Tenable.io Container Security last scanned the image, for example, `2018-12-31T13:51:17.243Z`."},"layers":{"type":"array","description":"The layers that represent the history of changes to the image.","items":{"$ref":"#/components/schemas/layer"}},"reportUrl":{"type":"string","description":"The URL of the latest available image analysis report."}}},"layer":{"type":"object","description":"Detailed information for an image layer.","properties":{"size":{"type":"number","format":"long","description":"The layer size in kilobytes."},"digest":{"type":"string","description":"A content-addressable layer identifier."}}},"repositoryListResponse":{"type":"object","description":"A list of Tenable.io Container Security repositories with pagination information.","properties":{"pagination":{"$ref":"#/components/schemas/pagination"},"items":{"type":"array","items":{"$ref":"#/components/schemas/repositoryDetails"}}}},"repositoryDetails":{"type":"object","description":"Details of a Tenable.io Container Security repository.","properties":{"name":{"type":"string","description":"The name of the repository."},"description":{"type":"string","description":"The description of the repository."},"imagesCount":{"type":"integer","format":"int64","description":"The total number of images in the repository."},"labelsCount":{"type":"integer","format":"int64","description":"The number of unique image name/tag combinations in the repository."},"vulnerabilitiesCount":{"type":"integer","format":"int64","description":"The total number of discovered vulnerabilities for the images in the repository."},"malwareCount":{"type":"integer","format":"int64","description":"The total number of known malware exploits for the images in the repository."},"pullCount":{"type":"integer","format":"int64","description":"The number of times the image was pulled from the repository."},"pushCount":{"type":"integer","format":"int64","description":"The number of times the image was uploaded to the repository"},"totalBytes":{"type":"integer","format":"int64","description":"The total size in bytes of the images in the repository."}},"required":["name","imagesCount","labelsCount","vulnerabilitiesCount","malwareCount","pullCount","pushCount","totalBytes"]},"imageReport":{"type":"object","description":"An image vulnerability report.","properties":{"os_release_name":{"type":"string","description":"The image operating system release name."},"malware":{"type":"array","description":"A list of malware files identified by the Tenable.io Container Security scan in the image.","items":{"$ref":"#/components/schemas/malware"},"uniqueItems":true},"sha256":{"type":"string","description":"The image SHA256 hash."},"os":{"type":"string","description":"The image operating system."},"risk_score":{"type":"integer","description":"The image risk score on a scale of 1-10. For more information about the risk score metric, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/containersecurity/Content/ContainerSecurity/RiskMetrics.htm)."},"findings":{"type":"array","description":"A list of vulnerabilities that Tenable.io Container Security identified in the image.","items":{"$ref":"#/components/schemas/finding"},"uniqueItems":true},"os_version":{"type":"string","description":"The image operating system version."},"created_at":{"type":"string","format":"date-time","description":"An ISO timestamp indicating the date and time when the image was created, for example, `2018-12-31T13:51:17.243Z`."},"installed_packages":{"type":"array","description":"A list of installed software packages for the image.","items":{"$ref":"#/components/schemas/installedPackage"}},"platform":{"type":"string","description":"The image platform, for example, `docker`."},"image_name":{"type":"string","description":"The name of the image."},"updated_at":{"type":"string","format":"date-time","description":"An ISO timestamp indicating the date and time when the image was last uploaded, for example, `2018-12-31T13:51:17.243Z`."},"digest":{"type":"string","description":"The image digest."},"tag":{"type":"string","description":"The tag identifying the image version.\n**Note**: Image tags are not equivalent to Tenable.io [asset tags](/reference#tags)."},"potentially_unwanted_programs":{"type":"array","description":"A list of potentially unwanted programs for the image.","items":{"$ref":"#/components/schemas/unwantedProgram"},"uniqueItems":true},"docker_image_id":{"type":"string","description":"The image Docker ID."},"os_architecture":{"type":"string","description":"An image processor architecture, for example, `AMD64`."}},"required":["name","imagesCount","labelsCount","vulnerabilitiesCount","malwareCount","pullCount","pushCount","totalBytes"]},"finding":{"type":"object","description":"The details for the discovered vulnerability and a list of associated software packages.","properties":{"nvdFinding":{"$ref":"#/components/schemas/nvdFinding"},"packages":{"type":"array","items":{},"uniqueItems":true}}},"nvdFinding":{"type":"object","description":"The details for the discovered vulnerability, including description, external references, and remediation information.","properties":{"cve":{"type":"string","description":"The Common Vulnerabilities and Exposures (CVE) ID for vulnerability."},"description":{"type":"string","description":"The extended description of the vulnerability."},"published_date":{"type":"string","description":"An ISO timestamp indicating the date when the vulnerability definition was published, for example, `2018-12-31T13:51:17.243Z`."},"modified_date":{"type":"string","description":"An ISO timestamp indicating the date when the vulnerability definition was updated, for example, `2018-12-31T13:51:17.243Z`."},"cvss_score":{"type":"string","description":"The CVSSv2 base score (intrinsic and fundamental characteristics of a vulnerability that are constant over time and user environments)."},"access_vector":{"type":"string","description":"The CVSSv2 Access Vector (AV) metric for the vulnerability indicating how the vulnerability can be exploited. Possible values include:\n - Local\n - Adjacent Network\n - Network"},"access_complexity":{"type":"string","description":"The CVSSv2 Access Complexity (AC) metric for the vulnerability. Possible values include:\n - High\n - Medium\n - Low"},"auth":{"type":"string","description":"The CVSSv2 Authentication (Au) metric for the vulnerability. The metric describes the number of times that an attacker must authenticate to a target to exploit it. Possible values include:\n - None required\n - Single\n - Multiple"},"availability_impact":{"type":"string","description":"The CVSSv2 availability impact metric for the vulnerability. The metric describes the impact on the availability of the target system. Possible values include:\n - None\n - Partial\n - Complete"},"confidentiality_impact":{"type":"string","description":"The CVSSv2 confidentiality impact metric for the vulnerability. The metric describes the impact on the confidentiality of data processed by the system. Possible values include:\n - None\n - Partial\n - Complete"},"integrity_impact":{"type":"string","description":"The CVSSv2 integrity impact metric for the vulnerability. The metric describes the impact on the integrity of the exploited system. Possible values include:\n - None\n - Partial\n - Complete"},"cwe":{"type":"string","description":"The Common Weakness Enumeration (CWE) ID for vulnerability."},"cpe":{"type":"array","description":"The systems the vulnerability affects identified by Common Platform Enumeration (CPE).","items":{"type":"string"}},"remediation":{"type":"string","description":"Remediation information for the vulnerability."},"references":{"type":"array","items":{"type":"string"},"description":"Additional references to third-party information about the vulnerability."}}},"package":{"type":"object","description":"A software packages affected by the vulnerability.","properties":{"name":{"type":"string","description":"The name of the package."},"version":{"type":"string","description":"The version of the package."},"type":{"type":"string","description":"The operating system or distribution associated with the package, for example, `linux`."}}},"installedPackage":{"type":"object","description":"A software package installed on the image.","properties":{"name":{"type":"string","description":"The name of the package."},"version":{"type":"string","description":"The version of the package."},"type":{"type":"string","description":"The operating system or distribution associated with the package, for example, `linux`."}}},"malware":{"type":"object","description":"The details of identified malware.","properties":{"infectedFile":{"type":"string","description":"The path of the infected file."},"fileTypeDescriptor":{"type":"string","description":"The file type of the infected file, for example, `ELF32`."},"md5":{"type":"string","description":"The MD5 signature of the infected file."},"sha256":{"type":"string","description":"The SHA256 signature of the infected file."}}},"unwantedProgram":{"description":"The unwanted program details.","type":"object","properties":{"infectedFile":{"type":"string","description":"The path of the program file."},"fileTypeDescriptor":{"type":"string","description":"The file type of the program file, for example, `ELF32`."},"md5":{"type":"string","description":"The MD5 signature of the program file."},"sha256":{"type":"string","description":"The SHA256 signature of the program file."}}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of records matching your search criteria. Must be in the int32 format."},"limit":{"type":"integer","description":"Maximum number of records requested (or service imposed limit if not in request). Must be in the int32 format."},"offset":{"type":"integer","description":"The number of skipped records in the returned result set. Must be in the int32 format."},"sort":{"description":"An array of objects representing the fields you specified as sort parameters in the request. This attribute is only present if your request message specifies sort parameters.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the sort field."},"order":{"type":"string","description":"The direction in which Tenable.io sorts on the field, `asc` for ascending or `desc` for descending.","enum":["asc","desc"]}}}}}}}},"x-explorer-enabled":true,"x-proxy-enabled":true,"x-samples-enabled":true,"x-samples-languages":["python","curl","node","powershell","ruby","javascript","objectivec","java","php","csharp","go","swift","kotlin"]} \ No newline at end of file diff --git a/app_gen/openapi-parsers/other/TIO-API-Downloads-API.json b/app_gen/openapi-parsers/other/TIO-API-Downloads-API.json new file mode 100644 index 00000000..54ec76f8 --- /dev/null +++ b/app_gen/openapi-parsers/other/TIO-API-Downloads-API.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"version":"1.0.0","title":"Downloads API","description":"The Downloads API allows you to access and download available Tenable products installation files and updates. You can use the API endpoints to list product pages, list downloads available for a specific product, and to download a file. The endpoints can also be used to determine and download latest version of a file to facilitate the automation of an installation.\n\n**Note:** Tenable Downloads API uses a different server URL than Tenable.io API: `https://www.tenable.com/downloads/api/v2/pages`.\n\n### Authentication\n\nThe Downloads API uses Bearer token authentication and requires a valid token in the Authorization header:\n```\nAuthorization: Bearer AbCdEf123456\n```\n\nTo access or reset your authentication token, navigate to the [Authentication Token](https://www.tenable.com/downloads/api_docs) page."},"tags":[{"name":"Downloads","description":"The Downloads API allows you to access and download available Tenable products installation files and updates. You can use the API endpoints to list product pages, list downloads available for a specific product, and to download a file. The endpoints can also be used to determine and download latest version of a file to facilitate the automation of an installation.\n\n**Note:** Tenable Downloads API uses a different server URL than Tenable.io API: `https://www.tenable.com/downloads/api/v2/pages`.\n\n### Authentication\n\nThe Downloads API uses Bearer token authentication and requires a valid token in the Authorization header:\n```\nAuthorization: Bearer AbCdEf123456\n```\n\nTo access or reset your authentication token, navigate to the [Authentication Token](https://www.tenable.com/downloads/api_docs) page."}],"servers":[{"url":"https://www.tenable.com/downloads/api/v2"}],"components":{"securitySchemes":{"Bearer":{"type":"apiKey","in":"header","name":"Authorization","description":"Example: Bearer "}},"schemas":{"Page":{"type":"object","properties":{"title":{"type":"string","description":"The name of the product.","example":"Nessus"},"page_slug":{"type":"string","description":"Product page slug, for example, `nessus`.","example":"nessus"},"description":{"type":"string","description":"The description of a product.","example":"Binary download files for Nessus Professional, Nessus Manager, and connecting Nessus Scanners to Tenable.io & Tenable.sc."},"files_index_url":{"type":"string","description":"The URL to list the product files available for download.","example":"https://www.tenable.com/downloads/api/v2/downloads/api/v2/pages/nessus"}}},"Download":{"type":"object","properties":{"file":{"type":"string","description":"The name of the file.","example":"Nessus-8.2.1-debian6_i386.deb"},"version":{"type":"string","description":"Product version.","example":"8.2.1"},"size":{"type":"integer","description":"The size of the file in bytes.","example":67331530},"release_date":{"type":"string","description":"Release date.","example":"01/24/2019"},"product_release_date":{"type":"string","description":"Product release date.","example":"01/24/2019"},"md5":{"type":"string","description":"The MD5 hash of the file.","example":"098f6bcd4621d373cade4e832627b4f6"},"sha256":{"type":"string","description":"The SHA256 hash of the file","example":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"},"file_url":{"type":"string","description":"The URL to download the file.","example":"https://www.tenable.com/downloads/api/v2/downloads/api/v2/pages/nessus/files/Nessus-8.2.1-debian6_i386.deb"}}},"LatestDownload":{"type":"object","properties":{"file":{"type":"string","description":"The name of the file.","example":"Nessus-8.2.1-debian6_i386.deb"},"version":{"type":"string","description":"Product version.","example":"8.2.1"},"size":{"type":"integer","description":"The size of the file in bytes.","example":67331530},"release_date":{"type":"string","description":"Release date.","example":"01/24/2019"},"product_release_date":{"type":"string","description":"Product release date.","example":"01/24/2019"},"md5":{"type":"string","description":"The MD5 hash of the file.","example":"098f6bcd4621d373cade4e832627b4f6"},"sha256":{"type":"string","description":"The SHA256 hash of the file","example":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"},"file_url":{"type":"string","description":"The URL to download the latest version of this file.","example":"https://www.tenable.com/downloads/api/v2/downloads/api/v2/pages/nessus/files/Nessus-latest-debian6_i386.deb"}}},"Releases":{"type":"object","properties":{"latest":{"properties":{"Product Name - X.X.X":{"type":"array","items":{"$ref":"#/components/schemas/LatestDownload"}}}},"Product Name - X.X.X":{"type":"array","items":{"$ref":"#/components/schemas/Download"}}}},"SigningKey":{"type":"object","properties":{"file":{"type":"string","description":"The name of the file.","example":"Tenable GPG Key - 2048 bit"},"size":{"type":"integer","description":"The size of the file in bytes.","example":1764},"md5":{"type":"string","description":"The MD5 hash of the file.","example":"098f6bcd4621d373cade4e832627b4f6"},"sha256":{"type":"string","description":"The SHA256 hash of the file.","example":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"},"file_url":{"type":"string","description":"The URL to download the file.","example":"https://www.tenable.com/downloads/api/v2/downloads/api/v2/pages/nessus/files/tenable-2048.gpg"}}},"NotFound":{"type":"object","properties":{"message":{"type":"string","description":"Not Found","example":"Page Not Found"}}},"Unauthorized":{"type":"object","properties":{"message":{"type":"string","description":"Unauthorized","example":"Unauthorized"}}},"InternalServerError":{"type":"object","properties":{"message":{"type":"string","description":"Server Error","example":"Server Error"}}}}},"security":[{"Bearer":[]}],"x-samples-languages":["python","curl","node","powershell","ruby","javascript","objectivec","java","php","csharp","go","swift","kotlin"],"paths":{"/pages":{"get":{"summary":"List product pages","description":"Returns a list of product pages.","tags":["Downloads"],"responses":{"200":{"description":"An array of product pages.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Page"}},"examples":{"response":{"value":[{"title":"Nessus","page_slug":"nessus","description":"Binary download files for Nessus Professional, Nessus Manager, and connecting Nessus Scanners to Tenable.io & Tenable.sc.\n","files_index_url":"https://www.tenable.com/downloads/api/v2/pages/nessus"}]}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Unauthorized"},"examples":{"response":{"value":{"message":"Unauthorized"}}}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"examples":{"response":{"value":{"message":"Server Error"}}}}}}}}},"/pages/{slug}":{"get":{"summary":"List downloadable files for a product","description":"Returns a JSON hash of all download files for a given product page.","tags":["Downloads"],"parameters":[{"in":"path","name":"slug","schema":{"type":"string"},"required":true,"description":"Product page slug, for example, `nessus`."}],"responses":{"200":{"description":"A JSON hash of releases for a given product","content":{"application/json":{"schema":{"type":"object","properties":{"releases":{"$ref":"#/components/schemas/Releases"},"signing_keys":{"type":"array","items":{"$ref":"#/components/schemas/SigningKey"}}}},"examples":{"response":{"value":{"releases":{"latest":{"Product Name - X.X.X":[{"file":"Nessus-8.2.1-debian6_i386.deb","version":"8.2.1","size":67331530,"release_date":"01/24/2019","product_release_date":"01/24/2019","md5":"098f6bcd4621d373cade4e832627b4f6","sha256":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08","file_url":"https://www.tenable.com/downloads/api/v2/pages/nessus/files/Nessus-latest-debian6_i386.deb"}]},"Product Name - X.X.X":[{"file":"Nessus-8.2.1-debian6_i386.deb","version":"8.2.1","size":67331530,"release_date":"01/24/2019","product_release_date":"01/24/2019","md5":"098f6bcd4621d373cade4e832627b4f6","sha256":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08","file_url":"https://www.tenable.com/downloads/api/v2/pages/nessus/files/Nessus-8.2.1-debian6_i386.deb"}]},"signing_keys":[{"file":"Tenable GPG Key - 2048 bit","size":1764,"md5":"098f6bcd4621d373cade4e832627b4f6","sha256":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08","file_url":"https://www.tenable.com/downloads/api/v2/pages/nessus/files/tenable-2048.gpg"}]}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Unauthorized"},"examples":{"response":{"value":{"message":"Unauthorized"}}}}}},"404":{"description":"Page Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFound"},"examples":{"response":{"value":{"message":"Page Not Found"}}}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"examples":{"response":{"value":{"message":"Server Error"}}}}}}}}},"/pages/{slug}/files/{file}":{"get":{"summary":"Download a file","description":"Downloads a requested file.","tags":["Downloads"],"parameters":[{"in":"path","name":"slug","schema":{"type":"string"},"required":true,"description":"Product page slug, for example, `nessus`."},{"in":"path","name":"file","schema":{"type":"string"},"required":true,"description":"File name, for example,. `Nessus-latest-x64.msi`."}],"responses":{"200":{"description":"Requested file"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Unauthorized"},"examples":{"response":{"value":{"message":"Unauthorized"}}}}}},"404":{"description":"File or Page Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFound"},"examples":{"response":{"value":{"message":"Page Not Found"}}}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalServerError"},"examples":{"response":{"value":{"message":"Server Error"}}}}}}}}}},"x-explorer-enabled":true,"x-proxy-enabled":true,"x-samples-enabled":true} \ No newline at end of file diff --git a/app_gen/openapi-parsers/other/TIO-API-Tenable-Platform.json b/app_gen/openapi-parsers/other/TIO-API-Tenable-Platform.json new file mode 100644 index 00000000..0377f176 --- /dev/null +++ b/app_gen/openapi-parsers/other/TIO-API-Tenable-Platform.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"Tenable Platform","version":"1.0.0"},"security":[{"cloud":[]}],"servers":[{"url":"https://cloud.tenable.com"}],"components":{"securitySchemes":{"cloud":{"type":"apiKey","in":"header","name":"X-ApiKeys","description":"Format - accessKey=ACCESS_KEY;secretKey=SECRET_KEY"}}},"x-samples-languages":["python","curl","node","powershell","ruby","javascript","objectivec","java","php","csharp","go","swift","kotlin"],"paths":{"/session":{"post":{"summary":"Create session","description":"**Note:** This endpoint is deprecated. Tenable best practice is to use API keys that are generated for specific user accounts. For your organization's integrations with the Tenable.io API, Tenable recommends you do not create and use session tokens.

Requires BASIC [16] user permissions. See Permissions.

","deprecated":true,"operationId":"session-create","tags":["Session"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","description":"The username for the person who is attempting to log in."},"password":{"type":"string","description":"The password for the person who is attempting to log in.","format":"password"}},"required":["username","password"]}}}},"responses":{"200":{"description":"Returns the session token.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"The session token."}}},"examples":{"response":{"value":{"token":"k83049e04e76ea2b696f76e1cc83a2e87b6a52adbc014967f915c469e5739ac3"}}}}}},"400":{"description":"Returned if the username format is not valid."},"401":{"description":"Returned if the username or password is invalid."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if too many users are connected.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"Get user session","description":"Returns the user session data.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-get","tags":["Session"],"responses":{"200":{"description":"Returns the user session data.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The unique ID of the user."},"uuid":{"type":"string","description":"The UUID of the user."},"uuid_id":{"type":"string","description":"The UUID of the user."},"username":{"type":"string","description":"The username for the user."},"user_name":{"type":"string","description":"The username for the user."},"email":{"type":"string","description":"The email address for the user."},"name":{"type":"string","description":"The full name for the user."},"type":{"type":"string","description":"The type of user (`local` or `ldap`)."},"permissions":{"type":"integer","description":"The user permissions as described in Permissions.","format":"int32"},"enabled":{"type":"integer","description":"If 1, the user is enabled."},"last_login_attempt":{"type":"integer","description":"The Unix timestamp of the last failed login attempt."},"login_fail_count":{"type":"integer","description":"The count of failed login attempts."},"login_fail_total":{"type":"integer","description":"The number of failed logins that may occur prior to the user being locked."},"two_factor":{"type":"object","description":"This attribute is only present if two-factor authentication is enabled for the user account.","properties":{"sms_phone":{"type":"string","description":"The mobile phone number Tenable.io uses during two-factor authentication for the user account."},"sms_enabled":{"type":"integer","description":"A value specifying whether two-factor authentication is enabled (`1`) or disabled (`0`) for the user account.","format":"int32"},"email_enabled":{"type":"integer","description":"A value specifying whether, in addition to sending a text message with the verification code, Tenable.io sends a backup email containing the verification code to the email associated with your user account. If this value is `0`, Tenable.io does not send a backup email message. If this value is `1`, Tenable.io sends a backup email message.","format":"int32"}}},"container_id":{"type":"integer","description":"The ID of the user's Tenable.io instance."},"container_uuid":{"type":"string","description":"The UUID of the user's Tenable.io instance."},"container_name":{"type":"string","description":"The name of the user's Tenable.io instance."},"features":{"type":"object","description":"A list of Tenable.io features enabled for the user's instance.","properties":{}},"apps":{"type":"object","description":"A list of Tenable.io products enabled for the user's instance.","properties":{"consec":{"type":"string","description":"The license status for Tenable.io Container Security, if enabled for the user's instance."},"was":{"type":"string","description":"The license status for Tenable.io Web Application Scanning, if enabled for the user's instance."}}},"group_uuids":{"type":"array","description":"The UUIDs of user groups to which the user belongs.","items":{"type":"string"}},"groups":{"description":"The list of user groups to which the user belongs.","type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the user group."},"name":{"type":"string","description":"The name of the user group."},"permissions":{"type":"integer","description":"The specified user's permissions for the user group. Default to `0`.","format":"int32"},"id":{"type":"integer","description":"The ID of the user group.","format":"int32"}}}},"lastlogin":{"type":"integer","description":"The Unix timestamp for the user's last login."},"connectors":{"type":"boolean","description":"Connectors for the user's Tenable.io instance."},"lockout":{"type":"integer","description":"Specifies whether the user account is locked out (`1`) or available (`0`)."}}},"examples":{"response":{"value":{"id":2,"uuid":"fb76f456-9a6f-4f63-8553-1cee234eb965","uuid_id":"fa76e456-9a6f-4f63-8553-1ced233eb965","username":"user2@example.com","user_name":"user2@example.com","email":"user2@example.org","name":"Sample User","type":"local","permissions":64,"enabled":true,"last_login_attempt":1540942030719,"login_fail_count":0,"login_fail_total":14,"two_factor":{"sms_phone":"+14108720555","sms_enabled":1,"email_enabled":0},"container_id":766315,"container_uuid":"3bc442f4-0cd1-4de0-95a3-3d8e587820ee","container_name":"demo","features":{"access_groups":true,"access_groups_migration":true,"advanced_search_v2":true,"agent_triage_m2":true,"agent_updates":true,"analytics":true,"analytics_v2":true,"asset_deleting_ui":true,"asset_management":true,"audits_workbench":false,"aws_connector_v1":true,"cfl_core_ssor":true,"connectors_gen2":false,"container_security":true,"container_security_gen2":true,"container_security_gen2_runtime":true,"credentials_mgmt":true,"credentials_mgmt_v2":true,"dashboards_gen2":false,"dashboards_gen2_blank_canvas":false,"dashboards_gen2_export":false,"dashboards_gen2_export_png":false,"dashboards_gen2_lumin_enabled":false,"dashboards_gen2_schedule":false,"dashboards_gen2_tag_filter":false,"dashboards_gen2_widget_filters":false,"dashboards_gen2_widget_library":false,"dynamic_tagging":true,"environment_management":true,"export_dashboard":true,"export_dashboard_pdf":true,"general_data_protection_compliance":true,"import_data":false,"indexing_v2":true,"lumin_beta_allowed":true,"lumin_beta_enabled":true,"modify_vulnerability":false,"pci_multiscan":true,"qualys_connector":true,"qualys_vuln_connector":true,"rbac":true,"recast_rules":true,"reporting":true,"scan_service":true,"scans_gen2":true,"state":true,"suggest_feature":true,"system":false,"tagging":true,"vm_service_query":true,"vulnerability_management_gen2":true,"was_discovery":true,"was_multi_scanning":true,"was_plugin_selection":true,"was_scan_progress":true,"webapp_scanning":true,"webapp_scanning_gen2":true},"apps":{},"group_uuids":[],"groups":[],"lastlogin":1543864186682}}}}}},"403":{"description":"Returned if the user does not have permission to view the session data."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update user settings","description":"Updates the settings for the current user.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-edit","tags":["Session"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Full name for the user."},"email":{"type":"string","description":"Email address for the user."}}}}}},"responses":{"200":{"description":"Returns the user session data.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID for the user."},"id":{"type":"integer","description":"The unique ID of the user."},"user_name":{"type":"string","description":"The username for the user."},"username":{"type":"string","description":"The username for the user."},"email":{"type":"string","description":"The email address for the user."},"name":{"type":"string","description":"The real name of the user."},"type":{"type":"string","description":"The type of user (`local` or `ldap`)."},"container_uuid":{"type":"string","description":"The UUID of the user's Tenable.io instance."},"whatsnew_version":{"type":"string","description":"The version of the \"what's new\" messaging that appears when the user logs into the user interface."},"aggregate":{"type":"integer","description":"If `1`, aggregate collection is enabled."},"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"last_login_attempt":{"type":"integer","description":"The Unix timestamp for the last failed login attempt."},"login_fail_count":{"type":"integer","description":"The number of failed login attempts for the user since the last successful login."},"login_fail_total":{"type":"integer","description":"The total number of failed login attempts for the user."},"enabled":{"type":"boolean","description":"Specifies whether the user account is enabled (true) or disabled (false)."},"two_factor":{"type":"object","description":"This attribute is only present if two-factor authentication is enabled for the user account.","properties":{"sms_phone":{"type":"string","description":"The mobile phone number Tenable.io uses during two-factor authentication for the user account."},"sms_enabled":{"type":"integer","description":"A value specifying whether two-factor authentication is enabled (`1`) or disabled (`0`) for the user account.","format":"int32"},"email_enabled":{"type":"integer","description":"A value specifying whether, in addition to sending a text message with the verification code, Tenable.io sends a backup email containing the verification code to the email associated with your user account. If this value is `0`, Tenable.io does not send a backup email message. If this value is `1`, Tenable.io sends a backup email message.","format":"int32"}}},"lockout":{"type":"integer","description":"Specifies whether the user account is locked out (`1`) or available (`0`)."},"group_uuids":{"type":"array","description":"The UUIDs of user groups to which the user belongs.","items":{"type":"string"}},"groups":{"description":"The list of user groups to which the user belongs.","type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the user group."},"name":{"type":"string","description":"The name of the user group."},"id":{"type":"integer","description":"The ID of the user group.","format":"int32"}}}},"lastlogin":{"type":"integer","description":"The last time the user logged in to Tenable.io in the Unix time format."},"uuid_id":{"type":"string","description":"The UUID for the user."}}},"examples":{"response":{"value":{"id":2,"user_name":"user2@example.com","username":"user2@example.com","email":"user2@example.org","name":"Sample User","type":"local","whatsnew_version":"","aggregate":true,"permissions":64,"last_login_attempt":1540942130719,"login_fail_count":0,"login_fail_total":14,"enabled":true,"uuid":"fa76f456-9a6f-4f63-8553-1cee233fb965","container_uuid":"3bc442f4-0cd1-4de0-95a3-3d8e587820fe","lastlogin":1543864196682,"uuid_id":"fa76f456-9a6f-4f63-8553-1cfe233eb965"}}}}}},"403":{"description":"Returned if the user does not have permission to edit the session data."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if the server failed to edit the user.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Log out user","description":"Logs the current user out and destroys the session.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-destroy","tags":["Session"],"responses":{"200":{"description":"Returned if the session has been properly destroyed.","content":{"application/json":{"schema":{}}}},"401":{"description":"Returned if no session exists."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/session/restore":{"post":{"summary":"Restore impersonated session","description":"Restores an impersonated session to the original user.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-restore","tags":["Session"],"responses":{"200":{"description":"Returned if the session has been properly restored.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"401":{"description":"Returned if no session exists."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/session/chpasswd":{"put":{"summary":"Change password","description":"Changes password for the current user.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-password","tags":["Session"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"current_password":{"type":"string","description":"The current password for the user.","format":"password"},"password":{"type":"string","description":"The new password for the user.","format":"password"}},"required":["password","current_password"]}}}},"responses":{"200":{"description":"Returned if the user password has been changed.","content":{"application/json":{"schema":{}}}},"400":{"description":"Returned if the password is too short."},"403":{"description":"Returned if the user does not have permission to change the password."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if the server failed to change the password.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/session/keys":{"put":{"summary":"Generate API keys","description":"Generates API keys for the current user.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-keys","tags":["Session"],"responses":{"200":{"description":"Returned if the user API keys were generated.","content":{"application/json":{"schema":{"type":"object","properties":{"accessKey":{"type":"string","description":"The access key for the user account in Tenable.io. Use this key in combination with the user's secret key to submit authorized API requests to Tenable.io."},"secretKey":{"type":"string","description":"The secret key for the user account in Tenable.io. Use this key in combination with the user's access key to submit authorized API requests to Tenable.io."}}},"examples":{"response":{"value":{"accessKey":"748a5a175273ea87b026d815378f328b4d02d89df070d7891bd869762adf5b69","secretKey":"d2c7a8d58c996a2eccba270de732d0c1833fb1107c2929cb5321c3f15c5bc0ee"}}}}}},"401":{"description":"Returned if the user is not logged in."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/session/two-factor/send-verification":{"post":{"summary":"Send verification code","description":"Start the process of enabling two-factor authentication by sending a one-time verification code to the provided phone number.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-send-code","tags":["Session"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sms_phone":{"type":"string","description":"The phone number where Tenable.io sends the one-time verification code. Must begin with the `+` sign."}},"required":["sms_phone"]}}}},"responses":{"200":{"description":"Returned if the one-time verification code was sent successfully to the provided phone number.","content":{"application/json":{"schema":{}}}},"400":{"description":"Returned if the verification code could not be sent."},"404":{"description":"Returned if the user does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/session/two-factor/verify-code":{"post":{"summary":"Validate verification code","description":"Validate the verification code sent to a phone number. If this request is successful, it enables two-factor authentication for the current user.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-verify-code","tags":["Session"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"verification_code":{"type":"string","description":"The verification code sent in the send-verification request."}},"required":["verification_code"]}}}},"responses":{"200":{"description":"Returned if two-factor authentication was successfully enabled for this user.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if the the verification code was empty, incorrect, or expired."},"404":{"description":"Returned if the user does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/session/two-factor":{"put":{"summary":"Configure two-factor authentication","description":"Configure the current user's two-factor authentication settings. Before you can change these settings, you must send and validate the verification code using the /session/two-factor/send-verification and /session/two-factor/verify-code endpoints.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"session-two-factor-settings","tags":["Session"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email_enabled":{"type":"boolean","description":"Specifies whether backup notification for two-factor authentication is enabled. If enabled, Tenable.io sends the two-factor verification code via e-mail, as well as via the default SMS message."},"sms_enabled":{"type":"boolean","description":"Specifies whether two-factor authentication is enabled. If enabled, Tenable.io sends the verification code via an SMS message. This parameter must be enabled to enable two-factor verification for the user."},"sms_phone":{"type":"string","description":"The phone number to use for two-factor authentication. Must begin with the `+` sign. This field is required when sms\\_enabled is set to `true`.","example":"+155555555555"}},"required":["email_enabled","sms_enabled"]}}}},"responses":{"200":{"description":"Returned if the two-factor authentication settings update was successful.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if the user does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/users":{"post":{"summary":"Create user","description":"Creates a new user.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-create","tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","description":"The login name for the user."},"password":{"type":"string","description":"The initial password for the user.","format":"password"},"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"name":{"type":"string","description":"The name of the user (for example, first and last name)."},"email":{"type":"string","description":"The email address of the user."}},"required":["username","password","permissions"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates the user.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the user."},"username":{"type":"string","description":"The username for the user."},"name":{"type":"string","description":"The name of the user (for example, first and last name)."},"email":{"type":"string","description":"The email address for the user."},"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"lastlogin":{"type":"integer","description":"The last time the user logged in to Tenable.io in the Unix time format."},"type":{"type":"string","description":"The type of user. The only supported type is `local`."},"login_fail_count":{"type":"integer","description":"The number of failed login attempts for the user since the last successful login."},"login_fail_total":{"type":"integer","description":"The total number of failed login attempts for the user."},"last_login_attempt":{"type":"integer","description":"The timestamp of the last failed login attempt for the user."},"enabled":{"type":"boolean","description":"Specifies whether the user account is enabled (true) or disabled (false)."},"lockout":{"type":"integer","description":"Specifies whether the user account is locked out (1) or available (0)."},"uuid_id":{"type":"string","description":"The unique UUID for the user."}}},"examples":{"response":{"value":{"id":5,"user_name":"user2@example.com","username":"user4@api.demo","email":"user2@example.com","name":"Test User","type":"local","aggregate":true,"permissions":32,"login_fail_count":0,"login_fail_total":0,"enabled":true,"uuid":"ed6fd6a6-9d02-4178-8a71-7dd8b000e526","container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","uuid_id":"ed6fd6a6-9d02-4178-8a71-7dd8b000e526"}}}}}},"400":{"description":"Returned if a field in the request is invalid."},"403":{"description":"Returned if you do not have permission to create a user."},"409":{"description":"Returned if you attempted to create a duplicate user."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List users","description":"Returns a list of users.

Requires BASIC [16] user permissions. If you use credentials with ADMIN [64] permissions, Tenable.io returns all fields for individual user details. Otherwise, user details include only the `uuid`, `id`, `username`, and `email` fields. See Permissions.

","operationId":"users-list","tags":["Users"],"responses":{"200":{"description":"Returns a list of users.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the user."},"username":{"type":"string","description":"The username for the user."},"name":{"type":"string","description":"The name of the user (for example, first and last name)."},"email":{"type":"string","description":"The email address for the user."},"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"lastlogin":{"type":"integer","description":"The last time the user logged in to Tenable.io in the Unix time format."},"type":{"type":"string","description":"The type of user. The only supported type is `local`."},"login_fail_count":{"type":"integer","description":"The number of failed login attempts for the user since the last successful login."},"login_fail_total":{"type":"integer","description":"The total number of failed login attempts for the user."},"last_login_attempt":{"type":"integer","description":"The timestamp of the last failed login attempt for the user."},"enabled":{"type":"boolean","description":"Specifies whether the user account is enabled (true) or disabled (false)."},"lockout":{"type":"integer","description":"Specifies whether the user account is locked out (1) or available (0)."},"uuid_id":{"type":"string","description":"The unique UUID for the user."}}},"examples":{"response":{"value":{"users":[{"id":2,"user_name":"admin@example.com","username":"admin@example.com","email":"admin@example.com","name":"Admin Example","type":"local","permissions":64,"login_fail_count":0,"login_fail_total":0,"enabled":true,"uuid":"7a676323-47bb-4838-9cec-c9f01448bb2d","container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","lastlogin":1544477990398,"uuid_id":"7a676323-47bb-4838-9cec-c9f01448bb2d"},{"id":4,"user_name":"user3@example.com","username":"user3@example.com","email":"user3@example.com","name":"User Sample 3rd","type":"local","permissions":32,"login_fail_count":0,"login_fail_total":0,"enabled":true,"uuid":"802ea9fe-701a-4c80-b001-59c252a178cb","container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","uuid_id":"802ea9fe-701a-4c80-b001-59c252a178cb"},{"id":5,"user_name":"user4@example.com","username":"user4@example.com","email":"user4@example.com","name":"User Test","type":"local","permissions":32,"login_fail_count":0,"login_fail_total":0,"enabled":true,"uuid":"ed6fd6a6-9d02-4178-8a71-7dd8b000e526","container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","uuid_id":"ed6fd6a6-9d02-4178-8a71-7dd8b000e526"}]}}}}}},"403":{"description":"Returned if you do not have permission to view the list of users."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}":{"get":{"summary":"Get user details","description":"Returns details for a specific user.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-details","tags":["Users"],"parameters":[{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the user details.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the user."},"username":{"type":"string","description":"The username for the user."},"name":{"type":"string","description":"The name of the user (for example, first and last name)."},"email":{"type":"string","description":"The email address for the user."},"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"lastlogin":{"type":"integer","description":"The last time the user logged in to Tenable.io in the Unix time format."},"type":{"type":"string","description":"The type of user. The only supported type is `local`."},"login_fail_count":{"type":"integer","description":"The number of failed login attempts for the user since the last successful login."},"login_fail_total":{"type":"integer","description":"The total number of failed login attempts for the user."},"last_login_attempt":{"type":"integer","description":"The timestamp of the last failed login attempt for the user."},"enabled":{"type":"boolean","description":"Specifies whether the user account is enabled (true) or disabled (false)."},"lockout":{"type":"integer","description":"Specifies whether the user account is locked out (1) or available (0)."},"uuid_id":{"type":"string","description":"The unique UUID for the user."}}},"examples":{"response":{"value":{"id":4,"user_name":"user3@example.com","username":"user3@example.com","email":"user3@example.com","name":"Test User","type":"local","permissions":32,"login_fail_count":0,"login_fail_total":0,"enabled":true,"uuid":"802ea9fe-701a-4c80-b001-59c252a178cb","container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","uuid_id":"802ea9fe-701a-4c80-b001-59c252a178cb"}}}}}},"403":{"description":"Returned if you do not have permission to view the given user details."},"404":{"description":"Returned if the user specified in the request does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update user","description":"Updates an existing user account.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-edit","tags":["Users"],"parameters":[{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"name":{"type":"string","description":"The name of the user (for example, first and last name)."},"email":{"type":"string","description":"The email address of the user."},"enabled":{"type":"boolean","description":"Specifies whether the user's account is enabled (true) or disabled (false)."}},"required":["permissions"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updates the user.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the user."},"username":{"type":"string","description":"The username for the user."},"name":{"type":"string","description":"The name of the user (for example, first and last name)."},"email":{"type":"string","description":"The email address for the user."},"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"lastlogin":{"type":"integer","description":"The last time the user logged in to Tenable.io in the Unix time format."},"type":{"type":"string","description":"The type of user. The only supported type is `local`."},"login_fail_count":{"type":"integer","description":"The number of failed login attempts for the user since the last successful login."},"login_fail_total":{"type":"integer","description":"The total number of failed login attempts for the user."},"last_login_attempt":{"type":"integer","description":"The timestamp of the last failed login attempt for the user."},"enabled":{"type":"boolean","description":"Specifies whether the user account is enabled (true) or disabled (false)."},"lockout":{"type":"integer","description":"Specifies whether the user account is locked out (1) or available (0)."},"uuid_id":{"type":"string","description":"The unique UUID for the user."}}},"examples":{"response":{"value":{"id":4,"user_name":"user3@example.com","username":"user3@example.com","email":"user3@example.com","name":"Test User","type":"local","whatsnew_version":"","aggregate":true,"permissions":32,"login_fail_count":0,"login_fail_total":0,"enabled":false,"uuid":"802ea9fe-701a-4c80-b001-59c252a178cb","container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","uuid_id":"802ea9fe-701a-4c80-b001-59c252a178cb"}}}}}},"400":{"description":"Returned if a field in the request is invalid."},"403":{"description":"Returned if you do not have permission to update a user."},"404":{"description":"Returned if the specified user does not exist."},"409":{"description":"Returned if you attempt to change your own account's enabled or disabled status."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete user","description":"Deletes a user.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-delete","tags":["Users"],"parameters":[{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io deleted the user.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to delete the user."},"404":{"description":"Returned if the user you attempted to delete does not exist."},"409":{"description":"Returned if you tried to delete your own account."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io failed to delete the user.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}/chpasswd":{"put":{"summary":"Change password","description":"Changes the password for a user.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-password","tags":["Users"],"parameters":[{"description":"The unique ID of the user whose password you want to change.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"current_password":{"type":"string","description":"The current password for the user.","format":"password"},"password":{"type":"string","description":"The new password for the user.","format":"password"}},"required":["current_password","password"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully changed the user password.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if Tenable.io cannot change the user password, because the new password is too short."},"403":{"description":"Returned if you do not have permission to change the user's password."},"404":{"description":"Returned if Tenable.io cannot find the specified user."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io failed to change the password.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}/enabled":{"put":{"summary":"Enable user account","description":"Enables or disables an existing user account.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-enabled","tags":["Users"],"parameters":[{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"The user's enabled or disabled status to be set (`true` to enable or `false` to disable)."}},"required":["enabled"]}}}},"responses":{"200":{"description":"Returns an array of user objects.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the user."},"username":{"type":"string","description":"The username for the user."},"name":{"type":"string","description":"The name of the user (for example, first and last name)."},"email":{"type":"string","description":"The email address for the user."},"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"lastlogin":{"type":"integer","description":"The last time the user logged in to Tenable.io in the Unix time format."},"type":{"type":"string","description":"The type of user. The only supported type is `local`."},"login_fail_count":{"type":"integer","description":"The number of failed login attempts for the user since the last successful login."},"login_fail_total":{"type":"integer","description":"The total number of failed login attempts for the user."},"last_login_attempt":{"type":"integer","description":"The timestamp of the last failed login attempt for the user."},"enabled":{"type":"boolean","description":"Specifies whether the user account is enabled (true) or disabled (false)."},"lockout":{"type":"integer","description":"Specifies whether the user account is locked out (1) or available (0)."},"uuid_id":{"type":"string","description":"The unique UUID for the user."}}},"examples":{"response":{"value":{"object":"user"}}}}}},"403":{"description":"Returned if you do not have permission to update a user."},"404":{"description":"Returned if the user that the request specified does not exist."},"409":{"description":"Returned if you tried to change your own permissions."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}/authorizations":{"get":{"summary":"Get user authorizations","description":"Returns user authorizations for accessing a Tenable.io instance. Access methods include user name and password, single sign-on (SSO) with SAML, and API.\n**Note:** All access methods are authorized by default.\n\nFor background information about managing user authorizations, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/Content/Settings/ManageUserAccessAuthorizations.htm).

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-list-auths","tags":["Users"],"parameters":[{"description":"The UUID of the user. You can find the user UUID by examining the output of the [GET /users](/reference#users-list) endpoint.","required":true,"name":"user_id","in":"path","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Returns authorizations for the user.","content":{"application/json":{"schema":{"type":"object","properties":{"account_uuid":{"type":"string","description":"The UUID of the container.","format":"uuid"},"user_uuid":{"type":"string","description":"The UUID of the user.","format":"uuid"},"api_permitted":{"type":"boolean","description":"Indicates whether API access is authorized for the user."},"password_permitted":{"type":"boolean","description":"Indicates whether user name and password login is authorized for the user."},"saml_permitted":{"type":"boolean","description":"Indicates whether SSO with SAML is authorized for the user."}}},"examples":{"response":{"value":{"account_uuid":"40ac4662-6af3-4a0b-b422-93387ec0f298","user_uuid":"1e623352-a68b-42e0-8af8-f1b7c10a2b72","api_permitted":true,"password_permitted":false,"saml_permitted":true}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified user.","content":{"application/json":{"schema":{"type":"object","description":"Tenable.io API error response.","properties":{"error":{"type":"string","description":"The extended description of the cause of the Tenable.io API error."}}},"examples":{"response":{"value":{"error":"User[UUID=b6a6900e-a616-4266-b2be-765de43348dd] not found."}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update user authorizations","description":"Updates user authorizations for accessing a Tenable.io instance. Use the endpoint to grant and revoke authorizations.\n\n**Note:** You cannot update authorizations for the current user.\n\nFor background information about managing user authorizations, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/Content/Settings/ManageUserAccessAuthorizations.htm).

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-update-auths","tags":["Users"],"parameters":[{"description":"The UUID of the user. You can find the user UUID by examining the output of the [GET /users](/reference#users-list) endpoint.","required":true,"name":"user_id","in":"path","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Specify `true` or `false` to grant or revoke authorizations.","properties":{"api_permitted":{"type":"boolean","description":"Indicates whether API access is authorized for the user."},"password_permitted":{"type":"boolean","description":"Indicates whether user name and password login is authorized for the user."},"saml_permitted":{"type":"boolean","description":"Indicates whether SSO with SAML is authorized for the user."}},"required":["api_permitted","password_permitted","saml_permitted"]}}}},"responses":{"204":{"description":"Returned if the user's authorizations have been updated.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if you specify invalid input parameters.","content":{"application/json":{"schema":{"type":"object","description":"Tenable.io API error response.","properties":{"error":{"type":"string","description":"The extended description of the cause of the Tenable.io API error."}}},"examples":{"response":{"value":{"error":"Unexpected character ('}' (code 125)): was expecting double-quote to start field name\n at [(String)\"{\r\n\"api_permitted\" : true,\r\n}\"; line: 3, column: 2]"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified user.","content":{"application/json":{"schema":{"type":"object","description":"Tenable.io API error response.","properties":{"error":{"type":"string","description":"The extended description of the cause of the Tenable.io API error."}}},"examples":{"response":{"value":{"error":"User[UUID=b6a6900e-a616-4266-b2be-765de43348dd] not found."}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}/keys":{"put":{"summary":"Generate API keys","description":"Generates the API keys for a user.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-keys","tags":["Users"],"parameters":[{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully generated the API keys for the user.","content":{"application/json":{"schema":{"type":"object","properties":{"accessKey":{"type":"string","description":"The access key for the user account in Tenable.io. Use this key in combination with the user's secret key to submit authorized API requests to Tenable.io."},"secretKey":{"type":"string","description":"The secret key for the user account in Tenable.io. Use this key in combination with the user's access key to submit authorized API requests to Tenable.io."}}},"examples":{"response":{"value":{"accessKey":"26e07fb07181cf86e1bc7a240ce398645cf2bb80bbbefc178f100d6f5ffc067d","secretKey":"4be00decc6ea29e65d2910f1d54d23c14190a267285de4b05a481b1e6d3f0fd6"}}}}}},"403":{"description":"Returned if you do not have permission to generate API keys for the user."},"404":{"description":"Returned if Tenable.io cannot find the specified user."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io failed to generate the keys.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}/impersonate":{"post":{"summary":"Impersonate user","description":"Allows the current administrator to impersonate the given user.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-impersonate","tags":["Users"],"parameters":[{"description":"The unique ID of the user you want to impersonate.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the impersonation was successful.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified user."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}/two-factor/send-verification":{"post":{"summary":"Send verification code","description":"Sends a one-time verification code to the user's phone number to start the process of enabling two-factor authentication.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-two-factor-enable","tags":["Users"],"parameters":[{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sms_phone":{"type":"string","description":"The phone number where Tenable.io sends the one-time verification code."}},"required":["sms_phone"]}}}},"responses":{"200":{"description":"Returned if Tenable.io sent the one-time verification code successfully to the specified phone number.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if Tenable.io cannot send the verification code."},"404":{"description":"Returned if Tenable.io cannot find the specified user."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}/two-factor/verify-code":{"post":{"summary":"Validate verification code","description":"Validate the verification code sent to a phone number. If this request is successful, it enables two-factor authentication for the specified user.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-two-factor-enable-verify","tags":["Users"],"parameters":[{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"verification_code":{"type":"string","description":"The verification code sent in the send-verification request."}},"required":["verification_code"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully validated the verification code and enabled two-factor authentication.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if Tenable.io failed to validate the verification code because the verification code was empty, incorrect, or expired."},"404":{"description":"Returned if the user specified in the request does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/users/{user_id}/two-factor":{"put":{"summary":"Configure two-factor authentication ","description":"Enables or disables a user's two-factor authentication settings.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"users-two-factor","tags":["Users"],"parameters":[{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email_enabled":{"type":"boolean","description":"Specifies whether backup notification for two-factor authentication is enabled. If enabled, Tenable.io sends the two-factor verification code via e-mail, as well as via the default SMS message."},"sms_enabled":{"type":"boolean","description":"Specifies whether two-factor authentication is enabled. If enabled, Tenable.io sends the verification code via an SMS message. This parameter must be enabled to enable two-factor verification for the user."},"sms_phone":{"type":"string","description":"The phone number to use for two-factor authentication. Must begin with the `+` sign. This field is required when sms\\_enabled is set to `true`.","example":"+155555555555"}},"required":["email_enabled","sms_enabled","sms_phone"]}}}},"responses":{"200":{"description":"Returned if the two-factor authentication settings update was successful.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if the user that the request specified does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/groups":{"post":{"summary":"Create group","description":"Create a group.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"groups-create","tags":["Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the group."}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates the user group.","content":{"application/json":{"schema":{"type":"object","properties":{"permissions":{"type":"integer","description":"The permissions for the group."},"name":{"type":"string","description":"The name of the group."},"uuid":{"type":"string","description":"The UUID for the group."},"id":{"type":"integer","description":"The unique ID of the group."}}},"examples":{"response":{"value":{"uuid":"59ec5f27-8206-48e7-aa6c-d8ce18fd0f73","name":"Read Only","permissions":0,"container_uuid":"f4fbe518-e648-49dd-b6a4-e80c1ff12805","id":2}}}}}},"400":{"description":"Returned if your request message contains an invalid parameter."},"403":{"description":"Returned if you do not have permission to create a group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to add the group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List groups","description":"Returns the group list.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"groups-list","tags":["Groups"],"responses":{"200":{"description":"Returns the groups list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"user_count":{"type":"integer","description":"The number of users in the group."},"permissions":{"type":"integer","description":"The permissions for the group."},"name":{"type":"string","description":"The name of the group."},"uuid":{"type":"string","description":"The UUID for the group."},"id":{"type":"integer","description":"The unique ID of the group."}}}},"examples":{"response":{"value":{"groups":[{"uuid":"3a0fb06a-ed61-45e0-84d8-8e4e2da586ca","name":"admins","permissions":0,"container_uuid":"f4fbe518-e648-49dd-b6a4-e80c1ff12805","user_count":0,"id":1},{"uuid":"59ec5f27-8206-48e7-aa6c-d8ce18fd0f73","name":"Read Only","permissions":0,"container_uuid":"f4fbe518-e648-49dd-b6a4-e80c1ff12805","user_count":0,"id":2}]}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/groups/{group_id}":{"put":{"summary":"Update group","description":"Edit a group.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"groups-edit","tags":["Groups"],"parameters":[{"description":"The unique ID of the group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the group."}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if Tenable.io updates the user group.","content":{"application/json":{"schema":{"type":"object","properties":{"user_count":{"type":"integer","description":"The number of users in the group."},"permissions":{"type":"integer","description":"The permissions for the group."},"name":{"type":"string","description":"The name of the group."},"uuid":{"type":"string","description":"The UUID for the group."},"id":{"type":"integer","description":"The unique ID of the group."}}},"examples":{"response":{"value":{"uuid":"59ec5f27-8206-48e7-aa6c-d8ce18fd0f73","name":"Read Only Users","permissions":0,"container_uuid":"f4fbe518-e648-49dd-b6a4-e80c1ff12805","user_count":0,"id":2}}}}}},"400":{"description":"Returned if your request message contains an invalid parameter."},"403":{"description":"Returned if you do not have permission to edit a group."},"404":{"description":"Returned if Tenable.io cannot find the specified group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to edit the group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete group","description":"Delete a group.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"groups-delete","tags":["Groups"],"parameters":[{"description":"The unique ID of the group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the specified user group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if Tenable.io cannot find the specified user group."},"403":{"description":"Returned if you do not have permission to delete the group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/groups/{group_id}/users":{"get":{"summary":"List users in group","description":"Return the group user list.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"groups-list-users","tags":["Groups"],"parameters":[{"description":"The unique ID of the group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns if the group user list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the user."},"username":{"type":"string","description":"The username for the user."},"name":{"type":"string","description":"The name of the user (for example, first and last name)."},"email":{"type":"string","description":"The email address for the user."},"permissions":{"type":"integer","description":"The user permissions for the user as described in Permissions.","format":"int32"},"lastlogin":{"type":"integer","description":"The last time the user logged in to Tenable.io in the Unix time format."},"type":{"type":"string","description":"The type of user. The only supported type is `local`."},"login_fail_count":{"type":"integer","description":"The number of failed login attempts for the user since the last successful login."},"login_fail_total":{"type":"integer","description":"The total number of failed login attempts for the user."},"last_login_attempt":{"type":"integer","description":"The timestamp of the last failed login attempt for the user."},"enabled":{"type":"boolean","description":"Specifies whether the user account is enabled (true) or disabled (false)."},"lockout":{"type":"integer","description":"Specifies whether the user account is locked out (1) or available (0)."},"uuid_id":{"type":"string","description":"The unique UUID for the user."}}}},"examples":{"response":{"value":{"users":[{"id":1,"user_name":"nessus_ms_agent","username":"nessus_ms_agent","name":"system","type":"local","permissions":128,"last_login_attempt":0,"login_fail_count":0,"login_fail_total":0,"enabled":false,"uuid":"47e6b2ea-4e3c-4c09-b137-72e9f53b97f6","container_uuid":"f4fbe518-e648-49dd-b6a4-e80c1ff12805","uuid_id":"47e6b2ea-4e3c-4c09-b137-72e9f53b97f6"},{"id":20,"user_name":"user2@example.com","username":"user2@example.com","email":"user2@example.com","name":"Sample User","type":"local","permissions":64,"last_login_attempt":0,"login_fail_count":0,"login_fail_total":0,"enabled":false,"uuid":"001e849b-16ca-4233-b1fe-b785b534c7b0","container_uuid":"f4fbe518-e648-49dd-b6a4-e80c1ff12805","uuid_id":"001e849b-16ca-4233-b1fe-b785b534c7b0"},{"id":2,"user_name":"user3@example.com","username":"user3@example.com","email":"user3@example.com","name":"user3@example.com","type":"local","permissions":64,"last_login_attempt":0,"login_fail_count":0,"login_fail_total":0,"enabled":false,"uuid":"e6b5cd6d-1e03-4697-8f81-33277a85f175","container_uuid":"f4fbe518-e648-49dd-b6a4-e80c1ff12805","uuid_id":"e6b5cd6d-1e03-4697-8f81-33277a85f175"}]}}}}}},"403":{"description":"Returned if you do not have permission to list a group's users."},"404":{"description":"Returned if Tenable.io cannot find the specified group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/groups/{group_id}/users/{user_id}":{"post":{"summary":"Add user to group","description":"Add a user to the group.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"groups-add-user","tags":["Groups"],"parameters":[{"description":"The unique ID of the group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully added the user to the group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to add users to a group."},"404":{"description":"Returned if Tenable.io cannot find the specified group or user."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to add the user to the group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete user from group","description":"Deletes a user from the group.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"groups-delete-user","tags":["Groups"],"parameters":[{"description":"The unique ID of the group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The unique ID of the user.","required":true,"name":"user_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io removes the user from the group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to delete users from the group."},"404":{"description":"Returned if Tenable.io cannot find the specified group or user."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to remove the user from the group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/settings/connectors":{"post":{"summary":"Create connector","description":"Creates a connector.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"connectors-create-connector","tags":["Connectors"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the connector. The name can contain only alphanumeric characters and comma (`,`), dot (`.`), dash (`-`), at sign (`@`), and underscore (`_`) characters."},"type":{"type":"string","description":"The type of the connector. Types include: \n* aws \n* aws_keyless \n* azure\n* gcp","enum":["aws","aws_keyless","azure"]},"network_uuid":{"type":"string","description":"The UUID of the [network](https://developer.tenable.com/docs/manage-networks-tio) to associate with the connector. You can find the UUID using the [GET /networks](/reference#networks-list) endpoint. If you do not specify a network, Tenable.io automatically associates the connector with the default network (UUID `00000000-0000-0000-0000-000000000000`).\n**Note**: Tenable recommends creating a network for each connector type in use to prevent asset records in different cloud environments from overwriting each other. For more information, see [Managing Networks](https://developer.tenable.com/docs/manage-networks-tio).","format":"UUID","default":"00000000-0000-0000-0000-000000000000"},"params":{"type":"object","description":"The connector parameters: \n* For AWS connectors, the parameters include the access key, secret key, associated accounts, and cloudtrails. \n* For keyless AWS connectors, the parameters include associated AWS accounts (sub-accounts) and cloudtrails. \n* For Azure connectors, the parameters include the application ID, tenant ID, client secret key, and an optional list of subscription IDs. If you don't provide subscription IDs, Tenable.io automatically discovers them.\n* For GCP connectors, the service account key.","properties":{"access_key":{"description":"The AWS access key.\nNote: The access key is not included in the keyless AWS connector parameters.","type":"string"},"secret_key":{"description":"For AWS connectors, the AWS secret key.","type":"string"},"trails":{"description":"For AWS connectors, a list of AWS cloudtrails associated with the connector. The trails must be available to be used by the connector. Use the [POST /settings/connectors/aws/cloudtrails](/reference#connectors-get-aws-cloudtrails) endpoint to check the `availability` property of cloudtrail objects.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"sub_accounts":{"description":"For AWS connectors, a list of AWS accounts associated with the connector.","type":"array","items":{"type":"object","properties":{"role_arn":{"type":"string","description":"The Amazon Resource Name (ARN) of the role generated based on the associated account ID."},"external_id":{"description":"The UUID of your Tenable.io instance used by AWS to identify it as a client application. You can obtain the UUID of your Tenable.io account using the GET /session endpoint. The UUID corresponds to the container_uuid attribute of the response message for that endpoint.","type":"string"},"trails":{"description":"For keyless AWS connectors, a list of AWS cloudtrails associated with the account.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on events instead of enumerating all assets in the account every single time."}}}},"application_id":{"description":"For Azure connectors, Azure application ID.","type":"string"},"tenant_id":{"type":"string","description":"For Azure connectors, Azure tenant ID."},"subscription_id":{"type":"string","description":"For Azure connectors, Azure subscription ID. If you do not provide subscription IDs, Tenable.io automatically discovers them."},"service_account_key":{"type":"string","description":"For GCP connectors, Base64-encoded string value of the service account key JSON file. For more information, see [GCP documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys).\n\n**Important!** The `service_account_key` value must include only the literal encoded string. Do not include the `data:application/json;base64` prefix."}}},"schedule":{"type":"object","description":"The data import schedule.","properties":{"units":{"type":"string","description":"The units of time for the import interval. Units can include:\n - days\n - hours\n - minutes\n - weeks"},"value":{"type":"integer","description":"The number of units between import intervals."}}}},"required":["name","type","params"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates a connector.","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"The type of the connector. Types include:\n - aws\n - aws_keyless\n - azure\n - gcp"},"human_type":{"type":"string","description":"The human-readable connector type."},"data_type":{"type":"string","description":"The data type imported by the connector. For Azure and AWS connectors, the value is always `assets`."},"name":{"type":"string","description":"The name of the connector. The name must be unique within a Tenable.io instance."},"network_uuid":{"type":"string","format":"UUID","description":"The UUID of the [network](https://developer.tenable.com/docs/manage-networks-tio) associated with the connector."},"status":{"type":"string","description":"The import status of the connector. Status values can include:\n - Completed—Tenable.io successfully used the connector to import assets (no imports scheduled)\n - Scheduled—Imports using the connector are scheduled for future dates\n - Saved—Tenable.io saved the connector configuration, but did not import assets at this time (no imports scheduled)\n - Error—Tenable.io failed to import assets using the connector"},"status_message":{"type":"string","description":"Extended description of the connector status. For information about connector error codes, see Connectors."},"schedule":{"type":"object","properties":{}},"date_created":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was created, for example, `2018-08-09T13:51:17.243Z`."},"date_modified":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was last modified or new records were imported, for example, `2018-08-09T13:51:17.243Z`."},"id":{"type":"string","description":"The UUID of the connector."},"container_uuid":{"type":"string","description":"The UUID of the Tenable.io instance."},"expired":{"type":"boolean","description":"Indicates whether the Vulnerability Management license for the Tenable.io instance associated with the connector is expired."},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on on the service provider event stream instead of enumerating all assets in the account every single time."},"last_sync_time":{"type":"string","description":"An ISO timestamp indicating the date and time of the last successful import, for example, `2018-08-09T13:51:17.243Z`."},"params":{"type":"object","description":"The connector parameters: \n* For AWS connectors, the parameters include the access key, secret key, associated accounts, and cloudtrails. \n* For keyless AWS connectors, the parameters include associated AWS accounts (sub-accounts) and cloudtrails. \n* For Azure connectors, the parameters include the application ID, tenant ID, client secret key, and an optional list of subscription IDs. If you don't provide subscription IDs, Tenable.io automatically discovers them.","properties":{"access_key":{"description":"For AWS connectors, the AWS access key.\nNote: The access key is not included in the keyless AWS connector parameters.","type":"string"},"trails":{"description":"For AWS connectors, a list of AWS cloudtrails associated with the connector.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"sub_accounts":{"description":"For AWS connectors, a list of AWS accounts associated with the connector.","type":"array","items":{"type":"object","properties":{"role_arn":{"type":"string","description":"The Amazon Resource Name (ARN) of the role generated based on the associated account ID."},"external_id":{"description":"The UUID of your Tenable.io instance used by AWS to identify it as a client application. You can obtain the UUID of your Tenable.io account using the GET /session endpoint. The UUID corresponds to the container_uuid attribute of the response message for that endpoint.","type":"string"},"trails":{"description":"For keyless AWS connectors, a list of AWS cloudtrails associated with the account.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on events instead of enumerating all assets in the account every single time."}}}},"status":{"description":"For Azure connectors, a list of import status records.","type":"array","items":{"type":"object","properties":{"last_event_seen":{"type":"string","description":"An ISO timestamp indicating the last time the connector found new or changed records and successfully imported them."},"release_timestamp":{"description":"An ISO timestamp indicating the last time the connector successfully completed an import (regardless of whether it found any changes).","type":"string"},"message":{"type":"string","description":"The extended import status message."}}}},"application_id":{"description":"For Azure connectors, Azure application ID.","type":"string"},"tenant_id":{"type":"string","description":"For Azure connectors, Azure tenant ID."},"subscription_id":{"type":"string","description":"For Azure connectors, Azure subscription ID. If you do not provide subscription IDs, Tenable.io automatically discovers them."},"service":{"description":"The service targeted by the connector. Values include:\n* aws \n* aws_keyless \n* azure\n* gcp","type":"string"}}}}},"examples":{"response":{"value":{"connector":{"type":"aws","human_type":"AWS","data_type":"assets","name":"AWS Connector - New","status":"Scheduled","status_message":"","schedule":{"units":"days","value":1},"schedule_full":{"units":"days","value":1},"date_created":"2019-03-24T20:50:23.635Z","id":"f2506bed-bffa-442b-bfde-506c52306111","container_uuid":"gdf930d-7e3d-452c-82e8-494c1be98ef19","expired":false,"incremental_mode":false,"params":{"access_key":"AJIAJLRNVRLZRDZLVBXR","trails":[{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":""}],"service":"aws"},"network_uuid":"11f04eb9-7c78-46c8-9025-fae048390f59"}}}}}}},"400":{"description":"Returned if you specify invalid input parameters."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List connectors","description":"Returns a list of connectors.

For information about connector error codes, see Connectors.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"connectors-list-connectors","tags":["Connectors"],"parameters":[{"description":"Maximum number of records requested (or service imposed limit if not in request). Must be in the int32 format. Default is 1000.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The number of records to skip in the returned result set. Must be in the int32 format. Default is 0.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The fields to sort on, for example, `sort=date_created:desc`. If you specify multiple fields, fields must be separated by commas. Sortable fields include: \n* date_created \n* name","required":false,"name":"sort","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of connectors with pagination information.","content":{"application/json":{"schema":{"type":"object","properties":{"connectors":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"The type of the connector. Types include:\n - aws\n - aws_keyless\n - azure\n - gcp"},"human_type":{"type":"string","description":"The human-readable connector type."},"data_type":{"type":"string","description":"The data type imported by the connector. For Azure and AWS connectors, the value is always `assets`."},"name":{"type":"string","description":"The name of the connector. The name must be unique within a Tenable.io instance."},"network_uuid":{"type":"string","format":"UUID","description":"The UUID of the [network](https://developer.tenable.com/docs/manage-networks-tio) associated with the connector."},"status":{"type":"string","description":"The import status of the connector. Status values can include:\n - Completed—Tenable.io successfully used the connector to import assets (no imports scheduled)\n - Scheduled—Imports using the connector are scheduled for future dates\n - Saved—Tenable.io saved the connector configuration, but did not import assets at this time (no imports scheduled)\n - Error—Tenable.io failed to import assets using the connector"},"status_message":{"type":"string","description":"Extended description of the connector status. For information about connector error codes, see Connectors."},"schedule":{"type":"object","properties":{}},"date_created":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was created, for example, `2018-08-09T13:51:17.243Z`."},"date_modified":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was last modified or new records were imported, for example, `2018-08-09T13:51:17.243Z`."},"id":{"type":"string","description":"The UUID of the connector."},"container_uuid":{"type":"string","description":"The UUID of the Tenable.io instance."},"expired":{"type":"boolean","description":"Indicates whether the Vulnerability Management license for the Tenable.io instance associated with the connector is expired."},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on on the service provider event stream instead of enumerating all assets in the account every single time."},"last_sync_time":{"type":"string","description":"An ISO timestamp indicating the date and time of the last successful import, for example, `2018-08-09T13:51:17.243Z`."},"params":{"type":"object","description":"The connector parameters: \n* For AWS connectors, the parameters include the access key, secret key, associated accounts, and cloudtrails. \n* For keyless AWS connectors, the parameters include associated AWS accounts (sub-accounts) and cloudtrails. \n* For Azure connectors, the parameters include the application ID, tenant ID, client secret key, and an optional list of subscription IDs. If you don't provide subscription IDs, Tenable.io automatically discovers them.","properties":{"access_key":{"description":"For AWS connectors, the AWS access key.\nNote: The access key is not included in the keyless AWS connector parameters.","type":"string"},"trails":{"description":"For AWS connectors, a list of AWS cloudtrails associated with the connector.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"sub_accounts":{"description":"For AWS connectors, a list of AWS accounts associated with the connector.","type":"array","items":{"type":"object","properties":{"role_arn":{"type":"string","description":"The Amazon Resource Name (ARN) of the role generated based on the associated account ID."},"external_id":{"description":"The UUID of your Tenable.io instance used by AWS to identify it as a client application. You can obtain the UUID of your Tenable.io account using the GET /session endpoint. The UUID corresponds to the container_uuid attribute of the response message for that endpoint.","type":"string"},"trails":{"description":"For keyless AWS connectors, a list of AWS cloudtrails associated with the account.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on events instead of enumerating all assets in the account every single time."}}}},"status":{"description":"For Azure connectors, a list of import status records.","type":"array","items":{"type":"object","properties":{"last_event_seen":{"type":"string","description":"An ISO timestamp indicating the last time the connector found new or changed records and successfully imported them."},"release_timestamp":{"description":"An ISO timestamp indicating the last time the connector successfully completed an import (regardless of whether it found any changes).","type":"string"},"message":{"type":"string","description":"The extended import status message."}}}},"application_id":{"description":"For Azure connectors, Azure application ID.","type":"string"},"tenant_id":{"type":"string","description":"For Azure connectors, Azure tenant ID."},"subscription_id":{"type":"string","description":"For Azure connectors, Azure subscription ID. If you do not provide subscription IDs, Tenable.io automatically discovers them."},"service":{"description":"The service targeted by the connector. Values include:\n* aws \n* aws_keyless \n* azure\n* gcp","type":"string"}}}}}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of records matching your search criteria. Must be in the int32 format."},"limit":{"type":"integer","description":"Maximum number of records requested (or service imposed limit if not in request). Must be in the int32 format."},"offset":{"type":"integer","description":"The number of skipped records in the returned result set. Must be in the int32 format."},"sort":{"description":"An array of the fields you specified as sort fields in the request, which Tenable.io uses to sort the returned data.","type":"array","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"connectors":[{"type":"aws","human_type":"AWS","data_type":"assets","name":"AWS Connector","status":"Saved","status_message":"","date_created":"2019-03-21T20:18:59.509Z","id":"e5cc1ab0-e64a-4636-8676-95d79a5a3c40","container_uuid":"gdf930d-7e3d-452c-82e8-494c1be98ef19","expired":false,"incremental_mode":false,"params":{"access_key":"AJIAJLRNVRLZRDZLVBXR","trails":[{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"}],"sub_accounts":[],"service":"aws"},"network_uuid":"11f04eb9-7c78-46c8-9025-fae048390f59"},{"type":"azure","human_type":"Azure","data_type":"assets","name":"Azure Connector","status":"Completed","status_message":"Import completed successfully","date_created":"2019-03-21T19:56:23.713Z","date_modified":"2019-03-21T20:09:01.241Z","id":"ec58a94d-31f5-42e7-b5be-2fef687c7fad","container_uuid":"gdf930d-7e3d-452c-82e8-494c1be98ef19","expired":false,"incremental_mode":false,"last_sync_time":"2019-03-21T20:09:01.241Z","params":{"status":{"c2fa7307-c53b-5ce0-a772-2ec880e85759":{"last_event_seen":"2019-03-21T20:08:59.190Z","release_timestamp":"2019-03-21T20:09:00.315Z","message":"Import completed successfully","state":"SUCCESS"},"a90ae1b5-20e2-4bf9-82b3-0082159365ea":{"last_event_seen":"2019-03-21T20:09:01.241Z","release_timestamp":"2019-03-21T20:09:01.504Z","message":"Import completed successfully","state":"SUCCESS"}},"application_id":"559829df-59ba-49e4-94a0-6e5af2b508di","tenant_id":"5a2b8079-0320-405f-ad21-17a3103014f7","subscription_id":[],"service":"azure"},"network_uuid":"00000000-0000-0000-0000-000000000000"},{"type":"aws_keyless","human_type":"AWS","data_type":"assets","name":"AWS Keyless Connector","status":"Saved","status_message":"","date_created":"2019-03-20T14:18:30.350Z","id":"cee93baa-ec30-4ccc-ab81-80719ba629ff","container_uuid":"gdf930d-7e3d-452c-82e8-494c1be98ef19","expired":false,"incremental_mode":false,"params":{"sub_accounts":[{"role_arn":"arn:aws:iam::795163652895:role/tenableio-connector","external_id":"gdf930d-7e3d-452c-82e8-494c1be98ef19","trails":[{"arn":"arn:aws:cloudtrail:us-east-1:795163652895:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"}],"incremental_mode":false,"account_id":"795163652895"}],"service":"aws"},"network_uuid":"13f04eb9-7c78-36c8-9025-fae048390f57"}],"pagination":{"total":3,"offset":0,"limit":50,"sort":[{"name":"date_created","order":"desc"}]}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update connector","description":"Updates the specified connector. You can change the connector name, associated service accounts, and schedule. You cannot change the connector type for an existing connector.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"connectors-update-connector","tags":["Connectors"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the connector."},"network_uuid":{"type":"string","description":"The UUID of the [network](https://developer.tenable.com/docs/manage-networks-tio) to associate with the connector. You can find the UUID using the [GET /networks](/reference#networks-list) endpoint. If you do not specify a network, Tenable.io automatically associates the connector with the default network (UUID `00000000-0000-0000-0000-000000000000`).\n**Note**: Tenable recommends creating a network for each connector type in use to prevent asset records in different cloud environments from overwriting each other. For more information, see [Managing Networks](https://developer.tenable.com/docs/manage-networks-tio).","format":"UUID","example":"00000000-0000-0000-0000-000000000000"},"params":{"type":"object","description":"The connector parameters: \n* For AWS connectors, the parameters include the access key, secret key, associated accounts, and cloudtrails. \n* For keyless AWS connectors, the parameters include associated AWS accounts (sub-accounts) and cloudtrails. \n* For Azure connectors, the parameters include the application ID, tenant ID, client secret key, and an optional list of subscription IDs. If you don't provide subscription IDs, Tenable.io automatically discovers them.\n* For GCP connectors, the service account key.","properties":{"access_key":{"description":"The AWS access key.\nNote: The access key is not included in the keyless AWS connector parameters.","type":"string"},"secret_key":{"description":"For AWS connectors, the AWS secret key.","type":"string"},"trails":{"description":"For AWS connectors, a list of AWS cloudtrails associated with the connector. The trails must be available to be used by the connector. Use the [POST /settings/connectors/aws/cloudtrails](/reference#connectors-get-aws-cloudtrails) endpoint to check the `availability` property of cloudtrail objects.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"sub_accounts":{"description":"For AWS connectors, a list of AWS accounts associated with the connector.","type":"array","items":{"type":"object","properties":{"role_arn":{"type":"string","description":"The Amazon Resource Name (ARN) of the role generated based on the associated account ID."},"external_id":{"description":"The UUID of your Tenable.io instance used by AWS to identify it as a client application. You can obtain the UUID of your Tenable.io account using the GET /session endpoint. The UUID corresponds to the container_uuid attribute of the response message for that endpoint.","type":"string"},"trails":{"description":"For keyless AWS connectors, a list of AWS cloudtrails associated with the account.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on events instead of enumerating all assets in the account every single time."}}}},"application_id":{"description":"For Azure connectors, Azure application ID.","type":"string"},"tenant_id":{"type":"string","description":"For Azure connectors, Azure tenant ID."},"subscription_id":{"type":"string","description":"For Azure connectors, Azure subscription ID. If you do not provide subscription IDs, Tenable.io automatically discovers them."},"service_account_key":{"type":"string","description":"For GCP connectors, Base64-encoded string value of the service account key JSON file. For more information, see [GCP documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys).\n\n**Important!** The `service_account_key` value must include only the literal encoded string. Do not include the `data:application/json;base64` prefix."}}},"schedule":{"type":"object","description":"The data import schedule.","properties":{"units":{"type":"string","description":"The units of time for the import interval. Units can include:\n - days\n - hours\n - minutes\n - weeks"},"value":{"type":"integer","description":"The number of units between import intervals."}}}},"required":["name","params"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updates a connector.","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"The type of the connector. Types include:\n - aws\n - aws_keyless\n - azure\n - gcp"},"human_type":{"type":"string","description":"The human-readable connector type."},"data_type":{"type":"string","description":"The data type imported by the connector. For Azure and AWS connectors, the value is always `assets`."},"name":{"type":"string","description":"The name of the connector. The name must be unique within a Tenable.io instance."},"network_uuid":{"type":"string","format":"UUID","description":"The UUID of the [network](https://developer.tenable.com/docs/manage-networks-tio) associated with the connector."},"status":{"type":"string","description":"The import status of the connector. Status values can include:\n - Completed—Tenable.io successfully used the connector to import assets (no imports scheduled)\n - Scheduled—Imports using the connector are scheduled for future dates\n - Saved—Tenable.io saved the connector configuration, but did not import assets at this time (no imports scheduled)\n - Error—Tenable.io failed to import assets using the connector"},"status_message":{"type":"string","description":"Extended description of the connector status. For information about connector error codes, see Connectors."},"schedule":{"type":"object","properties":{}},"date_created":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was created, for example, `2018-08-09T13:51:17.243Z`."},"date_modified":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was last modified or new records were imported, for example, `2018-08-09T13:51:17.243Z`."},"id":{"type":"string","description":"The UUID of the connector."},"container_uuid":{"type":"string","description":"The UUID of the Tenable.io instance."},"expired":{"type":"boolean","description":"Indicates whether the Vulnerability Management license for the Tenable.io instance associated with the connector is expired."},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on on the service provider event stream instead of enumerating all assets in the account every single time."},"last_sync_time":{"type":"string","description":"An ISO timestamp indicating the date and time of the last successful import, for example, `2018-08-09T13:51:17.243Z`."},"params":{"type":"object","description":"The connector parameters: \n* For AWS connectors, the parameters include the access key, secret key, associated accounts, and cloudtrails. \n* For keyless AWS connectors, the parameters include associated AWS accounts (sub-accounts) and cloudtrails. \n* For Azure connectors, the parameters include the application ID, tenant ID, client secret key, and an optional list of subscription IDs. If you don't provide subscription IDs, Tenable.io automatically discovers them.","properties":{"access_key":{"description":"For AWS connectors, the AWS access key.\nNote: The access key is not included in the keyless AWS connector parameters.","type":"string"},"trails":{"description":"For AWS connectors, a list of AWS cloudtrails associated with the connector.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"sub_accounts":{"description":"For AWS connectors, a list of AWS accounts associated with the connector.","type":"array","items":{"type":"object","properties":{"role_arn":{"type":"string","description":"The Amazon Resource Name (ARN) of the role generated based on the associated account ID."},"external_id":{"description":"The UUID of your Tenable.io instance used by AWS to identify it as a client application. You can obtain the UUID of your Tenable.io account using the GET /session endpoint. The UUID corresponds to the container_uuid attribute of the response message for that endpoint.","type":"string"},"trails":{"description":"For keyless AWS connectors, a list of AWS cloudtrails associated with the account.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on events instead of enumerating all assets in the account every single time."}}}},"status":{"description":"For Azure connectors, a list of import status records.","type":"array","items":{"type":"object","properties":{"last_event_seen":{"type":"string","description":"An ISO timestamp indicating the last time the connector found new or changed records and successfully imported them."},"release_timestamp":{"description":"An ISO timestamp indicating the last time the connector successfully completed an import (regardless of whether it found any changes).","type":"string"},"message":{"type":"string","description":"The extended import status message."}}}},"application_id":{"description":"For Azure connectors, Azure application ID.","type":"string"},"tenant_id":{"type":"string","description":"For Azure connectors, Azure tenant ID."},"subscription_id":{"type":"string","description":"For Azure connectors, Azure subscription ID. If you do not provide subscription IDs, Tenable.io automatically discovers them."},"service":{"description":"The service targeted by the connector. Values include:\n* aws \n* aws_keyless \n* azure\n* gcp","type":"string"}}}}},"examples":{"response":{"value":{"connector":{"type":"azure","human_type":"Azure","data_type":"assets","name":"Azure Connector - Updated","status":"Scheduled","status_message":"","schedule":{"units":"days","value":1},"schedule_full":{"units":"days","value":1},"date_created":"2019-03-24T23:21:42.898Z","id":"bc312ad1-6039-406b-b0a9-c0e311b05dc1","container_uuid":"gdf930d-7e3d-452c-82e8-494c1be98ef19","expired":false,"incremental_mode":false,"params":{"application_id":"559829df-59ba-49e4-94a0-6e5af2b508di","tenant_id":"5a2b8079-0320-405f-ad21-17a3103014f7","subscription_id":["a90ae1b5-20e2-4bf9-82b3-0082159365ea","c2fa7307-c53b-5ce0-a772-2ec880e85759"],"service":"azure"},"network_uuid":"13f04eb9-7c78-36c8-9025-fae048390f57"}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/settings/connectors{connector_id}":{"get":{"summary":"Get connector details","description":"Returns the details for the specified connector.

For information about connector error codes, see Connectors.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"connectors-connector-details","tags":["Connectors"],"parameters":[{"description":"The UUID of the connector to return details for.","required":true,"name":"connector_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the connector details.","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"The type of the connector. Types include:\n - aws\n - aws_keyless\n - azure\n - gcp"},"human_type":{"type":"string","description":"The human-readable connector type."},"data_type":{"type":"string","description":"The data type imported by the connector. For Azure and AWS connectors, the value is always `assets`."},"name":{"type":"string","description":"The name of the connector. The name must be unique within a Tenable.io instance."},"network_uuid":{"type":"string","format":"UUID","description":"The UUID of the [network](https://developer.tenable.com/docs/manage-networks-tio) associated with the connector."},"status":{"type":"string","description":"The import status of the connector. Status values can include:\n - Completed—Tenable.io successfully used the connector to import assets (no imports scheduled)\n - Scheduled—Imports using the connector are scheduled for future dates\n - Saved—Tenable.io saved the connector configuration, but did not import assets at this time (no imports scheduled)\n - Error—Tenable.io failed to import assets using the connector"},"status_message":{"type":"string","description":"Extended description of the connector status. For information about connector error codes, see Connectors."},"schedule":{"type":"object","properties":{}},"date_created":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was created, for example, `2018-08-09T13:51:17.243Z`."},"date_modified":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was last modified or new records were imported, for example, `2018-08-09T13:51:17.243Z`."},"id":{"type":"string","description":"The UUID of the connector."},"container_uuid":{"type":"string","description":"The UUID of the Tenable.io instance."},"expired":{"type":"boolean","description":"Indicates whether the Vulnerability Management license for the Tenable.io instance associated with the connector is expired."},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on on the service provider event stream instead of enumerating all assets in the account every single time."},"last_sync_time":{"type":"string","description":"An ISO timestamp indicating the date and time of the last successful import, for example, `2018-08-09T13:51:17.243Z`."},"params":{"type":"object","description":"The connector parameters: \n* For AWS connectors, the parameters include the access key, secret key, associated accounts, and cloudtrails. \n* For keyless AWS connectors, the parameters include associated AWS accounts (sub-accounts) and cloudtrails. \n* For Azure connectors, the parameters include the application ID, tenant ID, client secret key, and an optional list of subscription IDs. If you don't provide subscription IDs, Tenable.io automatically discovers them.","properties":{"access_key":{"description":"For AWS connectors, the AWS access key.\nNote: The access key is not included in the keyless AWS connector parameters.","type":"string"},"trails":{"description":"For AWS connectors, a list of AWS cloudtrails associated with the connector.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"sub_accounts":{"description":"For AWS connectors, a list of AWS accounts associated with the connector.","type":"array","items":{"type":"object","properties":{"role_arn":{"type":"string","description":"The Amazon Resource Name (ARN) of the role generated based on the associated account ID."},"external_id":{"description":"The UUID of your Tenable.io instance used by AWS to identify it as a client application. You can obtain the UUID of your Tenable.io account using the GET /session endpoint. The UUID corresponds to the container_uuid attribute of the response message for that endpoint.","type":"string"},"trails":{"description":"For keyless AWS connectors, a list of AWS cloudtrails associated with the account.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on events instead of enumerating all assets in the account every single time."}}}},"status":{"description":"For Azure connectors, a list of import status records.","type":"array","items":{"type":"object","properties":{"last_event_seen":{"type":"string","description":"An ISO timestamp indicating the last time the connector found new or changed records and successfully imported them."},"release_timestamp":{"description":"An ISO timestamp indicating the last time the connector successfully completed an import (regardless of whether it found any changes).","type":"string"},"message":{"type":"string","description":"The extended import status message."}}}},"application_id":{"description":"For Azure connectors, Azure application ID.","type":"string"},"tenant_id":{"type":"string","description":"For Azure connectors, Azure tenant ID."},"subscription_id":{"type":"string","description":"For Azure connectors, Azure subscription ID. If you do not provide subscription IDs, Tenable.io automatically discovers them."},"service":{"description":"The service targeted by the connector. Values include:\n* aws \n* aws_keyless \n* azure\n* gcp","type":"string"}}}}},"examples":{"response":{"value":{"connector":{"type":"aws","human_type":"AWS","data_type":"assets","name":"AWS Keyless Connector","status":"Scheduled","status_message":"","schedule":{"units":"days","value":1},"schedule_full":{"units":"days","value":1},"date_created":"2019-03-24T20:50:23.635Z","id":"f2506bed-bffa-442b-bfde-506c52306111","container_uuid":"gdf930d-7e3d-452c-82e8-494c1be98ef19","expired":false,"incremental_mode":false,"params":{"access_key":"AJIAJLRNVRLZRDZLVBXR","trails":[{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":""}],"service":"aws"},"network_uuid":"13f04eb9-7c78-36c8-9025-fae048390f57"}}}}}}},"404":{"description":"Returned if Tenable.io cannot not find the specified connector."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete connector","description":"Deletes the specified connector.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"connectors-delete-connector","tags":["Connectors"],"parameters":[{"description":"The UUID of the connector to delete.","required":true,"name":"connector_id","in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Returned if Tenable.io successfully deleted the specified connector.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified connector."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/settings/connectors/aws/cloudtrails":{"post":{"summary":"List AWS cloudtrails","description":"Returns a list of available AWS cloudtrails. You can then use the cloudtrails to [create an AWS connector](#connectors-create-connector).

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"connectors-get-aws-cloudtrails","tags":["Connectors"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"region":{"type":"array","description":"A complete list of available AWS regions as shown in the following example.","items":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}}},"credentials":{"type":"object","description":"For AWS connectors, the credentials object, including access key and secret key.","properties":{"access_key":{"type":"string","description":"The AWS access key."},"secret_key":{"type":"string","description":"The AWS secret key."}}},"account_id":{"type":"string","description":"For keyless AWS connectors, the AWS account ID."}},"required":["regions"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully retrieves the list of cloudtrails.","content":{"application/json":{"schema":{"type":"object","properties":{"trails":{"type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}}}},"examples":{"response":{"value":{"trails":[{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/TenableAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"},{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":"success"}]}}}}}},"400":{"description":"Returned if you specify invalid input parameters."},"403":{"description":"Returned if you specify invalid AWS credentials or account ID."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/settings/connectors/{connector_id}/import":{"post":{"summary":"Import data","description":"Imports data using a connector. This creates an asynchronous import job in Tenable.io. You can check the import status by examining the `status_message` property in [connector details](#connectors-connector-details).

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"connectors-import-assets-connector","tags":["Connectors"],"parameters":[{"description":"The UUID of the connector for which to import the data.","required":true,"name":"connector_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully schedules the connector for import.","content":{"application/json":{"schema":{"type":"object","properties":{"connector":{"type":"object","properties":{"type":{"type":"string","description":"The type of the connector. Types include:\n - aws\n - aws_keyless\n - azure\n - gcp"},"human_type":{"type":"string","description":"The human-readable connector type."},"data_type":{"type":"string","description":"The data type imported by the connector. For Azure and AWS connectors, the value is always `assets`."},"name":{"type":"string","description":"The name of the connector. The name must be unique within a Tenable.io instance."},"network_uuid":{"type":"string","format":"UUID","description":"The UUID of the [network](https://developer.tenable.com/docs/manage-networks-tio) associated with the connector."},"status":{"type":"string","description":"The import status of the connector. Status values can include:\n - Completed—Tenable.io successfully used the connector to import assets (no imports scheduled)\n - Scheduled—Imports using the connector are scheduled for future dates\n - Saved—Tenable.io saved the connector configuration, but did not import assets at this time (no imports scheduled)\n - Error—Tenable.io failed to import assets using the connector"},"status_message":{"type":"string","description":"Extended description of the connector status. For information about connector error codes, see Connectors."},"schedule":{"type":"object","properties":{}},"date_created":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was created, for example, `2018-08-09T13:51:17.243Z`."},"date_modified":{"type":"string","description":"An ISO timestamp indicating the date and time on which the connector was last modified or new records were imported, for example, `2018-08-09T13:51:17.243Z`."},"id":{"type":"string","description":"The UUID of the connector."},"container_uuid":{"type":"string","description":"The UUID of the Tenable.io instance."},"expired":{"type":"boolean","description":"Indicates whether the Vulnerability Management license for the Tenable.io instance associated with the connector is expired."},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on on the service provider event stream instead of enumerating all assets in the account every single time."},"last_sync_time":{"type":"string","description":"An ISO timestamp indicating the date and time of the last successful import, for example, `2018-08-09T13:51:17.243Z`."},"params":{"type":"object","description":"The connector parameters: \n* For AWS connectors, the parameters include the access key, secret key, associated accounts, and cloudtrails. \n* For keyless AWS connectors, the parameters include associated AWS accounts (sub-accounts) and cloudtrails. \n* For Azure connectors, the parameters include the application ID, tenant ID, client secret key, and an optional list of subscription IDs. If you don't provide subscription IDs, Tenable.io automatically discovers them.","properties":{"access_key":{"description":"For AWS connectors, the AWS access key.\nNote: The access key is not included in the keyless AWS connector parameters.","type":"string"},"trails":{"description":"For AWS connectors, a list of AWS cloudtrails associated with the connector.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"sub_accounts":{"description":"For AWS connectors, a list of AWS accounts associated with the connector.","type":"array","items":{"type":"object","properties":{"role_arn":{"type":"string","description":"The Amazon Resource Name (ARN) of the role generated based on the associated account ID."},"external_id":{"description":"The UUID of your Tenable.io instance used by AWS to identify it as a client application. You can obtain the UUID of your Tenable.io account using the GET /session endpoint. The UUID corresponds to the container_uuid attribute of the response message for that endpoint.","type":"string"},"trails":{"description":"For keyless AWS connectors, a list of AWS cloudtrails associated with the account.","type":"array","items":{"type":"object","properties":{"arn":{"type":"string","description":"Amazon Resource Name (ARN) of the cloudtrail."},"name":{"type":"string","description":"The name of the cloudtrail."},"region":{"type":"object","properties":{"name":{"type":"string","description":"The AWS region code, for example, `us-east-1`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions."},"friendly_name":{"description":"The AWS region name, for example, `US East (N. Virginia)`. The value of `All` indicates that the cloudtrail is associated with all AWS available regions.","type":"string"}}},"availability":{"description":"Indicates whether a cloudtrail is available to be used by a connector (logging is turned on in AWS, or it has at least one EventSelector with IncludeManagementEvents). Values include:\n - `success`—The cloudtrail is available.\n - `error`—The cloudtrail is not available.","type":"string","enum":["success","error"]}}}},"incremental_mode":{"type":"boolean","description":"Indicates whether a connector has completed the initial full import successfully. If the value is `true`, then the connector is in incremental mode where it imports assets based on events instead of enumerating all assets in the account every single time."}}}},"status":{"description":"For Azure connectors, a list of import status records.","type":"array","items":{"type":"object","properties":{"last_event_seen":{"type":"string","description":"An ISO timestamp indicating the last time the connector found new or changed records and successfully imported them."},"release_timestamp":{"description":"An ISO timestamp indicating the last time the connector successfully completed an import (regardless of whether it found any changes).","type":"string"},"message":{"type":"string","description":"The extended import status message."}}}},"application_id":{"description":"For Azure connectors, Azure application ID.","type":"string"},"tenant_id":{"type":"string","description":"For Azure connectors, Azure tenant ID."},"subscription_id":{"type":"string","description":"For Azure connectors, Azure subscription ID. If you do not provide subscription IDs, Tenable.io automatically discovers them."},"service":{"description":"The service targeted by the connector. Values include:\n* aws \n* aws_keyless \n* azure\n* gcp","type":"string"}}}}}}},"examples":{"response":{"value":{"connector":{"type":"aws","human_type":"AWS","data_type":"assets","name":"AWS Connector","status":"Scheduled","status_message":"Import completed successfully","date_created":"2019-03-25T17:21:19.495Z","date_modified":"2019-03-25T17:21:28.457Z","id":"8d70056d-eee5-4ef5-a5b2-0acc0262c59d","container_uuid":"gdf930d-7e3d-452c-82e8-494c1be98ef19","expired":false,"incremental_mode":false,"last_sync_time":"2019-03-25T17:21:28.457Z","params":{"access_key":"AJIAJLRNVRLZRDZLVBXR","trails":[{"arn":"arn:aws:cloudtrail:us-east-1:069647819620:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"All","friendly_name":"All"},"availability":""}],"sub_accounts":[{"role_arn":"arn:aws:iam::795163652895:role/tenableio-connector","external_id":"gdf930d-7e3d-452c-82e8-494c1be98ef19","trails":[{"arn":"arn:aws:cloudtrail:us-east-1:795163652895:trail/ExampleAWSTrail","name":"ExampleAWSTrail","region":{"name":"us-west-1","friendly_name":"us-west-1"},"availability":""}],"incremental_mode":false,"account_id":"795163652895"}],"service":"aws"},"network_uuid":"13f04eb9-7c78-36c8-9025-fae048390f57"}}}}}}},"404":{"description":"Returned if Tenable.io cannot find the connector you specified."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}}},"x-explorer-enabled":true,"x-proxy-enabled":true,"x-samples-enabled":true} \ No newline at end of file diff --git a/app_gen/openapi-parsers/other/TIO-API-Vulnerability-Management.json b/app_gen/openapi-parsers/other/TIO-API-Vulnerability-Management.json new file mode 100644 index 00000000..b36e2970 --- /dev/null +++ b/app_gen/openapi-parsers/other/TIO-API-Vulnerability-Management.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"Vulnerability Management","version":"1.0.0"},"security":[{"cloud":[]}],"servers":[{"url":"https://cloud.tenable.com"}],"components":{"securitySchemes":{"cloud":{"type":"apiKey","in":"header","name":"X-ApiKeys","description":"Format - accessKey=ACCESS_KEY;secretKey=SECRET_KEY"}}},"x-samples-languages":["python","curl","node","powershell","ruby","javascript","objectivec","java","php","csharp","go","swift","kotlin"],"paths":{"/access-groups":{"post":{"summary":"Create access group","description":"Creates an access group.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"access-groups-create","tags":["Access Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"The name of the access group you want to create. This name must be: \n* Unique within your Tenable.io instance. \n* A maximum of 255 characters. \n* Alphanumeric, but can include limited special characters (underscore, dash, parenthesis, brackets, colon). \n \n**Note:** You can add a maximum of 5,000 access groups to an individual container.","type":"string"},"all_assets":{"description":"This parameter must always be `false` or omitted from create requests to specify that the access group is a user-created group. If you submit a create request with this parameter set to `true`, the create request fails.","type":"boolean"},"all_users":{"description":"Specifies whether assets in the access group can be viewed by all or only some users in your organization: \n* If `true`, all users in your organization have Can View access to the assets defined in the rules parameter. Tenable.io ignores any principal parameters in your request. \n* If `false`, only specified users have Can View access to the assets defined in the rules parameter. You define which users or user groups have access in the principals parameter of the request. \n \nIf you omit this parameter, Tenable.io sets the parameter to `false` by default.","type":"boolean"},"rules":{"items":{"type":"object","properties":{"type":{"type":"string","description":"The type of asset rule. The asset rule type corresponds to the type of data you can specifiy in the terms parameter. For a complete list of supported rule types, use the GET /access-groups/filters endpoint."},"operator":{"type":"string","description":"The operator that specifies how Tenable.io matches the terms value to asset data. \n\nPossible operators include: \n - eq—Tenable.io matches the rule to assets based on an exact match of the specified term. Note: Tenable.io interprets the operator as `equals` for ipv4 rules that specify a single IP address, but interprets the operator as `contains` for ipv4 rules that specify an IP range or CIDR range.\n - match—Tenable.io matches the rule to assets based a partial match of the specified term.\n - starts—Tenable.io matches the rule to assets that start with the specified term.\n - ends—Tenable.io matches the rule to assets that end with the specified term.\n\nFor a complete list of operators by rule type, use the GET /access-groups/rules/filters endpoint."},"terms":{"description":"The values that Tenable.io uses to match an asset to the rule. A term must correspond to the rule type.\n\nFor example:\n - If the rule type is `aws_account`, the term is an AWS account ID.\n - If the rule type is `fqdn`, the term is a hostname or a fully-qualified domain name (FQDN).\n - If the rule type is `ipv4`, the term is an individual IPv4 address, a range of IPv4 addresses (for example, 172.204.81.57-172.204.81.60), or a CIDR range (for example, 172.204.81.57/24). \n\nFor a complete list of supported values by rule type, use the GET /access-groups/rules/filters endpoint. \n\nIf you specify multiple terms values, Tenable.io includes an asset in the access group if the asset's attributes match any of the terms in the rule.\n
You can specify up to 100,000 terms per asset rule.","type":"array","items":{"type":"string"}}}},"description":"An array of asset rules. Tenable.io uses these rules to assign assets to the access group. You can specify a maximum of 1,000 rules for an individual access group. If you specify multiple rules for an access group, Tenable.io assigns an asset to the access group if the asset matches any of the rules. You can only add rules to access groups if the all\\_assets parameter is set to `false`.","type":"array"},"principals":{"items":{"type":"object","properties":{"type":{"type":"string","description":"(Required) The type of principal. Valid values include:\n - user—Grants access to the user you specify.\n - group—Grants access to all users assigned to the user group you specify."},"principal_id":{"type":"string","description":"The UUID of a user or user group. This parameter is required if the request omits the `principal_name` parameter."},"principal_name":{"type":"string","description":"The name of the user or user group. This parameter is required if the request omits the `principal_id` parameter. If a request includes both `principal_id` and `principal_name`, Tenable.io assigns the user or user group to the access group based on the `principal_id` parameter, and ignores the `principal_name` parameter in the request. "}}},"description":"An array of principals. Each principal represents a user or user group assigned to the access group. You cannot add an access group as a principal to another access group. \n \nTenable.io handles data in this array based on the all\\_users parameter of the request: \n* If all\\_users is `true`, Tenable.io ignores any principal data in the request. You can omit this parameter from the request. \n* If all\\_users is `false`, Tenable.io adds the principal data to the access group.","type":"array"}},"required":["name","rules"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates an access group.","content":{"application/json":{"schema":{"type":"object","properties":{"container_uuid":{"type":"string","description":"The UUID of your Tenable.io instance."},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the access group was created."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the time and date on which the access group was last modified."},"id":{"type":"string","description":"The UUID of the access group."},"name":{"type":"string","description":"The name of the access group. This name must be: \n* Unique within your Tenable.io instance. \n* A maximum of 255 characters. \n* Alphanumeric, but can include limited special characters (underscore, dash, parenthesis, brackets, colon)."},"all_assets":{"type":"boolean","description":"Specifies whether the access group is the system-provided All Assets access group: \n - If `true`, the access group is the All Assets access group. The only change you can make to this access group is to refine user membership in the group. For more information, see descriptions of the all_users and principals parameters for the PUT /access-groups/{id} endpoint.\n - If `false`, the access group is a user-defined access group, and you can change all parameters for the group. This parameter is `false` for all access groups you create."},"all_users":{"type":"boolean","description":"Specifies whether assets in the access group can be viewed by all or only some users in your organization:\n - If `true`, all users in your organization have Can View access to the assets defined in the rules parameter. If `true` in a POST /access-groups or PUT /access-groups/{id} request, Tenable.io ignores any principal parameters in the request. \n - If `false`, only specified users have Can View access to the assets defined in the rules parameter. You define which users or user groups have access in the principals parameter of a POST /access-groups or PUT /access-groups/{id} request. \n\n**Note:** If a PUT /access-groups/{id} endpoint request sets this parameter to `true` for an access group where the parameter was previously set to `false`, Tenable.io removes all principal data previously associated with the access group."},"status":{"type":"string","description":"The status of the process evaluating and assigning assets to the access group. Possible values are: \n - PROCESSING—Tenable.io is currently evaluating assets against the asset rules for the access group. For an indication of evaluation progress, see the `processing_percent_complete` attribute for the access group.\n - COMPLETED—Tenable.io has successfully completed its evaluation of assets against the asset rules for the group.\n - ERROR—Tenable.io encountered an error while evaluating assets against asset rules for the access group. Rule validation typically prevents this status from occurring. However, if you encounter an ERROR status, Tenable recommends that you delete the existing asset rules, then recreate the rules after a short time has elapsed."},"created_by_uuid":{"type":"string","description":"The UUID of the user who created the access group."},"created_by_name":{"type":"string","description":"The name of the user who created the access group."},"updated_by_uuid":{"type":"string","description":"The UUID of the user who last modified the access group."},"updated_by_name":{"type":"string","description":"The name of the user who last modified the access group."},"processing_percent_complete":{"type":"integer","description":"The percentage of assets that Tenable.io has evaluated against the asset rules for the access group."}}},"examples":{"response":{"value":{"container_uuid":"8f9d0b84-ede2-4954-a0c9-0bde292ac38e","created_at":"2018-08-15T18:17:04.827Z","updated_at":"2018-08-15T18:17:04.827Z","id":"385f4765-cd32-4191-b6ae-d0d4522e073f","name":"Headquarters","all_assets":false,"all_users":false,"status":"COMPLETED","rules":[{"type":"aws_account","operator":"eq","terms":["123456789012"]},{"type":"fqdn","operator":"eq","terms":["www.example.com"]},{"type":"ipv4","operator":"eq","terms":["172.204.81.57"]}],"principals":[{"type":"user","principal_id":"085abc65-d709-44b2-ad04-bfd2862ad5a1","principal_name":"user1@example.com"},{"type":"user","principal_id":"b1219ca2-2578-49ac-88db-8a35dd02cc7d","principal_name":"user2@example.com"}],"created_by_uuid":"b1219ca2-2578-49ac-88db-8a35dd02cc7d","updated_by_uuid":"b1219ca2-2578-49ac-88db-8a35dd02cc7d","updated_by_name":"user3@example.com","created_by_name":"user3@example.com","processing_percent_complete":100}}}}}},"400":{"description":"Returned if Tenable.io encountered any of the following error conditions:\n - max_entries—your request exceeds the maximum number of 5,000 access groups.\n - duplicate—an access group with the name you specified already exists.\n - protected—you attempted to set the all_assets parameter to `true`, and you cannot create the system-provided access group, All Assets."},"403":{"description":"Returned if you do not have permission to create access groups."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List access groups","description":"Lists access groups without associated rules.

Requires BASIC [16] user permissions to list access groups to which you are assigned. Requires ADMINISTRATOR [64] permissions to list all access groups for your organization. See Permissions.

","operationId":"access-groups-list","tags":["Access Groups"],"parameters":[{"description":"A filter condition in the following format: `field:operator:value`. For a list of possible fields and operators, use the GET /access-groups/filters endpoint. You can specify multiple `f` parameters, separated by ampersand (&) characters. If you specify multiple `f` parameters, use the `ft` parameter to specify how Tenable.io applies the multiple filter conditions.","name":"f","in":"query","schema":{"type":"string"}},{"description":"If multiple \\`f\\` parameters are present, specifies whether Tenable.io applies \\`AND\\` or \\`OR\\` to conditions. Supported values are `and` and `or`. If you omit this parameter when using multiple `f` parameters, Tenable.io applies `AND` by default.","name":"ft","in":"query","schema":{"type":"string"}},{"description":"The search value that Tenable.io applies across the wildcard fields. Wildcard fields are specified in the `wf` parameter.","name":"w","in":"query","schema":{"type":"string"}},{"description":"A comma-separated list of fields where Tenable.io applies the search value specified in the `w` parameter. For a list of supported wildcard fields, use the GET /access-groups/filters endpoint.","name":"wf","in":"query","schema":{"type":"string"}},{"description":"Maximum number of records requested (or service imposed limit if not in request).","name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Offset from request (or zero).","name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The field or fields on which Tenable.io sorts the results. If you specify multiple fields, fields must be separated by commas. For a list of supported sort fields, use the GET /access-groups/filters endpoint.","name":"sort","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of access groups that you have permission to view.","content":{"application/json":{"schema":{"type":"object","properties":{"access_groups":{"type":"object","properties":{"container_uuid":{"type":"string","description":"The UUID of your Tenable.io instance."},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the access group was created."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the time and date on which the access group was last modified."},"id":{"type":"string","description":"The UUID of the access group."},"name":{"type":"string","description":"The name of the access group. This name must be: \n* Unique within your Tenable.io instance. \n* A maximum of 255 characters. \n* Alphanumeric, but can include limited special characters (underscore, dash, parenthesis, brackets, colon)."},"all_assets":{"type":"boolean","description":"Specifies whether the access group is the system-provided All Assets access group: \n - If `true`, the access group is the All Assets access group. The only change you can make to this access group is to refine user membership in the group. For more information, see descriptions of the all_users and principals parameters for the PUT /access-groups/{id} endpoint.\n - If `false`, the access group is a user-defined access group, and you can change all parameters for the group. This parameter is `false` for all access groups you create."},"all_users":{"type":"boolean","description":"Specifies whether assets in the access group can be viewed by all or only some users in your organization:\n - If `true`, all users in your organization have Can View access to the assets defined in the rules parameter. If `true` in a POST /access-groups or PUT /access-groups/{id} request, Tenable.io ignores any principal parameters in the request. \n - If `false`, only specified users have Can View access to the assets defined in the rules parameter. You define which users or user groups have access in the principals parameter of a POST /access-groups or PUT /access-groups/{id} request. \n\n**Note:** If a PUT /access-groups/{id} endpoint request sets this parameter to `true` for an access group where the parameter was previously set to `false`, Tenable.io removes all principal data previously associated with the access group."},"status":{"type":"string","description":"The status of the process evaluating and assigning assets to the access group. Possible values are: \n - PROCESSING—Tenable.io is currently evaluating assets against the asset rules for the access group. For an indication of evaluation progress, see the `processing_percent_complete` attribute for the access group.\n - COMPLETED—Tenable.io has successfully completed its evaluation of assets against the asset rules for the group.\n - ERROR—Tenable.io encountered an error while evaluating assets against asset rules for the access group. Rule validation typically prevents this status from occurring. However, if you encounter an ERROR status, Tenable recommends that you delete the existing asset rules, then recreate the rules after a short time has elapsed."},"created_by_uuid":{"type":"string","description":"The UUID of the user who created the access group."},"created_by_name":{"type":"string","description":"The name of the user who created the access group."},"updated_by_uuid":{"type":"string","description":"The UUID of the user who last modified the access group."},"updated_by_name":{"type":"string","description":"The name of the user who last modified the access group."},"processing_percent_complete":{"type":"integer","description":"The percentage of assets that Tenable.io has evaluated against the asset rules for the access group."}}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of records matching your search criteria.","format":"int32"},"limit":{"type":"integer","description":"Maximum number of records requested (or service imposed limit if not in request).","format":"int32"},"offset":{"type":"integer","description":"Offset from request (or zero).","format":"int32"},"sort":{"description":"The fields you specified as sort fields in the request, which Tenable.io uses to sort the returned data.","type":"array","items":{"type":"object","properties":{"name":{"description":"The name of the field on which Tenable.io sorted the results.","type":"string"},"order":{"description":"The order in which Tenable.io sorted the results. Possible values are ascending (`asc`) or descending (`desc`).","type":"string"}}}}}}}},"examples":{"response":{"value":{"access_groups":[{"container_uuid":"8f9d0b84-ede2-4954-a0c9-0bde292ac38e","created_at":"2018-08-09T21:26:00.397Z","updated_at":"2018-08-09T21:26:00.397Z","id":"529d57cf-bbc6-435f-9b57-896bb40bba8c","name":"Atlanta Office","all_assets":false,"all_users":false,"status":"COMLETED","created_by_uuid":"b1219ca2-2578-49ac-88db-8a35dd02cc7d","updated_by_uuid":"b1219ca2-2578-49ac-88db-8a35dd02cc7d","updated_by_name":"user@example.com","created_by_name":"user@example.com","processing_percent_complete":100}],"pagination":{"offset":0,"limit":2,"total":7,"sort":[{"name":"allAssets","order":"desc"},{"name":"updatedAt","order":"asc"}]}}}}}}},"403":{"description":"Returned if you do not have permission to view access groups."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/access-groups/{id}":{"put":{"summary":"Update access group","description":"Modifies an access group. This method overwrites the existing data.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"access-groups-edit","tags":["Access Groups"],"parameters":[{"description":"The UUID for the access group you want to modify.","required":true,"name":"id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"The name of the access group you want to modify.","type":"string"},"all_assets":{"description":"Specifies whether the access group you want to modify is the All Assets group or a user-defined group: \n* If you want to refine membership in the All Assets access group (the only change you can make to the All Assets group), this parameter must be `true`. Tenable.io ignores any rules parameters in your request, but overwrrites existing principals parameters with those in the request based on the all\\_users and principals parameters in the request. \n* If you want to modify a user-defined access group, this parameter must be `false`. Tenable.io overwrites the existing rules parameters with the rules parameters you specify in this request, and overwrites existing principals parameters based on the all\\_users and principals parameters in the request.","type":"boolean"},"all_users":{"description":"Specifies whether assets in the access group can be viewed by all or only some users in your organization: \n* If `true`, all users in your organization have Can View access to the assets defined in the rules parameter. Tenable.io ignores any principal parameters in your request. \n* If `false`, only specified users have Can View access to the assets defined in the rules parameter. You define which users or user groups have access in the principals parameter of the request. \n \nIf you omit this parameter, Tenable.io sets the parameter to `false` by default.","type":"boolean"},"rules":{"items":{"type":"object","properties":{"type":{"type":"string","description":"The type of asset rule. The asset rule type corresponds to the type of data you can specifiy in the terms parameter. For a complete list of supported rule types, use the GET /access-groups/filters endpoint."},"operator":{"type":"string","description":"The operator that specifies how Tenable.io matches the terms value to asset data. \n\nPossible operators include: \n - eq—Tenable.io matches the rule to assets based on an exact match of the specified term. Note: Tenable.io interprets the operator as `equals` for ipv4 rules that specify a single IP address, but interprets the operator as `contains` for ipv4 rules that specify an IP range or CIDR range.\n - match—Tenable.io matches the rule to assets based a partial match of the specified term.\n - starts—Tenable.io matches the rule to assets that start with the specified term.\n - ends—Tenable.io matches the rule to assets that end with the specified term.\n\nFor a complete list of operators by rule type, use the GET /access-groups/rules/filters endpoint."},"terms":{"description":"The values that Tenable.io uses to match an asset to the rule. A term must correspond to the rule type.\n\nFor example:\n - If the rule type is `aws_account`, the term is an AWS account ID.\n - If the rule type is `fqdn`, the term is a hostname or a fully-qualified domain name (FQDN).\n - If the rule type is `ipv4`, the term is an individual IPv4 address, a range of IPv4 addresses (for example, 172.204.81.57-172.204.81.60), or a CIDR range (for example, 172.204.81.57/24). \n\nFor a complete list of supported values by rule type, use the GET /access-groups/rules/filters endpoint. \n\nIf you specify multiple terms values, Tenable.io includes an asset in the access group if the asset's attributes match any of the terms in the rule.\n
You can specify up to 100,000 terms per asset rule.","type":"array","items":{"type":"string"}}}},"description":"An array of asset rules. Tenable.io uses these rules to assign assets to the access group. You can specify a maximum of 1,000 rules for an individual access group. If you specify multiple rules for an access group, Tenable.io assigns an asset to the access group if the asset matches any of the rules. You can only add rules to access groups if the all\\_assets parameter is set to `false`.","type":"array"},"principals":{"items":{"type":"object","properties":{"type":{"type":"string","description":"(Required) The type of principal. Valid values include:\n - user—Grants access to the user you specify.\n - group—Grants access to all users assigned to the user group you specify."},"principal_id":{"type":"string","description":"The UUID of a user or user group. This parameter is required if the request omits the `principal_name` parameter."},"principal_name":{"type":"string","description":"The name of the user or user group. This parameter is required if the request omits the `principal_id` parameter. If a request includes both `principal_id` and `principal_name`, Tenable.io assigns the user or user group to the access group based on the `principal_id` parameter, and ignores the `principal_name` parameter in the request. "}}},"description":"An array of principals. Each principal represents a user or user group assigned to the access group. You cannot add an access group as a principal to another access group.","type":"array"}},"required":["name","rules"]}}}},"responses":{"200":{"description":"Returned if Tenable.io has either modified the existing access group successfully or created a new access group because it could not find an existing access group with the specified UUID.","content":{"application/json":{"schema":{"type":"object","properties":{"container_uuid":{"type":"string","description":"The UUID of your Tenable.io instance."},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the access group was created."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the time and date on which the access group was last modified."},"id":{"type":"string","description":"The UUID of the access group."},"name":{"type":"string","description":"The name of the access group. This name must be: \n* Unique within your Tenable.io instance. \n* A maximum of 255 characters. \n* Alphanumeric, but can include limited special characters (underscore, dash, parenthesis, brackets, colon)."},"all_assets":{"type":"boolean","description":"Specifies whether the access group is the system-provided All Assets access group: \n - If `true`, the access group is the All Assets access group. The only change you can make to this access group is to refine user membership in the group. For more information, see descriptions of the all_users and principals parameters for the PUT /access-groups/{id} endpoint.\n - If `false`, the access group is a user-defined access group, and you can change all parameters for the group. This parameter is `false` for all access groups you create."},"all_users":{"type":"boolean","description":"Specifies whether assets in the access group can be viewed by all or only some users in your organization:\n - If `true`, all users in your organization have Can View access to the assets defined in the rules parameter. If `true` in a POST /access-groups or PUT /access-groups/{id} request, Tenable.io ignores any principal parameters in the request. \n - If `false`, only specified users have Can View access to the assets defined in the rules parameter. You define which users or user groups have access in the principals parameter of a POST /access-groups or PUT /access-groups/{id} request. \n\n**Note:** If a PUT /access-groups/{id} endpoint request sets this parameter to `true` for an access group where the parameter was previously set to `false`, Tenable.io removes all principal data previously associated with the access group."},"status":{"type":"string","description":"The status of the process evaluating and assigning assets to the access group. Possible values are: \n - PROCESSING—Tenable.io is currently evaluating assets against the asset rules for the access group. For an indication of evaluation progress, see the `processing_percent_complete` attribute for the access group.\n - COMPLETED—Tenable.io has successfully completed its evaluation of assets against the asset rules for the group.\n - ERROR—Tenable.io encountered an error while evaluating assets against asset rules for the access group. Rule validation typically prevents this status from occurring. However, if you encounter an ERROR status, Tenable recommends that you delete the existing asset rules, then recreate the rules after a short time has elapsed."},"rules":{"description":"An array of asset rules. Tenable.io uses these rules to assign assets to the access group. You can specify a maximum of 1,000 rules for an individual access group. If you specify multiple rules for an access group, Tenable.io assigns an asset to the access group if the asset matches any of the rules. You can only add rules to access groups if the all\\_assets parameter is set to `false`.","type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"The type of asset rule. The asset rule type corresponds to the type of data you can specifiy in the terms parameter. For a complete list of supported rule types, use the GET /access-groups/filters endpoint."},"operator":{"type":"string","description":"The operator that specifies how Tenable.io matches the terms value to asset data. \n\nPossible operators include: \n - eq—Tenable.io matches the rule to assets based on an exact match of the specified term. Note: Tenable.io interprets the operator as `equals` for ipv4 rules that specify a single IP address, but interprets the operator as `contains` for ipv4 rules that specify an IP range or CIDR range.\n - match—Tenable.io matches the rule to assets based a partial match of the specified term.\n - starts—Tenable.io matches the rule to assets that start with the specified term.\n - ends—Tenable.io matches the rule to assets that end with the specified term.\n\nFor a complete list of operators by rule type, use the GET /access-groups/rules/filters endpoint."},"terms":{"description":"The values that Tenable.io uses to match an asset to the rule. A term must correspond to the rule type.\n\nFor example:\n - If the rule type is `aws_account`, the term is an AWS account ID.\n - If the rule type is `fqdn`, the term is a hostname or a fully-qualified domain name (FQDN).\n - If the rule type is `ipv4`, the term is an individual IPv4 address, a range of IPv4 addresses (for example, 172.204.81.57-172.204.81.60), or a CIDR range (for example, 172.204.81.57/24). \n\nFor a complete list of supported values by rule type, use the GET /access-groups/rules/filters endpoint. \n\nIf you specify multiple terms values, Tenable.io includes an asset in the access group if the asset's attributes match any of the terms in the rule.\n
You can specify up to 100,000 terms per asset rule.","type":"array","items":{"type":"string"}}}}},"principals":{"description":"An array of principals. Each principal represents a user or user group assigned to the access group. You cannot add an access group as a principal to another access group.","type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"(Required) The type of principal. Valid values include:\n - user—Grants access to the user you specify.\n - group—Grants access to all users assigned to the user group you specify."},"principal_id":{"type":"string","description":"The UUID of a user or user group. This parameter is required if the request omits the `principal_name` parameter."},"principal_name":{"type":"string","description":"The name of the user or user group. This parameter is required if the request omits the `principal_id` parameter. If a request includes both `principal_id` and `principal_name`, Tenable.io assigns the user or user group to the access group based on the `principal_id` parameter, and ignores the `principal_name` parameter in the request. "}}}},"created_by_uuid":{"type":"string","description":"The UUID of the user who created the access group."},"created_by_name":{"type":"string","description":"The name of the user who created the access group."},"updated_by_uuid":{"type":"string","description":"The UUID of the user who last modified the access group."},"updated_by_name":{"type":"string","description":"The name of the user who last modified the access group."},"processing_percent_complete":{"type":"integer","description":"The percentage of assets that Tenable.io has evaluated against the asset rules for the access group."}}},"examples":{"response":{"value":{"container_uuid":"8f9d0b84-ede2-4954-a0c9-0bde292ac38e","created_at":"2018-08-15T18:17:04.827Z","updated_at":"2018-08-15T18:17:04.827Z","id":"385f4765-cd32-4191-b6ae-d0d4522e073f","name":"Headquarters","all_assets":false,"all_users":false,"status":"COMPLETED","rules":[{"type":"aws_account","operator":"eq","terms":["123456789012"]},{"type":"fqdn","operator":"eq","terms":["www.example.com"]},{"type":"ipv4","operator":"eq","terms":["172.204.81.57"]}],"principals":[{"type":"user","principal_id":"085abc65-d709-44b2-ad04-bfd2862ad5a1","principal_name":"user1@example.com"},{"type":"user","principal_id":"b1219ca2-2578-49ac-88db-8a35dd02cc7d","principal_name":"user2@example.com"}],"created_by_uuid":"b1219ca2-2578-49ac-88db-8a35dd02cc7d","updated_by_uuid":"b1219ca2-2578-49ac-88db-8a35dd02cc7d","updated_by_name":"user3@example.com","created_by_name":"user3@example.com","processing_percent_complete":100}}}}}},"400":{"description":"Returned if Tenable.io encountered any of the following error conditions:\n - incomplete—the body of your request did not include the required fields.\n - duplicate—an access group with the name you specified already exists.\n - protected—you attempted to update an access group where the `all_assets` parameter is set to `true`, and you cannot update the system-provided `All Assets` access group."},"403":{"description":"Returned if you do not have sufficient permissions to modify access groups."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete access group","description":"Deletes an access group.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"access-groups-delete","tags":["Access Groups"],"parameters":[{"description":"The UUID for the access group you want to delete.","required":true,"name":"id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deleted the access group you specified."},"403":{"description":"Returned if you do not have sufficient permissions to delete an access group."},"404":{"description":"Returned if Tenable.io could not find the access group you specified."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"Get access group details","description":"Returns details for a specific access group.

Requires BASIC [16] user permissions to view details for an access group to which you are assigned; however, details do not include principals information. Requires ADMINISTRATOR [64] to view details for any access group in your organization; in this case, details include principals information. See Permissions.

","operationId":"access-groups-details","tags":["Access Groups"],"parameters":[{"description":"The UUID of the access group where you want to view details.","required":true,"name":"id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the access group details.","content":{"application/json":{"schema":{"type":"object","properties":{"container_uuid":{"type":"string","description":"The UUID of your Tenable.io instance."},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the access group was created."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the time and date on which the access group was last modified."},"id":{"type":"string","description":"The UUID of the access group."},"name":{"type":"string","description":"The name of the access group. This name must be: \n* Unique within your Tenable.io instance. \n* A maximum of 255 characters. \n* Alphanumeric, but can include limited special characters (underscore, dash, parenthesis, brackets, colon)."},"all_assets":{"type":"boolean","description":"Specifies whether the access group is the system-provided All Assets access group: \n - If `true`, the access group is the All Assets access group. The only change you can make to this access group is to refine user membership in the group. For more information, see descriptions of the all_users and principals parameters for the PUT /access-groups/{id} endpoint.\n - If `false`, the access group is a user-defined access group, and you can change all parameters for the group. This parameter is `false` for all access groups you create."},"all_users":{"type":"boolean","description":"Specifies whether assets in the access group can be viewed by all or only some users in your organization:\n - If `true`, all users in your organization have Can View access to the assets defined in the rules parameter. If `true` in a POST /access-groups or PUT /access-groups/{id} request, Tenable.io ignores any principal parameters in the request. \n - If `false`, only specified users have Can View access to the assets defined in the rules parameter. You define which users or user groups have access in the principals parameter of a POST /access-groups or PUT /access-groups/{id} request. \n\n**Note:** If a PUT /access-groups/{id} endpoint request sets this parameter to `true` for an access group where the parameter was previously set to `false`, Tenable.io removes all principal data previously associated with the access group."},"status":{"type":"string","description":"The status of the process evaluating and assigning assets to the access group. Possible values are: \n - PROCESSING—Tenable.io is currently evaluating assets against the asset rules for the access group. For an indication of evaluation progress, see the `processing_percent_complete` attribute for the access group.\n - COMPLETED—Tenable.io has successfully completed its evaluation of assets against the asset rules for the group.\n - ERROR—Tenable.io encountered an error while evaluating assets against asset rules for the access group. Rule validation typically prevents this status from occurring. However, if you encounter an ERROR status, Tenable recommends that you delete the existing asset rules, then recreate the rules after a short time has elapsed."},"rules":{"description":"An array of asset rules. Tenable.io uses these rules to assign assets to the access group. You can specify a maximum of 1,000 rules for an individual access group. If you specify multiple rules for an access group, Tenable.io assigns an asset to the access group if the asset matches any of the rules. You can only add rules to access groups if the all\\_assets parameter is set to `false`.","type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"The type of asset rule. The asset rule type corresponds to the type of data you can specifiy in the terms parameter. For a complete list of supported rule types, use the GET /access-groups/filters endpoint."},"operator":{"type":"string","description":"The operator that specifies how Tenable.io matches the terms value to asset data. \n\nPossible operators include: \n - eq—Tenable.io matches the rule to assets based on an exact match of the specified term. Note: Tenable.io interprets the operator as `equals` for ipv4 rules that specify a single IP address, but interprets the operator as `contains` for ipv4 rules that specify an IP range or CIDR range.\n - match—Tenable.io matches the rule to assets based a partial match of the specified term.\n - starts—Tenable.io matches the rule to assets that start with the specified term.\n - ends—Tenable.io matches the rule to assets that end with the specified term.\n\nFor a complete list of operators by rule type, use the GET /access-groups/rules/filters endpoint."},"terms":{"description":"The values that Tenable.io uses to match an asset to the rule. A term must correspond to the rule type.\n\nFor example:\n - If the rule type is `aws_account`, the term is an AWS account ID.\n - If the rule type is `fqdn`, the term is a hostname or a fully-qualified domain name (FQDN).\n - If the rule type is `ipv4`, the term is an individual IPv4 address, a range of IPv4 addresses (for example, 172.204.81.57-172.204.81.60), or a CIDR range (for example, 172.204.81.57/24). \n\nFor a complete list of supported values by rule type, use the GET /access-groups/rules/filters endpoint. \n\nIf you specify multiple terms values, Tenable.io includes an asset in the access group if the asset's attributes match any of the terms in the rule.\n
You can specify up to 100,000 terms per asset rule.","type":"array","items":{"type":"string"}}}}},"principals":{"description":"An array of principals. Each principal represents a user or user group assigned to the access group. You cannot add an access group as a principal to another access group.","type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"(Required) The type of principal. Valid values include:\n - user—Grants access to the user you specify.\n - group—Grants access to all users assigned to the user group you specify."},"principal_id":{"type":"string","description":"The UUID of a user or user group. This parameter is required if the request omits the `principal_name` parameter."},"principal_name":{"type":"string","description":"The name of the user or user group. This parameter is required if the request omits the `principal_id` parameter. If a request includes both `principal_id` and `principal_name`, Tenable.io assigns the user or user group to the access group based on the `principal_id` parameter, and ignores the `principal_name` parameter in the request. "}}}},"created_by_uuid":{"type":"string","description":"The UUID of the user who created the access group."},"created_by_name":{"type":"string","description":"The name of the user who created the access group."},"updated_by_uuid":{"type":"string","description":"The UUID of the user who last modified the access group."},"updated_by_name":{"type":"string","description":"The name of the user who last modified the access group."},"processing_percent_complete":{"type":"integer","description":"The percentage of assets that Tenable.io has evaluated against the asset rules for the access group."}}},"examples":{"response":{"value":{"container_uuid":"7a818eb1-8351-4795-99b0-9610c8954cb3","created_at":"2018-11-27T21:17:18.883Z","updated_at":"2018-11-27T22:12:02.414Z","id":"d30542aa-84d4-4b38-9a74-6a4c665532b1","name":"Western Region","all_assets":false,"all_users":false,"version":2,"status":"COMPLETED","rules":[{"type":"ipv4","operator":"eq","terms":["172.204.81.57"],"principals":[{"type":"user","principal_id":"e7ecb50b-1330-4a8c-b8e5-ee00ec8c46f7","principal_name":"user1@example.com"},{"type":"user","principal_id":"a7162cb4-ebf7-4103-a250-34b1777cfbd0","principal_name":"user2@example.com"}],"created_by_uuid":"f3eda8e9-11f5-4ac8-966f-b758eed531c4","updated_by_uuid":"f3eda8e9-11f5-4ac8-966f-b758eed531c4","updated_by_name":"administrator@example.com","created_by_name":"administrator@example.com","processing_percent_complete":100}]}}}}}},"403":{"description":"Returned if you do not have permissions to view the access group details."},"404":{"description":"Returned if Tenable.io could not find the access group you specified."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/access-groups/filters":{"get":{"summary":"List access group filters","description":"Lists available filters for access groups.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"access-groups-list-filters","tags":["Access Groups"],"responses":{"200":{"description":"Returns a list of available filters for access groups. The response includes supported filter operators for each filter. Filter operators can include:\n - eq—filters on exact matches of the specified value\n - match—filters on partial matches of the specified value\n - date-lt —filters on dates earlier than the specified date\n - date-gt—filters on dates later than the specified date\n - date-eq—filters on dates equal to the specified date.\n\nThe sample below does not represent a complete list of supported filters.","content":{"application/json":{"schema":{"type":"object","properties":{"wildcard_fields":{"description":"The fields you can use as a wildcard (`wf` parameter) value in the GET /access-groups endpoint.","type":"array","items":{"type":"string"}},"filters":{"description":"The filters and operators for each field you can use when constructing filter (`f` parameter) values in the GET /access-groups endpoint.","type":"array","items":{"type":"object","properties":{"operators":{"description":"Corresponds to the operator component of the `f` parameter.","type":"array","items":{"type":"string"}},"control":{"description":"Indicates how the parameter appears in the Tenable.io user interface.","type":"string"},"name":{"description":"Corresponds to the field component of the `f` parameter.","type":"string"},"readable_name":{"description":"The name of the parameter as it appears in the Tenable.io user interface.","type":"string"}}}},"sort":{"description":"The fields you can use when constructing `sort` parameter values for the GET /access-groups endpoint.","type":"array","items":{"type":"object","properties":{"sortable_fields":{"description":"The names of the fields you can use when constructing `sort` parameter values for the GET /access-groups endpoint.","type":"string"}}}}}},"examples":{"response":{"value":{"wildcard_fields":["name","created_by_name","updated_by_name"],"filters":[{"operators":["eq","match"],"control":{"type":"entry"},"name":"name","readable_name":"Access Group Name"}],"sort":{"sortable_fields":["name","created_at","created_by_name","updated_at","updated_by_name"]}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/access-groups/rules/filters":{"get":{"summary":"List asset rule filters","description":"Lists available filters for asset rules.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"access-groups-list-rule-filters","tags":["Access Groups"],"responses":{"200":{"description":"Returns a list of filters. The sample below does not represent a complete list of supported filters.","content":{"application/json":{"schema":{"type":"object","properties":{"filters":{"description":"An array specifying values to use when constructing an asset rule for the POST /access-groups and PUT /access-groups/{id} methods.","type":"array","items":{"type":"object","properties":{"operators":{"description":"The operator that specifies how Tenable.io matches the terms value to asset data. Corresponds to the operator component of the rules parameter. Possible operators include: \n*eq—Tenable.io matches the rule to assets based on an exact match of the specified term. Note: Tenable.io interprets the operator as `equals` for ipv4 rules that specify a single IP address, but interprets the operator as `contains` for ipv4 rules that specify an IP range or CIDR range. \n* match—Tenable.io matches the rule to assets based a partial match of the specified term. \n* starts—Tenable.io matches the rule to assets that start with the specified term. \n* ends—Tenable.io matches the rule to assets that end with the specified term. For a complete list of operators by rule type, use the GET /access-groups/filters endpoint.","type":"array","items":{"type":"string"}},"control":{"description":"Indicates how the field appears in the Tenable.io user interface.","type":"string"},"name":{"description":"The name of the filter parameter. Corresponds to the asset rule type.","type":"string"},"readable_name":{"description":"The name of the field as it appears in the Tenable.io user interface.","type":"string"}}}}}},"examples":{"response":{"value":{"rules":[{"operators":["eq"],"name":"aws_account","readable_name":"AWS Account ID","placeholder":"Ex: 12345","control":{"type":"tag","regex":"^\\d+$"}},{"operators":["eq"],"name":"ipv4","readable_name":"IPv4 Address","placeholder":"Ex: 172.204.81.57","control":{"type":"tag","regex":"^(?=\\d+\\.\\d+\\.\\d+\\.\\d+(($|\\/)|($|-)))(([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.?){4}((\\/([0-9]|[1-2][0-9]|3[0-2]))|(-(([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.?){4}))?$"}},{"operators":["eq","match","starts","ends"],"name":"fqdn","readable_name":"FQDN/Hostname","placeholder":"Ex: company.com","control":{"type":"tag","regex":"^[a-zA-Z0-9-.*]+$"}}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agents/config":{"get":{"summary":"Get agent configuration","description":"Returns the configuration of agents associated with a specific scanner. Agent configuration controls agent settings for global agent software update enablement and agent auto-expiration.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-config-details","tags":["Agent Config"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the agent configuration.","content":{"application/json":{"schema":{"type":"object","properties":{"auto_unlink":{"type":"object","properties":{"enabled":{"type":"boolean","description":"If true, agent auto-unlink is enabled. Enabling auto-unlink causes it to take effect against all agents retroactively."},"expiration":{"type":"integer","description":"The expiration time for agents, in days. If an agent has not communicated in the specified number of days, Tenable.io classifies the agent as expired and auto-unlinks the agent if auto_unlink.enabled is `true`. Valid values are 1-365.","format":"int32"}}},"software_update":{"type":"boolean","description":"If true, software updates are enabled for agents pursuant to any agent exclusions that are in effect. If false, software updates are disabled for all agents, even if no agent exclusions are in effect."}}},"examples":{"response":{"value":{"auto_unlink":{"expiration":"30","enabled":"false"},"software_update":"true"}}}}}},"403":{"description":"Returned if you do not have permission to view the agent configuration."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update agent configuration","description":"Updates the configuration of agents associated with a specific scanner.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-config-edit","tags":["Agent Config"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"software_update":{"type":"boolean","description":"If true, software updates are enabled for agents pursuant to any agent exclusions that are in effect. If false, software updates are disabled for all agents, even if no agent exclusions are in effect."},"auto_unlink":{"type":"object","properties":{"enabled":{"type":"boolean","description":"If true, agent auto-unlink is enabled. Enabling auto-unlink causes it to take effect against all agents retroactively."},"expiration":{"type":"integer","description":"The expiration time for agents, in days. If an agent has not communicated in this number of days, it will be considered `expired` and auto-unlinked if auto\\_unlink.enabled is `true`. Valid values are 1-365.","format":"int32"}}}}}}}},"responses":{"200":{"description":"Returned if the agent configuration has been updated.","content":{"application/json":{"schema":{"type":"object","properties":{"auto_unlink":{"type":"object","properties":{"enabled":{"type":"boolean","description":"If true, agent auto-unlink is enabled. Enabling auto-unlink causes it to take effect against all agents retroactively."},"expiration":{"type":"integer","description":"The expiration time for agents, in days. If an agent has not communicated in the specified number of days, Tenable.io classifies the agent as expired and auto-unlinks the agent if auto_unlink.enabled is `true`. Valid values are 1-365.","format":"int32"}}},"software_update":{"type":"boolean","description":"If true, software updates are enabled for agents pursuant to any agent exclusions that are in effect. If false, software updates are disabled for all agents, even if no agent exclusions are in effect."}}},"examples":{"response":{"value":{"auto_unlink":{"expiration":"30","enabled":"false"},"software_update":"true"}}}}}},"403":{"description":"Returned if you do not have permission to update the agent config."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to update the agent configuration.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agents/exclusions":{"post":{"summary":"Create agent exclusion","description":"Creates a new agent exclusion.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-exclusions-create","tags":["Agent Exclusions"],"parameters":[{"description":"The ID of the scanner","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"schedule":{"type":"object","required":["starttime","rrules","timezone"],"properties":{"enabled":{"type":"boolean","description":"If true, the exclusion is scheduled."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","required":["freq"],"properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY).","enum":["ONETIME","DAILY","WEEKLY","MONTHLY","YEARLY"]},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}}},"required":["name","schedule"]}}}},"responses":{"200":{"description":"Returned if the agent exclusion has been created.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the exclusion."},"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"creation_date":{"type":"integer","description":"The creation date of the exclusion in unixtime."},"last_modification_date":{"type":"integer","description":"The last modification date for the exclusion in unixtime."},"schedule":{"type":"object","required":["starttime","rrules","timezone"],"properties":{"enabled":{"type":"boolean","description":"If true, the exclusion is scheduled."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","required":["freq"],"properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY).","enum":["ONETIME","DAILY","WEEKLY","MONTHLY","YEARLY"]},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}}}},"examples":{"response":{"value":{"schedule":{"endtime":"2019-11-29 19:35:00","enabled":true,"rrules":{"freq":"DAILY","interval":8,"byweekday":"SU,MO","bymonthday":9},"timezone":"US/Pacific","starttime":"2018-11-29 19:35:00"},"last_modification_date":1543541807,"creation_date":1543541807,"description":"Router scan exclusion","name":"Routers","id":124234}}}}}},"400":{"description":"Returned if an argument is missing or invalid."},"403":{"description":"Returned if you do not have permission to create an exclusion."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to create the exclusion.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List agent exclusions","description":"Returns the list of current agent exclusions.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-exclusions-list","tags":["Agent Exclusions"],"parameters":[{"description":"The ID of the scanner","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the exclusions.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the exclusion."},"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"creation_date":{"type":"integer","description":"The creation date of the exclusion in unixtime."},"last_modification_date":{"type":"integer","description":"The last modification date for the exclusion in unixtime."},"schedule":{"type":"object","required":["starttime","rrules","timezone"],"properties":{"enabled":{"type":"boolean","description":"If true, the exclusion is scheduled."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","required":["freq"],"properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY).","enum":["ONETIME","DAILY","WEEKLY","MONTHLY","YEARLY"]},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}}}}},"examples":{"response":{"value":{"exclusions":[{"schedule":{"endtime":"2019-11-29 19:35:00","enabled":true,"rrules":{"freq":"DAILY","interval":8,"byweekday":"SU,MO","bymonthday":9},"timezone":"US/Pacific","starttime":"2018-11-29 19:35:00"},"last_modification_date":1543541807,"creation_date":1543541807,"description":"Router scan exclusion","name":"Routers","id":124234},{"schedule":{"endtime":"2019-11-29 19:35:00","enabled":true,"rrules":{"freq":"DAILY","interval":8,"byweekday":"TU","bymonthday":11},"timezone":"US/Central","starttime":"2018-11-29 19:35:00"},"last_modification_date":2543541809,"creation_date":2543541809,"description":"Workstation scan exclusion","name":"Workstation","id":222456}]}}}}}},"403":{"description":"Returned if you do not have permission to view the exclusions."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agents/exclusions/{exclusion_id}":{"put":{"summary":"Update agent exclusion","description":"Updates an agent exclusion.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-exclusions-edit","tags":["Agent Exclusions"],"parameters":[{"description":"The ID of the exclusion to edit.","required":true,"name":"exclusion_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the scanner","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"schedule":{"type":"object","required":["starttime","rrules","timezone"],"properties":{"enabled":{"type":"boolean","description":"If true, the exclusion is scheduled."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","required":["freq"],"properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY).","enum":["ONETIME","DAILY","WEEKLY","MONTHLY","YEARLY"]},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}}},"required":["schedule"]}}}},"responses":{"200":{"description":"Returned if the exclusion has been modified.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"schedule":{"endtime":"2019-11-29 19:35:00","enabled":true,"rrules":{"freq":"DAILY","interval":8,"byweekday":"SU,MO","bymonthday":9},"timezone":"US/Pacific","starttime":"2018-11-29 19:35:00"},"last_modification_date":1543541807,"creation_date":1543541807,"description":"Router scan exclusion","name":"Routers","id":124234}}}}}},"403":{"description":"Returned if you do not have permission to modify the exclusion."},"404":{"description":"Returned if Tenable.io cannot find the specified exclusion."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to change the exclusion.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete agent exclusion","description":"Deletes an agent exclusion.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-exclusions-delete","tags":["Agent Exclusions"],"parameters":[{"description":"The ID of the exclusion to delete.","required":true,"name":"exclusion_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the scanner","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the exclusion has been successfully deleted.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to delete the exclusion."},"404":{"description":"Returned if Tenable.io cannot find the specified exclusion."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"Get agent exclusion details","description":"Returns details for the specified agent exclusion.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-exclusions-details","tags":["Agent Exclusions"],"parameters":[{"description":"The ID of the exclusion.","required":true,"name":"exclusion_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the scanner","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the exclusion details.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the exclusion."},"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"creation_date":{"type":"integer","description":"The creation date of the exclusion in unixtime."},"last_modification_date":{"type":"integer","description":"The last modification date for the exclusion in unixtime."},"schedule":{"type":"object","required":["starttime","rrules","timezone"],"properties":{"enabled":{"type":"boolean","description":"If true, the exclusion is scheduled."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","required":["freq"],"properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY).","enum":["ONETIME","DAILY","WEEKLY","MONTHLY","YEARLY"]},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}}}},"examples":{"response":{"value":{"schedule":{"endtime":"2019-11-29 19:35:00","enabled":true,"rrules":{"freq":"DAILY","interval":8,"byweekday":"SU,MO","bymonthday":9},"timezone":"US/Pacific","starttime":"2018-11-29 19:35:00"},"last_modification_date":1543541807,"creation_date":1543541807,"description":"Router scan exclusion","name":"Routers","id":124234}}}}}},"403":{"description":"Returned if you do not have permission to view the exclusion."},"404":{"description":"Returned if Tenable.io cannot find the specified exclusion."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agent-groups":{"post":{"summary":"Create agent group on scanner","description":"Creates an agent group on the scanner.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-groups-create","tags":["Agent Groups"],"parameters":[{"description":"The ID of the scanner to add the agent group to.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the agent group."}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if the agent group has been created.","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"description":"The agents in the group. The agent records can be filtered, sorted, and paginated.","type":"array","items":{"type":"string"}},"creation_date":{"type":"integer","description":"The creation date of the agent group in unixtime."},"id":{"type":"integer","description":"The unique ID of the agent group."},"last_modification_date":{"type":"integer","description":"The last modification date for the agent group in unixtime."},"name":{"type":"string","description":"The name of the agent group."},"owner":{"type":"string","description":"The username for the owner of the agent group."},"owner_id":{"type":"string","description":"The unique ID of the owner of the agent group."},"owner_name":{"type":"string","description":"The name for the owner of the agent group."},"owner_uuid":{"type":"string","description":"The UUID of the owner of the agent group."},"pagination":{"type":"object","properties":{}},"shared":{"type":"integer","description":"The shared status of the agent group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the agent group."},"uuid":{"type":"string","description":"The UUID of the agent group."}}},"examples":{"response":{"value":{"id":106592,"uuid":"8b05bd55-9105-48ec-9da1-2e10a2c9a4e0","name":"Western Region","creation_date":1544455100,"last_modification_date":1544455100,"timestamp":1544455100,"shared":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","user_permissions":0,"agents_count":0}}}}}},"400":{"description":"Returned if your request message contains an invalid parameter."},"403":{"description":"Returned if you do not have permission to create an agent group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to add the agent group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List agent groups for scanner","description":"Lists the agent groups for the scanner.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-groups-list","tags":["Agent Groups"],"parameters":[{"description":"The ID of the scanner to query for agent groups.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the agent groups list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"agents":{"description":"The agents in the group. The agent records can be filtered, sorted, and paginated.","type":"array","items":{"type":"string"}},"creation_date":{"type":"integer","description":"The creation date of the agent group in unixtime."},"id":{"type":"integer","description":"The unique ID of the agent group."},"last_modification_date":{"type":"integer","description":"The last modification date for the agent group in unixtime."},"name":{"type":"string","description":"The name of the agent group."},"owner":{"type":"string","description":"The username for the owner of the agent group."},"owner_id":{"type":"string","description":"The unique ID of the owner of the agent group."},"owner_name":{"type":"string","description":"The name for the owner of the agent group."},"owner_uuid":{"type":"string","description":"The UUID of the owner of the agent group."},"pagination":{"type":"object","properties":{}},"shared":{"type":"integer","description":"The shared status of the agent group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the agent group."},"uuid":{"type":"string","description":"The UUID of the agent group."}}}},"examples":{"response":{"value":{"groups":[{"id":106592,"uuid":"8b05bd55-9105-48ec-9da1-2e10a2c9a4e0","name":"slibs","creation_date":1544455100,"last_modification_date":1544455100,"timestamp":1544455100,"shared":1,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","user_permissions":128,"agents_count":0}]}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agent-groups/{group_id}":{"get":{"summary":"Get details for agent group","description":"Gets details for the agent group. Agent records which belong to this group will also be returned. You can apply filtering, sorting, or pagination to the agent records.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-groups-details","tags":["Agent Groups"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent group to query.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The starting record to retrieve. If this parameter is not supplied, the default value is 0.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The number of records to retrieve. If this parameter is not supplied, a default of 50 records is used. The minimum supported limit is 1, and the maximum supported limit is 5000.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The sort order of the returned records. Sort can only be applied to the sortable\\_fields specified by the filter capabilities. There may be no more than max\\_sort\\_fields number of columns used in the sort, as specified by the filter capabilities. Sort is applied, in order, in the following format: `:\\[asc|desc\\],:\\[asc|desc\\]`. For example, `sort=field1:asc,field2:desc` would first sort by field1, ascending, then sort by field2, descending.","required":false,"name":"sort","in":"query","schema":{"type":"string"}},{"description":"Apply a filter in the format `::`. For example, `field1:match:sometext` would match any records where the value of field1 contains `sometext`. You can use multiple query filters.","required":false,"name":"f","in":"query","schema":{"type":"string"}},{"description":"Filter type. If the filter type is `and`, the record is only returned if all filters match. If the filter type is `or`, the record is returned if any of the filters match.","required":false,"name":"ft","in":"query","schema":{"type":"string"}},{"description":"Wildcard filter text. Wildcard search is a mechanism where multiple fields of a record are filtered against one specific filter string. If any one of the wildcard\\_fields' values matches against the filter string, then the record matches the wildcard filter. For a record to be returned, it must pass the wildcard filter (if there is one) AND the set of standard filters. For example, if `w=wild&f=field1:match:one&f=field2:match:two&ft=or`, the record would match if the value of any supported wildcard\\_fields contained `wild`, AND either field1's value contained `one` or field2's value contained `two`.","required":false,"name":"w","in":"query","schema":{"type":"string"}},{"description":"A comma delimited subset of wildcard\\_fields to search when applying the wildcard filter. For example, `field1,field2`. If `w` is provided, but `wf` is not, then all wildcard\\_fields' values are searched against the wildcard filter text.","required":false,"name":"wf","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the agent group details.","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"description":"The agents in the group. The agent records can be filtered, sorted, and paginated.","type":"array","items":{"type":"string"}},"creation_date":{"type":"integer","description":"The creation date of the agent group in unixtime."},"id":{"type":"integer","description":"The unique ID of the agent group."},"last_modification_date":{"type":"integer","description":"The last modification date for the agent group in unixtime."},"name":{"type":"string","description":"The name of the agent group."},"owner":{"type":"string","description":"The username for the owner of the agent group."},"owner_id":{"type":"string","description":"The unique ID of the owner of the agent group."},"owner_name":{"type":"string","description":"The name for the owner of the agent group."},"owner_uuid":{"type":"string","description":"The UUID of the owner of the agent group."},"pagination":{"type":"object","properties":{}},"shared":{"type":"integer","description":"The shared status of the agent group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the agent group."},"uuid":{"type":"string","description":"The UUID of the agent group."}}},"examples":{"response":{"value":{"id":106592,"uuid":"8b05bd55-9105-48ec-9da1-2e10a2c9a4e0","name":"Western Region","creation_date":1544455100,"last_modification_date":1544455100,"timestamp":1544455100,"shared":1,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","user_permissions":128,"agents_count":0,"agents":[],"pagination":{"total":0,"limit":50,"offset":0,"sort":[{"name":"name","order":"asc"}]}}}}}}},"403":{"description":"Returned if you do not have permission to view the agent group."},"404":{"description":"Returned if Tenable.io cannot find the specified agent group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update name of agent group","description":"Changes the name of the agent group.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-groups-configure","tags":["Agent Groups"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent group to change.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name for the agent group."}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if the configuration was changed.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"To do":"Add response sample here"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified agent group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if an error occurred while saving the configuration.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete agent group from scanner","description":"Deletes an agent group from the scanner.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-groups-delete","tags":["Agent Groups"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent group to delete.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the agent group has been successfully deleted.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified agent group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the agent group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agent-groups/{group_id}/agents/{agent_id}":{"put":{"summary":"Add agent to agent group","description":"Adds an agent to the agent group.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-groups-add-agent","tags":["Agent Groups"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent to add.","required":true,"name":"agent_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the agent was added to the group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"To do":"Add response sample here"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified agent group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if an error occurred while attempting to add the agent.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete agent from agent group","description":"Deletes an agent from the agent group.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-groups-delete-agent","tags":["Agent Groups"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent to remove.","required":true,"name":"agent_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the agent has been successfully removed from the agent group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified agent."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to remove the agent from the agent group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agents":{"get":{"summary":"List agents for scanner","description":"Returns a list of agents for the specified scanner.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agents-list","tags":["Agents"],"parameters":[{"description":"The ID of the scanner to query for agents.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The starting record to retrieve. If you omit this parameter, Tenable.io uses the default value of 0.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The number of records to retrieve. If you omit this parameter, Tenable.io uses a default of 50 records. The minimum supported limit is 1, and the maximum supported limit is 5,000.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The sort order of the returned records. Sort can only be applied to the sortable\\_fields specified by the filter capabilities. There may be no more than max\\_sort\\_fields number of columns used in the sort, as specified by the filter capabilities. Sort is applied, in order, in the following format: `:\\[asc|desc\\],:\\[asc|desc\\]`. For example, `sort=field1:asc,field2:desc` would first sort by field1, ascending, then sort by field2, descending.","required":false,"name":"sort","in":"query","schema":{"type":"string"}},{"description":"Apply a filter in the format `::`. For example, `field1:match:sometext` would match any records where the value of field1 contains `sometext`. You can use multiple query filters.","required":false,"name":"f","in":"query","schema":{"type":"string"}},{"description":"Filter type. If the filter type is `and`, the record is only returned if all filters match. If the filter type is `or`, the record is returned if any of the filters match.","required":false,"name":"ft","in":"query","schema":{"type":"string"}},{"description":"Wildcard filter text. Wildcard search is a mechanism where multiple fields of a record are filtered against one specific filter string. If any one of the wildcard\\_fields' values matches against the filter string, then the record matches the wildcard filter. For a record to be returned, it must pass the wildcard filter (if there is one) AND the set of standard filters. For example, if `w=wild&f=field1:match:one&f=field2:match:two&ft=or`, the record would match if the value of any supported wildcard\\_fields contained `wild`, AND either field1's value contained `one` or field2's value contained `two`.","required":false,"name":"w","in":"query","schema":{"type":"string"}},{"description":"A comma-delimited subset of wildcard\\_fields to search when applying the wildcard filter. For example, `field1,field2`. If `w` is provided, but `wf` is not, then all wildcard\\_fields' values are searched against the wildcard filter text.","required":false,"name":"wf","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of agents.","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the agent."},"uuid":{"type":"string","description":"The UUID of the agent. Note: This value corresponds to the ID of the asset where the agent is installed. You can use this attribute to match agent data to asset data."},"name":{"type":"string","description":"The name of the agent."},"platform":{"type":"string","description":"The platform of the agent."},"distro":{"type":"string","description":"The agent software distribution."},"ip":{"type":"string","description":"The IP address of the agent."},"last_scanned":{"type":"integer","description":"The Unix timestamp when the agent last scanned the asset."},"plugin_feed_id":{"type":"string","description":"The currently loaded plugin set of the agent (null if the agent has no plugin set loaded)."},"core_build":{"type":"string","description":"Build number for the agent."},"core_version":{"type":"string","description":"Build version for the agent."},"linked_on":{"type":"integer","description":"The Unix timestamp when the link from Tenable.io to the agent was established."},"last_connect":{"type":"integer","description":"The Unix timestamp when the agent last communicated with Tenable.io."},"status":{"type":"string","description":"\"on\", \"off\", or \"init\". \"on\" means that the agent has connected recently, and is therefore likely ready to scan. \"off\" means that the agent has not been seen recently and should be considered offline. \"init\" means that the agent is online, but it is still processing plugin updates and is not ready to scan.","enum":["on","off","init"]},"groups":{"description":"Array of groups to which the agent belongs. Groups are returned in the form {\"name\": \"group name\", \"id\": \"group id\"}.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the agent group to which the agent belongs."},"id":{"type":"integer","description":"The unique ID of the agent group to which the agent belongs."}}}}}}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of records which match any applied filters. This number may be approximate."},"offset":{"type":"integer","description":"The index of the first record retrieved."},"limit":{"type":"integer","description":"The number of records returned with this response."},"sort":{"description":"The sorting parameters applied to response, in order of application.","type":"array","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"agents":[{"id":157,"uuid":"c6ce4255-b386-4e71-be64-0d06434bac5d","name":"GRD-LPTP","platform":"WINDOWS","distro":"win-x86-64","ip":"172.204.81.57","last_scanned":1515620036,"plugin_feed_id":"201801081515","core_build":"106","core_version":"7.0.0","linked_on":1456775443,"last_connect":1515674073,"status":"off","groups":[{"name":"CodyAgents","id":8},{"name":"Agent Group A","id":3316}]},{"id":14569,"uuid":"bce54c02-392d-4305-bc83-e1e1d8130afd","name":"scr-lce.lab.tenablesecurity.com","platform":"LINUX","distro":"es7-x86-64","ip":"172.204.81.57","plugin_feed_id":"201805161620","core_build":"13","core_version":"7.0.3","linked_on":1508329832,"last_connect":1526565530,"status":"off","groups":[{"name":"SC Research","id":1167}]},{"id":14570,"uuid":"0657efe4-0ba0-423a-ac5f-52a6e23d2e11","name":"scr-sc5.lab.tenablesecurity.com","platform":"LINUX","distro":"es7-x86-64","ip":"172.204.81.57","plugin_feed_id":"201805161620","core_build":"13","core_version":"7.0.3","linked_on":1508329886,"last_connect":1526565624,"status":"off","groups":[{"name":"SC Research","id":1167}]}],"pagination":{"total":3,"limit":50,"offset":0,"sort":[{"name":"name","order":"asc"}]}}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agent-groups/{agent_group_id}/agents":{"get":{"summary":"List agents for agent group","description":"Returns a list of agents for the specified agent group.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agent-group-list-agents","tags":["Agents"],"parameters":[{"description":"The ID of the scanner to query for agents. You can find the ID by using the [GET /scanners](/reference#scanners-list) endpoint.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent group to query for agents. You can find the ID by using the [GET /scanners/{scanner_id}/agent-groups](/reference#agent-groups-list) endpoint.","required":true,"name":"agent_group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The starting record to retrieve. If you omit this parameter, Tenable.io uses the default value of 0.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The number of records to retrieve. If you omit this parameter, Tenable.io uses a default of 50 records. The minimum supported limit is 1, and the maximum supported limit is 5,000.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The sort order of the returned records. Sort can only be applied to the sortable\\_fields specified by the filter capabilities. There may be no more than max\\_sort\\_fields number of columns used in the sort, as specified by the filter capabilities. Sort is applied, in order, in the following format: `:\\[asc|desc\\],:\\[asc|desc\\]`. For example, `sort=field1:asc,field2:desc` would first sort by field1, ascending, then sort by field2, descending.","required":false,"name":"sort","in":"query","schema":{"type":"string"}},{"description":"Apply a filter in the format `::`. For example, `field1:match:sometext` would match any records where the value of field1 contains `sometext`. You can use multiple query filters.","required":false,"name":"f","in":"query","schema":{"type":"string"}},{"description":"Filter type. If the filter type is `and`, the record is only returned if all filters match. If the filter type is `or`, the record is returned if any of the filters match.","required":false,"name":"ft","in":"query","schema":{"type":"string"}},{"description":"Wildcard filter text. Wildcard search is a mechanism where multiple fields of a record are filtered against one specific filter string. If any one of the wildcard\\_fields' values matches against the filter string, then the record matches the wildcard filter. For a record to be returned, it must pass the wildcard filter (if there is one) AND the set of standard filters. For example, if `w=wild&f=field1:match:one&f=field2:match:two&ft=or`, the record would match if the value of any supported wildcard\\_fields contained `wild`, AND either field1's value contained `one` or field2's value contained `two`.","required":false,"name":"w","in":"query","schema":{"type":"string"}},{"description":"A comma-delimited subset of wildcard\\_fields to search when applying the wildcard filter. For example, `field1,field2`. If `w` is provided, but `wf` is not, then all wildcard\\_fields' values are searched against the wildcard filter text.","required":false,"name":"wf","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of agents for an agent group.","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the agent."},"uuid":{"type":"string","description":"The UUID of the agent. Note: This value corresponds to the ID of the asset where the agent is installed. You can use this attribute to match agent data to asset data."},"name":{"type":"string","description":"The name of the agent."},"platform":{"type":"string","description":"The platform of the agent."},"distro":{"type":"string","description":"The agent software distribution."},"ip":{"type":"string","description":"The IP address of the agent."},"last_scanned":{"type":"integer","description":"The Unix timestamp when the agent last scanned the asset."},"plugin_feed_id":{"type":"string","description":"The currently loaded plugin set of the agent (null if the agent has no plugin set loaded)."},"core_build":{"type":"string","description":"Build number for the agent."},"core_version":{"type":"string","description":"Build version for the agent."},"linked_on":{"type":"integer","description":"The Unix timestamp when the link from Tenable.io to the agent was established."},"last_connect":{"type":"integer","description":"The Unix timestamp when the agent last communicated with Tenable.io."},"status":{"type":"string","description":"\"on\", \"off\", or \"init\". \"on\" means that the agent has connected recently, and is therefore likely ready to scan. \"off\" means that the agent has not been seen recently and should be considered offline. \"init\" means that the agent is online, but it is still processing plugin updates and is not ready to scan.","enum":["on","off","init"]},"groups":{"description":"Array of groups to which the agent belongs. Groups are returned in the form {\"name\": \"group name\", \"id\": \"group id\"}.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the agent group to which the agent belongs."},"id":{"type":"integer","description":"The unique ID of the agent group to which the agent belongs."}}}}}}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of records which match any applied filters. This number may be approximate."},"offset":{"type":"integer","description":"The index of the first record retrieved."},"limit":{"type":"integer","description":"The number of records returned with this response."},"sort":{"description":"The sorting parameters applied to response, in order of application.","type":"array","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"agents":[{"id":20,"uuid":"07e496f5-d2dc-4232-9733-12e5f7d05ae3","name":"Codys-MacBook-Pro.local","platform":"DARWIN","distro":"macosx","ip":"10.31.100.110","last_scanned":1545272687,"plugin_feed_id":"201812281741","core_build":"1","core_version":"7.2.1","linked_on":1452106253,"last_connect":1546264939,"status":"off","groups":[{"name":"Agent Group A","id":8},{"name":"Agent Group B","id":31},{"name":"Agent Group C","id":3315}],"supports_remote_logs":false},{"id":65,"uuid":"22f00428-3095-d55f-e620-aa7392c862757b06b5e32a5e47a1","name":"DC02","platform":"WINDOWS","distro":"win-x86-64","ip":"10.31.114.10","last_scanned":1478743235,"plugin_feed_id":"0","linked_on":1453821446,"status":"off","groups":[{"name":"Agent Group A","id":8},{"name":"Agent Group B","id":31},{"name":"Agent Group C","id":3316}],"supports_remote_logs":false},{"id":643,"uuid":"84b8e813-fad6-d1a5-e9f6-ac641d3b0b012596d192e3cf57f8","name":"DESKTOP-PSNDJQ6","platform":"WINDOWS","distro":"win-x86-64","ip":"172.16.0.3","last_scanned":1477011651,"plugin_feed_id":"0","linked_on":1468619962,"status":"off","groups":[{"name":"Agent Group A","id":8},{"name":"Agent Group C","id":3316}],"supports_remote_logs":false}],"pagination":{"total":3,"limit":50,"offset":0,"sort":[{"name":"name","order":"asc"}]}}}}}}},"400":{"description":"Returned if you specify invalid query parameters, for example:\n- invalid filter field name\n- invalid filter operator\n- invalid filter value\n- invalid wildcard filter field name\n- invalid filter type\n- invalid sort parameter","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"The description of the cause of the Tenable.io error."}}},"examples":{"response":{"value":{"error":"Bad value for date filter"}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agents/{agent_id}":{"get":{"summary":"Get agent details","description":"Returns the specified agent details for the specified scanner.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agents-get","tags":["Agents"],"parameters":[{"description":"The ID of the scanner to query for agents.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent to query.","required":true,"name":"agent_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the agent details.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the agent."},"uuid":{"type":"string","description":"The UUID of the agent. Note: This value corresponds to the ID of the asset where the agent is installed. You can use this attribute to match agent data to asset data."},"name":{"type":"string","description":"The name of the agent."},"platform":{"type":"string","description":"The platform of the agent."},"distro":{"type":"string","description":"The agent software distribution."},"ip":{"type":"string","description":"The IP address of the agent."},"last_scanned":{"type":"integer","description":"The Unix timestamp when the agent last scanned the asset."},"plugin_feed_id":{"type":"string","description":"The currently loaded plugin set of the agent (null if the agent has no plugin set loaded)."},"core_build":{"type":"string","description":"Build number for the agent."},"core_version":{"type":"string","description":"Build version for the agent."},"linked_on":{"type":"integer","description":"The Unix timestamp when the link from Tenable.io to the agent was established."},"last_connect":{"type":"integer","description":"The Unix timestamp when the agent last communicated with Tenable.io."},"status":{"type":"string","description":"\"on\", \"off\", or \"init\". \"on\" means that the agent has connected recently, and is therefore likely ready to scan. \"off\" means that the agent has not been seen recently and should be considered offline. \"init\" means that the agent is online, but it is still processing plugin updates and is not ready to scan.","enum":["on","off","init"]},"groups":{"description":"Array of groups to which the agent belongs. Groups are returned in the form {\"name\": \"group name\", \"id\": \"group id\"}.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the agent group to which the agent belongs."},"id":{"type":"integer","description":"The unique ID of the agent group to which the agent belongs."}}}}}},"examples":{"response":{"value":{"id":643,"uuid":"84b8e813-fad6-d1a5-e9f6-ac641d3b0b012596d192e3cf57f8","name":"DESKTOP-PSNDJQ6","platform":"WINDOWS","distro":"win-x86-64","ip":"172.204.81.57","last_scanned":1477011651,"plugin_feed_id":"0","linked_on":1468619962,"status":"off","groups":[{"name":"CodyAgents","id":8},{"name":"Agent Group A","id":3316}]}}}}}},"403":{"description":"Returned if you do not have permission to view the agent."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete agent","description":"Deletes an agent.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"agents-delete","tags":["Agents"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the agent to delete.","required":true,"name":"agent_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io succesfully deleted the agent.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified agent."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the agent.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agent-groups/{group_id}/agents/_bulk/add":{"post":{"summary":"Add agents to group","description":"Creates a bulk operation task to add agents to a group.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"bulk-add-agents","tags":["Agent Bulk Operations"],"parameters":[{"description":"The ID of the scanner for the agent group.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID or UUID of the agent group.","required":true,"name":"group_id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"items":{"type":"string"},"description":"Array of agent IDs or UUIDs to add to the group","type":"array"}},"required":["items"]},"example":{"items":[20,10,65]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates the bulk operation task.","content":{"application/json":{"schema":{"type":"object","properties":{"task_id":{"type":"string","description":"The UUID of the task."},"container_uuid":{"type":"string","description":"The UUID of the container where the task is operating."},"status":{"type":"string","description":"State of the task. \"NEW\" means that the task was created, but has not yet started running. \"RUNNING\" means that the task is in progress. \"COMPLETED\" means that the task is done. \"FAILED\" means that there was an error completing the task. \"STALE\" means that the task has not been updated in a long time.","enum":["NEW","RUNNING","COMPLETED","FAILED","STALE"]},"message":{"type":"string","description":"An informative, human-readable message about the state of the task."},"start_time":{"type":"integer","description":"Start time of the task in unix time milliseconds."},"end_time":{"type":"integer","description":"End time of the task in unix time milliseconds, if the task is finished."},"last_update_time":{"type":"integer","description":"Last time progress was made on executing the task in unix time milliseconds."},"total_work_units":{"type":"integer","description":"Total amount of work which the task will attempt to complete."},"total_work_units_completed":{"type":"integer","description":"Total amount of work that the task has completed."},"completion_percentage":{"type":"integer","description":"total_work_units_completed divided by total_work_units."}}},"examples":{"response":{"value":{"task_id":"14a27fad-8a0e-4769-ac87-4da6370a871a","container_uuid":"5043dfa2-7864-4785-aff7-80026f36efcb","status":"RUNNING","message":"Starting...","start_time":1544032287496,"last_update_time":1544032287501,"total_work_units":3,"total_work_units_completed":0,"completion_percentage":0}}}}}},"400":{"description":"Returned if your request message contains an invalid parameter."},"403":{"description":"Returned if you do not have permission to create a bulk operation task."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to create the bulk operation task.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agent-groups/{group_id}/agents/_bulk/{task_uuid}":{"get":{"summary":"Check agent group operation status","description":"Check the status of a bulk operation on an agent group.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"bulk-task-agent-group-status","tags":["Agent Bulk Operations"],"parameters":[{"description":"The ID of the scanner for the agent group.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID or UUID of the agent group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the task","required":true,"name":"task_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the bulk operation task status information.","content":{"application/json":{"schema":{"type":"object","properties":{"task_id":{"type":"string","description":"The UUID of the task."},"container_uuid":{"type":"string","description":"The UUID of the container where the task is operating."},"status":{"type":"string","description":"State of the task. \"NEW\" means that the task was created, but has not yet started running. \"RUNNING\" means that the task is in progress. \"COMPLETED\" means that the task is done. \"FAILED\" means that there was an error completing the task. \"STALE\" means that the task has not been updated in a long time.","enum":["NEW","RUNNING","COMPLETED","FAILED","STALE"]},"message":{"type":"string","description":"An informative, human-readable message about the state of the task."},"start_time":{"type":"integer","description":"Start time of the task in unix time milliseconds."},"end_time":{"type":"integer","description":"End time of the task in unix time milliseconds, if the task is finished."},"last_update_time":{"type":"integer","description":"Last time progress was made on executing the task in unix time milliseconds."},"total_work_units":{"type":"integer","description":"Total amount of work which the task will attempt to complete."},"total_work_units_completed":{"type":"integer","description":"Total amount of work that the task has completed."},"completion_percentage":{"type":"integer","description":"total_work_units_completed divided by total_work_units."}}},"examples":{"response":{"value":{"task_id":"14a27fad-8a0e-4769-ac87-4da6370a871a","container_uuid":"5043dfa2-7864-4785-aff7-80026f36efcb","status":"COMPLETED","message":"3 items completed, 0 failed.","start_time":1544032287496,"last_update_time":1544032287521,"end_time":1544032287521,"total_work_units":3,"total_work_units_completed":3,"completion_percentage":100}}}}}},"403":{"description":"Returned if you do not have permission to view the bulk operation task."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agents/_bulk/{task_uuid}":{"get":{"summary":"Check agent operation status","description":"Check the status of a bulk operation on agents.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"bulk-task-agent-status","tags":["Agent Bulk Operations"],"parameters":[{"description":"The ID of the scanner for the agent group.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the task","required":true,"name":"task_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the bulk operation task status information.","content":{"application/json":{"schema":{"type":"object","properties":{"task_id":{"type":"string","description":"The UUID of the task."},"container_uuid":{"type":"string","description":"The UUID of the container where the task is operating."},"status":{"type":"string","description":"State of the task. \"NEW\" means that the task was created, but has not yet started running. \"RUNNING\" means that the task is in progress. \"COMPLETED\" means that the task is done. \"FAILED\" means that there was an error completing the task. \"STALE\" means that the task has not been updated in a long time.","enum":["NEW","RUNNING","COMPLETED","FAILED","STALE"]},"message":{"type":"string","description":"An informative, human-readable message about the state of the task."},"start_time":{"type":"integer","description":"Start time of the task in unix time milliseconds."},"end_time":{"type":"integer","description":"End time of the task in unix time milliseconds, if the task is finished."},"last_update_time":{"type":"integer","description":"Last time progress was made on executing the task in unix time milliseconds."},"total_work_units":{"type":"integer","description":"Total amount of work which the task will attempt to complete."},"total_work_units_completed":{"type":"integer","description":"Total amount of work that the task has completed."},"completion_percentage":{"type":"integer","description":"total_work_units_completed divided by total_work_units."}}},"examples":{"response":{"value":{"task_id":"14a27fad-8a0e-4769-ac87-4da6370a871a","container_uuid":"5043dfa2-7864-4785-aff7-80026f36efcb","status":"COMPLETED","message":"3 items completed, 0 failed.","start_time":1544032287496,"last_update_time":1544032287521,"end_time":1544032287521,"total_work_units":3,"total_work_units_completed":3,"completion_percentage":100}}}}}},"403":{"description":"Returned if you do not have permission to view the bulk operation task."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agent-groups/{group_id}/agents/_bulk/remove":{"post":{"summary":"Remove agents from group","description":"Creates a bulk operation task to remove agents from a group.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"bulk-remove-agents","tags":["Agent Bulk Operations"],"parameters":[{"description":"The ID of the scanner for the agent group.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID or UUID of the agent group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"items":{"type":"string"},"description":"Array of agent IDs or UUIDs to remove from the group","type":"array"}},"required":["items"]},"example":{"items":[20,10,65]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates the bulk operation task.","content":{"application/json":{"schema":{"type":"object","properties":{"task_id":{"type":"string","description":"The UUID of the task."},"container_uuid":{"type":"string","description":"The UUID of the container where the task is operating."},"status":{"type":"string","description":"State of the task. \"NEW\" means that the task was created, but has not yet started running. \"RUNNING\" means that the task is in progress. \"COMPLETED\" means that the task is done. \"FAILED\" means that there was an error completing the task. \"STALE\" means that the task has not been updated in a long time.","enum":["NEW","RUNNING","COMPLETED","FAILED","STALE"]},"message":{"type":"string","description":"An informative, human-readable message about the state of the task."},"start_time":{"type":"integer","description":"Start time of the task in unix time milliseconds."},"end_time":{"type":"integer","description":"End time of the task in unix time milliseconds, if the task is finished."},"last_update_time":{"type":"integer","description":"Last time progress was made on executing the task in unix time milliseconds."},"total_work_units":{"type":"integer","description":"Total amount of work which the task will attempt to complete."},"total_work_units_completed":{"type":"integer","description":"Total amount of work that the task has completed."},"completion_percentage":{"type":"integer","description":"total_work_units_completed divided by total_work_units."}}},"examples":{"response":{"value":{"task_id":"de5a64f6-b391-4aef-b0bc-1249fdb9750d","container_uuid":"5043dfa2-7864-4785-aff7-80026f36efcb","status":"RUNNING","start_time":1544032937522,"last_update_time":1544032937522}}}}}},"400":{"description":"Returned if your request message contains an invalid parameter."},"403":{"description":"Returned if you do not have permission to create a bulk operation task."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to create the bulk operation task.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/agents/_bulk/unlink":{"post":{"summary":"Delete agents","description":"Creates a bulk operation task to unlink (delete) agents.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"bulk-unlink-agents","tags":["Agent Bulk Operations"],"parameters":[{"description":"The ID of the scanner to remove the agents from.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"items":{"type":"string"},"description":"Array of agent IDs or UUIDs to unlink (delete).","type":"array"}},"required":["items"]},"example":{"items":[20,10,65]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates the bulk operation task.","content":{"application/json":{"schema":{"type":"object","properties":{"task_id":{"type":"string","description":"The UUID of the task."},"container_uuid":{"type":"string","description":"The UUID of the container where the task is operating."},"status":{"type":"string","description":"State of the task. \"NEW\" means that the task was created, but has not yet started running. \"RUNNING\" means that the task is in progress. \"COMPLETED\" means that the task is done. \"FAILED\" means that there was an error completing the task. \"STALE\" means that the task has not been updated in a long time.","enum":["NEW","RUNNING","COMPLETED","FAILED","STALE"]},"message":{"type":"string","description":"An informative, human-readable message about the state of the task."},"start_time":{"type":"integer","description":"Start time of the task in unix time milliseconds."},"end_time":{"type":"integer","description":"End time of the task in unix time milliseconds, if the task is finished."},"last_update_time":{"type":"integer","description":"Last time progress was made on executing the task in unix time milliseconds."},"total_work_units":{"type":"integer","description":"Total amount of work which the task will attempt to complete."},"total_work_units_completed":{"type":"integer","description":"Total amount of work that the task has completed."},"completion_percentage":{"type":"integer","description":"total_work_units_completed divided by total_work_units."}}},"examples":{"response":{"value":{"task_id":"aed90273-b535-438e-9d85-30899636bbdd","container_uuid":"5043dfa2-7864-4785-aff7-80026f36efcb","status":"NEW"}}}}}},"400":{"description":"Returned if your request message contains an invalid parameter."},"403":{"description":"Returned if you do not have permission create a bulk operation task."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to create the bulk operation task.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/assets":{"get":{"summary":"List assets","description":"Lists up to 5,000 assets.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"assets-list-assets","tags":["Assets"],"responses":{"200":{"description":"Returns a list of assets.","content":{"application/json":{"schema":{"type":"object","description":"The list of assets with details, and the total assets count.","properties":{"assets":{"type":"array","description":"A list of assets with details.","items":{"type":"object","properties":{"id":{"type":"string","description":"The UUID of the asset."},"has_agent":{"type":"boolean","description":"A value specifying whether a Nessus agent scan detected the asset (`true`)."},"last_seen":{"type":"string","description":"The ISO timestamp of the scan that most recently detected the asset."},"last_scan_target":{"type":"string","description":"The IPv4 address, IPv6 address, or FQDN that the scanner last used to evaluate the asset."},"sources":{"type":"array","description":"The sources of the scans that identified the asset.","items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the entity that reported the asset details. Sources can include sensors, connectors, and API imports. Source names can be customized by your organization (for example, you specify a name when you import asset records). If your organization does not customize source names, system-generated names include:\n - AWS—You obtained the asset data from an Amazon Web Services connector.\n - NESSUS_AGENT—You obtained the asset data obtained from a Nessus agent scan.\n - PVS—You obtained the asset data from a Nessus Network Monitor (NNM) scan.\n - NESSUS_SCAN—You obtained the asset data from a Nessus scan.\n - WAS—You obtained the asset data from a Web Application Scanning scan."},"first_seen":{"type":"string","description":"The ISO timestamp when the source first reported the asset."},"last_seen":{"type":"string","description":"The ISO timestamp when the source last reported the asset."}}}}},"acr_score":{"type":"integer","description":"The Asset Criticality Rating (ACR) for the asset. Tenable assigns an ACR to each asset on your network to represent the asset's relative risk as an integer from 1 to 10. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*."},"acr_drivers":{"type":"array","description":"The key drivers that Tenable uses to calculate an asset's Tenable-provided ACR. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*.","items":{"type":"object","description":"Information about an asset characteristic that factored into the ACR score calculation.","properties":{"driver_name":{"type":"string","description":"The type of characteristic."},"driver_value":{"type":"array","description":"The characteristic value.","items":{"type":"string"}}}}},"exposure_score":{"type":"integer","description":"The Asset Exposure Score (AES) for the asset. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*."},"scan_frequency":{"type":"array","description":"Information about how often scans ran against the asset during specified intervals. This attribute is only present in assets if Lumin is added to your Tenable.io instance.","items":{"type":"object","description":"Information about how often scans ran against asset during a specified interval.","properties":{"interval":{"type":"integer","description":"The number of days over which Tenable searches for scans involving the asset."},"frequency":{"type":"integer","description":"The number of times that a scan ran against the asset during the specified interval."},"licensed":{"type":"boolean","description":"Indicates whether the asset was licensed at the time of the identified scans."}}}},"ipv4":{"description":"A list of IPv4 addresses for the asset.","type":"array","items":{"type":"string"}},"ipv6":{"description":"A list of IPv6 addresses for the asset.","type":"array","items":{"type":"string"}},"fqdn":{"description":"A list of fully-qualified domain names (FQDNs) for the asset.","type":"array","items":{"type":"string"}},"netbios_name":{"type":"array","description":"The NetBIOS name for the asset.","items":{"type":"string"}},"operating_system":{"type":"array","description":"The operating system installed on the asset.","items":{"type":"string"}},"agent_name":{"type":"array","description":"The names of any Nessus agents that scanned and identified the asset.","items":{"type":"string"}},"aws_ec2_name":{"type":"array","description":"The name of the virtual machine instance in AWS EC2.","items":{"type":"string"}},"mac_address":{"type":"array","description":"A list of MAC addresses for the asset.","items":{"type":"string"}}}}},"total":{"type":"integer","description":"The total number of assets in your Tenable.io instance."}}},"examples":{"response":{"value":{"assets":[{"id":"f56168ed-b719-4273-b58c-a340a09ffbce","has_agent":false,"last_seen":"2018-11-28T15:00:57.000Z","last_scan_target":"172.204.81.57","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:57.000Z","last_seen":"2018-11-28T15:00:57.000Z"}],"acr_score":8,"acr_drivers":[{"driver_name":"device_type","driver_value":["general_purpose"]},{"driver_name":"device_capability","driver_value":["pci"]},{"driver_name":"internet_exposure","driver_value":["internal"]}],"exposure_score":753,"scan_frequency":[{"interval":90,"frequency":3,"licensed":false},{"interval":30,"frequency":1,"licensed":false},{"interval":60,"frequency":1,"licensed":false}],"ipv4":["172.204.81.57"],"ipv6":[],"fqdn":["kubernetes.ad.demo.io"],"netbios_name":["kubernetes.ad.demo.io"],"operating_system":["Linux Kernel 3.10.0-862.14.4.el7.x86_64 on CentOS Linux release 7.5.1804 (Core)"],"agent_name":[],"aws_ec2_name":[],"mac_address":[]},{"id":"ed1c8fb3-68be-4c98-b5ef-88dd30f18ee9","has_agent":false,"last_seen":"2018-11-28T15:00:57.000Z","last_scan_target":"172.204.81.58","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T14:59:23.000Z","last_seen":"2018-11-28T15:00:57.000Z"}],"ipv4":["172.204.81.58"],"ipv6":[],"fqdn":["scanner.ad.demo.io"],"netbios_name":["scanner"],"operating_system":["Linux Kernel 4.4.0-104-generic on Ubuntu 16.04"],"agent_name":[],"aws_ec2_name":[],"mac_address":[]},{"id":"ee094cf6-e352-4f34-a65d-a3503b0ad199","has_agent":false,"last_seen":"2018-11-28T15:00:57.000Z","last_scan_target":"172.204.81.59","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T14:59:23.000Z","last_seen":"2018-11-28T15:00:57.000Z"}],"ipv4":["172.204.81.59"],"ipv6":[],"fqdn":["shane.ad.demo.io"],"netbios_name":["SHANE"],"operating_system":["Microsoft Windows 10 Pro"],"agent_name":[],"aws_ec2_name":[],"mac_address":[]},{"id":"33e32354-7f48-488c-88a5-49a63550b62a","has_agent":false,"last_seen":"2018-11-28T15:00:57.000Z","last_scan_target":"172.204.81.60","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T14:59:23.000Z","last_seen":"2018-11-28T15:00:57.000Z"}],"ipv4":["172.204.81.60"],"ipv6":[],"fqdn":["archie.ad.demo.io"],"netbios_name":["ARCHIE"],"operating_system":["Microsoft Windows 10 Pro"],"agent_name":[],"aws_ec2_name":[],"mac_address":[]}],"total":4}}}}}},"403":{"description":"Returned if you do not have permission to list assets."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/assets/{asset_uuid}":{"get":{"summary":"Get asset details","description":"Returns details of the specified asset.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"assets-asset-info","tags":["Assets"],"parameters":[{"description":"The UUID of the asset.","required":true,"name":"asset_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns details of the specified asset.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The UUID of the asset."},"has_agent":{"type":"boolean","description":"A value specifying whether a Nessus agent scan detected the asset."},"created_at":{"type":"string","description":"The time and date when Tenable.io created the asset record."},"updated_at":{"type":"string","description":"The time and date when the asset record was last updated."},"first_seen":{"type":"string","description":"The time and date when a scan first identified the asset."},"last_seen":{"type":"string","description":"The time and date of the scan that most recently identified the asset."},"last_scan_target":{"type":"string","description":"The IPv4 address, IPv6 address, or FQDN that the scanner last used to evaluate the asset."},"last_authenticated_scan_date":{"type":"string","description":"The time and date of the last credentialed scan run on the asset."},"last_licensed_scan_date":{"type":"string","description":"The time and date of the last scan that identified the asset as licensed. Tenable.io categorizes an asset as licensed if a scan of that asset has returned results from a non-discovery plugin within the last 90 days."},"sources":{"type":"array","description":"The sources of the scans that identified the asset.","items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the entity that reported the asset details. Sources can include sensors, connectors, and API imports. Source names can be customized by your organization (for example, you specify a name when you import asset records). If your organization does not customize source names, system-generated names include:\n - AWS—You obtained the asset data from an Amazon Web Services connector.\n - NESSUS_AGENT—You obtained the asset data obtained from a Nessus agent scan.\n - PVS—You obtained the asset data from a Nessus Network Monitor (NNM) scan.\n - NESSUS_SCAN—You obtained the asset data from a Nessus scan.\n - WAS—You obtained the asset data from a Web Application Scanning scan."},"first_seen":{"type":"string","description":"The ISO timestamp when the source first reported the asset."},"last_seen":{"type":"string","description":"The ISO timestamp when the source last reported the asset."}}}}},"tags":{"type":"array","description":"Category tags assigned to the asset in Tenable.io.","items":{"type":"object","properties":{"tag_uuid":{"type":"string","description":"The UUID of the tag."},"tag_key":{"type":"string","description":"The tag category (the first half of the category:value pair)."},"tag_value":{"type":"string","description":"The tag value (the second half of the category:value pair)."},"added_by":{"type":"string","description":"The UUID of the user who assigned the tag to the asset."},"added_at":{"type":"string","description":"The ISO timestamp when the tag was assigned to the asset."}}}},"acr_score":{"type":"integer","description":"The Asset Criticality Rating (ACR) for the asset. Tenable assigns an ACR to each asset on your network to represent the asset's relative risk as an integer from 1 to 10. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*."},"acr_drivers":{"type":"array","description":"The key drivers that Tenable uses to calculate an asset's Tenable-provided ACR. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*.","items":{"type":"object","description":"Information about an asset characteristic that factored into the ACR score calculation.","properties":{"driver_name":{"type":"string","description":"The type of characteristic."},"driver_value":{"type":"array","description":"The characteristic value.","items":{"type":"string"}}}}},"exposure_score":{"type":"integer","description":"The Asset Exposure Score (AES) for the asset. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*."},"scan_frequency":{"type":"array","description":"Information about how often scans ran against the asset during specified intervals.","items":{"type":"object","description":"Information about how often scans ran against asset during a specified interval.","properties":{"interval":{"type":"integer","description":"The number of days over which Tenable searches for scans involving the asset."},"frequency":{"type":"integer","description":"The number of times that a scan ran against the asset during the specified interval."},"licensed":{"type":"boolean","description":"Indicates whether the asset was licensed at the time of the identified scans."}}}},"network_id":{"type":"array","description":"The ID of the network object to which the asset belongs. For more information, see [Manage Networks](/docs/manage-networks-tio).","items":{"type":"string"}},"ipv4":{"type":"array","description":"The IPv4 addresses that scans have associated with the asset record.","items":{"type":"string"}},"ipv6":{"type":"array","description":"The IPv6 addresses that scans have associated with the asset record.","items":{"type":"string"}},"fqdn":{"type":"array","description":"The fully-qualified domain names that scans have associated with the asset record.","items":{"type":"string"}},"mac_address":{"type":"array","description":"The MAC addresses that scans have associated with the asset record.","items":{"type":"string"}},"netbios_name":{"type":"array","description":"The NetBIOS names that scans have associated with the asset record.","items":{"type":"string"}},"operating_system":{"type":"array","description":"The operating systems that scans have associated with the asset record.","items":{"type":"string"}},"system_type":{"type":"array","description":"The system types as reported by Plugin ID 54615. Possible values include `router`, `general-purpose`, `scan-host`, and `embedded`.","items":{"type":"string"}},"hostname":{"type":"array","description":"The hostnames that scans have associated with the asset record.","items":{"type":"string"}},"agent_name":{"type":"array","description":"The names of any Nessus agents that scanned and identified the asset.","items":{"type":"string"}},"bios_uuid":{"type":"array","description":"The BIOS UUID that scans have associated with the asset.","items":{"type":"string"}},"aws_ec2_instance_id":{"type":"array","description":"The unique identifier of the Linux instance in Amazon EC2. For more information, see the Amazon Elastic Compute Cloud Documentation.","items":{"type":"string"}},"aws_ec2_instance_ami_id":{"type":"array","description":"The unique identifier of the Linux AMI image in Amazon Elastic Compute Cloud (Amazon EC2). For more information, see the Amazon Elastic Compute Cloud Documentation.","items":{"type":"string"}},"aws_owner_id":{"type":"array","description":"The canonical user identifier for the AWS account associated with the virtual machine instance. For example, `79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be`. For more information, see AWS Account Identifiers in the AWS documentation.","items":{"type":"string"}},"aws_availability_zone":{"type":"array","description":"The availability zone where Amazon Web Services hosts the virtual machine instance, for example, `us-east-1a`. Availability zones are subdivisions of AWS regions. For more information, see Regions and Availability Zones in the AWS documentation.","items":{"type":"string"}},"aws_region":{"type":"array","description":"The region where AWS hosts the virtual machine instance, for example, `us-east-1`. For more information, see Regions and Availability Zones in the AWS documentation.","items":{"type":"string"}},"aws_vpc_id":{"type":"array","description":"The unique identifier for the public cloud that hosts the AWS virtual machine instance. For more information, see the Amazon Virtual Private Cloud User Guide.","items":{"type":"string"}},"aws_ec2_instance_group_name":{"type":"array","description":"The virtual machine instance's group in AWS.","items":{"type":"string"}},"aws_ec2_instance_state_name":{"type":"array","description":"The state of the virtual machine instance in AWS at the time of the scan.","items":{"type":"string"}},"aws_ec2_instance_type":{"type":"array","description":"The type of instance in AWS EC2.","items":{"type":"string"}},"aws_subnet_id":{"type":"array","description":"The unique identifier of the AWS subnet where the virtual machine instance was running at the time of the scan.","items":{"type":"string"}},"aws_ec2_product_code":{"type":"array","description":"The product code associated with the AMI used to launch the virtual machine instance in AWS EC2.","items":{"type":"string"}},"aws_ec2_name":{"type":"array","description":"The name of the virtual machine instance in AWS EC2.","items":{"type":"string"}},"azure_vm_id":{"type":"array","description":"The unique identifier of the Microsoft Azure virtual machine instance. For more information, see \"Accessing and Using Azure VM Unique ID\" in the Microsoft Azure documentation.","items":{"type":"string"}},"azure_resource_id":{"type":"array","description":"The unique identifier of the resource in the Azure Resource Manager. For more information, see the Azure Resource Manager Documentation.","items":{"type":"string"}},"gcp_project_id":{"type":"array","description":"The customized name of the project to which the virtual machine instance belongs in Google Cloud Platform (GCP). For more information, see \"Creating and Managing Projects\" in the GCP documentation.","items":{"type":"string"}},"gcp_zone":{"type":"array","description":"The zone where the virtual machine instance runs in GCP. For more information, see \"Regions and Zones\" in the GCP documentation.","items":{"type":"string"}},"gcp_instance_id":{"type":"array","description":"The unique identifier of the virtual machine instance in GCP.","items":{"type":"string"}},"ssh_fingerprint":{"type":"array","description":"The SSH key fingerprints that scans have associated with the asset record.","items":{"type":"string"}},"mcafee_epo_guid":{"type":"array","description":"The unique identifier of the asset in McAfee ePolicy Orchestrator (ePO). For more information, see the McAfee documentation.","items":{"type":"string"}},"mcafee_epo_agent_guid":{"type":"array","description":"The unique identifier of the McAfee ePO agent that identified the asset. For more information, see the McAfee documentation.","items":{"type":"string"}},"qualys_asset_id":{"type":"array","description":"The Asset ID of the asset in Qualys. For more information, see the Qualys documentation.","items":{"type":"string"}},"qualys_host_id":{"type":"array","description":"The Host ID of the asset in Qualys. For more information, see the Qualys documentation.","items":{"type":"string"}},"servicenow_sysid":{"type":"array","description":"The unique record identifier of the asset in ServiceNow. For more information, see the ServiceNow documentation.","items":{"type":"string"}},"installed_software":{"type":"array","description":"A list of Common Platform Enumeration (CPE) values that represent software applications a scan identified as present on an asset. This attribute supports the CPE 2.2 format. For more information, see the \"Component Syntax\" section of the [CPE Specification, Version 2.2](https://cpe.mitre.org/files/cpe-specification_2.2.pdf). For assets identified in Tenable scans, this attribute contains data only if a scan using [Nessus Plugin ID 45590](https://www.tenable.com/plugins/nessus/45590) has evaluated the asset.\n\n**Note:** If no scan detects an application within 30 days of the scan that originally detected the application, Tenable.io considers the detection of that application expired. As a result, the next time a scan evaluates the asset, Tenable.io removes the expired application from the installed_software attribute. This activity is logged as a `remove` type of `attribute_change` update in the asset activity log.","items":{"type":"string"}}}},"examples":{"response":{"value":{"id":"e60cf974-5b18-4ad1-aa1e-c897e46bd683","has_agent":false,"created_at":"2018-11-28T15:00:42.659Z","updated_at":"2018-11-28T17:28:46.984Z","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z","last_scan_target":"172.204.81.57","last_authenticated_scan_date":null,"last_licensed_scan_date":"2018-11-28T17:28:28.000Z","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z"}],"tags":[],"acr_score":8,"acr_drivers":[{"driver_name":"device_type","driver_value":["general_purpose"]},{"driver_name":"device_capability","driver_value":["pci"]},{"driver_name":"internet_exposure","driver_value":["internal"]}],"exposure_score":753,"scan_frequency":[{"interval":90,"frequency":3,"licensed":false},{"interval":30,"frequency":1,"licensed":false},{"interval":60,"frequency":1,"licensed":false}],"network_id":["00000000-0000-0000-0000-000000000000"],"ipv4":["172.204.81.57"],"ipv6":[],"fqdn":[],"mac_address":["00:50:56:a6:4c:0a"],"netbios_name":[],"operating_system":["Linux Kernel 3.10, Linux Kernel 3.5, Linux Kernel 3.8, Linux Kernel 3.9"],"system_type":["general-purpose"],"tenable_uuid":[],"hostname":[],"agent_name":[],"bios_uuid":[],"aws_ec2_instance_id":[],"aws_ec2_instance_ami_id":[],"aws_owner_id":[],"aws_availability_zone":[],"aws_region":[],"aws_vpc_id":[],"aws_ec2_instance_group_name":[],"aws_ec2_instance_state_name":[],"aws_ec2_instance_type":[],"aws_subnet_id":[],"aws_ec2_product_code":[],"aws_ec2_name":[],"azure_vm_id":[],"azure_resource_id":[],"gcp_project_id":[],"gcp_zone":[],"gcp_instance_id":[],"ssh_fingerprint":[],"mcafee_epo_guid":[],"mcafee_epo_agent_guid":[],"qualys_asset_id":[],"qualys_host_id":[],"servicenow_sysid":[],"installed_software":["cpe:/a:apple:itunes:12.8","cpe:/a:apple:quicktime:7.7.3","cpe:/a:openbsd:openssh:6.9","cpe:/a:google:chrome"],"bigfix_asset_id":[]}}}}}},"403":{"description":"Returned if you do not have permission to view information about an asset."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/api/v2/assets/bulk-jobs/acr":{"post":{"summary":"Update ACR","description":"Overwrites the Tenable-provided Asset Criticality Rating (ACR) for the specified assets. Tenable assigns an ACR to each asset on your network to represent the asset's relative risk as an integer from 1 to 10. For more information about ACR, see [Lumin metrics](http://docs.tenable.com/cloud/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"assets-bulk-update-acr","tags":["Assets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["acr_score","asset"],"description":"Parameters to update the ACR for an asset. For a request body example, see \"Update ACR for assets\" in [Bulk Asset Operations](/docs/bulk-asset-operations).","properties":{"acr_score":{"type":"integer","description":"The ACR score you want to assign to the asset. The ACR must be an integer from 1 to 10. "},"reason":{"type":"array","description":"The reasons you are updating the ACR for the assets. Supported values include:\n\n - Business Critical\n - In Scope For Compliance\n - Existing Mitigation Control\n - Dev only \n - Key drivers does not match \n - Other\n\nThis parameter corresponds to the **Overwrite Reasoning** parameter when editing an ACR in the Tenable.io Lumin user interface. For more information, see [Edit an ACR](https://docs.tenable.com/cloud/Content/Analysis/LuminEditACR.htm).","items":{"type":"string","enum":["Business Critical","In Scope For Compliance","Existing Mitigation Control","Dev only","Key drivers does not match","Other"]}},"note":{"type":"string","description":"Any notes you want to add to clarify the circumstances behind the update. This parameter corresponds to the **Note** parameter when editing an ACR in the Tenable.io Lumin user interface. For more information, see [Edit an ACR](https://docs.tenable.com/cloud/Content/Analysis/LuminEditACR.htm). "},"asset":{"type":"array","description":"The identifiers of the assets to update to the specified ACR. At least one asset object is required in this array.","items":{"type":"object","description":"Each object can contain a single instance of the properties described below. You can combine multiple instances of this object, each containing a different single property.","properties":{"id":{"type":"string","description":"The UUID for a specific asset."},"fqdn":{"type":"array","description":"Fully-qualified domain names (FQDNs) associated with the asset or assets.","items":{"type":"string"}},"mac_address":{"type":"array","description":"MAC addresses associated with the asset or assets.","items":{"type":"string"}},"netbios_name":{"type":"string","description":"The NetBIOS name for the asset."},"ipv4":{"type":"array","description":"IPv4 addresses associated with the asset or assets.","items":{"type":"string"}}}}}}}}}}},"responses":{"202":{"description":"Returned if Tenable.io successfully queues the update request."},"404":{"description":"Returned if your request is improperly formatted."}},"security":[{"cloud":[]}]}},"/api/v2/assets/bulk-jobs/move-to-network":{"post":{"summary":"Move assets","description":"Moves assets from the specified network to another network. You can use this endpoint to move assets from the default network to a user-defined network, from a user-defined network to the default network, and from one user-defined network to another user-defined network. This request creates an asynchronous job in Tenable.io.\n\nFor information about the assets move workflow and payload examples, see [Bulk Asset Operations](/docs/bulk-asset-operations).

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"assets-bulk-move","tags":["Assets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["source","destination","targets"],"description":"The parameters for moving assets from one network to another.","properties":{"source":{"type":"string","example":"00000000-0000-0000-0000-000000000000","description":"The UUID of the network currently associated with the assets. Use the [GET /networks](/reference#networks-list) endpoint with the name attribute as filter to find the UUID of the network."},"destination":{"type":"string","description":"The UUID of the network to associate with the specified assets. Use the [GET /networks](/reference#networks-list) endpoint with the name filter to find the UUID of the network. ","example":"11f04eb9-7c78-46c8-9025-fae048390f59"},"targets":{"type":"string","description":"The IPv4 addresses of the assets to move. The addresses can be represented as a comma-separated list, a range, or CIDR, for example `1.1.1.1, 2.2.2.2-2.2.2.200, 3.3.3.0/24`.","example":"172.204.81.57-172.204.81.69"}}}}}},"responses":{"202":{"description":"Returns the number of moved assets.","content":{"application/json":{"schema":{"type":"object","description":"Bulk operations results. Contains the number of assets affected by the operation (moved or deleted).","properties":{"asset_count":{"type":"integer","description":"The number of assets affected by the operation. "}}},"examples":{"response":{"value":{"data":{"asset_count":512}}}}}}},"400":{"description":"Returned if Tenable.io cannot find the specified assets."},"403":{"description":"Returned if you do not have permission to move assets."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/api/v2/assets/bulk-jobs/delete":{"post":{"summary":"Bulk delete assets","description":"Deletes the specified assets. This request creates an asynchronous delete job in Tenable.io.\n\nFor information about the assets bulk delete workflow and payload examples, see [Bulk Asset Operations](/docs/bulk-asset-operations).

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"assets-bulk-delete","tags":["Assets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"The query for selecting the assets to delete. Must include one or more filters. A filter must include an asset attribute, an operator, and a value. To get the list of supported filters, use the [GET /filters/workbenches/assets](/reference#filters-assets-filter) endpoint. Sets of multiple filters must be specified inside `and` or `or` arrays.\n\n**Note:** You can also nest conditions, for example, specify a set of `or` sub-conditions for a condition inside the `and` array.","properties":{"field":{"type":"string","description":"The name of the asset attribute to match. Asset attributes can include tags, for example, `tag.city`."},"operator":{"type":"string","description":"The operator to apply to the matched value, for example, `eq` (equals), `neq` (does not equal), or `contains`."},"value":{"type":"string","description":"The asset attribute value to match."},"and":{"type":"array","description":"To select assets that match all of multiple conditions, specify the conditions inside the `and` array.","items":{"type":"object","description":"The query for selecting the assets to delete. Includes an asset attribute, an operator, and a value. To get the list of supported filters, use the [GET /filters/workbenches/assets](/reference#filters-assets-filter) endpoint.","properties":{"field":{"type":"string","description":"The name of the asset attribute to match. Asset attributes can include tags, for example, `tag.city`."},"operator":{"type":"string","description":"The operator to apply to the matched value, for example, `eq` (equals), `neq` (does not equal), or `contains`."},"value":{"type":"string","description":"The asset attribute value to match."}}}},"or":{"type":"array","description":"To select assets that match any of multiple conditions, specify the conditions inside the `or` array.","items":{"type":"object","description":"The query for selecting the assets to delete. Includes an asset attribute, an operator, and a value. To get the list of supported filters, use the [GET /filters/workbenches/assets](/reference#filters-assets-filter) endpoint.","properties":{"field":{"type":"string","description":"The name of the asset attribute to match. Asset attributes can include tags, for example, `tag.city`."},"operator":{"type":"string","description":"The operator to apply to the matched value, for example, `eq` (equals), `neq` (does not equal), or `contains`."},"value":{"type":"string","description":"The asset attribute value to match."}}}}}}}}},"responses":{"202":{"description":"Returns the number of deleted assets.","content":{"application/json":{"schema":{"type":"object","description":"Bulk operations results. Contains the number of assets affected by the operation (moved or deleted).","properties":{"asset_count":{"type":"integer","description":"The number of assets affected by the operation. "}}},"examples":{"response":{"value":{"data":{"asset_count":512}}}}}}},"400":{"description":"Returned if you specify an invalid asset query, for example, using a malformed IPv4 address."},"403":{"description":"Returned if you do not have permission to delete assets in bulk."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"503":{"description":"Returned if Tenable.io is unavailable or not ready to process the request. Wait a moment and try your request again."}},"security":[{"cloud":[]}]}},"/import/assets":{"post":{"summary":"Import assets","description":"Imports asset data in JSON format.\n\nThe request size cannot exceed 5 MB. For example, if the average asset record you want to import is about 2 KB, you can import approximately 2,500 assets in a single request.\n\n**Note:** This endpoint does not support the network_id attribute in asset objects for import. Tenable.io automatically assigns imported assets to the default network object. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio).

Requires SCAN OPERATOR [24] user permissions and CAN CONFIGURE [64] scan permissions. See Permissions.

","operationId":"assets-import","tags":["Assets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"assets":{"type":"array","description":"An array of asset objects to import. Each asset object requires a value for at least one of the following properties: fqdn, ipv4, netbios\\_name, mac\\_address.\n\nFor an example of this request body, see [Add Asset Data to Tenable.io](/docs/add-asset-data-to-tenableio). For the complete list of importable asset attributes, see [Common Asset Attributes](/docs/common-asset-attributes#section-asset-attribute-definitions).","example":"[{\"ipv4\":\"172.204.81.57\",\"operating_system\":\"Windows 7 x64\"}]","items":{"type":"object","properties":{"mac_address":{"type":"array","description":"A list of MAC addresses for the asset.","items":{"type":"string"}},"netbios_name":{"type":"string","description":"The NetBIOS name for the asset."},"fqdn":{"type":"array","description":"A list of FQDNs for the asset.","items":{"type":"string"}},"ip_address":{"type":"array","description":"A list of IPv4 addresses for the asset. Tenable.io supports this legacy field for backwards compatibility, but for new requests, this field should be replaced by the ipv4 field.","items":{"type":"string"}},"ipv4":{"type":"array","description":"A list of IPv4 addresses for the asset.","items":{"type":"string"}},"ipv6":{"type":"array","description":"A list of IPv6 addresses for the asset.","items":{"type":"string"}},"hostname":{"type":"array","description":"A list of hostnames for the asset.","items":{"type":"string"}},"operating_system":{"type":"string","description":"The operating system installed on the asset."},"ssh_fingerprint":{"type":"string","description":"The SSH key fingerprints that scans have associated with the asset record."},"bios_uuid":{"type":"string","description":"The BIOS UUID of the asset."},"manufacturer_tpm_id":{"type":"string","description":"The manufacturer's unique identifier of the Trusted Platform Module (TPM) associated with the asset."},"mcafee_epo_guid":{"type":"string","description":"The unique identifier of the asset in McAfee ePolicy Orchestrator (ePO). For more information, see the McAfee documentation."},"mcafee_epo_agent_guid":{"type":"string","description":"The unique identifier of the McAfee ePO agent that identified the asset. For more information, see the McAfee documentation."},"symantec_ep_hardware_key":{"type":"string","description":"The hardware key for the asset in Symantec Endpoint Protection."},"qualys_asset_id":{"type":"string","description":"The Asset ID of the asset in Qualys. For more information, see the Qualys documentation."},"qualys_host_id":{"type":"string","description":"The Host ID of the asset in Qualys. For more information, see the Qualys documentation."},"servicenow_sys_id":{"type":"string","description":"The unique record identifier of the asset in ServiceNow. For more information, see the ServiceNow documentation."},"gcp_project_id":{"type":"string","description":"The customized name of the project to which the virtual machine instance belongs in Google Cloud Platform (GCP). For more information see \"Creating and Managing Projects\" in the GCP documentation."},"gcp_zone":{"type":"string","description":"The zone where the virtual machine instance runs in GCP. For more information, see \"Regions and Zones\" in the GCP documentation."},"gcp_instance_id":{"type":"string","description":"The unique identifier of the virtual machine instance in GCP."},"azure_vm_id":{"type":"string","description":"The unique identifier of the Microsoft Azure virtual machine instance. For more information, see \"Accessing and Using Azure VM Unique ID\" in the Microsoft Azure documentation."},"azure_resource_id":{"type":"string","description":"The unique identifier of the resource in the Azure Resource Manager. For more information, see the Azure Resource Manager Documentation."},"aws_availability_zone":{"type":"string","description":"The availability zone where Amazon Web Services hosts the virtual machine instance, for example, `us-east-1a`. Availability zones are subdivisions of AWS regions. For more information, see \"Regions and Availability Zones\" in the AWS documentation."},"aws_ec2_instance_id":{"type":"string","description":"The unique identifier of the Linux instance in Amazon EC2. For more information, see the Amazon Elastic Compute Cloud Documentation."},"aws_ec2_instance_ami_id":{"type":"string","description":"The unique identifier of the Linux AMI image in Amazon Elastic Compute Cloud (Amazon EC2). For more information, see the Amazon Elastic Compute Cloud Documentation."},"aws_ec2_instance_group_name":{"type":"string","description":"The virtual machine instance's group in AWS."},"aws_ec2_instance_state_name":{"type":"string","description":"The state of the virtual machine instance in AWS at the time of the scan."},"aws_ec2_instance_type":{"type":"string","description":"The type of instance in AWS EC2."},"aws_ec2_name":{"type":"string","description":"The name of the virtual machine instance in AWS EC2."},"aws_ec2_product_code":{"type":"string","description":"The product code associated with the AMI used to launch the virtual machine instance in AWS EC2."},"aws_owner_id":{"type":"string","description":"The canonical user identifier for the AWS account associated with the asset. For more information, see \"AWS Account Identifiers\" in the AWS documentation."},"aws_region":{"type":"string","description":"The region where AWS hosts the virtual machine instance, for example, `us-east-1`. For more information, see \"Regions and Availability Zones\" in the AWS documentation."},"aws_subnet_id":{"type":"string","description":"The unique identifier of the AWS subnet where the virtual machine instance was running at the time of the scan."},"aws_vpc_id":{"type":"string","description":"The unique identifier of the public cloud that hosts the AWS virtual machine instance. For more information, see the Amazon Virtual Private Cloud User Guide."},"installed_software":{"type":"array","description":"A list of Common Platform Enumeration (CPE) values that represent software applications a scan identified as present on an asset. The strings in this array must be valid CPE 2.2 values. For more information, see the \"Component Syntax\" section of the [CPE Specification, Version 2.2](https://cpe.mitre.org/files/cpe-specification_2.2.pdf).\n\n**Note:** If no scan detects an application within 30 days of the scan that originally detected the application, Tenable.io considers the detection of that application expired. As a result, the next time a scan evaluates the asset, Tenable.io removes the expired application from the installed_software attribute. This activity is logged as a `remove` type of `attribute_change` update in the asset activity log.","items":{"type":"string"}},"bigfix_asset_id":{"type":"array","items":{"type":"string"},"description":"The unique identifiers of the asset in IBM BigFix. For more information, see the IBM BigFix documentation."}}}},"source":{"type":"string","description":"A user-defined name for the source of the asset records you want to import.","example":"Custom Import"}},"required":["assets","source"]}}}},"responses":{"200":{"description":"Returns the import job UUID.","content":{"application/json":{"schema":{"type":"object","properties":{"asset_import_job_uuid":{"type":"string","description":"The asset import job UUID."}}},"examples":{"response":{"value":{"asset_import_job_uuid":"a90cf974-7b14-4ad1-aa1e-b897e46af689"}}}}}},"400":{"description":"Returned if you submitted a bad request."},"403":{"description":"Returned if you do not have permission to import assets."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/import/asset-jobs":{"get":{"summary":"List asset import jobs","description":"Lists asset import jobs.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"assets-list-import-jobs","tags":["Assets"],"responses":{"200":{"description":"Returns a list of asset import jobs.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"job_id":{"type":"string","description":"The UUID of the asset import job."},"container_id":{"type":"string","description":"The UUID of your Tenable.io container."},"source":{"type":"string","description":"A name for the source of the asset records that you define in the asset import request."},"batches":{"type":"integer","description":"The number of batches in the asset import job.","format":"int32"},"uploaded_assets":{"type":"integer","description":"The number of assets from the import job that Tenable.io successfully imported.","format":"int32"},"failed_assets":{"type":"integer","description":"The number of assets from the import job that Tenable.io failed to import."},"start_time":{"type":"integer","description":"The Unix timestamp when Tenable.io started processing the import job.","format":"int32"},"last_update_time":{"type":"integer","description":"The Unix timestamp when Tenable.io performed an action on the import job.","format":"int32"},"end_time":{"type":"integer","description":"The Unix timestamp when Tenable.io completed processing the import job.","format":"int32"},"status":{"type":"string","description":"The status of the import job. Possible values include: COMPLETED, ERROR."},"status_message":{"type":"string","description":"The description of why a job failed."}}}},"examples":{"response":{"value":{"asset_import_jobs":[{"job_id":"fd7646b5-2c7a-433e-8f2b-f3281b7726ef","container_id":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","source":"test1","batches":1,"uploaded_assets":0,"failed_assets":0,"start_time":1544480303548,"last_update_time":1544484511492,"end_time":1544484511492,"status":"ERROR","status_message":"Job failed by exceeding time limit"},{"job_id":"15759fd1-3483-4467-b04f-1bff11141c37","container_id":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","source":"test2","batches":1,"uploaded_assets":0,"failed_assets":0,"start_time":1544480429785,"last_update_time":1544484511496,"end_time":1544484511496,"status":"ERROR","status_message":"Job failed by exceeding time limit"}]}}}}}},"403":{"description":"Returned if you do not have permission to list asset import jobs."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/import/asset-jobs/{asset_import_job_uuid}":{"get":{"summary":"Get import job information","description":"Gets information about the specified import job.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"assets-import-job-info","tags":["Assets"],"parameters":[{"description":"The UUID of the asset import job.","required":true,"name":"asset_import_job_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns information about the specified import job.","content":{"application/json":{"schema":{"type":"object","properties":{"job_id":{"type":"string","description":"The UUID of the asset import job."},"container_id":{"type":"string","description":"The UUID of your Tenable.io container."},"source":{"type":"string","description":"A name for the source of the asset records that you define in the asset import request."},"batches":{"type":"integer","description":"The number of batches in the asset import job.","format":"int32"},"uploaded_assets":{"type":"integer","description":"The number of assets from the import job that Tenable.io successfully imported.","format":"int32"},"failed_assets":{"type":"integer","description":"The number of assets from the import job that Tenable.io failed to import."},"start_time":{"type":"integer","description":"The Unix timestamp when Tenable.io started processing the import job.","format":"int32"},"last_update_time":{"type":"integer","description":"The Unix timestamp when Tenable.io performed an action on the import job.","format":"int32"},"end_time":{"type":"integer","description":"The Unix timestamp when Tenable.io completed processing the import job.","format":"int32"},"status":{"type":"string","description":"The status of the import job. Possible values include: COMPLETED, ERROR."},"status_message":{"type":"string","description":"The description of why a job failed."}}},"examples":{"response":{"value":{"job_id":"fd7646b5-2c7a-433e-8f2b-f3281b7726ef","container_id":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","source":"test","batches":1,"uploaded_assets":0,"failed_assets":0,"start_time":1544480303548,"last_update_time":1544480303569,"end_time":1544480303548,"status":"IN_PROGRESS","status_message":"Example message."}}}}}},"403":{"description":"Returned if you do not have permission to list asset import jobs."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/audit-log/v1/events":{"get":{"summary":"View audit log","description":"This endpoint requests a list of events. Events can include the following:\n - audit.log.view—The system received and processed an audit-log request.\n - session.create—The system created a session for the user. This event can be triggered by user login or authentication using an API key.\n - session.delete—The session expired, or the user ended the session.\n - session.impersonation.end—An administrator ended a session where they impersonated another user.\n - session.impersonation.start—An administrator started a session where they impersonated another user.\n - user.authenticate.api-keys—The user authenticated a session start using an API key.\n - user.authenticate.mfa—The two-factor authentication challenge was successful, and login allowed.\n - user.authenticate.password—The user authenticated a session start using a password.\n - user.create—An administrator created a new user account.\n - user.delete—An administrator deleted the user account.\n - user.impersonation.end—An administrator stopped impersonating another user.\n - user.impersonation.start—An administrator started impersonating another user.\n - user.logout—The user logged out of the session.\n - user.update—Either an administrator or the user updated the user account.\n\nYou can specify various filters to limit the events that are returned, as well as the number of events. By default, a maximum of 50 events is returned.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"audit-log-events","tags":["Audit Log"],"parameters":[{"description":"A filter condition in the `field.operator:value` format. Filter conditions can include:\n* date.gt:<YYYY-MM-DD>—Tenable.io returns events only if the date when the events occurred is after the date you specify. For example: `f=date.gt:2017-12-31`\n* date.lt:<YYYY-MM-DD>—Tenable.io returns events only if the date when the events occurred is before the date you specify. For example: `f=date.lt:2017-12-31`\n* actor_id.match:<UUID>—Tenable.io returns only the events with a matching actor UUID. For example: `f=actor_id.match:6000a811-8422-4096-83d3-e4d44f44b97d`\n* target_id.match:<UUID>—Tenable.io returns only the events with a matching target UUID. For example: `f=target_id.match:6000a811-8422-4096-83d3-e4d44f44b97d`\n\nYou can specify multiple `f` parameters, separated by ampersand (&) characters. For example: `?f=date.gt:2018-12-31&f=date.lt:2019-06-01&f=actor_id.match:50f84b7f-d1d3-4182-bb46-79cf5c51812e&limit=5000`","required":false,"name":"f","in":"query","schema":{"type":"string"}},{"description":"Sets the limit for how many events Tenable.io should return by the call. By default, this value is 50. For example: `limit=5000`","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the audit log.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The unique ID of the event."},"action":{"type":"string","description":"The action that was taken by the user."},"crud":{"type":"string","description":"Indicates whether the action taken was creating (c), reading (r), updating (u), or deleting (d) an entity."},"is_failure":{"type":"boolean","description":"Indicates whether the action the user took succeeded or failed. Tenable.io logs an event regardless of whether a user action succeeds."},"received":{"type":"string","description":"The date and time the event occured in ISO 8601 format."},"description":{"type":"string","description":"A description of the event."},"actor":{"type":"object","properties":{"id":{"type":"string","description":"The UUID of the user that took the action."},"name":{"type":"string","description":"The username of the user that took the action."}}},"is_anonymous":{"type":"boolean","description":"Indicates whether the action was performed anonymously."},"target":{"type":"object","properties":{"id":{"type":"string","description":"The UUID of the target entity. For example, a user UUID."},"name":{"type":"string","description":"The name of the target entity. For example, a username."},"type":{"type":"string","description":"The type of entity that was the target of the action. For example, a user."}}},"fields":{"type":"object","properties":{"pair":{"type":"object","properties":{"key":{"type":"string","description":"A key. The exact string varies based on the action that was taken."},"value":{"type":"string","description":"A value that corresponds to a key. The value varies based on the request of the action that was taken."}}}}}}}},"examples":{"response":{"value":{"events":[{"id":"a4e9177aa45c48c9d46a2f24c5f97b24","action":"user.authenticate.password","crud":"u","is_failure":true,"received":"2018-08-06T23:09:40Z","description":null,"actor":{"id":"50f84b7f-d1d3-4182-bb46-79cf5c51806e","name":"user2@example.com"},"is_anonymous":null,"target":{"id":"50f84b7f-d1d3-4182-bb46-79cf5c51806e","name":"user2@example.com","type":"User"},"fields":[{"key":"message","value":"Invalid credentials."},{"key":"sessionToken","value":"-"},{"key":"X-Forwarded-For","value":"172.204.81.57, 172.204.81.57"},{"key":"X-Request-Uuid","value":"71a6630e83148694260ad838ddff5dce:dd19f39e7ec84ba80dec:8d7f958f8c3b770767af"}]},{"id":"9ed34e87d3474ff985759d14ss703e4c","action":"session.create","crud":"c","is_failure":false,"received":"2018-08-06T23:33:01Z","description":null,"actor":{"id":null,"name":null},"is_anonymous":true,"target":{"id":"50f84b7f-d1d3-4182-bb46-79cf5c51816e","name":"user2@example.com","type":"User"},"fields":[{"key":"X-Access-Type","value":"Created by username"}]},{"id":"dca7681afaf24048baff7b4e90b668d7","action":"session.delete","crud":"d","is_failure":false,"received":"2018-08-06T23:40:57Z","description":null,"actor":{"id":"50f84b7f-d1d3-4182-bb46-79cf5c51816e","name":"user2@example.com"},"is_anonymous":null,"target":{"id":"bcce340","name":null,"type":"Session"},"fields":[{"key":"message","value":"session timeout"}]},{"id":"a2498a85cb5740a28e532814c0ba8369","action":"user.impersonation.start","crud":"u","is_failure":false,"received":"2018-08-14T09:23:12Z","description":null,"actor":{"id":"92907192-57db-407e-98ff-053de7f12bab","name":"monitoring@example.com"},"is_anonymous":null,"target":{"id":"50f84b7f-d1d3-4182-bb46-79cd5c51806e","name":"user2@example.com","type":"User"},"fields":[{"key":"sessionToken","value":"-"},{"key":"X-Access-Type","value":"apikey"},{"key":"X-Forwarded-For","value":"172.204.81.57"},{"key":"X-Request-Uuid","value":"63e024e7fe25ed24ce1c7142781527ac:43cf99b77f783a962a1a"}]},{"id":"eaac53481de04f67bc7eeea07d2fb0f5","action":"session.delete","crud":"d","is_failure":false,"received":"2018-08-08T01:40:07Z","description":null,"actor":{"id":"50f84b7f-d1d3-4182-bb46-79cf9c51806e","name":"user2@example.com"},"is_anonymous":null,"target":{"id":"12d024e","name":null,"type":"Session"},"fields":[{"key":"message","value":"session timeout"}]}],"pagination":{"total":5,"limit":50}}}}}}},"403":{"description":"Returned if you do not have permission to view the audit log."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/credentials":{"post":{"summary":"Create managed credential","description":"Creates a managed credential object that you can use when configuring and running scans. You can grant other users the permission to use the managed credential object in scans and to edit the managed credential configuration.

Requires BASIC [16] user permissions. See Permissions.

","operationId":"credentials-create","tags":["Credentials"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"The name of the managed credential. This name must be unique within your Tenable.io instance.","type":"string"},"description":{"description":"The description of the managed credential object.","type":"string"},"type":{"description":"The type of credential object. For a list of supported credential types, use the GET /credentials/types endpoint.","type":"string"},"settings":{"description":"The configuration settings for the credential. The parameters of this object vary depending on the credential type. For more information, see [Determine Settings for a Credential Type](/docs/determine-settings-for-credential-type).","type":"object","properties":{}},"permissions":{"description":"A list of user permissions for the managed credential.","type":"array","items":{"type":"object","properties":{"grantee_uuid":{"type":"string","description":"The UUID of the user or user group granted permissions for the managed credential. \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."},"type":{"type":"string","description":"A value specifying whether the grantee is a user (`user`) or a user group (`group`). \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential.","enum":["user","group"]},"permissions":{"type":"integer","description":"A value specifying the permissions granted to the user or user group for the credential. Possible values are:\n - 32—The user can view credential information and use the credential in scans. Corresponds to the **Can Use** permission in the user interface.\n - 64—The user can view and edit credential settings, delete the credential, and use the credential in scans. Corresponds to the **Can Edit** permission in the user interface. \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."},"name":{"type":"string","description":"The name of the user or user group that you want to grant permissions for the managed credential. \n\nThis parameter is optional when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."}}}}},"required":["name","type","settings","permissions"]},"example":{"name":"Windows devices (Headquarters)","description":"Use for scans of Windows devices located at headquarters.","type":"Windows","settings":{"domain":"","username":"user@example.com","auth_method":"Password","password":"aJ^deq34Rc"},"permissions":[{"grantee_uuid":"08d242c3-9553-4ccc-835d-0c17ed942cdq","type":"user","permissions":64,"name":"user@example.com"}]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates a managed credential object.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the new managed credential object."}}},"examples":{"response":{"value":{"uuid":"cc43b17c-ee05-4369-95f7-af8f9bd8cad1"}}}}}},"400":{"description":"Returned if Tenable.io encounters invalid JSON in request body."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have permission to create managed credential objects."},"409":{"description":"Returned if a managed credential object with the same name already exists."},"415":{"description":"Returned if the request payload is in an unsupported format."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List managed credentials","description":"Lists managed credentials where you have been assigned at least CAN USE (32) permissions. \n\n**Note:** This endpoint does not list scan-specific or policy-specific credentials (that is, credentials stored in either a scan or a policy). To view a list of scan-specific or policy-specific credentials, use the editor details endpoint (GET /editor/{type}/{id}).

Requires CAN USE [32] credential permissions. See Permissions.

","operationId":"credentials-list","tags":["Credentials"],"parameters":[{"description":"A filter condition in the following format: `field:operator:value`. For managed credentials, you can only filter on the `name` field, using the following operators: \n* eq—The name of the returned credential is equal to the text you specify. \n* neq—The returned list of managed credentials excludes the credential object where the name is equal to the text you specify. \n* match—The returned list includes managed credentials where the name contains the text you specify at least partially.\n\nYou can specify multiple `f` parameters, separated by ampersand (&) characters. If you specify multiple `f` parameters, use the `ft` parameter to specify how Tenable.io applies the multiple filter conditions.","required":false,"name":"f","in":"query","schema":{"type":"string"}},{"description":"The operator that Tenable.io applies if multiple \\`f\\` parameters are present. The `OR` operator is the only supported value. If you omit this parameter and multiple `f` parameters are present, Tenable.io applies the `OR` operator by default.","required":false,"name":"ft","in":"query","schema":{"type":"string"}},{"description":"Maximum number of objects requested (or service imposed limit if not in request). Must be in the int32 format.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Offset from request (or zero). Must be in the int32 format.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"An array of objects specifyfing the sort order for the returned data.","required":false,"name":"sort","in":"query","schema":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The field on which Tenable.io sorts the results."},"order":{"type":"string","description":"The direction of the sort order. Supported values are `asc` (ascending) and `desc` (descending)."}}}}},{"description":"The UUID of a scan owner. This parameter limits the returned data to managed credentials assigned to scans owned by the specified user.","name":"referrer_owner_uuid","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of managed credentials.","content":{"application/json":{"schema":{"type":"object","properties":{"credentials":{"type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the managed credential object."},"name":{"type":"string","description":"The name of the managed credential object. You specify the name when you create or update the managed credential."},"description":{"type":"string","description":"The definition of the managed credential object. You can specify the description when you create or update the managed credential."},"category":{"type":"object","properties":{"id":{"type":"string","description":"The system name that uniquely identifies the credential category."},"name":{"type":"string","description":"The display name for the credential category in the user interface."}}},"type":{"type":"object","properties":{"id":{"type":"string","description":"The system name that uniquely identifies the credential type."},"name":{"type":"string","description":"The display name for the credential type in the user interface."}}},"created_date":{"type":"string","description":"The date (in Unix time) when the managed credential object was created."},"created_by":{"type":"object","properties":{"id":{"type":"integer","description":"The ID of the user who created the credential."},"display_name":{"type":"string","description":"The name of the user who created the credential."}}},"last_used_by":{"type":"object","properties":{"id":{"type":"integer","description":"The ID of the user who last used the credential in a scan."},"display_name":{"type":"string","description":"The name of the user who last used the credential in a scan."}}},"permissions":{"type":"integer","description":"A value specifying the permissions granted to the user or user group for the credential. For possible values, see \"Credential Roles\" in Permissions."},"user_permissions":{"type":"integer","description":"The permissions for the managed credential that are assigned to the user account submitting the API request. For possible values, see \"Credential Roles\" in Permissions."}}}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of objects matching your search criteria. Must be in the int32 format."},"limit":{"type":"integer","description":"Maximum number of objects requested (or service imposed limit if not in request). Must be in the int32 format."},"offset":{"type":"integer","description":"Offset from request (or zero). Must be in the int32 format."},"sort":{"description":"An array of objects representing the fields you specified as sort fields in the request message, which Tenable.io uses to sort the returned data.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The field on which Tenable.io sorts the results."},"order":{"type":"string","description":"The direction of the sort order. Supported values are `asc` (ascending) and `desc` (descending)."}}}}}}}},"examples":{"response":{"value":{"credentials":[{"uuid":"aa43b17c-ee05-4369-95f7-af8f9bd8cad0","name":"Windows devices (Headquarters)","description":"Use for scans of Windows devices located at headquarters.","category":{"id":"Host","name":"Host"},"type":{"id":"Windows","name":"Windows"},"created_date":1551295980,"created_by":{"id":15,"display_name":"user@example.com"},"last_used_by":{"id":null,"display_name":null},"permission":32,"user_permissions":32}],"pagination":{"total":1,"limit":50,"offset":0,"sort":[{"name":"created_date","order":"desc"}]}}}}}}},"400":{"description":"Returned if the query parameters in your request were invalid."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/credentials/{uuid}":{"get":{"summary":"Get managed credential details","description":"Returns details of the specified managed credential object.

Requires CAN USE [32] credential permissions. See Permissions.

","operationId":"credentials-details","tags":["Credentials"],"parameters":[{"description":"The UUID of the managed credential for which you want to view details.","required":true,"name":"uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the details of the specified managed credential object.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the managed credential object. You specify the name when you create or update the managed credential."},"description":{"type":"string","description":"The definition of the managed credential object. You specify the description when you create or update the managed credential."},"category":{"type":"object","properties":{"id":{"type":"string","description":"The system name that uniquely identifies the credential category."},"name":{"type":"string","description":"The display name for the credential category in the user interface."}}},"type":{"type":"object","properties":{"id":{"type":"string","description":"The system name that uniquely identifies the credential type."},"name":{"type":"string","description":"The display name for the credential type in the user interface."}}},"ad_hoc":{"type":"boolean","description":"A value specifying how a user creates a managed credential in the user interface. If `true`, the user created the credential during the scan configuration. If `false`, the user created the credential independently from scan configuration."},"user_permissions":{"type":"integer","description":"The permissions for the managed credential that are assigned to the user account submitting the API request. For possible values, see \"Credential Roles\" in Permissions."},"settings":{"description":"The configuration settings for the credential. The parameters of this object vary depending on the credential type. For more information, see [Determine Settings for a Credential Type](/docs/determine-settings-for-credential-type).","type":"object","properties":{}},"permissions":{"type":"array","items":{"type":"object","properties":{"grantee_uuid":{"type":"string","description":"The UUID of the user or user group granted permissions for the managed credential. \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."},"type":{"type":"string","description":"A value specifying whether the grantee is a user (`user`) or a user group (`group`). \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential.","enum":["user","group"]},"permissions":{"type":"integer","description":"A value specifying the permissions granted to the user or user group for the credential. Possible values are:\n - 32—The user can view credential information and use the credential in scans. Corresponds to the **Can Use** permission in the user interface.\n - 64—The user can view and edit credential settings, delete the credential, and use the credential in scans. Corresponds to the **Can Edit** permission in the user interface. \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."},"name":{"type":"string","description":"The name of the user or user group that you want to grant permissions for the managed credential. \n\nThis parameter is optional when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."}}}}}},"examples":{"response":{"value":{"name":"Windows devices (Headquarters)","description":"Use for scans of Windows devices located at headquarters.","category":{"id":"Host","name":"Host"},"type":{"id":"Windows","name":"Windows"},"ad_hoc":false,"user_permissions":64,"settings":{"domain":"","username":"user@example.com","auth_method":"Password","password":"********"},"permissions":[{"grantee_uuid":"e7fcb50b-1330-4a8c-b8e5-ee00ec8c56f8","type":"user","permissions":64,"name":"user1@tenable.com"},{"grantee_uuid":"08d242c3-9557-4ccc-835d-0c17ed942cde","type":"user","permissions":64,"name":"user2@example.com"},{"grantee_uuid":"9be14fe3-16f4-49e3-a015-234b8918b8de","type":"user","permissions":32,"name":"user3@example.com"}]}}}}}},"403":{"description":"Returned if you do not have sufficient permissions to view the specified managed credential object."},"404":{"description":"Returned if Tenable.io cannot find a managed credential object with the specified UUID."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}},"504":{"description":"Returned if Tenable.io is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":504,"error":"Gateway Timeout","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update managed credential","description":"Updates a managed credential object. \n\n**Note:** You cannot use this endpoint to update the credential type. If you create a managed credential with the incorrect type, create a new managed credential with the correct credential type, and delete the incorrect managed credential.

Requires CAN EDIT [64] credential permissions. See Permissions.

","operationId":"credentials-update","tags":["Credentials"],"parameters":[{"description":"The UUID of the managed credential object you want to update.","required":true,"name":"uuid","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"The new name of the managed credential object. This name must be unique within your Tenable.io instance.","type":"string"},"description":{"description":"The new description of the managed credential object.","type":"string"},"ad_hoc":{"description":"A value specifying if the credential is managed (`false`) versus stored in a scan or policy configuration (`true`). You can only set this parameter from `true` to `false`. You cannot set this parameter to `true`. If you omit this parameter, the value defaults to `false`.","type":"boolean"},"settings":{"description":"The configuration settings for the credential. The parameters of this object vary depending on the credential type. For more information, see [Determine Settings for a Credential Type](/docs/determine-settings-for-credential-type).","type":"object","properties":{}},"permissions":{"description":"User permissions for the managed credential.","type":"array","items":{"type":"object","properties":{"grantee_uuid":{"type":"string","description":"The UUID of the user or user group granted permissions for the managed credential. \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."},"type":{"type":"string","description":"A value specifying whether the grantee is a user (`user`) or a user group (`group`). \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential.","enum":["user","group"]},"permissions":{"type":"integer","description":"A value specifying the permissions granted to the user or user group for the credential. Possible values are:\n - 32—The user can view credential information and use the credential in scans. Corresponds to the **Can Use** permission in the user interface.\n - 64—The user can view and edit credential settings, delete the credential, and use the credential in scans. Corresponds to the **Can Edit** permission in the user interface. \n\nThis parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."},"name":{"type":"string","description":"The name of the user or user group that you want to grant permissions for the managed credential. \n\nThis parameter is optional when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential."}}}}},"required":["settings","permissions"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updates the managed credential object.","content":{"application/json":{"schema":{"type":"object","properties":{"updated":{"type":"boolean"}}},"examples":{"response":{"value":{"updated":true}}}}}},"400":{"description":"Returned if Tenable.io encounters invalid JSON in the request body."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have permission to update the specified managed credential object."},"404":{"description":"Returned if Tenable.io could not find the specified managed credential object, either because the object does not exist or because the object has been deleted."},"409":{"description":"Returned if a managed credential object with the same name already exists."},"415":{"description":"Returned if the request payload is in an unsupported format."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete managed credential","description":"Deletes the specified managed credential object. When you delete a managed credential object, Tenable.io also removes the credential from any scan that uses the credential.

Requires CAN EDIT [64] credential permissions. See Permissions.

","operationId":"credentials-delete","tags":["Credentials"],"parameters":[{"description":"The UUID for the managed credential object you want to delete.","required":true,"name":"uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the specified managed credential object.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}},"examples":{"response":{"value":{"deleted":true}}}}}},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have sufficient permissions to delete the specified managed credential object."},"404":{"description":"Returned if Tenable.io could not find the managed credential object you specified."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/credentials/types":{"get":{"summary":"List credential types","description":"Lists all credential types supported for managed credentials in Tenable.io. For more information about using the data returned by this endpoint to create managed credentials, see [Determine Settings for a Credential Type](/docs/determine-settings-for-credential-type).

Requires BASIC [16] user permissions. See Permissions.

","operationId":"credentials-list-credential-types","tags":["Credentials"],"responses":{"200":{"description":"Returns a list of supported credential types and associated settings.","content":{"application/json":{"schema":{"type":"object","properties":{"credentials":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The system name that uniquely identifies the category in Tenable.io."},"category":{"type":"string","description":"The display name for the category in the user interface."},"default_expand":{"type":"boolean","description":"A value specifying whether the list of credential types in the category appears as expanded by default in the user interface."},"types":{"description":"Supported configuration settings for an individual credential type.","type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The system name that uniquely identifies the credential type."},"name":{"type":"string","description":"The display name for the credential type in the user interface."},"max":{"type":"integer","description":"The maximum number of instances of this credential type that Tenable.io supports for an individual scan or policy."},"configuration":{"description":"The configuration settings for a credential type. For a definition of object attributes, see [Determine Settings for a Credential Type](docs/determine-settings-for-credential-type).","type":"array","items":{"type":"object","description":"The configuration settings for the credential. The parameters of this object vary depending on the credential type. For a list of possible configuration parameters, use the GET /credentials/types endpoint.","properties":{"type":{"type":"string","description":"The parameter input type. This attribute reflects how the user interface prompts for parameter input. Possible values include: \n - password—Prompts for input via text box.\n - text—Prompts for input via text box.\n - select—Prompts for input via selectable options.\n - file—Prompts user to upload file of input data.\n - toggle—Prompts user to toggle an option on or off.\n - checkbox—Prompts user to select options via checkboxes. Checkboxes can represent enabling a single option or can allow users to select from multiple, mutually-exclusive options."},"name":{"type":"string","description":"The display name for the credential configuration in the user interface."},"required":{"type":"boolean","description":"A value specifying whether the configuration parameter is required (`true`) or optional (`false`). If this attribute is absent, the parameter is optional."},"id":{"type":"string","description":"The system name for the credential parameter. Use this value as the parameter name in request messages configuring credentials."},"placeholder":{"type":"string","description":"An example of the parameter value. This value appears as example text in the user interface. \n\nThis attribute is only present for credential parameters that require text input in the user interface."},"options":{"description":"The supported options for the credential parameter.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The display name of the option in the user interface."},"id":{"type":"string","description":"The system name for the option."},"inputs":{"type":"array","description":"The additional inputs that are required if the user selects this option in the user interface. If the inputs parameter is empty (`\\[\\]`), selecting the option does not require additional user input.","items":{"type":"object","properties":{"type":{"type":"string","description":"The type of input prompt in the user interface. Possible values include:\n - password—Prompts for input via text box.\n - text—Prompts for input via text box.\n - select—Prompts for input via selectable options.\n - file—Prompts user to upload file of input data.\n - toggle—Prompts user to select one of two mutually-exclusive options in toggle format.\n - checkbox—Prompts user to select options via checkboxes. Checkboxes can represent enabling a single option or can allow users to select from multiple, mutually-exclusive options.\n - key-value— Prompts for text entry of a key-value pair via two text boxes."},"name":{"type":"string","description":"The display name of the option in the user interface."},"required":{"type":"boolean","description":"A value specifying whether the input is required (`true`) or optional (`false`)."},"placeholder":{"type":"string","description":"An example of the input value. This value appears as example text in the user interface. \n\nThis attribute is only present for credential parameters that require text input in the interface. \n\nIn cases where the input type is `key-value`, this attribute can be an array of strings."},"regex":{"type":"string","description":"A regular expression defining the valid input for the parameter in the user interface."},"hint":{"type":"string","description":"Helpful information about the input required, for example, \"PEM formatted certificate\". Hints appear in the user interface, but can contain information that is relevant to API requests."},"callback":{"type":"string","description":"Not supported as a parameter in managed credentials."},"default-row-count":{"type":"integer","description":"The number of text box rows that appear by default when the input type is `key-value`."},"hide-values":{"type":"boolean","description":"A value specifying whether the user interface hides the value by default when the input type is `key-value`. If `true`, dots appear instead of characters as you type the value in the user interface."},"id":{"type":"string","description":"The system name for the input. Use this value as the input name in request messages when configuring credentials."}}}}}}},"default":{"type":"string","description":"The option that appears as selected by default in the user interface."},"alt_ids":{"type":"string","description":"Not supported as a parameter in managed credentials."},"preferences":{"description":"Not supported as a parameter in managed credentials.","type":"array","items":{"type":"string"}}}}},"expand_settings":{"type":"boolean","description":"A value specifying whether the configuration settings appear expanded by default in the user interface."}}}}}}}}},"examples":{"response":{"value":{"credentials":[{"id":"Cloud Services","category":"Cloud Services","default_expand":false,"types":[{"id":"Amazon AWS","name":"Amazon AWS","max":1,"configuration":[{"type":"password","name":"AWS Access Key ID","required":true,"id":"access_key_id"},{"type":"password","name":"AWS Secret Key","required":true,"id":"secret_key"}],"expand_settings":true},{"id":"Microsoft Azure","name":"Microsoft Azure","max":1,"configuration":[{"type":"text","name":"Username","required":true,"id":"username"},{"type":"password","name":"Password","required":true,"id":"password"},{"type":"text","name":"Client Id","required":true,"id":"client_id"}],"expand_settings":true},{"id":"Office 365","name":"Office 365","max":1,"configuration":[{"type":"text","name":"Username","required":true,"id":"username"},{"type":"password","name":"Password","required":true,"id":"password"},{"type":"text","name":"Client Id","required":true,"id":"client_id"},{"type":"password","name":"Client Secret","required":true,"id":"client_secret"}]}]}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/credentials/file":{"post":{"summary":"Upload credentials file","description":"Uploads a file for use with a managed credential (for example, as a private key file for an SSH credential). For more information about using this file, see [Create a Managed Credential](https://developer.tenable.com/docs/create-managed-credential).

Requires BASIC [16] user permissions. See Permissions.

","operationId":"credentials-file-upload","tags":["Credentials"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"Filedata":{"type":"string","description":"The file to upload.","format":"binary"}}}}}},"responses":{"200":{"description":"Returns the name of the successfully uploaded file.","content":{"application/json":{"schema":{"type":"object","properties":{"fileuploaded":{"type":"string","description":"The name of the uploaded file. If the file with the same name already exists, Tenable.io appends an underscore with a number, for example ssh_private_key_1.txt. Use this attribute value when referencing the file for subsequent requests."}}},"examples":{"response":{"value":{"fileuploaded":"ssh_private_key_1.txt"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io cannot upload the file.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/editor/{type}/{id}":{"get":{"summary":"Get configuration details","description":"Gets the configuration details for the scan or policy.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"editor-details","tags":["Editor"],"parameters":[{"description":"The type of object (scan or policy).","required":true,"name":"type","in":"path","schema":{"type":"string","enum":["scan","policy"]}},{"description":"The unique ID of the object.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the object data. Note that the fields can vary depending on the template used for the scan or policy.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string"},"user_permissions":{"type":"integer"},"filter_attributes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The short name of the filter."},"readable_name":{"type":"string","description":"The long name of the filter."},"operators":{"description":"The comparison options for the filter.","type":"array","items":{"type":"object"}},"control":{"type":"object","properties":{"type":{"type":"string","description":"The input type (entry or dropdown)."},"readable_regest":{"type":"string","description":"The placeholder for the input."},"regex":{"type":"string","description":"A regex for checking the value of the input."},"options":{"description":"A list of options if the input is a dropdown.","type":"array","items":{"type":"object"}}}}}}},"settings":{"description":"Scan or policy settings organized into the Basic, Discovery, Assessment, Report, and Advanced configuration categories.","type":"object","properties":{"basic":{"type":"object","description":"The Basic scan settings are used to specify certain organizational and security-related aspects of the scan or policy, including the name of the scan, its targets, whether the scan is scheduled, and who has access to the scan, among other settings."},"discovery":{"type":"object","description":"The Discovery settings relate to discovery and port scanning, including port ranges and methods."},"assessment":{"type":"object","description":"You can use Assessment settings to configure how a scan identifies vulnerabilities, as well as what vulnerabilities are identified. This includes identifying malware, assessing the vulnerability of a system to brute force attacks, and the susceptibility of web applications."},"advanced":{"type":"object","description":"The Advanced settings provide increased control over scan efficiency and the operations of a scan, as well as the ability to enabled plugin debugging."}}},"credentials":{"description":"Credentials that grant the scanner access to the target system without requiring an agent. Credentialed scans can perform a wider variety of checks than non-credentialed scans, which can result in more accurate scan results. This facilitates scanning of a very large network to determine local exposures or compliance violations. You can configure credentials for Cloud Services, Database, Host, Miscellaneous, Mobile Device Management, and Plaintext Authentication.","type":"object","properties":{}},"compliance":{"description":"Plugins options enables you to select security checks by Plugin Family or individual plugins checks.","type":"object","properties":{}},"plugins":{"description":"The settings for compliance audit checks.","type":"object","properties":{}}}},"examples":{"response":{"value":{"is_was":null,"user_permissions":128,"owner":"user2@example.com","title":"Custom Scan","is_agent":null,"uuid":"ab4bacd2-05f6-425c-9d79-3ba3940ad1c24e51e1f403febe40","settings":{"basic":{"inputs":[{"type":"entry","name":"Name","id":"name","default":"KitchenSinkScan","required":true},{"type":"textarea","name":"Description","id":"description","default":null},{"type":"select","id":"include_aggregate","name":"Scan results","default":true,"options":[{"name":"Show in dashboard","value":"true"}]},{"type":"select","id":"folder_id","name":"Folder","default":9,"options":[{"name":"My Scans","id":9},{"name":"Trash","id":8}]},{"type":"select","id":"use_dashboard","name":"Dashboard","default":false,"options":[{"name":"Enabled","value":"true"},{"name":"Disabled","value":"false"}]}],"title":"Basic","groups":[{"title":"Permissions","name":"permissions","acls":[{"permissions":0,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"user2@example.com","name":"user2@example.com","id":2,"type":"user"}]}],"sections":[]}},"filter_attributes":[{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"bid","readable_name":"Bugtraq ID"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_canvas","readable_name":"CANVAS Exploit Framework"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["CANVAS","D2ExploitPack","White_Phosphorus"]},"name":"canvas_package","readable_name":"CANVAS Package"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"Cert VU reference (ie: 10031)","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"cert","readable_name":"CERT Vulnerability ID"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_core","readable_name":"CORE Exploit Framework"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"cpe","readable_name":"CPE"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"CVE-YYYY-ID (ie: CVE-2011-0018)","type":"entry","regex":"^(CVE|CAN)-(1999|20[01][0-9])-[0-9]{4,}$"},"name":"cve","readable_name":"CVE"},{"operators":["lt","gt","eq","neq","match","nmatch"],"control":{"readable_regex":"7.5","type":"entry","regex":"^[0-9]+(\\.[0-9]+)?$"},"name":"cvss_base_score","readable_name":"CVSS Base Score"},{"operators":["lt","gt","eq","neq","match","nmatch"],"control":{"readable_regex":"4.2","type":"entry","regex":"^[0-9]+(\\.[0-9]+)$"},"name":"cvss_temporal_score","readable_name":"CVSS Temporal Score"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":"^CVSS2#E:(U|POC|F|H|ND)/RL:(OF|T|W|U|ND)/RC:(UC|UR|C|ND)$"},"name":"cvss_temporal_vector","readable_name":"CVSS Temporal Vector"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":"^CVSS2#AV:(L|A|N)/AC:(H|M|L)/Au:(N|S|M)/C:(N|P|C)/I:(N|P|C)/A:(N|P|C)$"},"name":"cvss_vector","readable_name":"CVSS Vector"},{"operators":["lt","gt","eq","neq","match","nmatch"],"control":{"readable_regex":"7.5","type":"entry","regex":"^[0-9]+(\\.[0-9]+)?$"},"name":"cvss3_base_score","readable_name":"CVSS v3.0 Base Score"},{"operators":["lt","gt","eq","neq","match","nmatch"],"control":{"readable_regex":"4.2","type":"entry","regex":"^[0-9]+(\\.[0-9]+)$"},"name":"cvss3_temporal_score","readable_name":"CVSS v3.0 Temporal Score"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":"^CVSS:3.0/E:(U|POC|F|H|ND)/RL:(OF|T|W|U|ND)/RC:(UC|UR|C|ND)$"},"name":"cvss3_temporal_vector","readable_name":"CVSS v3.0 Temporal Vector"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":"^CVSS:3.0/AV:(N|A|L|P)/AC:(L|H)/PR:(N,L,H)/UI:(N|R)/S:(U|C)/C:(H|L|N)/I:(H|L|N)/A:(H|L|N)$"},"name":"cvss3_vector","readable_name":"CVSS v3.0 Vector"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"CWE reference (ie: 200)","type":"entry","regex":"^([0-9]+)$"},"name":"cwe","readable_name":"CWE"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"default_account","readable_name":"Default/Known Accounts"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_d2_elliot","readable_name":"Elliot Exploit Framework"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"d2_elliot_name","readable_name":"Elliot Exploit Name"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_available","readable_name":"Exploit Available"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"edb-id","readable_name":"Exploit Database ID"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_exploithub","readable_name":"ExploitHub"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["Exploits are available","No exploit is required","No known exploits are available"]},"name":"exploitability_ease","readable_name":"Exploitability Ease"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploited_by_malware","readable_name":"Exploited By Malware"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploited_by_nessus","readable_name":"Exploited By Nessus"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"IAVA reference (ie: 2011-A-0151)","type":"entry","regex":"^[0-9]+-[A-Za-z]-[0-9]+$"},"name":"iava","readable_name":"IAVA ID"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"IAVB reference (ie: 2011-B-0151)","type":"entry","regex":"^[0-9]+-[A-Za-z]-[0-9]+$"},"name":"iavb","readable_name":"IAVB ID"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"IAVM Severity (ie: IV)","type":"entry","regex":"^[ivIV]+"},"name":"stig_severity","readable_name":"IAVM Severity"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"IAVT reference (ie: 2011-A-0151)","type":"entry","regex":"^[0-9]+-[A-Za-z]-[0-9]+$"},"name":"iavt","readable_name":"IAVT ID"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"in_the_news","readable_name":"In The News"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"malware","readable_name":"Malware"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_metasploit","readable_name":"Metasploit Exploit Framework"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"metasploit_name","readable_name":"Metasploit Name"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"MS0X-YZT","type":"entry","regex":"^MS[0-9]+-[0-9]+$"},"name":"msft","readable_name":"Microsoft Bulletin"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"osvdb","readable_name":"OSVDB ID"},{"operators":["date-lt","date-gt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"},"name":"patch_publication_date","readable_name":"Patch Publication Date"},{"operators":["match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"description","readable_name":"Plugin Description"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9, ]+$","maxlength":9},"name":"plugin_id","readable_name":"Plugin ID"},{"operators":["date-lt","date-gt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"},"name":"plugin_modification_date","readable_name":"Plugin Modification Date"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"plugin_name","readable_name":"Plugin Name"},{"operators":["date-lt","date-gt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"},"name":"plugin_publication_date","readable_name":"Plugin Publication Date"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["local","remote"]},"name":"plugin_type","readable_name":"Plugin Type"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["None","Low","Medium","High","Critical"]},"name":"risk_factor","readable_name":"Risk Factor"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"secunia","readable_name":"Secunia ID"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"see_also","readable_name":"See Also"},{"operators":["match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"solution","readable_name":"Solution"},{"operators":["match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"synopsis","readable_name":"Synopsis"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"unsupported_by_vendor","readable_name":"Unsupported By Vendor"},{"operators":["date-lt","date-gt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"},"name":"vuln_publication_date","readable_name":"Vulnerability Publication Date"}],"name":"custom"}}}}}},"403":{"description":"Returned if you do not have permission to open the object."},"404":{"description":"Returned if Tenable.io cannot find the specified object."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/editor/{type}/templates":{"get":{"summary":"List templates","description":"Lists scan or policy templates.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"editor-list-templates","tags":["Editor"],"parameters":[{"description":"The type of templates to retrieve (scan or policy).","required":true,"name":"type","in":"path","schema":{"type":"string","enum":["scan","policy"]}}],"responses":{"200":{"description":"Returns the template list.","content":{"application/json":{"schema":{"type":"array","description":"The list of available templates.","items":{"type":"object","description":"Templates are used to create scans or policies with predefined parameters.","properties":{"unsupported":{"type":"boolean","description":"If true, template is not supported."},"cloud_only":{"type":"boolean","description":"If true, template is only available on the cloud."},"desc":{"type":"string","description":"The description of the template."},"subscription_only":{"type":"boolean","description":"If true, the template is only available for subscribers."},"is_was":{"type":"boolean","description":"If true, the template is for Web Application Scanning."},"title":{"type":"string","description":"The long name of the template."},"is_agent":{"type":"boolean","description":"If true, the template is for agent scans."},"uuid":{"type":"string","description":"The UUID for the template."},"manager_only":{"type":"boolean","description":"If true, can only be used by manager."},"name":{"type":"string","description":"The short name of the template."}}}},"examples":{"response":{"value":{"templates":[{"unsupported":false,"cloud_only":false,"desc":"A full system scan suitable for any host.","order":null,"subscription_only":false,"is_was":null,"title":"Basic Network Scan","is_agent":null,"uuid":"731a8e52-3ea6-a291-ec0a-d2ff0619c19d7bd788d6be818b65","manager_only":false,"name":"basic"},{"unsupported":false,"cloud_only":false,"desc":"Audit systems connected via Nessus Agents.","order":null,"subscription_only":false,"is_was":null,"title":"Policy Compliance Auditing","is_agent":true,"uuid":"523c833f-e434-a05f-5a52-0c0c2c160b7cd9c901634c382c2d","manager_only":false,"name":"agent_compliance"},{"unsupported":false,"cloud_only":false,"desc":"Scan for malware on systems connected via Nessus Agents.","order":null,"subscription_only":false,"is_was":null,"title":"Malware Scan","is_agent":true,"uuid":"fc2fa8b3-028b-83e8-2ebd-4705d0de38bc621fbb0e783517bc","manager_only":false,"name":"agent_malware"}]}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/editor/{type}/templates/{template_uuid}":{"get":{"summary":"Get template details","description":"Gets details for the specified template.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"editor-template-details","tags":["Editor"],"parameters":[{"description":"The type of template to retrieve (scan or policy).","required":true,"name":"type","in":"path","schema":{"type":"string","enum":["scan","policy"]}},{"description":"The UUID for the template.","required":true,"name":"template_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the template details. Note that the fields can vary for different template types.","content":{"application/json":{"schema":{"type":"object","properties":{"is_was":{"type":"boolean","description":"If `true`, the template is for Web Application Scanning. For Vulnerability Management, this value is always `null`."},"title":{"type":"string","description":"The long name of the template."},"name":{"type":"string","description":"The short name of the template."},"is_agent":{"type":"boolean","description":"If `true`, the template is for agent scans."},"filter_attributes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The short name of the filter."},"readable_name":{"type":"string","description":"The long name of the filter."},"operators":{"description":"The comparison options for the filter.","type":"array","items":{"type":"object"}},"control":{"type":"object","properties":{"type":{"type":"string","description":"The input type (entry or dropdown)."},"readable_regest":{"type":"string","description":"The placeholder for the input."},"regex":{"type":"string","description":"A regex for checking the value of the input."},"options":{"description":"A list of options if the input is a dropdown.","type":"array","items":{"type":"object"}}}}}}},"settings":{"description":"Scan or policy settings organized into the Basic, Discovery, Assessment, Report, and Advanced configuration categories.","type":"object","properties":{"basic":{"type":"object","description":"The Basic scan settings are used to specify certain organizational and security-related aspects of the scan or policy, including the name of the scan, its targets, whether the scan is scheduled, and who has access to the scan, among other settings."},"discovery":{"type":"object","description":"The Discovery settings relate to discovery and port scanning, including port ranges and methods."},"assessment":{"type":"object","description":"You can use Assessment settings to configure how a scan identifies vulnerabilities, as well as what vulnerabilities are identified. This includes identifying malware, assessing the vulnerability of a system to brute force attacks, and the susceptibility of web applications."},"advanced":{"type":"object","description":"The Advanced settings provide increased control over scan efficiency and the operations of a scan, as well as the ability to enabled plugin debugging."}}},"credentials":{"description":"Credentials that grant the scanner access to the target system without requiring an agent. Credentialed scans can perform a wider variety of checks than non-credentialed scans, which can result in more accurate scan results. This facilitates scanning of a very large network to determine local exposures or compliance violations. You can configure credentials for Cloud Services, Database, Host, Miscellaneous, Mobile Device Management, and Plaintext Authentication.","type":"object","properties":{}},"compliance":{"description":"Plugins options enables you to select security checks by Plugin Family or individual plugins checks.","type":"object","properties":{}},"plugins":{"description":"The settings for compliance audit checks.","type":"object","properties":{}}}},"examples":{"response":{"value":{"is_was":null,"user_permissions":null,"owner":null,"title":"Host Discovery","is_agent":null,"uuid":"bbd4f805-3966-d464-b2d1-0079eb89d69708c3a05ec2812bcf","settings":{"basic":{"inputs":[{"type":"entry","name":"Name","id":"name","required":true},{"type":"textarea","name":"Description","id":"description"},{"type":"select","id":"include_aggregate","name":"Scan results","default":true,"options":[{"name":"Keep private","value":"false"},{"name":"Show in dashboard","value":"true"}]},{"type":"select","id":"folder_id","name":"Folder","options":[{"name":"My Scans","id":9},{"name":"Trash","id":8}]},{"type":"select","id":"use_dashboard","name":"Dashboard","default":false,"options":[{"name":"Enabled","value":"true"},{"name":"Disabled","value":"false"}]},{"type":"select","id":"scanner_id","name":"Scanner","default":null,"options":[{"id":"00000000-0000-0000-0000-00000000000000000000000000001","name":"US Cloud Scanner","type":"local","network_name":"Default","linked":true,"status":"on"},{"id":"1b895828-62a9-5084-8bc5-d4864a927fb10523d1e84e3fef44","name":"AP Singapore Cloud Scanners","type":"local","network_name":"Default","linked":true,"status":"on"},{"id":"cdf44a84-b547-b66c-d997-920aa1e897cc7165fe2e344196bb","name":"Demo Scanner","type":"local","network_name":"Default","linked":true,"status":"on"},{"id":"06ab826a-301d-7829-d2c4-37f400c0f949ea8cce60f523eeef","name":"EU Frankfurt Cloud Scanners","type":"local","network_name":"Default","linked":true,"status":"on"},{"id":"15e29fb5-c378-4803-37f7-67752912247e812e6cf942b4fd2e","name":"US East Cloud Scanners","type":"local","network_name":"Default","linked":true,"status":"on"},{"id":"37b315c1-f31f-cc8e-7e78-585c609fc1d7eba88f8d1e7d24b3","name":"US West Cloud Scanners","type":"local","network_name":"Default","linked":true,"status":"on"}]},{"type":"multi_select","deprecated_by":"target_groups","id":"asset_lists","name":"Target Groups","default":[],"options":[{"acls":[{"permissions":64,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"system","name":"nessus_ms_agent","id":1,"type":"user"},{"permissions":64,"owner":0,"display_name":"user@example.com","name":"user@example.com","id":2,"type":"user"}],"default_list":0,"type":"system","members":"host.domain.com, host1.domain.com","name":"modified_test_group_yet_again_and-again","owner":"nessus_ms_agent","shared":1,"user_permissions":64,"last_modification_date":1533583518,"creation_date":1533577166,"owner_id":1,"id":13},{"acls":[{"permissions":64,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"system","name":"nessus_ms_agent","id":1,"type":"user"},{"permissions":64,"owner":0,"display_name":"user@example.com","name":"user@example.com","id":2,"type":"user"}],"default_list":0,"type":"system","members":"testtest1","name":"test group","owner":"nessus_ms_agent","shared":1,"user_permissions":64,"last_modification_date":1533580228,"creation_date":1533246042,"owner_id":1,"id":12}]},{"type":"textarea","id":"text_targets","name":"Targets","placeholder":"Example: 172.204.81.57-172.204.81.60, 172.156.65.8/24, test.com","required":true},{"type":"file","id":"file_targets","name":"Upload Targets"}],"title":"Basic","groups":[{"title":"Schedule","name":"schedule"},{"inputs":[{"type":"textarea","name":"Email Recipient(s)","placeholder":"Example: me@example.com, you@example.com"}],"title":"Notifications","name":"email","filters":[]},{"title":"Permissions","name":"permissions","acls":null}],"sections":[]},"discovery":{"inputs":null,"modes":{"id":"discovery_mode","name":"mode","type":"ui_radio","default":"Host enumeration","options":[{"desc":"","name":"Host enumeration"},{"desc":"","name":"OS Identification"},{"desc":"","name":"Port scan (common ports)"},{"desc":"","name":"Port scan (all ports)"},{"desc":"","name":"Custom"}]},"title":"Discovery","groups":[{"inputs":[{"type":"ui_checkbox","name":"Ping the remote host","id":"ping_the_remote_host","default":"yes","options":[{"inputs":null,"name":"no"},{"inputs":null,"name":"yes","sections":[{"inputs":[{"type":"checkbox","id":"fast_network_discovery","label":"Use fast network discovery","default":"no","hint":"If a host responds to ping, Nessus attempts to avoid false positives, performing additional tests to verify the response did not come from a proxy or load balancer. Fast network discovery bypasses those additional tests."}],"title":"General Settings","name":"general"},{"inputs":[{"type":"checkbox","id":"arp_ping","label":"ARP","default":"yes"},{"name":"TCP","inputs":[{"type":"medium-entry","name":"Destination ports","id":"tcp_ping_dest_ports","default":"built-in"}],"type":"ui_group","id":"tcp_ping","default":"yes"},{"name":"ICMP","inputs":[{"type":"checkbox","id":"icmp_unreach_means_host_down","label":"Assume ICMP unreachable from the gateway means the host is down","default":"no"},{"type":"medium-entry","name":"Maximum number of retries","id":"icmp_ping_retries","default":"2","regex":"^\\d+$"}],"type":"ui_group","id":"icmp_ping","default":"yes"},{"type":"checkbox","id":"udp_ping","label":"UDP","default":"no"}],"title":"Ping Methods","name":"protocols"}]}]}],"title":"Host Discovery","name":"host_discovery","sections":[{"inputs":[{"type":"checkbox","id":"scan_network_printers","label":"Scan Network Printers","default":"no"},{"type":"checkbox","id":"scan_netware_hosts","label":"Scan Novell Netware hosts","default":"no"},{"type":"checkbox","id":"scan_ot_devices","label":"Scan Operational Technology devices","default":"no"}],"title":"Fragile Devices","name":"fragile_devices"},{"inputs":[{"type":"file","name":"List of MAC addresses","id":"wol_mac_addresses","callback":"wol_mac_upload"},{"type":"medium-entry","name":"Boot time wait (in minutes)","id":"wol_wait_time","default":"5","regex":"^\\d+$"}],"title":"Wake-on-LAN","name":"wol"},{"inputs":[{"type":"radio","name":"Network Type","id":"network_type","options":["Mixed (use RFC 1918)","Private LAN","Public WAN (Internet)"],"default":"Mixed (use RFC 1918)"}],"title":"Network Type","name":"network_type"}]},{"inputs":null,"title":"Port Scanning","name":"network_discovery","sections":[{"inputs":[{"type":"checkbox","id":"unscanned_closed","label":"Consider unscanned ports as closed","default":"no"},{"type":"medium-entry","id":"portscan_range","label":"Port scan range:","default":"default"}],"title":"Ports","name":"ports"},{"inputs":[{"id":"tcp_scanner","type":"ui_group","inputs":[{"type":"radio-group","id":"tcp_firewall_detection","label":"Override automatic firewall detection","default":"Automatic (normal)","options":["Automatic (normal)","Do not detect RST rate limitation (soft)","Ignore closed ports (aggressive)","Disabled (softer)"],"optionsLabels":["","Use soft detection","Use aggressive detection","Disable detection"]}],"no_windows":true,"no_darwin":true,"default":"no","name":"TCP"},{"name":"SYN","inputs":[{"type":"radio-group","id":"syn_firewall_detection","label":"Override automatic firewall detection","default":"Automatic (normal)","options":["Automatic (normal)","Do not detect RST rate limitation (soft)","Ignore closed ports (aggressive)","Disabled (softer)"],"optionsLabels":["","Use soft detection","Use aggressive detection","Disable detection"]}],"type":"ui_group","id":"syn_scanner","default":"yes"},{"type":"checkbox","id":"udp_scanner","label":"UDP","default":"no","hint":"Due to the nature of the protocol, it is generally not possible for a port scanner to tell the difference between open and filtered UDP ports. Enabling the UDP port scanner may dramatically increase the scan time and produce unreliable results. Consider using the netstat or SNMP port enumeration options instead if possible."}],"title":"Network Port Scanners","name":"network_scanners"}]}],"sections":[]},"report":{"inputs":null,"modes":[{"desc":"","id":"default","name":"Default","default":true},{"desc":"","id":"default_output","name":"Default","default":true},{"desc":"","custom":true,"id":"custom","name":"Custom"}],"title":"Report","groups":[],"sections":[{"inputs":[{"type":"checkbox","id":"reverse_lookup","label":"Designate hosts by their DNS name","default":"no"},{"type":"checkbox","id":"log_live_hosts","label":"Display hosts that respond to ping","default":"yes"},{"type":"checkbox","id":"display_unreachable_hosts","label":"Display unreachable hosts","default":"no"}],"title":"Output","name":"report_output"}]}},"filter_attributes":[{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"bid","readable_name":"Bugtraq ID"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_canvas","readable_name":"CANVAS Exploit Framework"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["CANVAS","D2ExploitPack","White_Phosphorus"]},"name":"canvas_package","readable_name":"CANVAS Package"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"Cert VU reference (ie: 10031)","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"cert","readable_name":"CERT Vulnerability ID"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_core","readable_name":"CORE Exploit Framework"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"cpe","readable_name":"CPE"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"CVE-YYYY-ID (ie: CVE-2011-0018)","type":"entry","regex":"^(CVE|CAN)-(1999|20[01][0-9])-[0-9]{4,}$"},"name":"cve","readable_name":"CVE"},{"operators":["lt","gt","eq","neq","match","nmatch"],"control":{"readable_regex":"7.5","type":"entry","regex":"^[0-9]+(\\.[0-9]+)?$"},"name":"cvss_base_score","readable_name":"CVSS Base Score"},{"operators":["lt","gt","eq","neq","match","nmatch"],"control":{"readable_regex":"4.2","type":"entry","regex":"^[0-9]+(\\.[0-9]+)$"},"name":"cvss_temporal_score","readable_name":"CVSS Temporal Score"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":"^CVSS2#E:(U|POC|F|H|ND)/RL:(OF|T|W|U|ND)/RC:(UC|UR|C|ND)$"},"name":"cvss_temporal_vector","readable_name":"CVSS Temporal Vector"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":"^CVSS2#AV:(L|A|N)/AC:(H|M|L)/Au:(N|S|M)/C:(N|P|C)/I:(N|P|C)/A:(N|P|C)$"},"name":"cvss_vector","readable_name":"CVSS Vector"},{"operators":["lt","gt","eq","neq","match","nmatch"],"control":{"readable_regex":"7.5","type":"entry","regex":"^[0-9]+(\\.[0-9]+)?$"},"name":"cvss3_base_score","readable_name":"CVSS v3.0 Base Score"},{"operators":["lt","gt","eq","neq","match","nmatch"],"control":{"readable_regex":"4.2","type":"entry","regex":"^[0-9]+(\\.[0-9]+)$"},"name":"cvss3_temporal_score","readable_name":"CVSS v3.0 Temporal Score"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":"^CVSS:3.0/E:(U|POC|F|H|ND)/RL:(OF|T|W|U|ND)/RC:(UC|UR|C|ND)$"},"name":"cvss3_temporal_vector","readable_name":"CVSS v3.0 Temporal Vector"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":"^CVSS:3.0/AV:(N|A|L|P)/AC:(L|H)/PR:(N,L,H)/UI:(N|R)/S:(U|C)/C:(H|L|N)/I:(H|L|N)/A:(H|L|N)$"},"name":"cvss3_vector","readable_name":"CVSS v3.0 Vector"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"CWE reference (ie: 200)","type":"entry","regex":"^([0-9]+)$"},"name":"cwe","readable_name":"CWE"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"default_account","readable_name":"Default/Known Accounts"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_d2_elliot","readable_name":"Elliot Exploit Framework"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"d2_elliot_name","readable_name":"Elliot Exploit Name"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_available","readable_name":"Exploit Available"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"edb-id","readable_name":"Exploit Database ID"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_exploithub","readable_name":"ExploitHub"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["Exploits are available","No exploit is required","No known exploits are available"]},"name":"exploitability_ease","readable_name":"Exploitability Ease"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploited_by_malware","readable_name":"Exploited By Malware"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploited_by_nessus","readable_name":"Exploited By Nessus"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"IAVA reference (ie: 2011-A-0151)","type":"entry","regex":"^[0-9]+-[A-Za-z]-[0-9]+$"},"name":"iava","readable_name":"IAVA ID"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"IAVB reference (ie: 2011-B-0151)","type":"entry","regex":"^[0-9]+-[A-Za-z]-[0-9]+$"},"name":"iavb","readable_name":"IAVB ID"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"IAVM Severity (ie: IV)","type":"entry","regex":"^[ivIV]+"},"name":"stig_severity","readable_name":"IAVM Severity"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"IAVT reference (ie: 2011-A-0151)","type":"entry","regex":"^[0-9]+-[A-Za-z]-[0-9]+$"},"name":"iavt","readable_name":"IAVT ID"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"in_the_news","readable_name":"In The News"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"malware","readable_name":"Malware"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"exploit_framework_metasploit","readable_name":"Metasploit Exploit Framework"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"metasploit_name","readable_name":"Metasploit Name"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"MS0X-YZT","type":"entry","regex":"^MS[0-9]+-[0-9]+$"},"name":"msft","readable_name":"Microsoft Bulletin"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"osvdb","readable_name":"OSVDB ID"},{"operators":["date-lt","date-gt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"},"name":"patch_publication_date","readable_name":"Patch Publication Date"},{"operators":["match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"description","readable_name":"Plugin Description"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9, ]+$","maxlength":9},"name":"plugin_id","readable_name":"Plugin ID"},{"operators":["date-lt","date-gt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"},"name":"plugin_modification_date","readable_name":"Plugin Modification Date"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"plugin_name","readable_name":"Plugin Name"},{"operators":["date-lt","date-gt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"},"name":"plugin_publication_date","readable_name":"Plugin Publication Date"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["local","remote"]},"name":"plugin_type","readable_name":"Plugin Type"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["None","Low","Medium","High","Critical"]},"name":"risk_factor","readable_name":"Risk Factor"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"NUMBER","type":"entry","regex":"^[0-9]+$","maxlength":18},"name":"secunia","readable_name":"Secunia ID"},{"operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"see_also","readable_name":"See Also"},{"operators":["match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"solution","readable_name":"Solution"},{"operators":["match","nmatch"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"name":"synopsis","readable_name":"Synopsis"},{"operators":["eq","neq"],"control":{"type":"dropdown","list":["true","false"]},"name":"unsupported_by_vendor","readable_name":"Unsupported By Vendor"},{"operators":["date-lt","date-gt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"},"name":"vuln_publication_date","readable_name":"Vulnerability Publication Date"}],"name":"discovery"}}}}}},"403":{"description":"Returned if you do not have permission to open the template."},"404":{"description":"Returned if Tenable.io cannot find the specified template."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/editor/policy/{policy_id}/families/{family_id}/plugins/{plugin_id}":{"get":{"summary":"Get plugin details","description":"Gets the details of the plugin associated with the scan or policy.

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"editor-plugin-description","tags":["Editor"],"parameters":[{"description":"The ID of the policy to look up.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the family to lookup within the policy.","required":true,"name":"family_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the plugin to lookup within the family.","required":true,"name":"plugin_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the plugin output.","content":{"application/json":{"schema":{"type":"object","properties":{"plugindescription":{"description":"The detailed information for a Tenable.io plugin.","type":"object","properties":{"severity":{"type":"string","description":"The severity level of the vulnerabilities targeted by the plugin"},"pluginname":{"type":"string","description":"The name of the plugin."},"pluginattributes":{"type":"object","description":"The attributes of the plugin, including synopsis, description, solution, and risk information."},"pluginfamily":{"type":"string","description":"The name of the plugin family."},"pluginid":{"type":"integer","description":"The ID of the plugin."}}}}},"examples":{"response":{"value":{"plugindescription":{"severity":null,"pluginname":"Ubuntu 10.04 LTS / 10.10 / 11.04 / 11.10 : clamav vulnerability (USN-1258-1)","pluginattributes":{"synopsis":"The remote Ubuntu host is missing a security-related patch.","description":"Stephane Chazelas discovered the bytecode engine of ClamAV improperly handled recursion under certain circumstances. This could allow a remote attacker to craft a file that could cause ClamAV to crash, resulting in a denial of service.\n\nNote that Tenable Network Security has extracted the preceding description block directly from the Ubuntu security advisory. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues.","risk_information":{"cvss_vector":"CVSS2#AV:N/AC:M/Au:N/C:N/I:N/A:P","risk_factor":"Medium","cvss_base_score":"4.3","cvss_temporal_score":"3.2","cvss_temporal_vector":"CVSS2#E:U/RL:OF/RC:C"},"ref_information":{"ref":[{"name":"bid","values":{"value":["50183"]},"url":"http://www.securityfocus.com/bid/"},{"name":"usn","values":{"value":["1258-1"]},"ext":"/","url":"http://www.ubuntu.com/usn/usn-"},{"name":"cve","values":{"value":["CVE-2011-3627"]},"url":"http://web.nvd.nist.gov/view/vuln/detail?vulnId="}]},"plugin_name":"Ubuntu 10.04 LTS / 10.10 / 11.04 / 11.10 : clamav vulnerability (USN-1258-1)","see_also":["https://usn.ubuntu.com/1258-1/"],"fname":"ubuntu_USN-1258-1.nasl","usn":"1258-1","plugin_information":{"plugin_version":"1.8","plugin_id":56777,"plugin_type":"local","plugin_publication_date":"2011/11/11","plugin_family":"Ubuntu Local Security Checks","plugin_modification_date":"2018/12/01"},"solution":"Update the affected libclamav6 package.","vuln_information":{"cpe":"cpe:/o:canonical:ubuntu_linux:10.04:-:lts\ncpe:/o:canonical:ubuntu_linux:10.10\ncpe:/o:canonical:ubuntu_linux:11.04\ncpe:/o:canonical:ubuntu_linux:11.10","exploitability_ease":"No known exploits are available","exploit_available":"false","patch_publication_date":"2011/11/10"}},"pluginfamily":"Ubuntu Local Security Checks","pluginid":"56777"}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/editor/{type}/{object_id}/audits/{file_id}":{"get":{"summary":"Download audit file","description":"Downloads the specified custom audit file associated with the scan or policy. The file ID can be found in the scan or policy details using the /editor/{type}/{object_id} endpoint.

Requires CAN EDIT [32] policy permissions. See Permissions.

","operationId":"editor-audits","tags":["Editor"],"parameters":[{"description":"The type of template to retrieve (scan or policy).","required":true,"name":"type","in":"path","schema":{"type":"string","enum":["scan","policy"]}},{"description":"The unique ID of the object.","required":true,"name":"object_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the file to export.","required":true,"name":"file_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the audit file.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"},"examples":{"response":{"value":"#\r\n# (C) 2013-2017 Tenable, Inc.\r\n#\r\n# This script is released under the Tenable Subscription License and\r\n# may not be used from within scripts released under another license\r\n# without authorization from Tenable Network Security, Inc.\r\n#\r\n# See the following licenses for details:\r\n#\r\n# http://static.tenable.com/prod_docs/Nessus_6_SLA_and_Subscription_Agreement.pdf\r\n#\r\n# @PROFESSIONALFEED@\r\n# $Revision: 1.0 $\r\n# $Date: 2018/01/02 $\r\n#\r\n# Description:\r\n#\r\n# This document consists of a list of general Red Hat Linux best practices as suggested by the IT-Grundschutz BSI-Standard 100-2.\r\n# Tenable has made a best effort to map the settings specified in the standard to a proprietary\r\n# .audit format that will be used by the Unix compliance module to perform the audit.\r\n#\r\n# See Also :\r\n# https://www.bsi.bund.de/cae/servlet/contentblob/471430/publicationFile/28223/standard_100-2_e_pdf.pdf\r\n# https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Grundschutz/download/it-grundschutz-kataloge_2005_pdf_en_zip.zip?__blob=publicationFile\r\n#\r\n#\r\n#BSI-100-2 Red Hat Linux 2005\r\n# [the rest of the audit spec file]..."}}}}},"403":{"description":"Returned if you do not have permission to export the audit file."},"404":{"description":"Returned if Tenable.io cannot find the specified audit file."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/exclusions":{"post":{"summary":"Create exclusion","description":"Creates a new exclusion.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"exclusions-create","tags":["Exclusions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"members":{"type":"string","description":"The targets that you want excluded from scans. Specify multiple targets as a comma-separated string. Targets can be in the following formats:\n - an individual IPv4 address (192.168.1.1)\n - a range of IPv4 addresses (192.168.1.1-192.168.1.255)\n - CIDR notation (192.168.2.0/24)\n - a fully-qualified domain name (FQDN) (host.domain.com)"},"schedule":{"type":"object","description":"The schedule parameters for the exclusion.","properties":{"enabled":{"type":"boolean","description":"If `true`, the exclusion schedule is active."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","description":"The recurrence rules for the exclusion.","properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY)."},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma-separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}},"network_id":{"type":"string","description":"The ID of the network object associated with scanners where Tenable.io applies the exclusion. The default network ID is `00000000-0000-0000-0000-000000000000`. To determine the ID of a custom network, use the [GET /networks](/reference#networks-list) endpoint. If you omit this parameter from the request message, Tenable.io automatically assigns the exclusion to the default network. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."}},"required":["name","members"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates the exclusion.","content":{"application/json":{"schema":{"type":"object","properties":{"schedule":{"type":"object","description":"The schedule parameters for the exclusion.","properties":{"enabled":{"type":"boolean","description":"If `true`, the exclusion schedule is active."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","description":"The recurrence rules for the exclusion.","properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY)."},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma-separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}},"id":{"type":"integer","description":"The unique ID of the exclusion."},"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"members":{"type":"string","description":"The targets that you want excluded from scans. Specify multiple targets as a comma-separated string. Targets can be in the following formats:\n - an individual IPv4 address (192.168.1.1)\n - a range of IPv4 addresses (192.168.1.1-192.168.1.255)\n - CIDR notation (192.168.2.0/24)\n - a fully-qualified domain name (FQDN) (host.domain.com)"},"creation_date":{"type":"integer","description":"The creation date of the exclusion in Unix time."},"network_id":{"type":"string","description":"The ID of the network object associated with scanners where Tenable.io applies the exclusion. The default network ID is `00000000-0000-0000-0000-000000000000`. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"last_modification_date":{"type":"integer","description":"The last modification date for the exclusion in Unix time."}}},"examples":{"response":{"value":{"schedule":{"endtime":null,"enabled":false,"rrules":null,"timezone":null,"starttime":null},"network_id":"00000000-0000-0000-0000-000000000000","last_modification_date":1544459404,"creation_date":1544459404,"members":"192.168.1.1-192.168.1.255,192.168.2.0/24,host.domain.com","description":null,"name":"Western Region","id":1}}}}}},"400":{"description":"Returned if your request message contains invalid parameters."},"403":{"description":"Returned if you do not have permission to create an exclusion."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to create the exclusion.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List exclusions","description":"Lists exclusions for your Tenable.io scans.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"exclusions-list","tags":["Exclusions"],"responses":{"200":{"description":"Returns the exclusions.","content":{"application/json":{"schema":{"type":"array","description":"A list of exclusion objects.","items":{"type":"object","properties":{"schedule":{"type":"object","description":"The schedule parameters for the exclusion.","properties":{"enabled":{"type":"boolean","description":"If `true`, the exclusion schedule is active."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","description":"The recurrence rules for the exclusion.","properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY)."},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma-separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}},"id":{"type":"integer","description":"The unique ID of the exclusion."},"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"members":{"type":"string","description":"The targets that you want excluded from scans. Specify multiple targets as a comma-separated string. Targets can be in the following formats:\n - an individual IPv4 address (192.168.1.1)\n - a range of IPv4 addresses (192.168.1.1-192.168.1.255)\n - CIDR notation (192.168.2.0/24)\n - a fully-qualified domain name (FQDN) (host.domain.com)"},"creation_date":{"type":"integer","description":"The creation date of the exclusion in Unix time."},"network_id":{"type":"string","description":"The ID of the network object associated with scanners where Tenable.io applies the exclusion. The default network ID is `00000000-0000-0000-0000-000000000000`. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"last_modification_date":{"type":"integer","description":"The last modification date for the exclusion in Unix time."}}}},"examples":{"response":{"value":{"exclusions":[{"schedule":{"endtime":null,"enabled":false,"rrules":null,"timezone":null,"starttime":null},"network_id":"00000000-0000-0000-0000-000000000000","last_modification_date":1544459404,"creation_date":1544459404,"members":"192.168.1.1-192.168.1.255,192.168.2.0/24,host.domain.com","description":null,"name":"Western Region","id":1}]}}}}}},"403":{"description":"Returned if you do not have permission to view the exclusions."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/exclusions/import":{"post":{"summary":"Import exclusion","description":"Import exclusions from an [exclusion import file](/docs/import-file-formats) that you have previously uploaded via the [POST /file/upload](/reference#file-upload) endpoint.\n\n**Note:** This endpoint does not support the network_id attribute in exclusion objects for import. Tenable.io automatically assigns imported exclusions to the default network object. To assign imported exclusions to a custom network, use the [PUT /exclusions/exclusion_id](/reference#exclusions-edit) endpoint after import. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio).

Requires STANDARD [32] user permissions. See Permissions.

","operationId":"exclusions-import","tags":["Exclusions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"The name of the file to import as provided by the response from file: upload."}},"required":["file"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully imports the exclusion file.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to import the exclusion.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/exclusions/{exclusion_id}":{"get":{"summary":"Get exclusion details","description":"Returns exclusion details.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"exclusions-details","tags":["Exclusions"],"parameters":[{"description":"The ID of the exclusion.","required":true,"name":"exclusion_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the exclusion details.","content":{"application/json":{"schema":{"type":"object","properties":{"schedule":{"type":"object","description":"The schedule parameters for the exclusion.","properties":{"enabled":{"type":"boolean","description":"If `true`, the exclusion schedule is active."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","description":"The recurrence rules for the exclusion.","properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY)."},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma-separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}},"id":{"type":"integer","description":"The unique ID of the exclusion."},"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"members":{"type":"string","description":"The targets that you want excluded from scans. Specify multiple targets as a comma-separated string. Targets can be in the following formats:\n - an individual IPv4 address (192.168.1.1)\n - a range of IPv4 addresses (192.168.1.1-192.168.1.255)\n - CIDR notation (192.168.2.0/24)\n - a fully-qualified domain name (FQDN) (host.domain.com)"},"creation_date":{"type":"integer","description":"The creation date of the exclusion in Unix time."},"network_id":{"type":"string","description":"The ID of the network object associated with scanners where Tenable.io applies the exclusion. The default network ID is `00000000-0000-0000-0000-000000000000`. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"last_modification_date":{"type":"integer","description":"The last modification date for the exclusion in Unix time."}}},"examples":{"response":{"value":{"schedule":{"endtime":null,"enabled":false,"rrules":null,"timezone":null,"starttime":null},"network_id":"00000000-0000-0000-0000-000000000000","last_modification_date":1544459404,"creation_date":1544459404,"members":"192.168.1.1-192.168.1.255,192.168.2.0/24,host.domain.com","description":null,"name":"Western Region","id":1}}}}}},"403":{"description":"Returned if you do not have permission to view the exclusion."},"404":{"description":"Returned if Tenable.io cannot find the specified exclusion."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update an exclusion","description":"Updates an exclusion.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"exclusions-edit","tags":["Exclusions"],"parameters":[{"description":"The ID of the exclusion to update.","required":true,"name":"exclusion_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the exclusion."},"description":{"type":"string","description":"The description of the exclusion."},"members":{"type":"string","description":"The targets that you want excluded from scans. Specify multiple targets as a comma-separated string. Targets can be in the following formats:\n - an individual IPv4 address (192.168.1.1)\n - a range of IPv4 addresses (192.168.1.1-192.168.1.255)\n - CIDR notation (192.168.2.0/24)\n - a fully-qualified domain name (FQDN) (host.domain.com)"},"schedule":{"type":"object","description":"The schedule parameters for the exclusion.","properties":{"enabled":{"type":"boolean","description":"If `true`, the exclusion schedule is active."},"starttime":{"type":"string","description":"The start time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"endtime":{"type":"string","description":"The end time of the exclusion formatted as `YYYY-MM-DD HH:MM:SS`."},"timezone":{"type":"string","description":"The timezone for the exclusion as returned by [scans: timezones](/reference#scans-timezones)."},"rrules":{"type":"object","description":"The recurrence rules for the exclusion.","properties":{"freq":{"type":"string","description":"The frequency of the rule (ONETIME, DAILY, WEEKLY, MONTHLY, YEARLY)."},"interval":{"type":"integer","description":"The interval of the rule."},"byweekday":{"type":"string","description":"A comma-separated string of days to repeat a WEEKLY freq rule on (SU,MO,TU,WE,TH,FR, or SA)."},"bymonthday":{"type":"integer","description":"The day of the month to repeat a MONTHLY freq rule on."}}}}},"network_id":{"type":"string","description":"The ID of the network object associated with scanners where Tenable.io applies the exclusion. The default network ID is `00000000-0000-0000-0000-000000000000`. To determine the ID of a custom network, use the [GET /networks](/reference#networks-list) endpoint. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."}}}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully modifies the exclusion.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to modify the exclusion."},"404":{"description":"Returned if Tenable.io cannot find the specified exclusion."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to change the exclusion.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete an exclusion","description":"Deletes an exclusion.

Requires SCAN MANAGER [40] user permissions. See Permissions.

","operationId":"exclusions-delete","tags":["Exclusions"],"parameters":[{"description":"The ID of the exclusion to delete.","required":true,"name":"exclusion_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the exclusion.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to delete the exclusion."},"404":{"description":"Returned if Tenable.io cannot find the specified exclusion."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/vulns/export":{"post":{"summary":"Export vulnerabilities","description":"Exports vulnerabilities that match the request criteria. \n\n**Important!**\nFor more information on using this endpoint, see guidelines and limitations described in [Retrieve Vulnerability Data from Tenable.io](/docs/retrieve-vulnerability-data-from-tenableio).

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"exports-vulns-request-export","tags":["Exports"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"num_assets":{"type":"integer","description":"The maximum number of vulnerabilities per exported chunk. Note that this number does not represent the number of assets per chunk. Instead, it is equal to the number of assets times the number of vulnerabilities on each asset. The range of supported chunk sizes is a minimum of 50 (the default size) to a maximum of 5,000. If you specify a value outside this range, the system uses the upper- or lower-bound value.","format":"int32"},"filters":{"type":"object","description":"Specifies filters for exported vulnerabilities. For example filters, see Refine Vulnerability Export Requests.","properties":{"cidr_range":{"description":"Restricts search for vulnerabilities to assets assigned an IP address within the specified CIDR range. For example, 0.0.0.0/0 restricts the search to 0.0.0.1 and 255.255.255.254.","type":"string"},"first_found":{"type":"integer","description":"Returns vulnerabilities that were first found between the specified date (in Unix time) and now.","format":"int64"},"last_found":{"type":"integer","description":"Returns vulnerabilities that were last found between the specified date (in Unix time) and now.","format":"int64"},"last_fixed":{"type":"integer","description":"Returns vulnerabilities that were fixed between the specified date (in Unix time) and now.","format":"int64"},"plugin_family":{"items":{"type":"string"},"description":"The plugin family of the exported vulnerabilities. This filter is case-sensitive. If your request omits this parameter, the export includes all vulnerabilities, regardless of plugin family. For a list of supported plugin family values, use the /plugins/families endpoint.","type":"array"},"network_id":{"type":"string","description":"The ID of the network object associated with scanners that detected the vulnerabilities you want to export. The default network ID is `00000000-0000-0000-0000-000000000000`. To determine the ID of a custom network, use the [GET /networks](/reference#networks-list) endpoint. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"severity":{"items":{"type":"string"},"description":"The severity of the vulnerabilities to include in the export. Defaults to all severity levels. The severity of a vulnerability is defined using the Common Vulnerability Scoring System (CVSS) base score. Supported array values are:\n - info—The vulnerability has a CVSS score of 0.\n - low—The vulnerability has a CVSS score between 0.1 and 3.9.\n - medium—The vulnerability has a CVSS score between 4.0 and 6.9.\n - high—The vulnerability has a CVSS score between 7.0 and 9.9.\n - critical—The vulnerability has a CVSS score of 10.0.","type":"array"},"since":{"type":"integer","description":"The start date (in Unix time) for the range of data you want to export. Use this filter in conjunction with the state filter as follows:\n - If the state filter is set to `open`, the export includes data for vulnerabilities that were first seen on or after the since date you specify.\n - If the state filter is set to `reopened`, the export includes data for vulnerabilities that were last seen on or after the since date you specify.\n - If the state filter is set to `fixed`, the export includes data for vulnerabilities that were fixed on or after the since date you specify.\n - If you do not include the state filter in your request, the export includes data for open vulnerabilities that were first seen on or after the since date you specify, AND reopened vulnerabilities that were last seen on or after the since date you specify.\n**Note:** This filter cannot be used in conjunction with the `first_found`, `last_found`, or `last_fixed` filters.","format":"int64"},"state":{"items":{"type":"string"},"description":"The state of the vulnerabilities you want the export to include. Supported, case-insensitive values are:\n - open—The vulnerability is currently present on a host.\n - reopened—The vulnerability was previously marked as fixed on a host, but has returned.\n - fixed—The vulnerability was present on a host, but is no longer detected.\n\nThis parameter is required if your request includes `first_found`, `last_found`, or `last_fixed` parameters. If your request omits this parameter, the export includes default states `open` and `reopened` only.","type":"array"},"tag.":{"items":{"type":"string"},"description":"Returns vulnerabilities on assets with the specified asset tags. The filter is defined as \"tag\", a period (\".\"), and the tag category name. The value of the filter is an array of tag values. For more information about tags, see the Tenable.io Vulnerability Management User Guide.","type":"array"},"vpr_score":{"description":"Returns vulnerabilities with the specified Vulnerability Priority Rating (VPR) score or scores. You can combine properties in this object to specify VPR ranges. For example, to export vulnerabilities greater than or equal to 9.0 but lesser than or equal to 9.9, the object would contain a `gte` property of 9.0 and an `lte` property of 9.9. \n\nFor more information about VPR, see Severity vs. VPR in the Tenable.io Vulnerability Management User Guide.","type":"object","properties":{"eq":{"type":"array","items":{"type":"integer"},"description":"Returns vulnerabilities with a VPR equal to the specified score or scores. This property cannot be combined with the following range operators: `lt`, `gt`, `lte`, or `gte`."},"neq":{"type":"array","items":{"type":"integer"},"description":"Returns vulnerabilities with a VPR not equal to the specified score or scores. This property can be combined with the `eq` property."},"gt":{"type":"integer","description":"Returns vulnerabilities with a VPR greater than the specified score. This property cannot be combined with the `eq` property."},"gte":{"type":"integer","description":"Returns vulnerabilities with a VPR greater than or equal to the specified score. This property cannot be combined with the `eq` property."},"lt":{"type":"integer","description":"Returns vulnerabilities with a VPR lesser than the specified score. This property cannot be combined with the `eq` property."},"lte":{"type":"integer","description":"Returns vulnerabilities with a VPR lesser than or equal to the specified score. This property cannot be combined with the `eq` property."}}}}}},"required":["num_assets"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully queues an export request.","content":{"application/json":{"schema":{"type":"object","properties":{"export_uuid":{"type":"string","description":"The UUID of the vulnerabilities export job."}}},"examples":{"response":{"value":{"export_uuid":"73376c41-1508-46b7-8587-483d159cd956"}}}}}},"400":{"description":"Returned if your request message contains an invalid filter."},"403":{"description":"Returned if you do not have permission to export vulnerabilities."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/vulns/export/{export_uuid}/status":{"get":{"summary":"Get vulnerabilities export status","description":"Returns the status of a vulnerability export request. Tenable.io processes the chunks in parallel, so the chunks may not complete in order.

Requires ADMINISTRATOR [64] user permissions. See Permissions.

","operationId":"exports-vulns-export-status","tags":["Exports"],"parameters":[{"description":"The UUID for the export request.","required":true,"name":"export_uuid","in":"path","schema":{"type":"string","description":"The unique identifier of an export request. This value corresponds to the value returned in the /vulns/export response message."}}],"responses":{"200":{"description":"Returns the status of the specified export job.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"The status of the export request. Possible values include:\n - QUEUED—Tenable.io has queued the export request until it completes other requests currently in process.\n - PROCESSING—Tenable.io has started processing the export request.\n - FINISHED—Tenable.io has completed processing the export request. The list of chunks is complete.\n - CANCELLED—An administrator has cancelled the export request.\n - ERROR—Tenable.io encountered an error while processing the export request. Tenable recommends that you retry the request. If the status persists on retry, contact Support."},"chunks_available":{"type":"array","description":"A list of completed chunks available for download.","items":{"type":"integer","format":"int32"}},"chunks_failed":{"type":"array","description":"A list of chunks for which the export process failed. If a chunk fails processing, submit the export request again. If the chunk continues to fail, contact Support.","items":{"type":"integer","format":"int32"}},"chunks_cancelled":{"type":"array","description":"A list of chunks for which the export process was cancelled. If a chunk fails processing, Tenable.io automatically cancels all subsequent chunks queued for export in the same request. ","items":{"type":"integer","format":"int32"}}}},"examples":{"response":{"value":{"status":"PROCESSING","chunks_available":[1,2,3,4,5,6,8],"chunks_failed":[],"chunks_cancelled":[]}}}}}},"403":{"description":"Returned if you do not have permission to view the export status."},"404":{"description":"Returned if Tenable.io cannot find an export job with the specified UUID."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
\n

429 Too Many Requests

\n
\n
\n
nginx
\n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/vulns/export/{export_uuid}/chunks/{chunk_id}":{"get":{"summary":"Download vulnerabilities chunk","description":"Downloads exported vulnerabilities chunk by ID as a JSON file. The response content type is `application/octet-stream`.\nChunks are available for download for up to 24 hours after they have been created. Tenable.io returns a 404 message for expired chunks.\nExport chunks do not include an attribute if that attribute is empty in the vulnerability record.
    A successful response message contains attributes that correspond CVSS codes; these codes are described fully in the following documents:
  • CVSSv2 codes in [A Complete Guide to the Common Vulnerability Scoring System, Version 2.0](https://www.first.org/cvss/v2/guide)
  • CVSSv3 codes in the [Common Vulnerability Scoring System v3.0: Specification Document](https://www.first.org/cvss/specification-document).

    • Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"exports-vulns-download-chunk","tags":["Exports"],"parameters":[{"description":"The UUID of the export request.","required":true,"name":"export_uuid","in":"path","schema":{"type":"string"}},{"description":"The ID of the chunk you want to export.","required":true,"name":"chunk_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if file is downloaded successfully.","content":{"application/json":{"schema":{"type":"object","description":"A chunk of vulnerabilities information.","properties":{"asset":{"type":"array","description":"Information about the asset where the scan detected the vulnerability.","items":{"type":"object","properties":{"agent_uuid":{"type":"string","description":"The UUID of the agent that performed the scan where the vulnerability was found."},"bios_uuid":{"type":"string","description":"The BIOS UUID of the asset where the vulnerability was found."},"device_type":{"type":"string","description":"The type of asset where the vulnerability was found."},"fqdn":{"type":"string","description":"The fully-qualified domain name of the asset where a scan found the vulnerability."},"hostname":{"type":"string","description":"The host name of the asset where a scan found the vulnerability."},"uuid":{"type":"string","description":"The UUID of the asset where a scan found the vulnerability."},"ipv6":{"type":"string","description":"The IPv6 address of the asset where a scan found the vulnerability."},"last_authenticated_results":{"type":"string","description":"The last date credentials were used successfully to scan the asset."},"last_unauthenticated_results":{"type":"string","description":"The last date when the asset was scanned without using credentials"},"mac_address":{"type":"string","description":"The MAC address of the asset where a scan found the vulnerability."},"netbios_name":{"type":"string","description":"The NETBIOS name of the asset where a scan found the vulnerability."},"netbios_workgroup":{"type":"string","description":"The NETBIOS workgroup of the asset where a scan found the vulnerability."},"operating_system":{"type":"string","description":"The operating system of the asset where a scan found the vulnerability."},"network_id":{"type":"string","description":"The ID of the network object associated with scanners that identified the asset. The default network ID is `00000000-0000-0000-0000-000000000000`. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"tracked":{"type":"boolean","description":"A value specifying whether Tenable.io tracks the asset in the asset management system. Tenable.io still assigns untracked assets identifiers in scan results, but these identifiers change with each new scan of the asset. This parameter is relevant to PCI-type scans and in certain cases where there is not enough information in a scan to identify the asset. Untracked assets appear in the scan history, but do not appear in workbenches or reports."}}}},"output":{"type":"string","description":"The text output of the Nessus scanner."},"plugin":{"type":"array","description":"Information about the plugin that detected the vulnerability.","items":{"type":"object","properties":{"bid":{"type":"integer","description":"The Bugtraq ID for the plugin."},"canvas_package":{"type":"string","description":"The name of the CANVAS exploit pack that includes the vulnerability."},"checks_for_default_account":{"type":"boolean","description":"A value specifying whether the plugin checks for default accounts."},"checks_for_malware":{"type":"boolean","description":"A value specifying whether the plugin checks for malware."},"cpe":{"type":"string","description":"The Common Platform Enumeration (CPE) number for the plugin."},"cve":{"type":"string","description":"The Common Vulnerability and Exposure (CVE) ID for the plugin."},"cvss3_base_score":{"type":"string","description":"The CVSSv3 base score (intrinsic and fundamental characteristics of a vulnerability that are constant over time and user environments)."},"cvss3_temporal_score":{"type":"string","description":"The CVSSv3 temporal score (characteristics of a vulnerability that change over time but not among user environments)."},"cvss3_temporal_vector":{"type":"array","description":"CVSSv3 temporal metrics for the vulnerability.","items":{"type":"object","properties":{"Exploitability":{"type":"string","description":"The CVSSv2 Exploit Maturity Code (E) for the vulnerability the plugin covers. Possible values include: \n - Unproven—Corresponds to the Unproven (U) value for the E metric\n - Proof-of-concept—Corresponds to the Proof-of-Concept (POC) value for the E metric\n - Functional—Corresponds to the Functional (F) value for the E metric\n - High—Corresponds to the High (H) value for the E metric\n - Not-defined—Corresponds to the Not Defined (ND) value for the E metric"},"RemediationLevel":{"type":"string","description":"The CVSSv3 Remediation Level (RL) temporal metric for the vulnerability the plugin covers. Possible values include: \n - O—Official Fix\n - T—Temporary Fix\n - W—Workaround\n - U—Unavailable\n - X—Not Defined"},"ReportConfidence":{"type":"string","description":"The CVSSv3 Report Confidence (RC) temporal metric for the vulnerability the plugin covers. Possible values include: \n - U—Unknown\n - R—Reasonable\n - C—Confirmed\n - X—Not Defined"}}}},"cvss3_vector":{"type":"array","description":"Additional CVSSv3 metrics for the vulnerability.","items":{"type":"object","properties":{"AccessComplexity":{"type":"string","description":"The CVSSv3 Access Complexity (AC) metric for the vulnerability the plugin covers. Possible values include: \n - H—High\n - M—Medium\n - L—Low"},"AccessVector":{"type":"string","description":"The CVSSv2 Attack Vector (AV) metric for the vulnerability the plugin covers. Possible values include:\n - Network—Corresponds to the Network (N) value for the AV metric. \n - Adjacent Network—Corresponds to the Adjacent Network (A) value for the AV metric. \n - Local—Corresponds to the Local (L) value for the AV metric"},"Authentication":{"type":"string","description":"The CVSSv2 Authentication (Au) metric for the vulnerability the plugin covers. Possible values include:\n - None required—Corresponds to the None (N) value for the Au metric. \n - Requires-single-instance—Corresponds to the Single (S) value for the Au metric. \n - Requires-multiple-instances—Corresponds to the Multiple (M) value for the Au metric"},"Availability-Impact":{"type":"string","description":"The CVSSv2 availability impact metric for the vulnerability the plugin covers. Possible values include: \n - H—High\n - L—Low\n - N—None"},"Confidentiality-Impact":{"type":"string","description":"The CVSSv3 confidentiality impact metric of the vulnerability the plugin covers to the vulnerable component. Possible values include: \n - H—High\n - L—Low\n - N—None"},"Integrity-Impact":{"type":"string","description":"The CVSSv3 integrity impact metric for the vulnerability the plugin covers. Possible values include:\n - H—High\n - L—Low\n - N—None"}}}},"cvss_base_score":{"type":"string","description":"The CVSSv2 base score (intrinsic and fundamental characteristics of a vulnerability that are constant over time and user environments)."},"cvss_temporal_score":{"type":"string","description":"The CVSSv2 temporal score (characteristics of a vulnerability that change over time but not among user environments)."},"cvss_temporal_vector":{"type":"array","description":"CVSSv2 temporal metrics for the vulnerability.","items":{"type":"object","properties":{"Exploitability":{"type":"string","description":"The CVSSv2 Exploitability (E) temporal metric for the vulnerability the plugin covers. Possible values include:\n - U—Unproven\n - POC—Proof-of-Concept\n - F—Functional\n - H—High\n - ND—Not Defined"},"RemediationLevel":{"type":"string","description":"The CVSSv2 Remediation Level (RL) temporal metric for the vulnerability the plugin covers. Possible values include: \n - OF—Official Fix\n - TF—Temporary Fix\n - W—Workaround\n - U—Unavailable\n - ND—Not Defined"},"ReportConfidence":{"type":"string","description":"The CVSSv2 Report Confidence (RC) temporal metric for the vulnerability the plugin covers. Possible values include: \n - UC—Unconfirmed\n - UR—Uncorroborated\n - C—Confirmed\n - ND—Not Defined"}}}},"cvss_vector":{"type":"array","description":"Additional CVSSv2 metrics for the vulnerability.","items":{"type":"object","properties":{"AccessComplexity":{"type":"string","description":"The CVSSv2 Access Complexity (AC) metric for the vulnerability the plugin covers. Possible values include:\n - H—High\n - M—Medium\n - L—Low"},"AccessVector":{"type":"string","description":"The CVSSv2 Access Vector (AV) metric for the vulnerability the plugin covers. Possible values include: \n - L—Local\n - A—Adjacent Network\n - N—Network"},"Authentication":{"type":"string","description":"The CVSSv2 Authentication (Au) metric for the vulnerability the plugin covers. Possible values include: \n - N—None\n - S—Single\n - M—Multiple"},"Availability-Impact":{"type":"string","description":"The CVSSv2 availability impact metric for the vulnerability the plugin covers. Possible values include: \n - N—None\n - P—Partial\n - C—Complete"},"Confidentiality-Impact":{"type":"string","description":"The CVSSv2 confidentiality impact metric for the vulnerability the plugin covers. Possible values include: \n - N—None\n - P—Partial\n - C—Complete"},"Integrity-Impact":{"type":"string","description":"The CVSSv2 integrity impact metric for the vulnerability the plugin covers. Possible values include: \n - N—None\n - P—Partial\n - C—Complete"}}}},"d2_elliot_name":{"type":"string","description":"The name of the exploit in the D2 Elliot Web Exploitation framework."},"description":{"type":"string","description":"Full text description of the vulnerability."},"exploit_available":{"type":"boolean","description":"A value specifying whether a public exploit exists for the vulnerability."},"exploit_framework_canvas":{"type":"boolean","description":"A value specifying whether an exploit exists in the Immunity CANVAS framework."},"exploit_framework_core":{"type":"boolean","description":"A value specifying whether an exploit exists in the CORE Impact framework."},"exploit_framework_d2_elliot":{"type":"boolean","description":"A value specifying whether an exploit exists in the D2 Elliot Web Exploitation framework."},"exploit_framework_exploithub":{"type":"boolean","description":"A value specifying whether an exploit exists in the ExploitHub framework."},"exploit_framework_metasploit":{"type":"boolean","description":"A value specifying whether an exploit exists in the Metasploit framework."},"exploitability_ease":{"type":"string","description":"Description of how easy it is to exploit the issue."},"exploited_by_malware":{"type":"boolean","description":"The vulnerability discovered by this plugin is known to be exploited by malware."},"exploited_by_nessus":{"type":"boolean","description":"A value specifying whether Nessus exploited the vulnerability during the process of identification."},"exploithub_sku":{"type":"string","description":"The SKU number of the exploit in the ExploitHub framework."},"family":{"type":"string","description":"The family to which plugin belongs."},"family_id":{"type":"integer","description":"The ID of the plugin family."},"has_patch":{"type":"boolean","description":"A value specifying whether the vendor has published a patch for the vulnerability."},"id":{"type":"integer","description":"The ID of the plugin that identified the vulnerability."},"in_the_news":{"type":"boolean","description":"A value specifying whether this plugin has received media attention (for example, ShellShock, Meltdown)."},"metasploit_name":{"type":"string","description":"The name of the related exploit in the Metasploit framework."},"ms_bulletin":{"type":"string","description":"The Microsoft security bulletin that the plugin covers."},"name":{"type":"string","description":"The name of the plugin that identified the vulnerability."},"patch_publication_date":{"type":"string","description":"The date on which the vendor published a patch for the vulnerability."},"modification_date":{"type":"string","description":"The date on which the plugin was last modified."},"publication_date":{"type":"string","description":"The date on which the plugin was published."},"risk_factor":{"type":"string","description":"The risk factor associated with the plugin. Possible values are: Low, Medium, High, or Critical."},"see_also":{"type":"string","description":"Links to external websites that contain helpful information about the vulnerability."},"solution":{"type":"string","description":"Remediation information for the vulnerability."},"stig_severity":{"type":"string","description":"Security Technical Implementation Guide (STIG) severity code for the vulnerability."},"synopsis":{"type":"string","description":"Brief description of the plugin or vulnerability."},"type":{"type":"string","description":"The general type of plugin check (for example, `local` or `remote`)."},"unsupported_by_vendor":{"type":"boolean","description":"Software found by this plugin is unsupported by the software's vendor (for example, Windows 95 or Firefox 3)."},"usn":{"type":"string","description":"Ubuntu security notice that the plugin covers."},"version":{"type":"string","description":"The version of the plugin used to perform the check."},"vuln_publication_date":{"type":"string","description":"The publication date of the plugin."},"xrefs":{"type":"string","description":"External references (for example, OSVDB, Secunia, or MS Advisory)."},"vpr":{"type":"object","description":"Information about the Vulnerability Priority Rating (VPR) for the vulnerability.","properties":{"score":{"type":"integer","description":"The Vulnerability Priority Rating (VPR) for the vulnerability. If a plugin is designed to detect multiple vulnerabilities, the VPR represents the highest value calculated for a vulnerability associated with the plugin. For more information, see Severity vs. VPR in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"drivers":{"type":"object","description":"The key drivers Tenable uses to calculate a vulnerability's VPR. For more information, see Vulnerability Priority Rating Drivers.","properties":{}},"updated":{"type":"string","description":"The ISO timestamp when Tenable.io last imported the VPR for this vulnerability. Tenable.io imports a VPR value the first time you scan a vulnerability on your network. Then, Tenable.io automatically re-imports new and updated VPR values daily."}}}}}},"port":{"type":"array","description":"Information about the port the scanner used to connect to the asset.","items":{"type":"object","properties":{"port":{"type":"string","description":"The port the scanner used to communicate with the asset."},"protocol":{"type":"string","description":"The protocol the scanner used to communicate with the asset."},"service":{"type":"string","description":"The service the scanner used to communicate with the asset."}}}},"recast_reason":{"type":"string","description":"The text that appears in the Comment field of the recast rule in the Tenable.io user interface."},"recast_rule_uuid":{"type":"string","description":"The UUID of the recast rule that applies to the plugin."},"scan":{"type":"array","description":"Information about the latest scan that detected the vulnerability.","items":{"type":"object","properties":{"completed_at":{"type":"string","description":"The ISO timestamp when the scan completed."},"schedule_uuid":{"type":"string","description":"The schedule UUID for the scan that found the vulnerability."},"started_at":{"type":"string","description":"The ISO timestamp when the scan started."},"uuid":{"type":"string","description":"The UUID of the scan that found the vulnerability."}}}},"severity":{"type":"string","description":"The severity of the vulnerability as defined using the Common Vulnerability Scoring System (CVSS) base score. Possible values include `info` (CVSS score of 0), `low` (CVSS score between 0.1 and 3.9), `medium` (CVSS score between 4.0 and 6.9), `high` (CVSS score between 7.0 and 9.9), and `critical` (CVSS score of 10.0)."},"severity_id":{"type":"integer","format":"int32","description":"The code for the severity assigned when a user recast the risk associated with the vulnerability. Possible values include: \n - 0—The vulnerability has a CVSS score of 0, which corresponds to the \"info\" severity level.\n - 1—The vulnerability has a CVSS score between 0.1 and 3.9, which corresponds to the \"low\" severity level.\n - 2—The vulnerability has a CVSS score between 4.0 and 6.9, which corresponds to the \"medium\" severity level.\n - 3—The vulnerability has a CVSS score between 7.0 and 9.9, which corresponds to the \"high\" severity level.\n - 4—The vulnerability has a CVSS score of 10.0, which corresponds to the \"critical\" severity level."},"severity_default_id":{"type":"integer","format":"int32","description":"The code for the severity originally assigned to a vulnerability before a user recast the risk associated with the vulnerability. Possible values are the same as for the `severity_id` attribute."},"severity_modification_type":{"type":"string","description":"The type of modification a user made to the vulnerability's severity. Possible values include: \n - none—No modification has been made.\n - recasted—A user in the Tenable.io user interface has recast the risk associated with the vulnerability. \n - accepted—A user in the Tenable.io user interface has accepted the risk associated with the vulnerability. \n\nFor more information about recast and accept rules, see the Tenable.io Vulnerability Management User Guide."},"first_found":{"type":"string","description":"The ISO date when a scan first detected the vulnerability on the asset."},"last_fixed":{"type":"string","description":"The ISO date when a scan no longer detects the previously detected vulnerability on the asset."},"last_found":{"type":"string","description":"The ISO date when a scan last detected the vulnerability on the asset."},"state":{"type":"string","description":"The state of the vulnerability as determined by the Tenable.io state service. Possible values include: \n - open—The vulnerability is currently present on an asset. \n - reopened—The vulnerability was previously marked as fixed on an asset, but has been detected again by a new scan. \n - fixed—The vulnerability was present on an asset, but is no longer detected."}}},"examples":{"response":{"value":{"asset":{"fqdn":"example.com","hostname":"172.106.217.225","uuid":"150dee8f-6090-4a9c-907c-54a1c39ddab0","ipv4":"172.156.65.8","operating_system":["Apple Mac OS X 10.5.8"],"network_id":"00000000-0000-0000-0000-000000000000","tracked":true},"output":"The observed version of Google Chrome is : \n Chrome/21.0.1180.90","plugin":{"cve":["CVE-2016-1620","CVE-2016-1614","CVE-2016-1613","CVE-2016-1612","CVE-2016-1618","CVE-2016-1617","CVE-2016-1616","CVE-2016-1615","CVE-2016-1619"],"cvss_base_score":9.3,"cvss_temporal_score":6.9,"cvss_temporal_vector":{"exploitability":"Unproven","remediation_level":"Official-fix","report_confidence":"Confirmed","raw":"E:U/RL:OF/RC:C"},"cvss_vector":{"access_complexity":"Medium","access_vector":"Network","authentication":"None required","availability_impact":"Complete","confidentiality_impact":"Complete","integrity_impact":"Complete","raw":"AV:N/AC:M/Au:N/C:C/I:C/A:C"},"description":"The version of Google Chrome on the remote host is prior to 48.0.2564.82 and is affected by the following vulnerabilities: \n\n - An unspecified vulnerability exists in Google V8 when handling compatible receiver checks hidden behind receptors. An attacker can exploit this to have an unspecified impact. No other details are available. (CVE-2016-1612)\n - A use-after-free error exists in `PDFium` due to improper invalidation of `IPWL_FocusHandler` and `IPWL_Provider` upon destruction. An attacker can exploit this to dereference already freed memory, resulting in the execution of arbitrary code. (CVE-2016-1613)\n - An unspecified vulnerability exists in `Blink` that is related to the handling of bitmaps. An attacker can exploit this to access sensitive information. No other details are available. (CVE-2016-1614)\n - An unspecified vulnerability exists in `omnibox` that is related to origin confusion. An attacker can exploit this to have an unspecified impact. No other details are available. (CVE-2016-1615)\n - An unspecified vulnerability exists that allows an attacker to spoof a displayed URL. No other details are available. (CVE-2016-1616)\n - An unspecified vulnerability exists that is related to history sniffing with HSTS and CSP. No other details are available. (CVE-2016-1617)\n - A flaw exists in `Blink` due to the weak generation of random numbers by the ARC4-based random number generator. An attacker can exploit this to gain access to sensitive information. No other details are available. (CVE-2016-1618)\n - An out-of-bounds read error exists in `PDFium` in file `fx_codec_jpx_opj.cpp` in the `sycc4{22,44}_to_rgb()` functions. An attacker can exploit this to cause a denial of service by crashing the application linked using the library. (CVE-2016-1619)\n - Multiple vulnerabilities exist, the most serious of which allow an attacker to execute arbitrary code via a crafted web page. (CVE-2016-1620)\n - A flaw in `objects.cc` is triggered when handling cleared `WeakCells`, which may allow a context-dependent attacker to have an unspecified impact. No further details have been provided. (CVE-2016-2051)","family":"Web Clients","family_id":1000020,"has_patch":false,"id":9062,"name":"Google Chrome < 48.0.2564.82 Multiple Vulnerabilities","risk_factor":"HIGH","see_also":["http://googlechromereleases.blogspot.com/2016/01/beta-channel-update_20.html"],"solution":"Update the Chrome browser to 48.0.2564.82 or later.","synopsis":"The remote host is utilizing a web browser that is affected by multiple vulnerabilities.","vpr":{"score":5.9,"drivers":{"age_of_vuln":{"lower_bound":366,"upper_bound":730},"exploit_code_maturity":"UNPROVEN","cvss_impact_score_predicted":false,"cvss3_impact_score":5.9,"threat_intensity_last28":"VERY_LOW","threat_sources_last28":["No recorded events"],"product_coverage":"LOW"},"updated":"2019-02-07T10:08:58Z"}},"port":{"port":0,"protocol":"TCP"},"scan":{"completed_at":"2018-05-23T20:59:47Z","schedule_uuid":"413765fb-e941-7eea-ca8b-0a79182a2806e1b6640fe8a2217b","started_at":"2018-05-23T20:59:47Z","uuid":"e2c070ae-ec37-d9ff-f003-2e89b7e5e1ab8af3a9957a077904"},"severity":"high","severity_id":3,"severity_default_id":3,"severity_modification_type":"NONE","first_found":"2018-05-23T20:59:47Z","last_found":"2018-05-23T20:59:47Z","state":"OPEN"}}}}}},"400":{"description":"Returned if the chunk ID is invalid or the chunk is not ready for download."},"403":{"description":"Returned if you do not have permission to export vulnerabilities."},"404":{"description":"Returned if Tenable.io cannot find a chunk with the specified UUID."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/vulns/export/{export_uuid}/cancel":{"post":{"summary":"Cancel vuln export","description":"Cancels the specified export job. If you cancel an export job, Tenable.io finishes any chunk that is currently processing, terminates the processing of any unprocessed chunks, and updates the job status to `CANCELLED`. If a cancelled job includes completed chunks, you can download those chunks for three days after cancellation.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"exports-vulns-export-cancel","tags":["Exports"],"parameters":[{"description":"The UUID for the export request.","required":true,"name":"export_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully cancels the specified export request.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Text describing the export job status, `CANCELLED`."}}},"examples":{"response":{"value":{"status":"CANCELLED"}}}}}},"400":{"description":"Returned if Tenable.io cannot cancel the request."},"401":{"description":"Returned if Tenable.io cannot find an export job for the specified UUID."},"403":{"description":"Returned if you do not have permission to cancel export jobs."},"404":{"description":"Returned if Tenable.io cannot find an export with the specified UUID."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/vulns/export/status":{"get":{"summary":"Get vuln export jobs","description":"Retrieves a list of vulnerability export jobs. This list includes the 1,000 most recent export jobs regardless of status. However, this list includes completed jobs only if the job completed in the previous three days.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"exports-vulns-export-status-recent","tags":["Exports"],"responses":{"200":{"description":"Returns a list of recent vulnerability export jobs.","content":{"application/json":{"schema":{"type":"object","properties":{"exports":{"type":"array","items":{"description":"Information about the export job.","type":"object","properties":{"uuid":{"type":"string","description":"The UUID for the export request."},"status":{"type":"string","description":"The status of the export request. Possible values include:\n - QUEUED—Tenable.io has queued the export request until it completes other requests currently in process.\n - PROCESSING—Tenable.io has started processing the export request.\n - FINISHED—Tenable.io has completed processing the export request. The list of chunks is complete.\n - CANCELLED—An administrator has cancelled the export request.\n - ERROR—Tenable.io encountered an error while processing the export request. Tenable recommends that you retry the request. If the status persists on retry, contact Support."},"chunks_available":{"type":"array","description":"A list of completed chunks available for download.","items":{"type":"integer"}},"total_chunks":{"type":"integer","description":"The total number of chunks associated with the export job as a whole."},"finished_chunks":{"type":"integer","description":"The number of chunks that have been processed and are available for download."},"filters":{"type":"object","description":"The filters used in the export job request. For a list of possible filters, see the [POST /vulns/export](/reference#exports-vulns-export-request-export) and [POST /assets/export](/reference#exports-assets-request-export) endpoints.","properties":{}},"num_assets_per_chunk":{"type":"integer","description":"The number of assets contained in each export chunk."},"created":{"type":"integer","description":"The Unix timestamp when the export job was created."}}}}}},"examples":{"response":{"value":{"exports":[{"uuid":"5174760d-c669-4185-b8ee-e99148be1a37","status":"FINISHED","total_chunks":0,"finished_chunks":0,"filters":{"state":["OPEN","REOPENED"],"tags":{},"cidr_range":"172.204.81.57/24","since":0,"first_found":0,"last_found":0,"last_fixed":0},"num_assets_per_chunk":0,"created":1566420439782},{"uuid":"9ec1b0d5-26f1-4078-9929-9eccd8a2e514","status":"FINISHED","total_chunks":5,"finished_chunks":5,"filters":{"since":0,"first_found":0,"last_found":0,"last_fixed":0},"num_assets_per_chunk":5,"created":1566418722831}]}}}}}},"400":{"description":"Returned if your request message is invalid."},"403":{"description":"Returned if you do not have permissions for the request."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/assets/export":{"post":{"summary":"Export assets","description":"Exports all assets that match the request criteria.\n\n**Important!**\nFor more information on using this endpoint, see guidelines and limitations described in [Retrieve Vulnerability Data from Tenable.io](/docs/retrieve-vulnerability-data-from-tenableio).

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"exports-assets-request-export","tags":["Exports"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"chunk_size":{"type":"integer","description":"Specifies the number of assets per exported chunk. The range is 100-10000. If you specify a value outside of that range, Tenable.io returns a 400 error. Using smaller chunks size can improve performance.","format":"int32"},"filters":{"type":"object","description":"Specifies filters for exported assets. To return all assets, omit the filters object. If your request specifies multiple filters, the system combines the filters using the AND search operator.","properties":{"created_at":{"type":"integer","description":"Returns all assets created later than the date specified. The specified date must be in the Unix timestamp format.","format":"int64"},"updated_at":{"type":"integer","description":"Returns all assets updated later than the date specified. The specified date must be in the Unix timestamp format.","format":"int64"},"terminated_at":{"type":"integer","description":"Returns all assets terminated later than the date specified. The specified date must be in the Unix timestamp format.","format":"int64"},"deleted_at":{"type":"integer","description":"Returns all assets deleted later than the date specified. The specified date must in the Unix timestamp format.","format":"int64"},"first_scan_time":{"type":"integer","description":"Returns all assets with a first scan time later than the date specified. The specified date must be in the Unix timestamp format.","format":"int64"},"last_authenticated_scan_time":{"type":"integer","description":"Returns all assets with a last credentialed scan time later than the date specified. The specified date must be in the Unix timestamp format.","format":"int64"},"last_assessed":{"type":"integer","description":"Returns all assets with a last assessed time later than the date specified. Tenable.io considers an asset assessed if it has been scanned by a credentialed or non-credentialed scan. The specified date must be in the Unix timestamp format.","format":"int64"},"servicenow_sysid":{"type":"boolean","description":"If `true`, returns all assets that have a ServiceNow Sys ID, regardless of value. If `false`, returns all assets that do not have a ServiceNow Sys ID."},"sources":{"items":{"type":"string"},"description":"Returns assets that have the specified source. An asset source is the entity that reported the asset details. Sources can include sensors, connectors, and API imports. If your request specifies multiple sources, Tenable.io returns all assets that have been seen by any of the specified sources.\n\nThe items in the sources array must correspond to the names of the sources as defined in your organization's implementation of Tenable.io. Commonly used names include:\n - AWS—You obtained the asset data from an Amazon Web Services connector.\n - NESSUS_AGENT—You obtained the asset data obtained from a Nessus agent scan.\n - PVS—You obtained the asset data from a Nessus Network Monitor (NNM) scan.\n - NESSUS_SCAN—You obtained the asset data from a Nessus scan.\n - WAS—You obtained the asset data from a Web Application Scanning scan.","type":"array"},"has_plugin_results":{"type":"boolean","description":"If `true`, Tenable.io returns all assets that have plugin results. If `false`, Tenable.io returns all assets that do not have plugin results. An asset may not have plugin results if the asset details originated from a connector, an API import, or a discovery scan, rather than a vulnerabilities scan."},"tag.":{"type":"string","description":"Returns all assets with the specified tag. The filter is defined as \"tag\", a period (\".\"), and the tag category name. The value of the filter is the tag value. For more information about tags, see [Tenable.io Vulnerability Management User Guide](https://docs.tenable.com/cloud/Content/Settings/TagFormatAndApplication.htm)."},"network_id":{"type":"string","description":"The ID of the network object associated with scanners that identified the assets you want to export. The default network ID is `00000000-0000-0000-0000-000000000000`. To determine the ID of a custom network, use the [GET /networks](/reference#networks-list) endpoint. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."}}}},"required":["chunk_size"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully queues an export request.","content":{"application/json":{"schema":{"type":"object","properties":{"export_uuid":{"type":"string"}}},"examples":{"response":{"value":{"export_uuid":"5091ab8e-fb57-41d1-8166-9422f3b39aea"}}}}}},"400":{"description":"Returned if your request message contains any invalid filters or is itself invalid."},"403":{"description":"Returned if you do not have permission to export assets."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/assets/export/{export_uuid}/status":{"get":{"summary":"Get assets export status","description":"Returns the status of an assets export request. Tenable.io processes the chunks in parallel, so the chunks may not complete in order.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"exports-assets-export-status","tags":["Exports"],"parameters":[{"description":"The UUID for the export request.","required":true,"name":"export_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the status of the specified export job.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"The status of the export request. Possible values include:\n - QUEUED—Tenable.io has queued the export request until it completes other requests currently in process.\n - PROCESSING—Tenable.io has started processing the export request.\n - FINISHED—Tenable.io has completed processing the export request. The list of chunks is complete.\n - CANCELLED—An administrator has cancelled the export request.\n - ERROR—Tenable.io encountered an error while processing the export request. Tenable recommends that you retry the request. If the status persists on retry, contact Support."},"chunks_available":{"type":"array","description":"A comma-separated list of completed chunks available for download.","items":{"type":"integer","format":"int32"}}}},"examples":{"response":{"value":{"status":"FINISHED","chunks_available":[1,2,3,4]}}}}}},"403":{"description":"Returned if you do not have permission to view the export status."},"404":{"description":"Returned if Tenable.io cannot find an export with the specified UUID."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/assets/export/{export_uuid}/chunks/{chunk_id}":{"get":{"summary":"Download assets chunk","description":"Downloads exported assets chunk by ID. Tenable.io processes the chunks in parallel, so the chunks may not complete in order. Chunks are available for download for up to 24 hours after they have been created. Tenable.io returns a 404 message for expired chunks.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"exports-assets-download-chunk","tags":["Exports"],"parameters":[{"description":"The UUID of the export request.","required":true,"name":"export_uuid","in":"path","schema":{"type":"string"}},{"description":"The ID of the asset chunk you want to export.","required":true,"name":"chunk_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the file is downloaded successfully. The response body excludes an attribute if the attribute is empty in the asset record.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The UUID of the asset in Tenable.io."},"has_agent":{"type":"boolean","description":"Specifies whether a Nessus agent scan identified the asset."},"has_plugin_results":{"type":"boolean","description":"Specifies whether the asset has plugin results associated with it."},"created_at":{"type":"string","description":"The time and date when Tenable.io created the asset record."},"terminated_at":{"type":"string","description":"The time and date when a user terminated the Amazon Web Service (AWS) virtual machine instance of the asset."},"terminated_by":{"type":"string","description":"The user who terminated the AWS instance of the asset."},"updated_at":{"type":"string","description":"The time and date when the asset record was last updated."},"deleted_at":{"type":"string","description":"The time and date when a user deleted the asset record. When a user deletes an asset record, Tenable.io retains the record until the asset ages out of the license count."},"deleted_by":{"type":"string","description":"The user who deleted the asset record."},"first_seen":{"type":"string","description":"The time and date when a scan first identified the asset."},"last_seen":{"type":"string","description":"The time and date of the scan that most recently identified the asset."},"first_scan_time":{"type":"string","description":"The time and date of the first scan run against the asset."},"last_scan_time":{"type":"string","description":"The time and date of the last scan run against the asset."},"last_authenticated_scan_date":{"type":"string","description":"The time and date of the last credentialed scan run on the asset."},"last_licensed_scan_date":{"type":"string","description":"The time and date of the last scan that identified the asset as licensed. Tenable.io categorizes an asset as licensed if a scan of that asset has returned results from a non-discovery plugin within the last 90 days."},"azure_vm_id":{"type":"string","description":"The unique identifier of the Microsoft Azure virtual machine instance. For more information, see \"Accessing and Using Azure VM Unique ID\" in the Microsoft Azure documentation."},"azure_resource_id":{"type":"string","description":"The unique identifier of the resource in the Azure Resource Manager. For more information, see the Azure Resource Manager Documentation."},"gcp_project_id":{"type":"string","description":"The unique identifier of the virtual machine instance in Google Cloud Platform (GCP)."},"gcp_zone":{"type":"string","description":"The customized name of the project to which the virtual machine instance belongs in GCP. For more information see \"Creating and Managing Projects\" in the GCP documentation."},"gcp_instance_id":{"type":"string","description":"The zone where the virtual machine instance runs in GCP. For more information, see \"Regions and Zones\" in the GCP documentation."},"aws_ec2_instance_ami_id":{"type":"string","description":"The unique identifier of the Linux AMI image in Amazon Elastic Compute Cloud (Amazon EC2). For more information, see the Amazon Elastic Compute Cloud Documentation."},"aws_ec2_instance_id":{"type":"string","description":"The unique identifier of the Linux instance in Amazon EC2. For more information, see the Amazon Elastic Compute Cloud Documentation."},"agent_uuid":{"type":"string","description":"The unique identifier of the Nessus agent that identified the asset."},"bios_uuid":{"type":"string","description":"The BIOS UUID of the asset."},"network_id":{"type":"string","description":"The ID of the network object associated with scanners that identified the asset. The default network ID is `00000000-0000-0000-0000-000000000000`. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"network_name":{"type":"string","description":"The ID of the network object associated with scanners that identified the asset. The default network name is `Default`. All other network names are user-defined. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"aws_owner_id":{"type":"string","description":"The canonical user identifier for the AWS account associated with the virtual machine instance. For example, `79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be`. For more information, see AWS Account Identifiers in the AWS documentation."},"aws_availability_zone":{"type":"string","description":"The availability zone where Amazon Web Services hosts the virtual machine instance, for example, `us-east-1a``. Availability zones are subdivisions of AWS regions. For more information, see \"Regions and Availability Zones\" in the AWS documentation."},"aws_region":{"type":"string","description":"The region where AWS hosts the virtual machine instance, for example, `us-east-1`. For more information, see \"Regions and Availability Zones\" in the AWS documentation."},"aws_vpc_id":{"type":"string","description":"The unique identifier for the virtual public cloud that hosts the AWS virtual machine instance. For more information, see the Amazon Virtual Private Cloud User Guide."},"aws_ec2_instance_group_name":{"type":"string","description":"The virtual machine instance's group in AWS."},"aws_ec2_instance_state_name":{"type":"string","description":"The state of the virtual machine instance in AWS at the time of the scan."},"aws_ec2_instance_type":{"type":"string","description":"The type of instance in AWS EC2."},"aws_subnet_id":{"type":"string","description":"The unique identifier of the AWS subnet where the virtual machine instance was running at the time of the scan."},"aws_ec2_product_code":{"type":"string","description":"The product code associated with the AMI used to launch the virtual machine instance in AWS EC2."},"aws_ec2_name":{"type":"string","description":"The name of the virtual machine instance in AWS EC2."},"mcafee_epo_guid":{"type":"string","description":"The unique identifier of the asset in McAfee ePolicy Orchestrator (ePO). For more information, see the McAfee documentation."},"mcafee_epo_agent_guid":{"type":"string","description":"The unique identifier of the McAfee ePO agent that identified the asset. For more information, see the McAfee documentation."},"servicenow_sysid":{"type":"string","description":"The unique record identifier of the asset in ServiceNow. For more information, see the ServiceNow documentation."},"bigfix_asset_id":{"type":"string","description":"The unique identifiers of the asset in IBM BigFix. For more information, see the IBM BigFix documentation."},"agent_names":{"type":"array","description":"The names of any Nessus agents that scanned and identified the asset.","items":{"type":"string"}},"installed_software":{"type":"array","description":"A list of Common Platform Enumeration (CPE) values that represent software applications a scan identified as present on an asset. This attribute supports the CPE 2.2 format. For more information, see the \"Component Syntax\" section of the [CPE Specification, Version 2.2](https://cpe.mitre.org/files/cpe-specification_2.2.pdf). For assets identified in Tenable scans, this attribute contains data only if a scan using [Nessus Plugin ID 45590](https://www.tenable.com/plugins/nessus/45590) has evaluated the asset.\n\n**Note:** If no scan detects an application within 30 days of the scan that originally detected the application, Tenable.io considers the detection of that application expired. As a result, the next time a scan evaluates the asset, Tenable.io removes the expired application from the installed_software attribute. This activity is logged as a `remove` type of `attribute_change` update in the asset activity log.","items":{"type":"string"}},"ipv4s":{"type":"array","description":"The IPv4 addresses that scans have associated with the asset record.","items":{"type":"string"}},"ipv6s":{"type":"array","description":"The IPv6 addresses that scans have associated with the asset record.","items":{"type":"string"}},"fqdns":{"type":"array","description":"The fully-qualified domain names that scans have associated with the asset record.","items":{"type":"string"}},"mac_addresses":{"type":"array","description":"The MAC addresses that scans have associated with the asset record.","items":{"type":"string"}},"netbios_names":{"type":"array","description":"The NetBIOS names that scans have associated with the asset record.","items":{"type":"string"}},"operating_systems":{"type":"array","description":"The operating systems that scans have associated with the asset record.","items":{"type":"string"}},"system_types":{"type":"array","description":"The system types as reported by Plugin ID 54615. Possible values include `router`, `general-purpose`, `scan-host`, and `embedded`.","items":{"type":"string"}},"hostnames":{"type":"array","description":"The hostnames that scans have associated with the asset record.","items":{"type":"string"}},"ssh_fingerprints":{"type":"array","description":"The SSH key fingerprints that scans have associated with the asset record.","items":{"type":"string"}},"qualys_asset_ids":{"type":"array","description":"The Asset ID of the asset in Qualys. For more information, see the Qualys documentation.\n\n**Note:** Tenable is enabling Qualys asset import for customers in a rolling fashion. For more information, contact your Tenable representative.","items":{"type":"string"}},"qualys_host_ids":{"type":"array","description":"The Host ID of the asset in Qualys. For more information, see the Qualys documentation.\n\n**Note:** Tenable is enabling Qualys asset import for customers in a rolling fashion. For more information, contact your Tenable representative.","items":{"type":"string"}},"manufacturer_tpm_ids":{"type":"array","description":"The manufacturer's unique identifiers of the Trusted Platform Module (TPM) associated with the asset.","items":{"type":"string"}},"symantec_ep_hardware_keys":{"type":"array","description":"The hardware keys for the asset in Symantec Endpoint Protection.","items":{"type":"string"}},"sources":{"type":"array","description":"The sources of the scans that identified the asset.","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the entity that reported the asset details. Sources can include sensors, connectors, and API imports. Source names can be customized by your organization (for example, you specify a name when you import asset records). If your organization does not customize source names, system-generated names include:\n - AWS—You obtained the asset data from an Amazon Web Services connector.\n - NESSUS_AGENT—You obtained the asset data obtained from a Nessus agent scan.\n - PVS—You obtained the asset data from a Nessus Network Monitor (NNM) scan.\n - NESSUS_SCAN—You obtained the asset data from a Nessus scan.\n - WAS—You obtained the asset data from a Web Application Scanning scan."},"first_seen":{"type":"string","description":"The ISO timestamp when the source first reported the asset."},"last_seen":{"type":"string","description":"The ISO timestamp when the source last reported the asset."}}}},"tags":{"type":"array","description":"Category tags assigned to the asset in Tenable.io.","items":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the tag."},"key":{"type":"string","description":"The tag category (the first half of the category:value pair)."},"value":{"type":"string","description":"The tag value (the second half of the category:value pair)."},"added_by":{"type":"string","description":"The UUID of the user who assigned the tag to the asset."},"added_at":{"type":"string","description":"The ISO timestamp when the tag was assigned to the asset."}}}},"network_interfaces":{"type":"array","description":"The network interfaces that scans identified on the asset.","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the interface."},"mac_address":{"type":"array","description":"The MAC addresses of the interface.","items":{"type":"string"}},"ipv4":{"type":"array","description":"One or more IPv4 addresses belonging to the interface.","items":{"type":"string"}},"ipv6":{"type":"array","description":"One or more IPv6 addresses belonging to the interface.","items":{"type":"string"}},"fqdn":{"type":"array","description":"One or more FQDN belonging to the interface.","items":{"type":"string"}}}}}}},"examples":{"response":{"value":{"id":"60d5a1e7-aec0-45d3-b196-c2356b1567b9","has_agent":false,"has_plugin_results":true,"created_at":"2017-12-14T20:40:44.535Z","terminated_at":null,"terminated_by":null,"updated_at":"2018-02-23T22:27:58.599Z","deleted_at":null,"deleted_by":null,"first_seen":"2017-12-14T20:40:23.447Z","last_seen":"2018-02-23T22:27:52.869Z","first_scan_time":"2017-12-14T20:40:23.447Z","last_scan_time":"2018-02-23T22:27:52.869Z","last_authenticated_scan_date":null,"last_licensed_scan_date":"2018-02-23T22:27:52.869Z","azure_vm_id":null,"azure_resource_id":null,"gcp_project_id":null,"gcp_zone":null,"gcp_instance_id":null,"aws_ec2_instance_ami_id":null,"aws_ec2_instance_id":null,"agent_uuid":null,"bios_uuid":null,"aws_owner_id":null,"aws_availability_zone":null,"aws_region":null,"aws_vpc_id":null,"aws_ec2_instance_group_name":null,"aws_ec2_instance_state_name":null,"aws_ec2_instance_type":null,"aws_subnet_id":null,"aws_ec2_product_code":null,"aws_ec2_name":null,"mcafee_epo_guid":null,"mcafee_epo_agent_guid":null,"servicenow_sysid":null,"bigfix_asset_id":null,"agent_names":[],"installed_software":["cpe:/a:apple:itunes:12.8","cpe:/a:apple:quicktime:7.7.3","cpe:/a:openbsd:openssh:6.9","cpe:/a:google:chrome"],"ipv4s":["172.1.2.57"],"ipv6s":[],"fqdns":["172-1-2-57.lightspeed.hstntx.sbcglobal.net"],"mac_addresses":[],"netbios_names":[],"operating_systems":[],"system_types":[],"hostnames":[],"ssh_fingerprints":[],"qualys_asset_ids":[],"qualys_host_ids":[],"manufacturer_tpm_ids":[],"symantec_ep_hardware_keys":[],"sources":[{"name":"NESSUS_SCAN","first_seen":"2017-12-14T20:40:23.447Z","last_seen":"2018-02-23T22:27:52.869Z"}],"tags":[{"uuid":"6ee5761f-5c99-434b-aecb-e09b755921b7","key":"Geographic Area","value":"APAC","added_by":"e7ecb50b-1330-4a8c-b8e5-ee00ec8c46f8","added_at":"2018-02-13T14:53:13.817Z"}],"network_interfaces":[{"name":"enccw0.0.1234","mac_address":["00-00-5E-00-53-00","00-00-5E-00-53-FF"],"ipv4":["172.204.81.57","172.82.157.177"],"ipv6":["2001:DB8:1234:1234/32"],"fqdn":["example.com"]}]}}}}}},"400":{"description":"Returned if the chunk ID is invalid or the chunk is not ready for download."},"403":{"description":"Returned if you do not have permission to export assets."},"404":{"description":"Returned if Tenable.io cannot find a chunk with the specified UUID, or if the chunk with the specified UUID has expired."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/assets/export/{export_uuid}/cancel":{"post":{"summary":"Cancel asset export","description":"Cancels the specified export job. If you cancel an export job, Tenable.io finishes any chunk that is currently processing, terminates the processing of any unprocessed chunks, and updates the job status to `CANCELLED`. If a cancelled job includes completed chunks, you can download those chunks for three days after cancellation.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"exports-assets-export-cancel","tags":["Exports"],"parameters":[{"description":"The UUID for the export request.","required":true,"name":"export_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully cancels the specified export request.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Text describing the export job status, `CANCELLED`."}}},"examples":{"response":{"value":{"status":"CANCELLED"}}}}}},"400":{"description":"Returned if your request message is invalid.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer","description":"The HTTP error code."},"message":{"type":"string","description":"Text describing the error condition Tenable.io encountered. Possible values include:\n\n - Cannot cancel a completed job\n - Export UUID is invalid"}}},"examples":{"response":{"value":{"status":400,"message":"Cannot cancel a completed job"}}}}}},"404":{"description":"Returned if Tenable.io cannot find an export job with the specified UUID.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer","description":"The HTTP error code."},"message":{"type":"string","description":"Text describing the error condition Tenable.io encountered."}}},"examples":{"response":{"value":{"status":404,"message":"Job with UUID: {export_uuid} not found"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/assets/export/status":{"get":{"summary":"Get asset export jobs","description":"Retrieves a list of asset export jobs. This list includes the 1,000 most recent export jobs regardless of status. However, this list includes completed jobs only if the job completed in the previous three days.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"exports-assets-export-status-recent","tags":["Exports"],"responses":{"200":{"description":"Returns a list of recent asset export jobs.","content":{"application/json":{"schema":{"type":"object","properties":{"exports":{"type":"array","items":{"description":"Information about the export job.","type":"object","properties":{"uuid":{"type":"string","description":"The UUID for the export request."},"status":{"type":"string","description":"The status of the export request. Possible values include:\n - QUEUED—Tenable.io has queued the export request until it completes other requests currently in process.\n - PROCESSING—Tenable.io has started processing the export request.\n - FINISHED—Tenable.io has completed processing the export request. The list of chunks is complete.\n - CANCELLED—An administrator has cancelled the export request.\n - ERROR—Tenable.io encountered an error while processing the export request. Tenable recommends that you retry the request. If the status persists on retry, contact Support."},"chunks_available":{"type":"array","description":"A list of completed chunks available for download.","items":{"type":"integer"}},"total_chunks":{"type":"integer","description":"The total number of chunks associated with the export job as a whole."},"finished_chunks":{"type":"integer","description":"The number of chunks that have been processed and are available for download."},"filters":{"type":"object","description":"The filters used in the export job request. For a list of possible filters, see the [POST /vulns/export](/reference#exports-vulns-export-request-export) and [POST /assets/export](/reference#exports-assets-request-export) endpoints.","properties":{}},"num_assets_per_chunk":{"type":"integer","description":"The number of assets contained in each export chunk."},"created":{"type":"integer","description":"The Unix timestamp when the export job was created."}}}}}},"examples":{"response":{"value":{"exports":[{"uuid":"9ec1b0d5-26f1-4079-9839-9eccf8a2e513","status":"FINISHED","filters":{"since":0,"first_found":0,"last_found":0,"last_fixed":0},"total_chunks":5,"finished_chunks":5,"num_assets_per_chunk":5,"created":1566418722831}]}}}}}},"400":{"description":"Returned if your request message is invalid."},"403":{"description":"Returned if you do not have permissions for the request."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/file/upload":{"post":{"summary":"Upload file","description":"Uploads a file.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"file-upload","tags":["File"],"parameters":[{"description":"Send value of `1` when uploading an encrypted file.","required":false,"name":"no_enc","in":"query","schema":{"type":"integer","format":"int32","enum":[0,1]}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"Filedata":{"type":"string","description":"The file to upload.","format":"binary"}}}}}},"responses":{"200":{"description":"Returns the name of the successfully uploaded file.","content":{"application/json":{"schema":{"type":"object","properties":{"fileuploaded":{"type":"string","description":"The name of the uploaded file. If the file with the same name already exists, Tenable.io appends an underscore with a number, for example scan-targets_1.txt. Use this attribute value when referencing the file for subsequent requests."}}},"examples":{"response":{"value":{"fileuploaded":"scan_targets.txt"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io cannot upload the file.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/filters/scans/agents":{"get":{"summary":"List agent filters","description":"Lists the filtering, sorting, and pagination capabilities available for agent records on endpoints that support them.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"filters-agents-filters","tags":["Filters"],"responses":{"200":{"description":"Returns the filtering, sorting, and pagination capabilities.","content":{"application/json":{"schema":{"type":"object","properties":{"wildcard_fields":{"description":"Array of strings which represent each field which supports \"wildcard\" search. Wildcard search is a mechanism where multiple fields of a record are filtered against one specific filter string. If any one of the supported fields' values matches against the filter string, then the record matches the wildcard filter. Note that for a record to be returned, it must pass the wildcard filter (if there is one) AND the set of standard filters.","type":"array","items":{"type":"string"}},"filters":{"type":"array","description":"A list of filters available for the record type.","items":{"type":"object","properties":{"name":{"type":"string","description":"The field name to be used in request query strings when applying the filter."},"readable_name":{"type":"string","description":"The filter's display label."},"control":{"type":"object","properties":{"readable_regex":{"type":"string","description":"Provides a human-readable \"hint\" which describes what the filter string should look like."},"type":{"type":"string","description":"The type of UI control which represents the filter."},"regex":{"type":"string","description":"A regex which can be used by a user interface to validate input."}}},"operators":{"description":"Strings which represent the comparison operations which can be used for the filter.","type":"array","items":{"type":"string"}}}}},"sort":{"type":"object","description":"The sorting parameters supported for data returned by the endpoint.","properties":{"max_sort_fields":{"type":"integer","description":"Maximum number of fields that may be specified for sorting. If this is parameter is not present, any number of fields may be used."},"sortable_fields":{"description":"Fields by which the returned list of records may be sorted.","type":"array","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"wildcard_fields":["core_version","distro","groups"],"filters":[{"name":"core_version","readable_name":"Version","operators":["eq","neq","match","nmatch"],"control":{"readable_regex":"X.Y.Z","type":"entry","regex":".*"}},{"name":"distro","readable_name":"Distro","operators":["match","nmatch"],"control":{"readable_regex":"Distro Name (e.g. es7-x86-64)","type":"entry","regex":".*"}},{"name":"groups","readable_name":"Member of Group","operators":["eq","neq"],"control":{"type":"dropdown","list":[{"name":"None","id":-1},{"name":"slibs","id":106592}]}}],"sort":{"sortable_fields":["core_version","distro","ip","last_connect","last_scanned","name","platform","plugin_feed_id"]}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/filters/workbenches/assets":{"get":{"summary":"List asset filters","description":"Lists the filtering, sorting, and pagination capabilities available for assets on endpoints that support them. For more information about these filters, see [Workbench Filters](/docs/workbench-filters].

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"filters-assets-filter","tags":["Filters"],"responses":{"200":{"description":"Returns the filtering, sorting, and pagination capabilities.","content":{"application/json":{"schema":{"type":"object","properties":{"wildcard_fields":{"description":"Array of strings which represent each field which supports \"wildcard\" search. Wildcard search is a mechanism where multiple fields of a record are filtered against one specific filter string. If any one of the supported fields' values matches against the filter string, then the record matches the wildcard filter. Note that for a record to be returned, it must pass the wildcard filter (if there is one) AND the set of standard filters.","type":"array","items":{"type":"string"}},"filters":{"type":"array","description":"A list of filters available for the record type.","items":{"type":"object","properties":{"name":{"type":"string","description":"The field name to be used in request query strings when applying the filter."},"readable_name":{"type":"string","description":"The filter's display label."},"control":{"type":"object","properties":{"readable_regex":{"type":"string","description":"Provides a human-readable \"hint\" which describes what the filter string should look like."},"type":{"type":"string","description":"The type of UI control which represents the filter."},"regex":{"type":"string","description":"A regex which can be used by a user interface to validate input."}}},"operators":{"description":"Strings which represent the comparison operations which can be used for the filter.","type":"array","items":{"type":"string"}}}}},"sort":{"type":"object","description":"The sorting parameters supported for data returned by the endpoint.","properties":{"max_sort_fields":{"type":"integer","description":"Maximum number of fields that may be specified for sorting. If this is parameter is not present, any number of fields may be used."},"sortable_fields":{"description":"Fields by which the returned list of records may be sorted.","type":"array","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"filters":[{"control":{"list":[{"name":"AWS Connector","value":"AWS"},{"name":"Agent","value":"NESSUS_AGENT"},{"name":"Microsoft Azure","value":"AZURE"},{"name":"NNM","value":"PVS"},{"name":"Nessus","value":"NESSUS_SCAN"},{"name":"Qualys Connector","value":"QUALYS"},{"name":"Web Application Scan","value":"WAS"}],"type":"dropdown_multi"},"name":"sources","operators":["set-has","set-hasnot","set-hasonly"],"readable_name":"Source"},{"control":{"list":["true","false"],"type":"dropdown"},"name":"belongs_to_access_group","operators":["eq"],"readable_name":"Belongs to Access Group"},{"control":{"list":[{"name":"Headquarters","value":"Headquarters"},{"name":"Los Angeles","value":"Los Angeles"},{"name":"New York","value":"New York"}],"type":"dropdown_multi"},"name":"tag.Location","operators":["set-has","set-hasnot"],"readable_name":"Location"}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/filters/workbenches/vulnerabilities":{"get":{"summary":"List vulnerability filters","description":"Returns the filters available for the Vulnerabilities Workbench. For more information about these filters, see [Workbench Filters](/docs/workbench-filters].

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-vulnerabilities-filters","tags":["Filters"],"responses":{"200":{"description":"Returns the filters object.","content":{"application/json":{"schema":{"type":"object","properties":{"wildcard_fields":{"description":"Array of strings which represent each field which supports \"wildcard\" search. Wildcard search is a mechanism where multiple fields of a record are filtered against one specific filter string. If any one of the supported fields' values matches against the filter string, then the record matches the wildcard filter. Note that for a record to be returned, it must pass the wildcard filter (if there is one) AND the set of standard filters.","type":"array","items":{"type":"string"}},"filters":{"type":"array","description":"A list of filters available for the record type.","items":{"type":"object","properties":{"name":{"type":"string","description":"The field name to be used in request query strings when applying the filter."},"readable_name":{"type":"string","description":"The filter's display label."},"control":{"type":"object","properties":{"readable_regex":{"type":"string","description":"Provides a human-readable \"hint\" which describes what the filter string should look like."},"type":{"type":"string","description":"The type of UI control which represents the filter."},"regex":{"type":"string","description":"A regex which can be used by a user interface to validate input."}}},"operators":{"description":"Strings which represent the comparison operations which can be used for the filter.","type":"array","items":{"type":"string"}}}}},"sort":{"type":"object","description":"The sorting parameters supported for data returned by the endpoint.","properties":{"max_sort_fields":{"type":"integer","description":"Maximum number of fields that may be specified for sorting. If this is parameter is not present, any number of fields may be used."},"sortable_fields":{"description":"Fields by which the returned list of records may be sorted.","type":"array","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"filters":[{"name":"host.id","readable_name":"Asset ID","control":{"type":"entry","regex":"[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*","readable_regex":"01234567-abcd-ef01-2345-6789abcdef01"},"operators":["eq","neq","match","nmatch"],"group_name":null},{"name":"plugin.attributes.bid","readable_name":"Bugtraq ID","control":{"type":"entry","regex":"^[0-9]+(,[0-9]+)*","readable_regex":"NUMBER","maxlength":18},"operators":["eq","neq","match","nmatch"],"group_name":null},{"name":"plugin.attributes.exploit_framework_canvas","readable_name":"CANVAS Exploit Framework","control":{"type":"dropdown","list":["true","false"]},"operators":["eq","neq"],"group_name":null}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/filters/credentials":{"get":{"summary":"List credential filters","description":"Returns the filtering, sorting, and pagination capabilities available for scan credentials on endpoints that support them.

      Requires BASIC (16) user permissions. See Permissions.

      ","operationId":"credentials-filters","tags":["Filters"],"responses":{"200":{"description":"Returns the filtering, sorting, and pagination capabilities for scan credentials.","content":{"application/json":{"schema":{"type":"object","properties":{"wildcard_fields":{"description":"Array of strings which represent each field which supports \"wildcard\" search. Wildcard search is a mechanism where multiple fields of a record are filtered against one specific filter string. If any one of the supported fields' values matches against the filter string, then the record matches the wildcard filter. Note that for a record to be returned, it must pass the wildcard filter (if there is one) AND the set of standard filters.","type":"array","items":{"type":"string"}},"filters":{"type":"array","description":"A list of filters available for the record type.","items":{"type":"object","properties":{"name":{"type":"string","description":"The field name to be used in request query strings when applying the filter."},"readable_name":{"type":"string","description":"The filter's display label."},"control":{"type":"object","properties":{"readable_regex":{"type":"string","description":"Provides a human-readable \"hint\" which describes what the filter string should look like."},"type":{"type":"string","description":"The type of UI control which represents the filter."},"regex":{"type":"string","description":"A regex which can be used by a user interface to validate input."}}},"operators":{"description":"Strings which represent the comparison operations which can be used for the filter.","type":"array","items":{"type":"string"}}}}},"sort":{"type":"object","description":"The sorting parameters supported for data returned by the endpoint.","properties":{"max_sort_fields":{"type":"integer","description":"Maximum number of fields that may be specified for sorting. If this is parameter is not present, any number of fields may be used."},"sortable_fields":{"description":"Fields by which the returned list of records may be sorted.","type":"array","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"wildcard_fields":["name","description","type"],"filters":[{"name":"name","readable_name":"Credential Name","control":{"readable_regex":"TEXT","type":"entry","regex":".*"},"operators":["eq","neq","match","nmatch"]},{"name":"type","readable_name":"Credential Type","control":{"type":"dropdown_multi","list":[{"id":"Windows","name":"Windows"}]},"operators":["eq","neq"]},{"name":"created_date","readable_name":"Created Date","control":{"type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$","readable_regex":"YYYY/MM/DD"},"operators":["date-lt","date-gt","date-eq","date-neq"]}],"sort":{"sortable_fields":["name","type","created_date"]}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/filters/scans/reports":{"get":{"summary":"List scan filters","description":"Lists the filtering, sorting, and pagination capabilities available for scan records on endpoints that support them.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"filters-scan-filters","tags":["Filters"],"responses":{"200":{"description":"Returns the filtering, sorting, and pagination capabilities.","content":{"application/json":{"schema":{"type":"object","properties":{"filters":{"type":"array","description":"A list of filters available for the record type.","items":{"type":"object","properties":{"name":{"type":"string","description":"The field name to be used in request query strings when applying the filter."},"readable_name":{"type":"string","description":"The filter's display label."},"control":{"type":"object","properties":{"readable_regex":{"type":"string","description":"Provides a human-readable \"hint\" which describes what the filter string should look like."},"type":{"type":"string","description":"The type of UI control which represents the filter."},"regex":{"type":"string","description":"A regex which can be used by a user interface to validate input."}}},"operators":{"description":"Strings which represent the comparison operations which can be used for the filter.","type":"array","items":{"type":"string"}}}}}}},"examples":{"response":{"value":{"filters":[{"name":"host.id","readable_name":"Asset ID","control":{"type":"entry","regex":"[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*","readable_regex":"01234567-abcd-ef01-2345-6789abcdef01"},"operators":["eq","neq","match","nmatch"],"group_name":null},{"name":"plugin.attributes.exploit_framework_canvas","readable_name":"CANVAS Exploit Framework","control":{"type":"dropdown","list":["true","false"]},"operators":["eq","neq"],"group_name":null},{"name":"plugin.name","readable_name":"Plugin Name","control":{"type":"entry","regex":".*","readable_regex":"TEXT"},"operators":["eq","neq","match","nmatch"],"group_name":null}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/filters/scans/reports/history":{"get":{"summary":"List scan history filters","description":"Lists the filtering, sorting, and pagination capabilities available for scan history records on endpoints that support them.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"filters-scan-history-filters","tags":["Filters"],"responses":{"200":{"description":"Returns the filtering, sorting, and pagination capabilities.","content":{"application/json":{"schema":{"type":"object","properties":{"wildcard_fields":{"description":"Array of strings which represent each field which supports \"wildcard\" search. Wildcard search is a mechanism where multiple fields of a record are filtered against one specific filter string. If any one of the supported fields' values matches against the filter string, then the record matches the wildcard filter. Note that for a record to be returned, it must pass the wildcard filter (if there is one) AND the set of standard filters.","type":"array","items":{"type":"string"}},"filters":{"type":"array","description":"A list of filters available for the record type.","items":{"type":"object","properties":{"name":{"type":"string","description":"The field name to be used in request query strings when applying the filter."},"readable_name":{"type":"string","description":"The filter's display label."},"control":{"type":"object","properties":{"readable_regex":{"type":"string","description":"Provides a human-readable \"hint\" which describes what the filter string should look like."},"type":{"type":"string","description":"The type of UI control which represents the filter."},"regex":{"type":"string","description":"A regex which can be used by a user interface to validate input."}}},"operators":{"description":"Strings which represent the comparison operations which can be used for the filter.","type":"array","items":{"type":"string"}}}}},"sort":{"type":"object","description":"The sorting parameters supported for data returned by the endpoint.","properties":{"max_sort_fields":{"type":"integer","description":"Maximum number of fields that may be specified for sorting. If this is parameter is not present, any number of fields may be used."},"sortable_fields":{"description":"Fields by which the returned list of records may be sorted.","type":"array","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"wildcard_fields":[],"filters":[{"name":"start_date","readable_name":"Start Date","operators":["date-gt","date-lt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"}},{"name":"end_date","readable_name":"End Date","operators":["date-gt","date-lt","date-eq","date-neq"],"control":{"readable_regex":"YYYY/MM/DD","type":"datefield","regex":"^[0-9]{4}/[0-9]{2}/[0-9]{2}$"}},{"name":"status","readable_name":"Scan Status","operators":["eq","neq","nmatch","match"],"control":{"readable_regex":"TEXT","type":"entry","regex":".*"}}],"sort":{"sortable_fields":["start_date","end_date","status"]}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/folders":{"post":{"summary":"Create folder","description":"Creates a new folder for the current user.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"folders-create","tags":["Folders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the folder.\n**Note:** Tenable.io does not allow the following special characters in folder names: `( ) [ ] : ; = + / | ? , ^ % & $`"}},"required":["name"]}}}},"responses":{"200":{"description":"Returns the new folder ID.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The ID of the created folder."}}},"examples":{"response":{"value":{"id":55}}}}}},"400":{"description":"Returned if the folder name is invalid."},"403":{"description":"Returned if you do not have permission to create a folder."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to create the folder.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List folders","description":"Lists the current user's scan folders.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"folders-list","tags":["Folders"],"responses":{"200":{"description":"Returns the folder list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the folder."},"name":{"type":"string","description":"The name of the folder."},"type":{"type":"string","description":"The type of the folder (main, trash, custom)."},"default_tag":{"type":"integer","description":"Whether or not the folder is the default (1 or 0)."},"custom":{"type":"integer","description":"The custom status of the folder (1 or 0)."},"unread_count":{"type":"integer","description":"The number of unread scans in the folder."}}}},"examples":{"response":{"value":{"folders":[{"unread_count":0,"custom":0,"default_tag":0,"type":"trash","name":"Trash","id":18},{"unread_count":6,"custom":0,"default_tag":1,"type":"main","name":"My Scans","id":19},{"unread_count":0,"custom":1,"default_tag":0,"type":"custom","name":"Linux Scans","id":50},{"unread_count":0,"custom":1,"default_tag":0,"type":"custom","name":"Daily Scans","id":55}]}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/folders/{folder_id}":{"put":{"summary":"Rename folder","description":"Renames a folder for the current user.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"folders-edit","tags":["Folders"],"parameters":[{"description":"The ID of the folder to edit.","required":true,"name":"folder_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the folder.\n**Note:** Tenable.io does not allow the following special characters in folder names: `( ) [ ] : ; = + / | ? , ^ % & $`"}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully renames the folder.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you attempt to rename a system folder."},"404":{"description":"Returned if Tenable.io cannot find the specified folder."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to rename the folder.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete folder","description":"Deletes a folder.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"folders-delete","tags":["Folders"],"parameters":[{"description":"The ID of the folder to delete.","required":true,"name":"folder_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the folder.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you attempt to delete a system folder."},"404":{"description":"Returned if Tenable.io cannot find the specified folder."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the folder.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/networks":{"post":{"summary":"Create network","description":"Creates a network object that you associate with scanners and scanner groups. \n\n**Note:** You cannot add AWS assets to network objects. For AWS assets, use the network segmentation provided by AWS instead.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-create","tags":["Networks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"The name of the network object. This name must be unique within your Tenable.io instance, cannot duplicate the name of a previously deleted network, and cannot be `default`. \n \n**Note:** You can add a maximum of 50,000 network objects to an individual Tenable.io instance.","type":"string"},"description":{"description":"The description of the network object.","type":"string"}},"required":["name"]},"example":{"name":"Area 51","description":"classified"}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates a network object.","content":{"application/json":{"schema":{"type":"object","properties":{"owner_uuid":{"type":"string","description":"The UUID of the owner of the network object. The owner of the network object does not have any additional permissions above administrator permissions."},"created":{"type":"integer","description":"The date (in Unix milliseconds) when the network object was created."},"modified":{"type":"integer","description":"The date (in Unix milliseconds) when the network object was last updated."},"uuid":{"type":"string","description":"The UUID of the network object."},"name":{"type":"string","description":"The name of the network object."},"description":{"type":"string","description":"The description of the network object."},"is_default":{"type":"boolean","description":"Indicates whether the network object is the default network object. The default network object is a system-generated object that contains any scanners and scanner groups not yet assigned to a custom network object. You cannot update or delete the default network object."},"created_by":{"type":"string","description":"The UUID of the user who created the network object."},"modified_by":{"type":"string","description":"The UUID of the user who last updated the network object."},"deleted_at":{"type":"integer","description":"The date (in Unix time) when the network object was deleted. This attribute is present for deleted network objects only. To view deleted network objects, use the `includeDeleted` parameter on the [/GET networks](/reference#networks-list) endpoint."},"created_in_seconds":{"type":"integer","description":"The date (in Unix seconds) when the network object was created."},"modified_in_seconds":{"type":"integer","description":"The date (in Unix seconds) when the network object was last updated."}}},"examples":{"response":{"value":{"owner_uuid":"08d242c3-9553-4ccc-835d-0c17ed942cdf","created":1557526802865,"modified":1557526802865,"scanner_count":0,"uuid":"1b17ee49-8f17-4c8f-8536-bff9aff73429","name":"Headquarters","description":"Network devices at Columbia, MD location","is_default":false,"created_by":"18d242c3-9553-4ccc-835d-0d17ed942cef","modified_by":"18d242c3-9553-4ccc-835d-0d17ed942cef","created_in_seconds":1557526802,"modified_in_seconds":1557526802}}}}}},"400":{"description":"Returned if Tenable.io encounters invalid JSON in request body."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have permission to create network objects."},"409":{"description":"Returned if a network object with the same name already exists."},"415":{"description":"Returned if the request payload is in an unsupported format."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List networks","description":"Lists network objects for your organization.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-list","tags":["Networks"],"parameters":[{"description":"A filter condition in the following format: `field:operator:value`. For network objects, you can only filter on the `name` field, using the following operators: \n* eq—The name of the returned network object is equal to the text you specify. \n* neq—The returned list of network objects excludes the network object where the name is equal to the text you specify. \n* match—The returned list includes network objects where the name contains the text you specify at least partially.\n\nYou can specify multiple `f` parameters, separated by ampersand (&) characters. If you specify multiple `f` parameters, use the `ft` parameter to specify how Tenable.io applies the multiple filter conditions.","required":false,"name":"f","in":"query","schema":{"type":"string"}},{"description":"The operator that Tenable.io applies if multiple \\`f\\` parameters are present. The `OR` operator is the only supported value. If you omit this parameter and multiple `f` parameters are present, Tenable.io applies the `OR` operator by default.","required":false,"name":"ft","in":"query","schema":{"type":"string"}},{"description":"Maximum number of objects requested (or service imposed limit if not in request).","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Offset from request (or zero).","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Objects that specify the sort order for the returned data.","required":false,"name":"sort","in":"query","schema":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The field on which Tenable.io sorts the results."},"order":{"type":"string","description":"The direction of the sort order. Supported values are `asc` (ascending) and `desc` (descending)."}}}}},{"description":"Indicates whether Tenable.io includes deleted network objects in the response message. Deleted network objects contain the additional attribute, `deleted\\_at`, which specifies the date (in Unix time) when the network object was deleted.","name":"includeDeleted","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Returns a list of network objects.","content":{"application/json":{"schema":{"type":"object","properties":{"networks":{"type":"array","items":{"type":"object","properties":{"owner_uuid":{"type":"string","description":"The UUID of the owner of the network object. The owner of the network object does not have any additional permissions above administrator permissions."},"created":{"type":"integer","description":"The date (in Unix milliseconds) when the network object was created."},"modified":{"type":"integer","description":"The date (in Unix milliseconds) when the network object was last updated."},"uuid":{"type":"string","description":"The UUID of the network object."},"name":{"type":"string","description":"The name of the network object."},"description":{"type":"string","description":"The description of the network object."},"is_default":{"type":"boolean","description":"Indicates whether the network object is the default network object. The default network object is a system-generated object that contains any scanners and scanner groups not yet assigned to a custom network object. You cannot update or delete the default network object."},"created_by":{"type":"string","description":"The UUID of the user who created the network object."},"modified_by":{"type":"string","description":"The UUID of the user who last updated the network object."},"deleted_at":{"type":"integer","description":"The date (in Unix time) when the network object was deleted. This attribute is present for deleted network objects only. To view deleted network objects, use the `includeDeleted` parameter on the [/GET networks](/reference#networks-list) endpoint."},"created_in_seconds":{"type":"integer","description":"The date (in Unix seconds) when the network object was created."},"modified_in_seconds":{"type":"integer","description":"The date (in Unix seconds) when the network object was last updated."}}}},"pagination":{"type":"array","items":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of objects matching your search criteria.","format":"int32"},"limit":{"type":"integer","description":"Maximum number of objects requested (or service imposed limit if not in request).","format":"int32"},"offset":{"type":"integer","description":"Offset from request (or zero).","format":"int32"},"sort":{"description":"An array of the fields you specified as sort fields in the request, which Tenable.io uses to sort the returned data.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The field on which Tenable.io sorts the results."},"order":{"type":"string","description":"The direction of the sort order. Supported values are `asc` (ascending) and `desc` (descending)."}}}}}}}}},"examples":{"response":{"value":{"networks":[{"owner_uuid":"c31d1cd4-6e77-4a15-a10d-54e854e3cfa9","created":154474408527,"modified":154474408527,"scanner_count":10,"uuid":"00000000-0000-0000-0000-000000000000","name":"Default","is_default":true,"created_by":"c31d1cd4-6e77-4a15-a10d-54e854e3cfa9","modified_by":"c31d1cd4-6e77-4a15-a10d-54e854e3cfa9","created_in_seconds":1544744085,"modified_in_seconds":1544744085},{"owner_uuid":"08d242c3-9553-4ccc-835d-0c17ed942cdf","created":1557526802865,"modified":1557526802865,"scanner_count":1,"uuid":"1b17ee49-8f17-4c8f-8536-bff9aff73429","name":"Headquarters","description":"Network devices at Columbia, MD location","is_default":false,"created_by":"18d242c3-9553-4ccc-835d-0d17ed942cef","modified_by":"18d242c3-9553-4ccc-835d-0d17ed942cef","created_in_seconds":1557526802,"modified_in_seconds":1557526802}],"pagination":{"total":2,"limit":50,"offset":0,"sort":[{"name":"name","order":"asc"}]}}}}}}},"400":{"description":"Returned if the query parameters in your request were invalid."},"403":{"description":"Returned if you do not have sufficient permissions to list network objects."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/networks/{network_id}":{"get":{"summary":"Get network details","description":"Returns the details of the specified network object.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-details","tags":["Networks"],"parameters":[{"description":"The UUID of the network object for which you want to view details.","required":true,"name":"network_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the details of the specified network object.","content":{"application/json":{"schema":{"type":"object","properties":{"owner_uuid":{"type":"string","description":"The UUID of the owner of the network object. The owner of the network object does not have any additional permissions above administrator permissions."},"created":{"type":"integer","description":"The date (in Unix milliseconds) when the network object was created."},"modified":{"type":"integer","description":"The date (in Unix milliseconds) when the network object was last updated."},"uuid":{"type":"string","description":"The UUID of the network object."},"name":{"type":"string","description":"The name of the network object."},"description":{"type":"string","description":"The description of the network object."},"is_default":{"type":"boolean","description":"Indicates whether the network object is the default network object. The default network object is a system-generated object that contains any scanners and scanner groups not yet assigned to a custom network object. You cannot update or delete the default network object."},"created_by":{"type":"string","description":"The UUID of the user who created the network object."},"modified_by":{"type":"string","description":"The UUID of the user who last updated the network object."},"deleted_at":{"type":"integer","description":"The date (in Unix time) when the network object was deleted. This attribute is present for deleted network objects only. To view deleted network objects, use the `includeDeleted` parameter on the [/GET networks](/reference#networks-list) endpoint."},"created_in_seconds":{"type":"integer","description":"The date (in Unix seconds) when the network object was created."},"modified_in_seconds":{"type":"integer","description":"The date (in Unix seconds) when the network object was last updated."}}},"examples":{"response":{"value":{"owner_uuid":"08d242c3-9553-4ccc-835d-0c17ed942cdf","created":1557526802865,"modified":1557526802865,"scanner_count":0,"uuid":"1b17ee49-8f17-4c8f-8536-bff9aff73429","name":"Headquarters","description":"Network devices at Columbia, MD location","is_default":false,"created_by":"18d242c3-9553-4ccc-835d-0d17ed942cef","modified_by":"18d242c3-9553-4ccc-835d-0d17ed942cef","created_in_seconds":1557526802,"modified_in_seconds":1557526802}}}}}},"404":{"description":"Returned if Tenable.io cannot find a network object with the specified UUID."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update network","description":"Updates the name or description of a network object.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-update","tags":["Networks"],"parameters":[{"description":"The UUID of the network object you want to update. You cannot update the default network object.","required":true,"name":"network_id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"The new name of the network object. This name must be unique within your Tenable.io instance, cannot duplicate the name of a previously deleted network, and cannot be `default`","type":"string"},"description":{"description":"The new description of the network object.","type":"string"}}}}}},"responses":{"200":{"description":"Returns successfully updated details of the network object.","content":{"application/json":{"schema":{"type":"object","properties":{"owner_uuid":{"type":"string","description":"The UUID of the owner of the network object. The owner of the network object does not have any additional permissions above administrator permissions."},"created":{"type":"integer","description":"The date (in Unix milliseconds) when the network object was created."},"modified":{"type":"integer","description":"The date (in Unix milliseconds) when the network object was last updated."},"uuid":{"type":"string","description":"The UUID of the network object."},"name":{"type":"string","description":"The name of the network object."},"description":{"type":"string","description":"The description of the network object."},"is_default":{"type":"boolean","description":"Indicates whether the network object is the default network object. The default network object is a system-generated object that contains any scanners and scanner groups not yet assigned to a custom network object. You cannot update or delete the default network object."},"created_by":{"type":"string","description":"The UUID of the user who created the network object."},"modified_by":{"type":"string","description":"The UUID of the user who last updated the network object."},"deleted_at":{"type":"integer","description":"The date (in Unix time) when the network object was deleted. This attribute is present for deleted network objects only. To view deleted network objects, use the `includeDeleted` parameter on the [/GET networks](/reference#networks-list) endpoint."},"created_in_seconds":{"type":"integer","description":"The date (in Unix seconds) when the network object was created."},"modified_in_seconds":{"type":"integer","description":"The date (in Unix seconds) when the network object was last updated."}}},"examples":{"response":{"value":{"owner_uuid":"08d242c3-9553-4ccc-835d-0c17ed942cdf","created":1557526802865,"modified":1557526802865,"scanner_count":0,"uuid":"1b17ee49-8f17-4c8f-8536-bff9aff73429","name":"Columbia office","description":"Network devices at Columbia, MD location","is_default":false,"created_by":"18d242c3-9553-4ccc-835d-0d17ed942cef","modified_by":"18d242c3-9553-4ccc-835d-0d17ed942cef","created_in_seconds":1557526802,"modified_in_seconds":1557526802}}}}}},"400":{"description":"Returned if Tenable.io encounters invalid JSON in the request body or if you attempted to change the default network object."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have permission to update the specified network object."},"404":{"description":"Returned if Tenable.io could not find the specified network object, either because the object does not exist or because the object has been deleted."},"409":{"description":"Returned if a network object with the same name already exists."},"415":{"description":"Returned if the request payload is in an unsupported format."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete network","description":"Deletes the specified network object. Before you delete a network object, consider moving assets to a different network using the [bulk asset move](/reference#assets-bulk-move) endpoint. If you delete a network object, Tenable.io:\n - Returns the scanners and scanner groups associated with the deleted object to the default network object.\n - Retains any asset records for the deleted network until the assets age out of your licensed assets count. \n\n**Note:** You can view deleted network objects using the `includeDeleted` filter in a [/GET networks](/reference#networks-list) request.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-delete","tags":["Networks"],"parameters":[{"description":"UUID for the network object you want to delete. You cannot delete the default network object.","required":true,"name":"network_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deleted the specified network object.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if you attempted to delete the default network object."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have sufficient permissions to delete the specified network object."},"404":{"description":"Returned if Tenable.io could not find the network object you specified."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/networks/{network_id}/scanners/{scanner_uuid}":{"post":{"summary":"Assign scanners","description":"
        Associates a scanner or scanner group with a network object. Use this endpoint to:
      • Assign a scanner or scanner group to a custom network object.
      • Return a scanner or scanner group to the default network object.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-assign-scanner","tags":["Networks"],"parameters":[{"description":"The UUID of the network object where you want to assign a scanner or scanner group.","required":true,"name":"network_id","in":"path","schema":{"type":"string"}},{"description":"The UUID of the scanner or scanner group you want to assign to the network object. To get UUID values, use the [GET /networks/{network_id}/assignable-scanners](/reference#networks-list-assignable-scanners) endpoint.","required":true,"name":"scanner_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully assigned the scanner or scanner group to the network object.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if you attempt to assign an AWS scanner to the network object. For AWS assets, use network segmentation processes offered by AWS instead."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have sufficient permissions to assign a scanner or scanner group to the specified network object."},"404":{"description":"Returned if Tenable.io could not find either the network object, the scanner object, or the scanner group object you specified."},"409":{"description":"Returned if Tenable.io determines that assigning the scanner or scanner group causes a network conflict. A response message with this code includes a response body under the following conditions: \n* The scanner you attempted to assign is a member of more than one scanner group. \n* The scanner group you attempted to assign contains one or more scanners that belong to another scanner group.","content":{"application/json":{"schema":{"type":"object","properties":{"conflicts":{"type":"array","items":{"type":"object","properties":{"scanner_uuid":{"type":"string","description":"The UUID of the scanner involved in the conflict."},"scanner_groups":{"description":"A list of scanner groups involved in the conflict.","type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the scanner group."},"name":{"type":"string","description":"The name of the scanner group."}}}}}}}}},"examples":{"response":{"value":{"conflicts":[{"scanner_uuid":"94b4be1a-d658-20a8-131b-ac0fa06f04b5db3eac57471272de","scanner_groups":[{"uuid":"32a3d0d2-3c41-432e-8479-7da815a09b23","name":"Northeast sector"}]}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/networks/{network_id}/scanners":{"get":{"summary":"List network scanners","description":"Lists all scanners and scanner groups belonging to the specified network object.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-list-scanners","tags":["Networks"],"parameters":[{"description":"The UUID of the network object for which you want to list assigned scanners and scanner groups.","required":true,"name":"network_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of scanners and scanner groups for the specified network object.","content":{"application/json":{"schema":{"type":"object","description":"A list of scanners and scanner groups.","properties":{"scanners":{"type":"array","items":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The date on which the scanner was linked to the Tenable.io instance.","format":"int32"},"distro":{"type":"string","description":"The scanner software distribution."},"engine_build":{"type":"string","description":"The build of the engine running on the scanner."},"engine_version":{"type":"string","description":"The version of the engine running on the scanner."},"group":{"type":"boolean","description":"Indicates whether the object represents a single scanner (`false`) or a scanner group (`true`)."},"id":{"type":"integer","description":"The unique ID of the scanner.","format":"int32"},"key":{"type":"string","description":"An alpha-numeric sequence of characters used when linking a scanner to Tenable.io."},"last_connect":{"type":"integer","description":"The Unix timestamp when the scanner last connected to the Tenable.io instance.","format":"int32"},"last_modification_date":{"type":"integer","description":"The Unix timestamp when the scanner was last updated."},"linked":{"type":"integer","description":"Indicates if the scanner is enabled (`1`) or not disabled (`0`)."},"loaded_plugin_set":{"type":"string","description":"The current plugin set on the scanner."},"name":{"type":"string","description":"The user-defined name of the scanner."},"num_hosts":{"type":"integer","description":"The number of hosts that the scanner's analysis have discovered."},"num_scans":{"type":"integer","description":"The number of scan tasks the scanner is currently executing."},"num_sessions":{"type":"integer","description":"The number of active sessions between the scanner and hosts."},"num_tcp_sessions":{"type":"integer","description":"The number of active TCP sessions between the scanner and hosts."},"owner":{"type":"string","description":"The scanner owner."},"owner_id":{"type":"integer","description":"The ID of the scanner owner."},"owner_name":{"type":"string","description":"The name of the scanner owner."},"owner_uuid":{"type":"string","description":"The UUID of the scanner owner."},"platform":{"type":"string","description":"The platform of the scanner."},"pool":{"type":"boolean","description":"Indicates whether the scanner is part of a scanner group."},"report_frequency":{"type":"integer","description":"The frequency (in seconds) at which the scanner polls the Tenable.io instance."},"settings":{"type":"object","description":""},"scan_count":{"type":"integer","description":"The current number of scans currently running on the scanner."},"source":{"type":"string","description":"Historical attribute. Always `service`."},"status":{"type":"string","description":"The scanner's current status. Possible values are:\n - on—The scanner has connected in the last five minutes.\n - off—The scanner has not connected in the last five minutes."},"timestamp":{"type":"integer","description":"Equivalent to the last_modification_date."},"type":{"type":"string","description":"The type of scanner (local or remote)."},"uuid":{"type":"string","description":"The UUID of the scanner."},"remote_uuid":{"type":"string","description":"The UUID of the Nessus installation on the scanner."},"supports_remote_logs":{"type":"boolean","description":"Indicates whether the scanner supports remote logging."}}}}}},"examples":{"response":{"value":{"scanners":[{"creation_date":1521065518,"distro":"2.6.32-504.8.1.el6.x86_64","engine_build":"201710101","engine_version":"NNM 5.4.0","group":false,"id":215898,"key":"bd98a384ff0e91c8f94fa7f786f8827c1eb7b28dffcfb9895f9d85bd8f0a7d53","last_connect":1524524576,"last_modification_date":1524523493,"linked":1,"loaded_plugin_set":"201803271415","name":"NNM-540","num_hosts":0,"num_scans":0,"num_sessions":0,"num_tcp_sessions":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"c31d1cd4-6e77-4a15-a10d-54e854e3cfa9","platform":"LINUX","pool":false,"report_frequency":3600,"settings":{},"scan_count":0,"source":"service","status":"off","timestamp":1524523493,"type":"managed_pvs","uuid":"34f9ddc5-eed0-4b87-80b2-1b2b6ccec8f8","remote_uuid":"131658d2-9a1a-3daa-5ca5-196f200a3e3f9b9a533780bda648","supports_remote_logs":false}]}}}}}},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have sufficient permissions to list scanners and scanner groups for the specified network object."},"404":{"description":"Returned if Tenable.io could not find the network object you specified."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"post":{"summary":"Bulk assign scanners","description":"Bulk assigns scanners and scanner groups to a custom network. The data in this request payload overwrites the full list of specified scanners and scanner groups previously assigned to the specified network object. If the payload excludes scanners or scanner groups previously assigned to the specified network object, Tenable.io returns those scanners and scanner groups to the default network. You cannot use this endpoint to bulk return scanners and scanner groups from multiple custom networks to the default network.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-assign-scanner-bulk","tags":["Networks"],"parameters":[{"description":"The UUID of the network object where you want to bulk assign scanners and scanner groups.","required":true,"name":"network_id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scanner_uuids":{"description":"A list of UUIDs for the scanners and scanner groups you want to bulk move to a network object. To get values for this list, use the [GET /networks/{network_id}/assignable-scanners](/reference#networks-list-assignable-scanners) endpoint.","type":"array","items":{"type":"string"}}},"required":["scanner_uuids"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully assigned the scanners and scanner groups to the network object.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if you attempt to assign an AWS scanner to the network object. For AWS assets, use network segmentation processes offered by AWS instead."},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have sufficient permissions to assign a scanner or scanner group to the specified network object."},"404":{"description":"Returned if Tenable.io could not find either the network object, the scanner object, or the scanner group object you specified."},"409":{"description":"Returned if Tenable.io determines that assigning the scanner or scanner group causes a network conflict. A response message with this code includes a response body under the following conditions: \n* A scanner you attempted to assign is a member of more than one scanner group. \n* A scanner group you attempted to assign contains one or more scanners that belong to another scanner group.","content":{"application/json":{"schema":{"type":"object","properties":{"conflicts":{"type":"array","items":{"type":"object","properties":{"scanner_uuid":{"type":"string","description":"The UUID of the scanner involved in the conflict."},"scanner_groups":{"description":"A list of scanner groups involved in the conflict.","type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the scanner group."},"name":{"type":"string","description":"The name of the scanner group."}}}}}}}}},"examples":{"response":{"value":{"conflicts":[{"scanner_uuid":"94b4be1a-d658-20a8-131b-ac0fa06f04b5db3eac57471272de","scanner_groups":[{"uuid":"32a3d0d2-3c41-432e-8479-7da815a09b23","name":"Northeast sector"}]}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/networks/{network_id}/assignable-scanners":{"get":{"summary":"List assignable scanners","description":"Lists all scanners and scanner groups not yet assigned to a custom network object. This list includes all scanner groups within the default network AND any scanners within the default network that do not belong to a scanner group.

      Requires ADMINISTRATOR [64] user permissions.

      ","operationId":"networks-list-assignable-scanners","tags":["Networks"],"parameters":[{"description":"The UUID of the default network object.","required":true,"name":"network_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of assignable scanners and scanner groups in the default network object.","content":{"application/json":{"schema":{"type":"object","description":"A list of scanners and scanner groups.","properties":{"scanners":{"type":"array","items":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The date on which the scanner was linked to the Tenable.io instance.","format":"int32"},"distro":{"type":"string","description":"The scanner software distribution."},"engine_build":{"type":"string","description":"The build of the engine running on the scanner."},"engine_version":{"type":"string","description":"The version of the engine running on the scanner."},"group":{"type":"boolean","description":"Indicates whether the object represents a single scanner (`false`) or a scanner group (`true`)."},"id":{"type":"integer","description":"The unique ID of the scanner.","format":"int32"},"key":{"type":"string","description":"An alpha-numeric sequence of characters used when linking a scanner to Tenable.io."},"last_connect":{"type":"integer","description":"The Unix timestamp when the scanner last connected to the Tenable.io instance.","format":"int32"},"last_modification_date":{"type":"integer","description":"The Unix timestamp when the scanner was last updated."},"linked":{"type":"integer","description":"Indicates if the scanner is enabled (`1`) or not disabled (`0`)."},"loaded_plugin_set":{"type":"string","description":"The current plugin set on the scanner."},"name":{"type":"string","description":"The user-defined name of the scanner."},"num_hosts":{"type":"integer","description":"The number of hosts that the scanner's analysis have discovered."},"num_scans":{"type":"integer","description":"The number of scan tasks the scanner is currently executing."},"num_sessions":{"type":"integer","description":"The number of active sessions between the scanner and hosts."},"num_tcp_sessions":{"type":"integer","description":"The number of active TCP sessions between the scanner and hosts."},"owner":{"type":"string","description":"The scanner owner."},"owner_id":{"type":"integer","description":"The ID of the scanner owner."},"owner_name":{"type":"string","description":"The name of the scanner owner."},"owner_uuid":{"type":"string","description":"The UUID of the scanner owner."},"platform":{"type":"string","description":"The platform of the scanner."},"pool":{"type":"boolean","description":"Indicates whether the scanner is part of a scanner group."},"report_frequency":{"type":"integer","description":"The frequency (in seconds) at which the scanner polls the Tenable.io instance."},"settings":{"type":"object","description":""},"scan_count":{"type":"integer","description":"The current number of scans currently running on the scanner."},"source":{"type":"string","description":"Historical attribute. Always `service`."},"status":{"type":"string","description":"The scanner's current status. Possible values are:\n - on—The scanner has connected in the last five minutes.\n - off—The scanner has not connected in the last five minutes."},"timestamp":{"type":"integer","description":"Equivalent to the last_modification_date."},"type":{"type":"string","description":"The type of scanner (local or remote)."},"uuid":{"type":"string","description":"The UUID of the scanner."},"remote_uuid":{"type":"string","description":"The UUID of the Nessus installation on the scanner."},"supports_remote_logs":{"type":"boolean","description":"Indicates whether the scanner supports remote logging."}}}}}},"examples":{"response":{"value":{"scanners":[{"creation_date":1521065518,"distro":"2.6.32-504.8.1.el6.x86_64","engine_build":"201710101","engine_version":"NNM 5.4.0","group":false,"id":215898,"key":"bd98a384ff0e91c8f94fa7f786f8827c1eb7b28dffcfb9895f9d85bd8f0a7d53","last_connect":1524524576,"last_modification_date":1524523493,"linked":1,"loaded_plugin_set":"201803271415","name":"NNM-540","num_hosts":0,"num_scans":0,"num_sessions":0,"num_tcp_sessions":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"c31d1cd4-6e77-4a15-a10d-54e854e3cfa9","platform":"LINUX","pool":false,"report_frequency":3600,"settings":{},"scan_count":0,"source":"service","status":"off","timestamp":1524523493,"type":"managed_pvs","uuid":"34f9ddc5-eed0-4b87-80b2-1b2b6ccec8f8","remote_uuid":"131658d2-9a1a-3daa-5ca5-196f200a3e3f9b9a533780bda648","supports_remote_logs":false}]}}}}}},"401":{"description":"Returned if Tenable.io cannot authenticate the user account that submitted the request."},"403":{"description":"Returned if you do not have sufficient permissions to list scanners and scanner groups for the default network object."},"404":{"description":"Returned if Tenable.io could not find the network object you specified."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/permissions/{object_type}/{object_id}":{"get":{"summary":"Get object permissions","description":"Returns the object's permissions.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"permissions-list","tags":["Permissions"],"parameters":[{"description":"The type of object.","required":true,"name":"object_type","in":"path","schema":{"type":"string","enum":["scanner"],"default":"scanner"}},{"description":"The unique ID of the object.","required":true,"name":"object_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the object permissions.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"owner":{"type":"integer","description":"The unique ID of the owner of the object."},"type":{"type":"string","description":"The type of permission (default, user, group).","enum":["default","user","group"]},"permissions":{"type":"integer","description":"The permission value to grant access as described in Permissions.","format":"int32"},"id":{"type":"integer","description":"The unique ID of the user if type is user."},"name":{"type":"string","description":"The name of the user or group."},"display_name":{"type":"string","description":"The display-friendly name of the user or group."}}}},"examples":{"response":{"value":{"acls":[{"type":"user","id":1,"uuid":"47e6b2ea-4e3c-4c09-b137-72e9f53b97f6","name":"system","display_name":"system","permissions":128,"owner":1},{"type":"default","permissions":16}]}}}}}},"403":{"description":"Returned if you do not have permission to view the object."},"404":{"description":"Returned if Tenable.io cannot find the specified object."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update object permissions","description":"Updates the permissions for a Tenable.io object.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"permissions-change","tags":["Permissions"],"parameters":[{"description":"The type of object.","required":true,"name":"object_type","in":"path","schema":{"type":"string","enum":["scanner"],"default":"scanner"}},{"description":"The unique ID of the object (for example, scanner).","required":true,"name":"object_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"acls":{"type":"array","items":{"type":"object","properties":{"owner":{"type":"integer","description":"The unique ID of the owner of the object."},"type":{"type":"string","description":"The type of permission (default, user, group).","enum":["default","user","group"]},"permissions":{"type":"integer","description":"The permission value to grant access as described in Permissions.","format":"int32"},"id":{"type":"integer","description":"The unique ID of the user if type is user."},"name":{"type":"string","description":"The name of the user or group."},"display_name":{"type":"string","description":"The display-friendly name of the user or group."}}}}}}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updates the object permissions.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to edit the object."},"404":{"description":"Returned if Tenable.io cannot find the specified object."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/plugins/families":{"get":{"summary":"List plugin families","description":"Returns the list of plugin families.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"plugins-families","tags":["Plugins"],"parameters":[{"description":"Specifies whether to return all plugin families. If `true`, the plugin families hidden in Tenable.io UI, for example, Port Scanners, are included in the list.","required":false,"name":"all","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Returns the list of plugin families.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the family."},"name":{"type":"string","description":"The name of the family."},"count":{"type":"integer","description":"The number of plugins in the family."}}}},"examples":{"response":{"value":{"families":[{"count":11342,"name":"AIX Local Security Checks","id":1},{"count":1164,"name":"Amazon Linux Local Security Checks","id":35},{"count":114,"name":"Backdoors","id":17}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/plugins/families/{id}":{"get":{"summary":"List plugins in family","description":"Returns the list of plugins in a family.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"plugins-family-details","tags":["Plugins"],"parameters":[{"description":"The ID of the family to lookup.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the list of plugins in a family.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the family."},"id":{"type":"integer","description":"The unique ID of the family."},"plugins":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The ID of the plugin."},"name":{"type":"string","description":"The name of the plugin."}}}}}},"examples":{"response":{"value":{"plugins":[{"id":22372,"name":"AIX 5.1 : IY19744"},{"id":22373,"name":"AIX 5.1 : IY20486"},{"id":22374,"name":"AIX 5.1 : IY21309"}],"name":"AIX Local Security Checks","id":1}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/plugins/plugin/{id}":{"get":{"summary":"Get plugin details","description":"Returns details for a specified plugin.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"plugins-plugin-details","tags":["Plugins"],"parameters":[{"description":"The ID of the plugin.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the plugin details.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The ID of the plugin."},"name":{"type":"string","description":"The name of the plugin."},"family_name":{"type":"string","description":"The name of the plugin family."},"attributes":{"type":"array","description":"The plugin attributes.","items":{"type":"object","properties":{"attribute_name":{"type":"string","description":"The name of the attribute."},"attribute_value":{"type":"string","description":"The value of the attribute."}}}}}},"examples":{"response":{"value":{"attributes":[{"attribute_value":"aix_IY19744.nasl","attribute_name":"fname"},{"attribute_value":"AIX 5.1 : IY19744","attribute_name":"plugin_name"},{"attribute_value":"$Revision: 1.5 $","attribute_name":"script_version"},{"attribute_value":"http://www-912.ibm.com/eserver/support/fixes/","attribute_name":"solution"},{"attribute_value":"High","attribute_name":"risk_factor"},{"attribute_value":"The remote host is missing AIX Critical Security Patch number IY19744\n(SECURITY: Buffer Overflow in xntpd).\n\nYou should install this patch for your system to be up-to-date.","attribute_name":"description"},{"attribute_value":"2006/09/16","attribute_name":"plugin_publication_date"},{"attribute_value":"The remote host is missing a vendor-supplied security patch","attribute_name":"synopsis"}],"family_name":"AIX Local Security Checks","name":"AIX 5.1 : IY19744","id":22372}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/policies":{"post":{"summary":"Create policy","description":"Creates a policy.

      Requires STANDARD [32] user permissions. See Permissions.

      ","operationId":"policies-create","tags":["Policies"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The uuid for the editor template to use.","example":"ab4bacd2-05f6-425c-9d79-3ba3940ad1c24e51e1f403febe40"},"settings":{"type":"object","properties":{}}},"required":["uuid"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully saves the policy.","content":{"application/json":{"schema":{"type":"object","properties":{"policy_id":{"type":"integer"},"policy_name":{"type":"string"}}},"examples":{"response":{"value":{"policy_id":"integer","policy_name":"string"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encounters an error while attempting to save the policy.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List policies","description":"Returns a list of policies.

      Requires STANDARD [32] user permissions. See Permissions.

      ","operationId":"policies-list","tags":["Policies"],"responses":{"200":{"description":"Returns the policy list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the policy."},"template_uuid":{"type":"string","description":"The UUID for the template the policy uses."},"name":{"type":"string","description":"The name of the policy."},"description":{"type":"string","description":"The description of the policy."},"owner_id":{"type":"string","description":"The unique ID of the owner of the policy."},"owner":{"type":"string","description":"The username for the owner of the policy."},"shared":{"type":"integer","description":"The shared status of the policy."},"user_permissions":{"type":"integer","description":"The sharing permissions for the policy."},"creation_date":{"type":"integer","description":"The creation date of the policy in unixtime."},"last_modification_date":{"type":"integer","description":"The last modification date for the policy in unixtime."},"visibility":{"type":"integer","description":"The visibility of the target (private or shared)."},"no_target":{"type":"boolean","description":"If true, the policy does not use targets."}}}},"examples":{"response":{"value":{"policies":[{"no_target":"false","template_uuid":"ad629e16-03b6-8c1d-cef6-ef8c9dd3c658d24bd260ef5f9e66","description":"An example policy.","name":"Test Policy 1","owner":"api@api.demo","visibility":"shared","shared":1,"user_permissions":128,"last_modification_date":1545938690,"creation_date":1545938690,"owner_id":3,"id":43},{"no_target":"false","template_uuid":"65d5b7ce-8d3b-d0df-f473-40633bb6122108a510a44374a167","description":"An example policy using the Shellshock template.","name":"Test Policy 2","owner":"api@api.demo","visibility":"shared","shared":1,"user_permissions":128,"last_modification_date":1545938735,"creation_date":1545938735,"owner_id":3,"id":44}]}}}}}},"403":{"description":"Returned if you do not have permission to view the policy list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/policies/{policy_id}/copy":{"post":{"summary":"Copy policy","description":"Copies a policy.

      Requires STANDARD [32] user permissions and CAN EDIT [32] policy permissions. See Permissions.

      ","operationId":"policies-copy","tags":["Policies"],"parameters":[{"description":"The id of the policy to copy.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the policy object.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"name":"Copy of Test Policy 1","id":45}}}}}},"403":{"description":"Returned if you do not have permission to copy the policy."},"404":{"description":"Returned if Tenable.io cannot find the specified policy."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to copy the policy.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/policies/import":{"post":{"summary":"Import policy","description":"Imports an existing policy uploaded using POST /file/upload (.nessus format only).

      Requires STANDARD [32] user permissions. See Permissions.

      ","operationId":"policies-import","tags":["Policies"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"The name of the file to import as provided by the response from file-upload."}},"required":["file"]}}}},"responses":{"200":{"description":"Returns the policy object.","content":{"application/json":{"schema":{"type":"object","properties":{"private":{"type":"integer"},"no_target":{"type":"string"},"template_uuid":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"shared":{"type":"integer"},"user_permissions":{"type":"integer"},"last_modification_date":{"type":"integer"},"creation_date":{"type":"integer"},"owner_id":{"type":"integer"},"id":{"type":"integer"}}},"examples":{"response":{"value":{"private":"integer","no_target":"string","template_uuid":"string","description":"string","name":"string","owner":"string","shared":"integer","user_permissions":"integer","last_modification_date":"integer","creation_date":"integer","owner_id":"integer","id":"integer"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to import the policy.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/policies/{policy_id}/export":{"get":{"summary":"Export policy","description":"Exports the specified policy.

      Requires STANDARD [32] user permissions and CAN EDIT [32] policy permissions. See Permissions.

      ","operationId":"policies-export","tags":["Policies"],"parameters":[{"description":"The ID of the policy to export.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the policy in nessus (XML) format.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"To do":"Add response sample here"}}}}}},"403":{"description":"Returned if you do not have permission to export the policy."},"404":{"description":"Returned if Tenable.io cannot find the specified policy."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/policies/{policy_id}":{"get":{"summary":"List policy details","description":"Returns the details for the specified policy.

      Requires STANDARD [32] user permissions and CAN USE [32] policy permissions. See Permissions.

      ","operationId":"policies-details","tags":["Policies"],"parameters":[{"description":"The ID of the policy to retrieve.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the policy details. This response can be edited and passed directly to the [PUT /policies/{policy_id}](/reference#was-policies-configure) endpoint.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string"},"audits":{"type":"object"},"credentials":{"type":"object"},"plugins":{"type":"object"},"scap":{"type":"object"},"settings":{"type":"object"}}},"examples":{"response":{"value":{"plugins":{"SMTP problems":{"status":"enabled"},"Backdoors":{"status":"enabled"},"Ubuntu Local Security Checks":{"status":"enabled"},"Gentoo Local Security Checks":{"status":"enabled"},"Oracle Linux Local Security Checks":{"status":"enabled"},"RPC":{"status":"enabled"},"Brute force attacks":{"status":"enabled"},"Gain a shell remotely":{"status":"enabled"},"Service detection":{"status":"enabled"},"DNS":{"status":"enabled"},"Mandriva Local Security Checks":{"status":"enabled"},"Junos Local Security Checks":{"status":"enabled"},"Misc.":{"status":"enabled"},"FTP":{"status":"enabled"},"Slackware Local Security Checks":{"status":"enabled"},"Default Unix Accounts":{"status":"enabled"},"AIX Local Security Checks":{"status":"enabled"},"SNMP":{"status":"enabled"},"OracleVM Local Security Checks":{"status":"enabled"},"CGI abuses":{"status":"enabled"},"Settings":{"status":"enabled"},"CISCO":{"status":"enabled"},"Firewalls":{"status":"enabled"},"Databases":{"status":"enabled"},"Debian Local Security Checks":{"status":"enabled"},"Fedora Local Security Checks":{"status":"enabled"},"Netware":{"status":"enabled"},"Huawei Local Security Checks":{"status":"enabled"},"Windows : User management":{"status":"enabled"},"VMware ESX Local Security Checks":{"status":"enabled"},"Virtuozzo Local Security Checks":{"status":"enabled"},"CentOS Local Security Checks":{"status":"enabled"},"Peer-To-Peer File Sharing":{"status":"enabled"},"General":{"status":"enabled"},"Policy Compliance":{"status":"enabled"},"Amazon Linux Local Security Checks":{"status":"enabled"},"Solaris Local Security Checks":{"status":"enabled"},"F5 Networks Local Security Checks":{"status":"enabled"},"Denial of Service":{"status":"enabled"},"Windows : Microsoft Bulletins":{"status":"enabled"},"SuSE Local Security Checks":{"status":"enabled"},"Palo Alto Local Security Checks":{"status":"enabled"},"Red Hat Local Security Checks":{"status":"enabled"},"PhotonOS Local Security Checks":{"status":"enabled"},"HP-UX Local Security Checks":{"status":"enabled"},"Mobile Devices":{"status":"enabled"},"CGI abuses : XSS":{"status":"enabled"},"FreeBSD Local Security Checks":{"status":"enabled"},"Windows":{"status":"enabled"},"MacOS X Local Security Checks":{"status":"enabled"},"Scientific Linux Local Security Checks":{"status":"enabled"},"Web Servers":{"status":"enabled"},"SCADA":{"status":"enabled"}},"settings":{"cisco_offline_configs":"","apm_force_updates":"yes","region_hkg_pref_name":"yes","http_login_max_redir":"0","portscan_range":"default","icmp_unreach_means_host_down":"no","start_cotp_tsap":"8","ssh_port":"22","av_grace_period":"0","http_login_auth_regex_nocase":"no","snmp_port":"161","enable_admin_shares":"no","arista_offline_configs":"","icmp_ping_retries":"2","syn_firewall_detection":"Automatic (normal)","snmp_scanner":"yes","sonicos_offline_configs":"","slice_network_addresses":"no","patch_audit_over_rsh":"no","aws_verify_ssl":"yes","was_http_request_timeout":"5","tcp_ping":"yes","additional_snmp_port3":"161","dell_f10_offline_configs":"","test_default_oracle_accounts":"no","only_portscan_if_enum_failed":"yes","apm_update_timeout":"5","ssl_prob_ports":"Known SSL ports","ping_the_remote_host":"yes","office365filecontents_max_cumulative_size":"","attempt_least_privilege":"no","region_dfw_pref_name":"yes","modbus_start_reg":"0","max_simult_tcp_sessions_per_scan":"","scan_network_printers":"no","stop_scan_on_disconnect":"no","report_verbosity":"Normal","was_plugins_rate_limiter_requests_per_second":"25","fast_network_discovery":"no","udp_ping":"no","scan_malware":"no","never_send_win_creds_in_the_clear":"yes","log_live_hosts":"no","enum_domain_users_start_uid":"1000","name":"Test Policy 1","description":"An example policy.","tcp_scanner":"no","watchguard_offline_configs":"","office365filecontents_modified_within_x_days":"","http_login_invert_auth_regex":"no","smtp_from":"nobody@example.com","smtp_domain":"example.com","thorough_tests":"no","office365filecontents_exclude_paths":"","scan_webapps":"no","dont_use_ntlmv1":"yes","reverse_lookup":"no","smtp_to":"postmaster@[AUTO_REPLACED_IP]","adtran_aos_offline_configs":"","procurve_config_to_audit":"Saved/(show config)","microsoft_azure_subscriptions_ids":"","max_hosts_per_scan":"80","region_syd_pref_name":"yes","start_remote_registry":"no","patch_audit_over_telnet":"no","aws_use_https":"yes","stop_cotp_tsap":"8","unscanned_closed":"no","scan_netware_hosts":"no","ssh_client_banner":"OpenSSH_5.0","enumerate_all_ciphers":"yes","netapp_offline_configs":"","office365filecontents_file_extensions":"","use_kernel_congestion_detection":"no","was_http_request_concurrency":"10","wol_wait_time":"5","huawei_offline_configs":"","report_paranoia":"Normal","http_login_auth_regex_on_headers":"no","wmi_netstat_scanner":"yes","provided_creds_only":"yes","wol_mac_addresses":"","region_lon_pref_name":"yes","cert_expiry_warning_days":"60","bluecoat_proxysg_offline_configs":"","display_unreachable_hosts":"no","verify_open_ports":"no","enum_local_users_end_uid":"1200","udp_scanner":"no","was_plugins_autothrottle":"yes","ssh_known_hosts":"","safe_checks":"yes","patch_audit_over_rexec":"no","fortios_offline_configs":"","brocade_offline_configs":"","allow_post_scan_editing":"yes","report_superseded_patches":"yes","max_simult_tcp_sessions_per_host":"","region_iad_pref_name":"yes","tcp_ping_dest_ports":"built-in","enum_domain_users_end_uid":"1200","max_checks_per_host":"5","junos_offline_configs":"","check_crl":"no","svc_detection_on_all_ports":"yes","scan_ot_devices":"no","syn_scanner":"yes","cisco_config_to_audit":"Saved/(show config)","detect_ssl":"yes","request_windows_domain_info":"yes","network_receive_timeout":"5","aws_ui_region_type":"Rest of the World","additional_snmp_port1":"161","additional_snmp_port2":"161","reduce_connections_on_congestion":"no","icmp_ping":"yes","network_type":"Mixed (use RFC 1918)","checkpoint_gaia_offline_configs":"","office365filecontents_max_size":"","procurve_offline_configs":"","was_browser_cluster_job_timeout":"10","arp_ping":"yes","region_ord_pref_name":"yes","enum_local_users_start_uid":"1000","fireeye_offline_configs":"","extremeos_offline_configs":"","http_login_method":"POST","ssh_netstat_scanner":"yes","silent_dependencies":"yes","enable_plugin_debugging":"no","http_reauth_delay":0,"timeout_abort_threshold":"100","office365filecontents_include_paths":"","modbus_end_reg":"16","host_tagging":"yes"},"uuid":"ad629e16-03b6-8c1d-cef6-ef8c9dd3c658d24bd260ef5f9e66"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update policy","description":"Updates the parameters of a policy.

      Requires STANDARD [32] user permissions and CAN EDIT [32] policy permissions. See Permissions.

      ","operationId":"policies-configure","tags":["Policies"],"parameters":[{"description":"The ID of the policy to change.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io updated the configuration of the specified policy.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"To do":"Add response sample here"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified policy."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encounters an error while attempting to save the configuration.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete policy","description":"Deletes a policy.

      Requires STANDARD [32] user permissions and CAN EDIT [32] policy permissions. See Permissions.

      ","operationId":"policies-delete","tags":["Policies"],"parameters":[{"description":"The ID of the policy to delete.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the policy.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to delete the policy."},"404":{"description":"Returned if Tenable.io cannot find the specified policy."},"405":{"description":"Returned if the policy is in use by a scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanner-groups":{"post":{"summary":"Create scanner group","description":"Creates a new scanner group. \n\nYou cannot use this endpoint to assign the new scanner group to a network object. Tenable.io automatically assigns new scanner groups to the default network object. To assign a scanner group to a network object, use the [POST /networks/{network_id}/scanners/{scanner_uuid}](/reference#networks-assign-scanner) endpoint.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanner-groups-create","tags":["Scanner Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name for the new scanner group."},"type":{"type":"string","description":"The type of scanner group. If you omit this parameter, Tenable.io automatically uses the default (`load_balancing`).","enum":["load_balancing"]}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates the scanner group.","content":{"application/json":{"schema":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner group in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scanner group in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scanner group."},"owner":{"type":"string","description":"The username of the owner of the scanner group."},"default_permissions":{"type":"integer","description":"The access permissions for the Default group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scanner group."},"shared":{"type":"integer","description":"The shared status of the scanner-group."},"scan_count":{"type":"integer","description":"The number of scans currently tasked to the scanner group."},"scanner_count":{"type":"string","description":"The number of scanners associated with this scanner group."},"uuid":{"type":"string","description":"The UUID of the scanner group."},"token":{"type":"string","description":"The unique token for a scanner group."},"flag":{"type":"string","description":"The flag indicating what type of scanner group."},"type":{"type":"string","description":"The type of scanner group. This is set to \"load_balancing\" by default."},"name":{"type":"string","description":"The name of the scanner group."},"network_name":{"type":"string","description":"The name of the network object associated with the scanner group. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"id":{"type":"integer","description":"The unique ID of the scanner group."},"scanner_id":{"type":"integer","description":"The unique scanner ID of the scanner group."}}},"examples":{"response":{"value":{"creation_date":1545331392,"last_modification_date":1545331392,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"scan_count":0,"uuid":"5bf560f7-1730-4006-bb38-c1ec69e73797","type":"load_balancing","name":"Example Group","id":102826,"owner_name":"system"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to create the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List scanner groups","description":"Lists scanner groups for your Tenable.io instance.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanner-groups-list","tags":["Scanner Groups"],"responses":{"200":{"description":"Returns the scanner group list.","content":{"application/json":{"schema":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner group in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scanner group in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scanner group."},"owner":{"type":"string","description":"The username of the owner of the scanner group."},"default_permissions":{"type":"integer","description":"The access permissions for the Default group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scanner group."},"shared":{"type":"integer","description":"The shared status of the scanner-group."},"scan_count":{"type":"integer","description":"The number of scans currently tasked to the scanner group."},"scanner_count":{"type":"string","description":"The number of scanners associated with this scanner group."},"uuid":{"type":"string","description":"The UUID of the scanner group."},"token":{"type":"string","description":"The unique token for a scanner group."},"flag":{"type":"string","description":"The flag indicating what type of scanner group."},"type":{"type":"string","description":"The type of scanner group. This is set to \"load_balancing\" by default."},"name":{"type":"string","description":"The name of the scanner group."},"network_name":{"type":"string","description":"The name of the network object associated with the scanner group. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"id":{"type":"integer","description":"The unique ID of the scanner group."},"scanner_id":{"type":"integer","description":"The unique scanner ID of the scanner group."}}},"examples":{"response":{"value":{"scanner_pools":[{"creation_date":1545326169,"last_modification_date":1545345793,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"user_permissions":128,"shared":1,"scan_count":0,"scanner_count":1,"uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","type":"load_balancing","name":"New Group Name","network_name":"Default","id":102823,"scanner_id":144057,"scanner_uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","owner_name":"system"},{"creation_date":1545326198,"last_modification_date":1545345902,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"user_permissions":128,"shared":1,"scan_count":0,"scanner_count":1,"uuid":"80ac7fcd-429b-4858-9d85-207577f6a35c","type":"load_balancing","name":"Group New Name","network_name":"Columbia","id":102824,"scanner_id":144058,"scanner_uuid":"80ac7fcd-429b-4858-9d85-207577f6a35c","owner_name":"system"},{"creation_date":1545331154,"last_modification_date":1545331154,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"user_permissions":128,"shared":1,"scan_count":0,"scanner_count":2,"uuid":"f00b532a-cbcd-4f9e-9292-9174083332df","type":"load_balancing","name":"test1","network_name":"San Francisco","id":102825,"scanner_id":144059,"scanner_uuid":"f00b532a-cbcd-4f9e-9292-9174083332df","owner_name":"system"}]}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanner-groups/{group_id}":{"get":{"summary":"List scanner group details","description":"Returns details for the specified scanner group.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanner-groups-details","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the scanner group details.","content":{"application/json":{"schema":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner group in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scanner group in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scanner group."},"owner":{"type":"string","description":"The username of the owner of the scanner group."},"default_permissions":{"type":"integer","description":"The access permissions for the Default group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scanner group."},"shared":{"type":"integer","description":"The shared status of the scanner-group."},"scan_count":{"type":"integer","description":"The number of scans currently tasked to the scanner group."},"scanner_count":{"type":"string","description":"The number of scanners associated with this scanner group."},"uuid":{"type":"string","description":"The UUID of the scanner group."},"token":{"type":"string","description":"The unique token for a scanner group."},"flag":{"type":"string","description":"The flag indicating what type of scanner group."},"type":{"type":"string","description":"The type of scanner group. This is set to \"load_balancing\" by default."},"name":{"type":"string","description":"The name of the scanner group."},"network_name":{"type":"string","description":"The name of the network object associated with the scanner group. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"id":{"type":"integer","description":"The unique ID of the scanner group."},"scanner_id":{"type":"integer","description":"The unique scanner ID of the scanner group."}}},"examples":{"response":{"value":{"creation_date":1545326169,"last_modification_date":1545326169,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"user_permissions":128,"shared":1,"scan_count":0,"uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","type":"load_balancing","name":"New Scanner Group","network_name":"Default","id":102823,"scanner_id":144057,"scanner_uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","owner_name":"system"}}}}}},"403":{"description":"Returned if you do not have permission to view the scanner group."},"404":{"description":"Returned if Tenable.io cannot find the specified scanner group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update scanner group","description":"Updates a scanner group. \n\nYou cannot use this endpoint to assign a scanner group to a network object. Instead, use the [POST /networks/{network_id}/scanners/{scanner_uuid}](/reference#networks-assign-scanner) endpoint.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanner-groups-edit","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The new name for the scanner group."}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updates the scanner group.","content":{"application/json":{"schema":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner group in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scanner group in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scanner group."},"owner":{"type":"string","description":"The username of the owner of the scanner group."},"default_permissions":{"type":"integer","description":"The access permissions for the Default group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scanner group."},"shared":{"type":"integer","description":"The shared status of the scanner-group."},"scan_count":{"type":"integer","description":"The number of scans currently tasked to the scanner group."},"scanner_count":{"type":"string","description":"The number of scanners associated with this scanner group."},"uuid":{"type":"string","description":"The UUID of the scanner group."},"token":{"type":"string","description":"The unique token for a scanner group."},"flag":{"type":"string","description":"The flag indicating what type of scanner group."},"type":{"type":"string","description":"The type of scanner group. This is set to \"load_balancing\" by default."},"name":{"type":"string","description":"The name of the scanner group."},"network_name":{"type":"string","description":"The name of the network object associated with the scanner group. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"id":{"type":"integer","description":"The unique ID of the scanner group."},"scanner_id":{"type":"integer","description":"The unique scanner ID of the scanner group."}}},"examples":{"response":{"value":{"owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","created":1545326169245,"modified":1545345793683,"container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","id":102823,"name":"New Group Name","type":"load_balancing","distributed":false,"default_permissions":16,"network_name":"Default","shared":1,"user_permissions":128,"created_in_seconds":1545326169,"modified_in_seconds":1545345793}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scanner group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to update the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete a scanner group","description":"Deletes a scanner group.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanner-groups-delete","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the specified scanner group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scanner group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanner-groups/{group_id}/scanners":{"get":{"summary":"List scanners within scanner group","description":"Lists scanners associated with the scanner group.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanner-groups-list-scanners","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the list of scanners in the group.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scanner."},"uuid":{"type":"string","description":"The UUID of the scanner."},"name":{"type":"string","description":"The user-defined name of the scanner."},"network_name":{"type":"string","description":"The name of the network object associated with the scanner. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"type":{"type":"string","description":"The type of scanner (local or remote)."},"status":{"type":"string","description":"The status of the scanner (on or off)."},"scan_count":{"type":"integer","description":"The current number of running scans on the scanner."},"engine_version":{"type":"string","description":"The version of the scanner."},"platform":{"type":"string","description":"The platform of the scanner."},"loaded_plugin_set":{"type":"string","description":"The current plugin set on the scanner."},"registration_code":{"type":"string","description":"The registration code of the scanner."},"owner":{"type":"string","description":"The owner of the scanner."},"key":{"type":"string","description":"An alpha-numeric sequence of characters used when linking a scanner to Tenable.io."},"license":{"type":"object","properties":{"type":{"type":"string","description":"The license type."},"ips":{"type":"integer","description":"The number of hosts the scanner is licensed to use."},"agents":{"type":"integer","description":"The number of hosts agents scanner is licensed to use."},"scanners":{"type":"integer","description":"The number of scanners the scanner is licensed to use."}}}}}},"examples":{"response":{"value":{"scanners":[{"creation_date":1543416914,"group":true,"id":141483,"key":"e3eeefeacca0d998c466af126549d68ef0f4e0d0ba3ab04a6e59a1d8a8a57079","last_connect":null,"last_modification_date":1543416914,"license":null,"linked":1,"name":"EU Frankfurt Cloud Scanners","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"source":"service","status":"on","timestamp":1543416914,"type":"local","uuid":"06ab826a-301d-7829-d2c4-37f400c0f949ea8cce60f523eeef"},{"creation_date":1543416914,"group":true,"id":141482,"key":"83520d0f4da8265cb52f7b558a3319ecb36d4b0ea490d4f9ec4ca9f2e1eee8b9","last_connect":null,"last_modification_date":1543416914,"license":null,"linked":1,"name":"AP Singapore Cloud Scanners","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"source":"service","status":"on","timestamp":1543416914,"type":"local","uuid":"1b895828-62a9-5084-8bc5-d4864a927fb10523d1e84e3fef44"},{"creation_date":1545331392,"group":true,"id":144060,"key":"7ceeb2b90a14d640093e5c6b6a163d0e625e575f71ee7dec9b4d97b90a874d98","last_connect":null,"last_modification_date":1545331392,"linked":1,"name":"Example Group","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"source":"service","status":"on","timestamp":1545331392,"type":"pool","uuid":"5bf560f7-1730-4006-bb38-c1ec69e73797"}]}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanner-groups/{group_id}/scanners/{scanner_id}":{"post":{"summary":"Add scanner to scanner group","description":"Adds a scanner to the scanner group.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"scanner-groups-add-scanner","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the scanner to add to the scanner group.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully adds the scanner to the scanner group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"To do":"Add response sample here"}}}}}},"400":{"description":"Returned if you attempt to add a scanner group to another scanner group."},"409":{"description":"Returned if you attempt to add a scanner to a scanner group that the scanner is already a member of.","content":{"text/html":{"examples":{"response":{"value":{"error":"Scanner 00000000-0000-0000-0000-00000000000000000000000000001 already exists in group 9b7b3d08-cc43-4e67-adc6-41b706c0b680"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to add the scanner to the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Remove scanner from scanner group","description":"Remove a scanner from the scanner group.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"scanner-groups-delete-scanner","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the scanner to remove from the scanner group.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully removes the scanner from the scanner group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to remove the scanner from the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners":{"get":{"summary":"List scanners","description":"Returns the scanner list.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-list","tags":["Scanners"],"responses":{"200":{"description":"Returns the scanner list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scanner."},"uuid":{"type":"string","description":"The UUID of the scanner."},"name":{"type":"string","description":"The user-defined name of the scanner."},"network_name":{"type":"string","description":"The name of the network object associated with the scanner. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"type":{"type":"string","description":"The type of scanner (local or remote)."},"status":{"type":"string","description":"The status of the scanner (on or off)."},"scan_count":{"type":"integer","description":"The current number of running scans on the scanner."},"engine_version":{"type":"string","description":"The version of the scanner."},"platform":{"type":"string","description":"The platform of the scanner."},"loaded_plugin_set":{"type":"string","description":"The current plugin set on the scanner."},"registration_code":{"type":"string","description":"The registration code of the scanner."},"owner":{"type":"string","description":"The owner of the scanner."},"key":{"type":"string","description":"An alpha-numeric sequence of characters used when linking a scanner to Tenable.io."},"license":{"type":"object","properties":{"type":{"type":"string","description":"The license type."},"ips":{"type":"integer","description":"The number of hosts the scanner is licensed to use."},"agents":{"type":"integer","description":"The number of hosts agents scanner is licensed to use."},"scanners":{"type":"integer","description":"The number of scanners the scanner is licensed to use."}}}}}},"examples":{"response":{"value":{"scanners":[{"creation_date":1543416914,"group":true,"id":141483,"key":"e3eeefeacca0d998c466af126549d68ef0f4e0d0ba3ab04a6e59a1d8a8a57079","last_connect":null,"last_modification_date":1543416914,"license":{"agents":512,"ips":1024,"scanners":2,"users":10,"enterprise_pause":false,"expiration_date":1551160800,"evaluation":false,"apps":{"was":{"mode":"eval","expiration_date":1549299101}},"scanners_used":0,"agents_used":0},"linked":1,"name":"EU Frankfurt Cloud Scanners","network_name":"Default","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"shared":1,"source":"service","status":"on","timestamp":1543416914,"type":"local","user_permissions":64,"uuid":"06ab826a-301d-7829-d2c4-37f400c0f949ea8cce60f523eeef"},{"creation_date":1543416914,"group":true,"id":141484,"key":"70d1969c3a1a14697ad51f27f1ee4afe48ef535051d90f5481e32fd78005f05a","last_connect":null,"last_modification_date":1543416914,"license":{"agents":512,"ips":1024,"scanners":2,"users":10,"enterprise_pause":false,"expiration_date":1551160800,"evaluation":false,"apps":{"was":{"mode":"eval","expiration_date":1549299101}},"scanners_used":0,"agents_used":0},"linked":1,"name":"US Cloud Scanner","network_name":"Default","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"shared":1,"source":"service","status":"on","timestamp":1543416914,"type":"local","user_permissions":64,"uuid":"00000000-0000-0000-0000-00000000000000000000000000001"},{"creation_date":1543416914,"group":true,"id":141486,"key":"288b2c20c88c1c5d4dbd9a561713cfc91bacccb65d1d4f6e2edee244afc43c87","last_connect":null,"last_modification_date":1543416914,"license":{"agents":512,"ips":1024,"scanners":2,"users":10,"enterprise_pause":false,"expiration_date":1551160800,"evaluation":false,"apps":{"was":{"mode":"eval","expiration_date":1549299101}},"scanners_used":0,"agents_used":0},"linked":1,"name":"US West Cloud Scanners","network_name":"Default","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"shared":1,"source":"service","status":"on","timestamp":1543416914,"type":"local","user_permissions":64,"uuid":"37b315c1-f31f-cc8e-7e78-585c609fc1d7eba88f8d1e7d24b3"}]}}}}}},"403":{"description":"Returned if you do not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}":{"get":{"summary":"Get scanner details","description":"Returns details for the specified scanner.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-details","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the scanner details.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scanner."},"uuid":{"type":"string","description":"The UUID of the scanner."},"name":{"type":"string","description":"The user-defined name of the scanner."},"network_name":{"type":"string","description":"The name of the network object associated with the scanner. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"type":{"type":"string","description":"The type of scanner (local or remote)."},"status":{"type":"string","description":"The status of the scanner (on or off)."},"scan_count":{"type":"integer","description":"The current number of running scans on the scanner."},"engine_version":{"type":"string","description":"The version of the scanner."},"platform":{"type":"string","description":"The platform of the scanner."},"loaded_plugin_set":{"type":"string","description":"The current plugin set on the scanner."},"registration_code":{"type":"string","description":"The registration code of the scanner."},"owner":{"type":"string","description":"The owner of the scanner."},"key":{"type":"string","description":"An alpha-numeric sequence of characters used when linking a scanner to Tenable.io."},"license":{"type":"object","properties":{"type":{"type":"string","description":"The license type."},"ips":{"type":"integer","description":"The number of hosts the scanner is licensed to use."},"agents":{"type":"integer","description":"The number of hosts agents scanner is licensed to use."},"scanners":{"type":"integer","description":"The number of scanners the scanner is licensed to use."}}}}},"examples":{"response":{"value":{"creation_date":1543416914,"group":true,"id":141482,"key":"83520d0f4da8265cb52f7b558a3319ecb36d4b0ea490d4f9ec4ca9f2e1eee8b9","last_connect":null,"last_modification_date":1543416914,"license":null,"linked":1,"name":"AP Singapore Cloud Scanners","network_name":"Default","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"shared":1,"source":"service","status":"on","timestamp":1543416914,"type":"local","user_permissions":64,"uuid":"1b895828-62a9-5084-8bc5-d4864a927fb10523d1e84e3fef44"}}}}}},"403":{"description":"Returned if you do not have permission to view the specified scanner."},"404":{"description":"Returned if Tenable.io cannot find the specified scanner."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update scanner","description":"Updates the specified scanner. \n\nYou cannot use this endpoint to assign the scanner to a network object. Instead, use the [POST /networks/{network_id}/scanners/{scanner_uuid}](/reference#networks-assign-scanner) endpoint.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-edit","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"force_plugin_update":{"type":"integer","description":"Pass 1 to force a plugin update.","format":"int32"},"force_ui_update":{"type":"integer","description":"Pass 1 to force a UI update.","format":"int32"},"finish_update":{"type":"integer","description":"Pass 1 to reboot the scanner and run the latest software update (only valid if automatic updates are disabled).","format":"int32"},"registration_code":{"type":"string","description":"Sets the registration code for the scanner."},"aws_update_interval":{"type":"integer","description":"For Amazon Web Services scanners this will tell the scanner how often to check in Tenable.io.","format":"int32"}}}}}},"responses":{"200":{"description":"Returned if Tenable.io succesfully updates the specified scanner.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scanner."},"uuid":{"type":"string","description":"The UUID of the scanner."},"name":{"type":"string","description":"The user-defined name of the scanner."},"network_name":{"type":"string","description":"The name of the network object associated with the scanner. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."},"type":{"type":"string","description":"The type of scanner (local or remote)."},"status":{"type":"string","description":"The status of the scanner (on or off)."},"scan_count":{"type":"integer","description":"The current number of running scans on the scanner."},"engine_version":{"type":"string","description":"The version of the scanner."},"platform":{"type":"string","description":"The platform of the scanner."},"loaded_plugin_set":{"type":"string","description":"The current plugin set on the scanner."},"registration_code":{"type":"string","description":"The registration code of the scanner."},"owner":{"type":"string","description":"The owner of the scanner."},"key":{"type":"string","description":"An alpha-numeric sequence of characters used when linking a scanner to Tenable.io."},"license":{"type":"object","properties":{"type":{"type":"string","description":"The license type."},"ips":{"type":"integer","description":"The number of hosts the scanner is licensed to use."},"agents":{"type":"integer","description":"The number of hosts agents scanner is licensed to use."},"scanners":{"type":"integer","description":"The number of scanners the scanner is licensed to use."}}}}},"examples":{"response":{"value":{"owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","created":1543416914744,"modified":1545944374309,"container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","uuid":"06ab826a-301d-7829-d2c4-37f400c0f949ea8cce60f523eeef","id":141483,"network_id":"00000000-0000-0000-0000-000000000000","key":"e3eeefeacca0d998c466af126549d68ef0f4e0d0ba3ab04a6e59a1d8a8a57079","name":"EU Frankfurt Cloud Scanners","type":"local","system":true,"linked":1,"settings":{},"network_name":"Default","default_permissions":16,"shared":1,"user_permissions":64,"status":"on","lce":false,"pvs":false,"aws":false,"industrial_security":false,"webapp":false,"group":true,"can_factory_reset":false,"scanner_scanner":false,"system_group_scanner":true,"system_scanner_scanner":false,"system_webapp_scanner":false,"created_in_seconds":1543416914,"modified_in_seconds":1545944374}}}}}},"403":{"description":"Returned if you attempt to update a cloud scanner where you don't have edit permissions, or if you attempt to set a registration code for an Amazon Web Services scanner."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encounters an internal error while attempting to update the scanner.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete scanner","description":"Deletes and unlinks a scanner from Tenable.io.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-delete","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes/unlinks the scanner.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you attempt to delete the local scanner."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the scanner.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/key":{"get":{"summary":"Get scanner key","description":"Gets the key of the requested scanner.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-get-scanner-key","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the scanner key.","content":{"application/json":{"schema":{"type":"object","properties":{"scanner_id":{"type":"string","description":"The key of the scanner."}}},"examples":{"response":{"value":{"key":"615f4cbeda288998dffe534d36385a422cd4e27be34dcdd7738dc0a01b787f5f"}}}}}},"403":{"description":"Returned if you do not have permission to view scanner data."},"404":{"description":"Returned if Tenable.io cannot find the specified scanner."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/aws-targets":{"get":{"summary":"List AWS scan targets","description":"Lists AWS scan targets if the requested scanner is an Amazon Web Services scanner.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-get-aws-targets","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the AWS target list for the specified scanner.","content":{"application/json":{"schema":{"type":"object","properties":{"scanner_id":{"type":"integer","description":"The ID of the scanner."},"instance_id":{"type":"string","description":"Unique instance identifier from Amazon."},"private_ip":{"type":"string","description":"Private IP address of the AWS instance."},"public_ip":{"type":"string","description":"Public IP address of the AWS instance."},"state":{"type":"string","description":"The state of the instance. Can be one of the following values: `running`, `stopped`, or `terminated`."},"zone":{"type":"string","description":"The availability zone for the instance. Example: `us-east-1a`, `us-east-1b`, etc..."},"type":{"type":"string","description":"The size of the instance. Example: `t2.small`, `t2.medium`, etc..."},"name":{"type":"string","description":"The user-defined name of the instance."}}},"examples":{"response":{"value":{"object":"aws-target"}}}}}},"403":{"description":"Returned if you do not have permission to view scanner data."},"404":{"description":"Returned if Tenable.io cannot find the specified scanner, or if the scanner is not an AWS scanner."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/scans":{"get":{"summary":"List running scans","description":"Lists scans running on the requested scanner.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-get-scans","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the list of scans running on the specified scanner.","content":{"application/json":{"schema":{"type":"object","properties":{"scanner_uuid":{"type":"string","description":"The UUID of the scanner the scan belongs to."},"name":{"type":"string","description":"The name of the scan."},"status":{"type":"string","description":"Scan status. One of pending, processing, stopping, pausing, paused, resuming, or running."},"id":{"type":"string","description":"The scan UUID."},"scan_id":{"type":"integer","description":"The ID of the scan."},"user":{"type":"string","description":"The username of the owner of the scan."},"last_modification_date":{"type":"integer","description":"The last time the scan was modified."},"start_time":{"type":"integer","description":"When the scan was started."},"remote":{"type":"boolean","description":"true if the scan is running remotely; false otherwise."},"network_id":{"type":"string","description":"The ID of the network object associated with the scanner currently running the scan. The default network ID is `00000000-0000-0000-0000-000000000000`. To determine the ID of a custom network, use the [GET /networks](/reference#networks-list) endpoint. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio)."}}},"examples":{"response":{"value":{"scans":[{"scan_id":36,"scanner_uuid":"00000000-0000-0000-0000-00000000000000000000000000001","name":"Basic Scan","status":"pending","id":"cd5c32e9-0b66-4c31-b61a-8d1bdd8a67ad","user":"API Demo User","user_uuid":"394a4be9-782d-406a-9d0a-695188260f0b","last_modification_date":1545945321,"start_time":1545945321,"network_id":"df4656fc-9ba8-4efb-bd1f-ff83991ec107"}]}}}}}},"403":{"description":"Returned if you do not have permission to view scanner data."},"404":{"description":"Returned if Tenable.io cannot find the specified scanner."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/scans/{scan_uuid}/control":{"post":{"summary":"Allow control of running scans","description":"Allows control of scans that are currently running on a scanner.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-control-scans","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the scan.","required":true,"name":"scan_uuid","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","description":"An action to perform on a scan. Valid values are `stop`, `pause`, and `resume`."}},"required":["action"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully completes the specified action.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to perform the specified action."},"404":{"description":"Returned if Tenable.io cannot find the specified scan or scanner."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/link":{"put":{"summary":"Toggle scanner link state","description":"Enables or disables the link state of the scanner identified by `scanner_id`.

      Requires SCAN MANAGER [40] user permissions. See Permissions.

      ","operationId":"scanners-toggle-link-state","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"link":{"type":"integer","description":"Pass `1` enable the link. Pass `0` to disable.","format":"int32"}},"required":["link"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updates the link state of the scanner.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you attempt to edit a cloud scanner where you do not have edit permissions."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans":{"post":{"summary":"Create scan","description":"Creates a scan configuration.

      Requires SCAN OPERATOR [24] user permissions. See Permissions.

      ","operationId":"scans-create","tags":["Scans"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID for the editor template to use. Use the [GET /editor/scan/templates] (#editor-list-templates) endpoint to find the template UUID.","example":"ab4bacd2-05f6-425c-9d79-3ba3940ad1c24e51e1f403febe40"},"settings":{"type":"object","properties":{"name":{"type":"string","description":"The name of the scan."},"description":{"type":"string","description":"The description of the scan."},"policy_id":{"type":"integer","description":"The unique ID of the policy to use. If your user permissions are set to SCAN OPERATOR [24], this parameter is required. Use the [GET /policies] (#policies-list) endpoint to find the policy ID.","format":"int32"},"folder_id":{"type":"integer","description":"The unique ID of the destination folder for the scan. Use the [GET /folders] (#folders-list) endpoint to find the folder ID.","format":"int32"},"scanner_id":{"type":"integer","description":"The unique ID of the scanner to use. Use the [GET /scanners] (#scanners-list) endpoint to find the scanner ID.","example":"1","format":"int32"},"scanner_uuid":{"type":"string","description":"The UUID of the scanner to use. Use the [GET /scanners] (#scanners-list) endpoint to find the scanner UUID."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"launch":{"type":"string","description":"When to launch the scan. (i.e. ON\\_DEMAND, DAILY, WEEKLY, MONTHLY, YEARLY)","enum":["ON_DEMAND","DAILY","WEEKLY","MONTHLY","YEARLY"]},"starttime":{"type":"string","description":"The starting time and date for the scan (i.e. YYYYMMDDTHHMMSS).","example":"20140826T133000"},"rrules":{"type":"string","description":"The interval at which the scan repeats. The interval is formatted as a string of three values delimited by semi-colons. These values are: the frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). For a scan that runs every three weeks on Monday Wednesday and Friday, the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`.","example":"FREQ=DAILY;INTERVAL=1"},"timezone":{"type":"string","description":"The timezone for the scan schedule. Use the [GET /scans/timezones] (#scans-timezones) endpoint to find the scanner ID.","example":"America/New_York"},"text_targets":{"type":"string","description":"The list of targets to scan.","example":"localhost"},"file_targets":{"type":"string","description":"The name of a file containing the list of targets to scan. Use the [POST /files/upload] (#file-upload) endpoint to upload the file to Tenable.io.","example":"scan_targets.txt"},"tag_targets":{"type":"array","description":"The list of asset tag identifiers the scan uses to determine which assets it evaluates. For more information about tag-based scans, see [Manage Tag-Based Scans](/docs/manage-tag-based-scans-tio).","items":{"type":"string","description":"The UUID for an asset tag value. For more information about asset tags, see [Manage Asset Tags](/docs/manage-asset-tags-tio)."}},"agent_group_id":{"items":{"type":"string"},"description":"An array of agent group UUIDs to scan. Required if the scan is an agent scan.","type":"array"},"emails":{"type":"string","description":"A comma-separated list of accounts that receive the email summary report.","example":"test1@test.com, test2@test.com"},"acls":{"items":{"type":"object","properties":{"permissions":{"type":"integer","description":"The scan permission. For more information, see [Permissions](/docs/permissions)."},"owner":{"type":"integer","description":"A value that indicates whether the user or user group specified in the object owns the scan. Possible values include: `null` (system-owned permissions), `0` (the user is not the owner of the scan), `1` (the user is the owner of the scan)."},"display_name":{"type":"string","description":"The name of the user or group granted the specified permissions, as it appears in the Tenable.io user interface."},"name":{"type":"string","description":"The name of the user or group granted the specified permissions."},"id":{"type":"integer","description":"A number representing the order in which the user or user groups display in the Permissions tab in the Tenable.io user interface."},"type":{"type":"string","description":"The type of scan permissions: `default` (default permissions for the scan), `user` (permissions for an individual user), or `group` (permissions for a user group)."}}},"description":"An array containing permissions to apply to the scan.","type":"array","example":"[{\"type\": \"default\", \"permissions\": 16}, {\"type\": \"user\", \"permissions\": 64, \"name\": \"admin\", \"id\": 1, \"owner\": 1}]"}},"required":["name","enabled","text_targets"]}},"required":["uuid"]}}}},"responses":{"200":{"description":"Returned if Tenable.io creates the scan configuration successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"container_id":{"type":"string","description":"The unique ID of your Tenable.io instance."},"owner_uuid":{"type":"string","description":"The unique ID of the scan owner."},"uuid":{"type":"string","description":"The UUID of the schedule for the scan."},"name":{"type":"string","description":"The user-defined scan name."},"description":{"type":"string","description":"A brief user-defined description of the scan."},"policy_id":{"type":"integer","description":"The unique ID of the policy associated with the scan."},"scanner_id":{"type":"integer","description":"The unique ID of the scanner that the scan is configured to use.","example":"1","format":"int32"},"scanner_uuid":{"type":"string","description":"The UUID of the scanner that the scan is configured to use."},"emails":{"type":"string","description":"A comma-separated list of accounts that receive the email summary report.","example":"test1@test.com, test2@test.com"},"sms":{"type":"string","description":"A comma-separated list of mobile phone numbers that receive notification of the scan."},"enabled":{"type":"boolean","description":"A value indicating whether the scan schedule is active (`true`) or inactive (`false`)."},"dashboard_file":{"type":"string","description":"The name of the dashboard file associated with the scan."},"include_aggregate":{"type":"boolean","description":"A value indicating whether the scan results appear in dashboards."},"scan_time_window":{"type":"string","description":"The time frame during which agents must report in order to be included and visible in vulnerability reports. For non-agent scans, this attribute is `null`."},"custom_targets":{"type":"string","description":"Targets you specify in the text_targets parameter of the request message that creates or modifies the scan configuration."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"rrules":{"type":"string","description":"The interval at which the scan repeats. The interval is formatted as a string of three values delimited by semi-colons. These values are: the frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). For a scan that runs every three weeks on Monday Wednesday and Friday, the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`. If the scan is not scheduled to recur, this attribute is `null`. "},"timezone":{"type":"string","description":"The timezone for the scan."},"notification_filters":{"type":"array","description":"A list of filters that Tenable.io applies to determine whether it sends a notification email on scan completion to the recipients specified in the `emails` attribute.","items":{"type":"object","properties":{"value":{"type":"string","description":"The attribute value Tenable.io filters on. For example, when filtering on severity, this attribute might specify `Critical`."},"quality":{"type":"string","description":"The operator Tenable.io applies to the filter value, for example, `eq`."},"filter":{"type":"string","description":"The attribute name. For example, use the `risk_factor` attribute if you want to filter on vulnerability severity."}}}},"tag_targets":{"type":"array","description":"The list of asset tag identifiers the scan uses to determine which assets it evaluates. For more information about tag-based scans, see [Manage Tag-Based Scans](/docs/manage-tag-based-scans-tio).","items":{"type":"string","description":"The UUID for an asset tag value. For more information about asset tags, see [Manage Asset Tags](/docs/manage-asset-tags-tio)."}},"shared":{"type":"boolean","description":"If `1`, the scan is shared with users other than the scan owner. The level of sharing is specified in the `acls` attribute of the scan details."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan.","format":"int32"},"default_permissions":{"type":"integer","description":"The default permissions for the scan.","format":"int32"},"owner":{"type":"string","description":"The owner of the scan."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scan."},"last_modification_date":{"type":"integer","description":"For newly-created scans, the date on which the scan configuration was created. For scans that have been launched at least once, this attribute does not represent the date on which the scan configuration was last modified. Instead, it represents the date on which the scan was last launched, in Unix time format. Tenable.io updates this attribute each time the scan launches.","format":"int32"},"creation_date":{"type":"integer","description":"For newly-created scans, the date on which the scan configuration was originally created. For scans that have been launched at least once, this attribute does not represent the date on which the scan configuration was originally created. Instead, it represents the date on which the scan was first launched, in Unix time format.","format":"int32"},"type":{"type":"string","description":"The type of scan."},"id":{"type":"integer","description":"The unique ID of the scan.","format":"int32"}}},"examples":{"response":{"value":{"scan":{"container_id":"40ac4662-6af3-4a0b-b422-93387ec0f298","owner_uuid":"50f84b7f-d1d3-4182-bb46-79cf5c51806e","uuid":"template-c311aa94-82d0-7827-6c71-72978df15544f4f660ba792e0b0f","name":"Full Network Scan","description":"Scan all hosts daily","policy_id":16,"scanner_id":null,"scanner_uuid":"00000000-0000-0000-0000-00000000000000000000000000001","emails":null,"sms":"","enabled":true,"dashboard_file":null,"include_aggregate":true,"scan_time_window":null,"custom_targets":"host1.example.com\r\nhost2.example.com\r\nhost3.example.com\r\nhost4.example.com\r\nhost5.example.com\r\n","starttime":null,"rrules":null,"timezone":"US-Central","notification_filters":null,"tag_targets":["6fb39d03-fc3d-470d-96f1-6dbb7bef1f51","715f11cc-c503-4a73-9dc2-cbfb1089616d"],"shared":0,"user_permissions":128,"default_permissions":0,"owner":"user2@example.com","owner_id":2,"last_modification_date":1544145190,"creation_date":1544145190,"type":"public","id":26}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encounters an error while attempting to save the scan.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List scans","description":"Returns a list of scans where you have at least CAN VIEW [16] permissions.

      Requires BASIC [16] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-list","tags":["Scans"],"parameters":[{"description":"The ID of the folder where the scans you want to list are stored.","required":false,"name":"folder_id","in":"query","schema":{"type":"integer"}},{"description":"Limit the results to those scans that have only changed since the specified time.","required":false,"name":"last_modification_date","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Returns the scan list.","content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the folder."},"name":{"type":"string","description":"The name of the folder."},"type":{"type":"string","description":"The type of the folder (main, trash, custom)."},"default_tag":{"type":"integer","description":"Whether or not the folder is the default (1 or 0)."},"custom":{"type":"integer","description":"The custom status of the folder (1 or 0)."},"unread_count":{"type":"integer","description":"The number of unread scans in the folder."}}}},"scans":{"type":"array","items":{"type":"object","properties":{"legacy":{"type":"boolean","description":"A value indicating whether the scan results were created before a change in storage method. If `true`, Tenable.io stores the results in the old storage method. If `false`, Tenable.io stores the results in the new storage method."},"permissions":{"type":"integer","description":"The requesting user's permissions for the scan.","format":"int32"},"type":{"type":"string","description":"The type of scan."},"read":{"type":"boolean","description":"A value indicating whether the user account associated with the request message has viewed the scan in the Tenable.io user interface. If `1`, the user account has viewed the scan results."},"last_modification_date":{"type":"integer","description":"For newly-created scans, the date on which the scan configuration was created. For scans that have been launched at least once, this attribute does not represent the date on which the scan configuration was last modified. Instead, it represents the date on which the scan was last launched, in Unix time format. Tenable.io updates this attribute each time the scan launches.","format":"int32"},"creation_date":{"type":"integer","description":"For newly-created scans, the date on which the scan configuration was originally created. For scans that have been launched at least once, this attribute does not represent the date on which the scan configuration was originally created. Instead, it represents the date on which the scan was first launched, in Unix time format.","format":"int32"},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"uuid":{"type":"string","description":"The UUID of the scan."},"shared":{"type":"boolean","description":"If `true`, the scan is shared with users other than the scan owner. The level of sharing is specified in the `acls` attribute of the scan details."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan.","format":"int32"},"owner":{"type":"string","description":"The owner of the scan."},"schedule_uuid":{"type":"string","description":"The UUID for a specific instance in the scan schedule."},"timezone":{"type":"string","description":"The timezone for the scan."},"rrules":{"type":"string","description":"The interval at which the scan repeats. The interval is formatted as a string of three values delimited by semi-colons. These values are: the frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). For a scan that runs every three weeks on Monday Wednesday and Friday, the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`. If the scan is not scheduled to recur, this attribute is `null`. "},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"enabled":{"type":"boolean","description":"A value indicating whether the scan schedule is active (`true`) or inactive (`false`)."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"name":{"type":"string","description":"The name of the scan."},"id":{"type":"integer","description":"The unique ID of the scan.","format":"int32"}}}},"timestamp":{"type":"integer","description":"The Unix timestamp when Tenable.io received the list request.","format":"int32"}}},"examples":{"response":{"value":{"folders":[{"unread_count":0,"custom":0,"default_tag":0,"type":"trash","name":"Trash","id":8},{"unread_count":0,"custom":0,"default_tag":1,"type":"main","name":"My Scans","id":9}],"scans":[{"legacy":false,"permissions":128,"type":null,"read":true,"last_modification_date":1430934526,"creation_date":1430933086,"status":"imported","uuid":"2776e999-1f5b-45b9-2e15-65a7be35b2e3ab8f7ecb158c480e","shared":false,"user_permissions":128,"owner":"user2@example.com","schedule_uuid":"0fafc7a8-c5f6-fe9d-68b9-4d60ab0d9d2cf60557ee0e264228","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":false,"name":"KitchenSinkScan","id":11},{"permissions":128,"type":null,"read":true,"last_modification_date":0,"creation_date":0,"status":"empty","uuid":null,"shared":false,"user_permissions":128,"owner":"user2@example.com","schedule_uuid":"template-c311aa94-82d0-7827-6c71-72978df15544f4f660ba792e0b0f","timezone":"US-Central","rrules":null,"starttime":null,"enabled":true,"control":true,"name":"Full Network Scan","id":26},{"permissions":128,"type":null,"read":true,"last_modification_date":0,"creation_date":0,"status":"empty","uuid":null,"shared":false,"user_permissions":128,"owner":"user2@example.com","schedule_uuid":"template-5f050b0a-6be6-1eb8-68dc-5a2f459ec7f710499ad7667391df","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":true,"name":"test-scan-2","id":25},{"permissions":128,"type":null,"read":true,"last_modification_date":0,"creation_date":0,"status":"empty","uuid":null,"shared":false,"user_permissions":128,"owner":"user2@example.com","schedule_uuid":"template-95626145-6b38-96bc-238f-148b1353910d0c27f2a930c2c843","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":true,"name":"test-scan","id":23},{"permissions":128,"type":null,"read":true,"last_modification_date":0,"creation_date":0,"status":"empty","uuid":null,"shared":false,"user_permissions":128,"owner":"user2@example.com","schedule_uuid":"template-ac665a85-2fca-f34c-9294-9bdc5bc925aee1c068e70183409e","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":true,"name":"basic Scan","id":21}],"timestamp":1544146142}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}":{"get":{"summary":"Get scan details","description":"Returns details for the specified scan.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-details","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to retrieve. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string","format":"uuid"}},{"description":"The history\\_id of the historical data that should be returned.","name":"history_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The history\\_uuid of the historical data that should be returned.","name":"history_uuid","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the scan details.","content":{"application/json":{"schema":{"type":"object","properties":{"info":{"type":"object","properties":{"owner":{"type":"string","description":"The owner of the scan."},"name":{"type":"string","description":"The name of the scan."},"no_target":{"type":"boolean","description":"Indicates whether the scan based on this policy can specify targets."},"folder_id":{"type":"integer","description":"The unique ID of the destination folder for the scan.","format":"int32"},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan.","format":"int32"},"schedule_uuid":{"type":"string","description":"The UUID for a specific instance in the scan schedule."},"edit_allowed":{"type":"boolean","description":"If `true`, the requesting user can edit this scan configuration."},"scanner_name":{"type":"string","description":"The name of the scanner configured to run the scan."},"policy":{"type":"string","description":"The name of the scan template associated with the scan."},"shared":{"type":"boolean","description":"If `true`, the scan is shared with users other than the owner. The level of sharing is specified in the `acls` attribute of the scan details."},"object_id":{"type":"integer","description":"","format":"int32"},"tag_targets":{"type":"array","description":"The list of asset tag identifiers the scan uses to determine which assets it evaluates. For more information about tag-based scans, see [Manage Tag-Based Scans](/docs/manage-tag-based-scans-tio).","items":{"type":"string","description":"The UUID for an asset tag value. For more information about asset tags, see [Manage Asset Tags](/docs/manage-asset-tags-tio)."}},"acls":{"type":"array","description":"An array of objects that control sharing permissions for the scan.","items":{"type":"object","properties":{"permissions":{"type":"integer","description":"The scan permission. For more information, see [Permissions](/docs/permissions)."},"owner":{"type":"integer","description":"A value that indicates whether the user or user group specified in the object owns the scan. Possible values include: `null` (system-owned permissions), `0` (the user is not the owner of the scan), `1` (the user is the owner of the scan)."},"display_name":{"type":"string","description":"The name of the user or group granted the specified permissions, as it appears in the Tenable.io user interface."},"name":{"type":"string","description":"The name of the user or group granted the specified permissions."},"id":{"type":"integer","description":"A number representing the order in which the user or user groups display in the Permissions tab in the Tenable.io user interface."},"type":{"type":"string","description":"The type of scan permissions: `default` (default permissions for the scan), `user` (permissions for an individual user), or `group` (permissions for a user group)."}}}},"hostcount":{"type":"integer","description":"The total number of assets scanned for vulnerabilities.","format":"int32"},"uuid":{"type":"string","description":"The UUID of the scan."},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"scan_type":{"type":"string","description":"The type of scan: `local` (a credentialed scan performed over the network), `remote` (an uncredentialed scan performed over the network, `agent` (a scan on a local host that a Nessus agent performs directly), or `null` (the scan has never been launched, or the scan is imported)."},"targets":{"type":"string","description":"A comma-delimited list of IPv4 addresses that are configured as targets for the scan."},"alt_targets_used":{"type":"boolean","description":"If `true`, Tenable.io did not not launched with a target list. This parameter is `true` for agent scans."},"pci-can-upload":{"type":"boolean","description":"If `true`, you can submit the results of the scan for PCI ASV review. For more information, see [PCI ASV](https://docs.tenable.com/cloud/Content/PCI_ASV/Welcome.htm) in the Tenable.io Vulnerability Management User Guide."},"scan_start":{"type":"integer","description":"The Unix timestamp when the scan instance started.","format":"int32"},"timestamp":{"type":"integer","description":"The Unix timestamp when the scan instance finished."},"scan_end":{"type":"integer","description":"The Unix timestamp when the scan instance finished."},"haskb":{"type":"boolean","description":"Indicates whether a scan has a Knowledge Base (KB) associated with it. A KB is an ASCII text file containing a log of information relevant to the scan performed and results found."},"hasaudittrail":{"type":"boolean","description":"Indicates whether the scan is configured to create an audit trail."},"scanner_start":{"type":"string","description":"The scan's start time, if the scan is imported."},"scanner_end":{"type":"string","description":"The scan's end time, if the scan is imported."}}},"comphosts":{"type":"array","items":{"type":"object","properties":{"totalchecksconsidered":{"type":"integer","description":"The total number of checks considered on the host."},"numchecksconsidered":{"type":"integer","description":"The number of checks considered on the host."},"scanprogresstotal":{"type":"integer","description":"The total scan progress for the host."},"scanprogresscurrent":{"type":"integer","description":"The current scan progress for the host."},"host_index":{"type":"string","description":"The index for the host."},"score":{"type":"integer","description":"The overall score for the host."},"severitycount":{"type":"object","properties":{}},"progress":{"type":"string","description":"The scan progress of the host."},"critical":{"type":"integer","description":"The percentage of critical findings on the host."},"high":{"type":"integer","description":"The percentage of high findings on the host."},"medium":{"type":"integer","description":"The percentage of medium findings on the host."},"low":{"type":"integer","description":"The percentage of low findings on the host."},"info":{"type":"integer","description":"The percentage of info findings on the host."},"host_id":{"type":"integer","description":"The unique ID of the host."},"hostname":{"type":"string","description":"The name of the host."}}}},"hosts":{"type":"array","items":{"type":"object","properties":{"totalchecksconsidered":{"type":"integer","description":"The total number of checks considered on the host."},"numchecksconsidered":{"type":"integer","description":"The number of checks considered on the host."},"scanprogresstotal":{"type":"integer","description":"The total scan progress for the host."},"scanprogresscurrent":{"type":"integer","description":"The current scan progress for the host."},"host_index":{"type":"string","description":"The index for the host."},"score":{"type":"integer","description":"The overall score for the host."},"severitycount":{"type":"object","properties":{}},"progress":{"type":"string","description":"The scan progress of the host."},"critical":{"type":"integer","description":"The percentage of critical findings on the host."},"high":{"type":"integer","description":"The percentage of high findings on the host."},"medium":{"type":"integer","description":"The percentage of medium findings on the host."},"low":{"type":"integer","description":"The percentage of low findings on the host."},"info":{"type":"integer","description":"The percentage of info findings on the host."},"host_id":{"type":"integer","description":"The unique ID of the host."},"hostname":{"type":"string","description":"The name of the host."}}}},"notes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"The title of the note."},"message":{"type":"string","description":"The specific message of the note."},"severity":{"type":"integer","description":"The severity of the note."}}}},"remediations":{"type":"object"},"vulnerabilities":{"type":"array","items":{"type":"object","properties":{"count":{"type":"integer","description":"The number of vulnerabilities found."},"plugin_name":{"type":"string","description":"The name of the vulnerability plugin."},"vuln_index":{"type":"integer","description":"The index of the vulnerability plugin."},"severity":{"type":"integer","description":"The severity rating of the plugin."},"plugin_id":{"type":"integer","description":"The unique ID of the vulnerability plugin."},"severity_index":{"type":"integer","description":"The severity index order of the plugin."},"plugin_family":{"type":"string","description":"The parent family of the vulnerability plugin."}}}},"filters":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The short name of the filter."},"readable_name":{"type":"string","description":"The long name of the filter."},"operators":{"description":"The comparison options for the filter.","type":"array","items":{"type":"string"}},"control":{"type":"object","properties":{"type":{"type":"string","description":"The input type (entry or dropdown)."},"readable_regest":{"type":"string","description":"The placeholder for the input."},"regex":{"type":"string","description":"A regex for checking the value of the input."},"options":{"description":"A list of options if the input is a dropdown.","type":"array","items":{"type":"string"}}}}}}},"history":{"type":"array","items":{"type":"object","properties":{"alt_targets_used":{"type":"boolean","description":"If `true`, Tenable.io did not not launched with a target list. This parameter is `true` for agent scans."},"scheduler":{"type":"integer","description":"If `true`, Tenable.io launched the scan automatically from a schedule."},"status":{"type":"string","description":"The status of the historical data."},"type":{"type":"string","description":"The type of scan: local, remote, or agent."},"uuid":{"type":"string","description":"The UUID of the historical data."},"last_modification_date":{"type":"integer","description":"The last modification date for the historical data in Unix time."},"creation_date":{"type":"integer","description":"The creation date for the historical data in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scan."},"history_id":{"type":"integer","description":"The unique ID of the historical data."}}}},"compliance":{"type":"array","items":{"type":"object","properties":{"count":{"type":"integer","description":"The number of vulnerabilities found."},"plugin_name":{"type":"string","description":"The name of the vulnerability plugin."},"vuln_index":{"type":"integer","description":"The index of the vulnerability plugin."},"severity":{"type":"integer","description":"The severity rating of the plugin."},"plugin_id":{"type":"integer","description":"The unique ID of the vulnerability plugin."},"severity_index":{"type":"integer","description":"The severity index order of the plugin."},"plugin_family":{"type":"string","description":"The parent family of the vulnerability plugin."}}}}}},"examples":{"response":{"value":{"info":{"owner":"user2@example.com","name":"KitchenSinkScan","no_target":false,"folder_id":9,"control":false,"user_permissions":128,"schedule_uuid":"0fafc7a8-c5f6-fe9d-68b9-4d60ab0d9d2cf60557ee0e264228","edit_allowed":false,"scanner_name":null,"policy":null,"shared":false,"object_id":11,"tag_targets":["6fb39d03-fc3d-470d-96f1-6dbb7bef1f51","715f11cc-c503-4a73-9dc2-cbfb1089616d"],"acls":[{"permissions":0,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"user2@example.com","name":"user2@example.com","id":2,"type":"user"}],"hostcount":10,"uuid":"2776e999-1f5b-45b9-2e15-65a7be35b2e3ab8f7ecb158c480e","status":"imported","scan_type":null,"targets":null,"alt_targets_used":null,"pci-can-upload":null,"scan_start":1430933086,"timestamp":1430934526,"scan_end":1430934526,"haskb":true,"hasaudittrail":true,"scanner_start":null,"scanner_end":null},"history":[{"history_id":10328682,"owner_id":2,"creation_date":1430933086,"last_modification_date":1430934526,"uuid":"2776e999-1f5b-45b9-2e15-65a7be35b2e3ab8f7ecb158c480e","type":null,"status":"imported","scheduler":0,"alt_targets_used":false}],"hosts":[{"asset_id":5,"host_id":5,"hostname":"172.204.81.57","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":156,"severitylevel":0},{"count":1,"severitylevel":1},{"count":6,"severitylevel":2},{"count":3,"severitylevel":3},{"count":0,"severitylevel":4}]},"severity":166,"score":3766,"info":156,"low":1,"medium":6,"high":3,"critical":0,"host_index":0},{"asset_id":3,"host_id":3,"hostname":"172.204.81.57","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":52,"severitylevel":0},{"count":11,"severitylevel":1},{"count":100,"severitylevel":2},{"count":58,"severitylevel":3},{"count":32,"severitylevel":4}]},"severity":253,"score":388162,"info":52,"low":11,"medium":100,"high":58,"critical":32,"host_index":1},{"asset_id":9,"host_id":9,"hostname":"172.204.81.57","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":115,"severitylevel":0},{"count":5,"severitylevel":1},{"count":21,"severitylevel":2},{"count":45,"severitylevel":3},{"count":16,"severitylevel":4}]},"severity":202,"score":207265,"info":115,"low":5,"medium":21,"high":45,"critical":16,"host_index":2}],"vulnerabilities":[{"count":68,"plugin_id":34220,"plugin_name":"Netstat Portscanner (WMI)","severity":0,"plugin_family":"Port scanners","vuln_index":1},{"count":65,"plugin_id":34252,"plugin_name":"Microsoft Windows Remote Listeners Enumeration (WMI)","severity":0,"plugin_family":"Windows","vuln_index":2},{"count":41,"plugin_id":14272,"plugin_name":"netstat portscanner (SSH)","severity":0,"plugin_family":"Port scanners","vuln_index":3}],"comphosts":[{"asset_id":5,"host_id":5,"hostname":"172.204.81.57","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":0,"severitylevel":0},{"count":145,"severitylevel":1},{"count":62,"severitylevel":2},{"count":0,"severitylevel":3},{"count":86,"severitylevel":4}]},"score":867650,"info":0,"low":145,"medium":62,"high":0,"critical":86,"host_index":0,"severity":293},{"asset_id":3,"host_id":3,"hostname":"172.204.81.57","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":0,"severitylevel":0},{"count":52,"severitylevel":1},{"count":1,"severitylevel":2},{"count":0,"severitylevel":3},{"count":45,"severitylevel":4}]},"score":450620,"info":0,"low":52,"medium":1,"high":0,"critical":45,"host_index":1,"severity":98},{"asset_id":9,"host_id":9,"hostname":"172.204.81.57","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":0,"severitylevel":0},{"count":61,"severitylevel":1},{"count":23,"severitylevel":2},{"count":0,"severitylevel":3},{"count":64,"severitylevel":4}]},"score":642910,"info":0,"low":61,"medium":23,"high":0,"critical":64,"host_index":2,"severity":148}],"compliance":[{"count":5,"host_id":0,"hostname":null,"plugin_family":"Unix Compliance Checks","plugin_id":"143e17d31e1a30830fcc2c3539d803d0","plugin_name":"BSI-100-2: S 4.13: /etc/group consistency - Careful allocation of identifiers","severity":1,"severity_index":0},{"count":5,"host_id":0,"hostname":null,"plugin_family":"Unix Compliance Checks","plugin_id":"21361aed1df4e64051bc939431ca3096","plugin_name":"BSI-100-2: S 4.105: No world writeable files - Preventing unauthorised acquisition of administrator rights","severity":3,"severity_index":1},{"count":5,"host_id":0,"hostname":null,"plugin_family":"Unix Compliance Checks","plugin_id":"5784176f032f448cb73b6cd4d4cff8be","plugin_name":"BSI-100-2: S 4.105: Rlogind must be deactivated","severity":3,"severity_index":2}],"filters":[{"name":"host.id","readable_name":"Asset ID","control":{"type":"entry","regex":"[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*","readable_regex":"01234567-abcd-ef01-2345-6789abcdef01"},"operators":["eq","neq","match","nmatch"],"group_name":"vulnerability"},{"name":"plugin.attributes.bid","readable_name":"Bugtraq ID","control":{"type":"entry","regex":"^[0-9]+(,[0-9]+)*","readable_regex":"NUMBER","maxlength":18},"operators":["eq","neq","match","nmatch"],"group_name":"vulnerability"},{"name":"plugin.attributes.exploit_framework_canvas","readable_name":"CANVAS Exploit Framework","control":{"type":"dropdown","list":["true","false"]},"operators":["eq","neq"],"group_name":"vulnerability"}],"notes":[{"message":"One or more AirWatch API settings are not set","title":"MDM501 AirWatch API settings misconfiguration"},{"message":"Unable to connect to the ActiveSync server.","title":"MDM501 ActiveSync connection error"},{"message":"ADSI server (matrix.tenablesecurity.com) could not connect to server.","title":"adsi_enum_directory_trusts.nbin: ADSI error"}],"remediations":{"num_cves":2536,"num_hosts":10,"num_remediated_cves":2283,"num_impacted_hosts":8,"remediations":[{"vulns":1,"value":"96a449d372af3d23ca9a4f9f9a2ea73e","hosts":1,"remediation":"FreeBSD : gpgme -- heap-based buffer overflow in gpgsm status handler (90ca3ba5-19e6-11e4-8616-001b3856973b): Update the affected package."},{"vulns":296,"value":"319a8ba9e264c876028b2a5abf5a1b0e","hosts":1,"remediation":"FreeBSD : mozilla -- multiple vulnerabilities (d0c97697-df2c-4b8b-bff2-cec24dc35af8): Update the affected packages."},{"vulns":0,"value":"c22105fdc2756d11c39d1380e58c8fbc","hosts":1,"remediation":"RHEL 6 / 7 : postgresql (RHSA-2015:0750): Update the affected packages."}]}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update scan","description":"Updates the scan configuration. For example, you can enable or disable a scan, change the scan name, description, folder, scanner, targets, and schedule parameters.

      Requires SCAN MANAGER [40] user permissions and CAN CONFIGURE [64] scan permissions. See Permissions.

      \nNote: You can specify scan targets as text, input file, or target groups.","operationId":"scans-configure","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to update. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"settings":{"type":"object","properties":{"name":{"type":"string","description":"The name of the scan."},"description":{"type":"string","description":"The description of the scan."},"folder_id":{"type":"integer","description":"The unique ID of the destination folder for the scan.","format":"int32"},"scanner_id":{"type":"integer","description":"The unique ID of the scanner to use.","example":"1","format":"int32"},"scanner_uuid":{"type":"string","description":"The UUID of the scanner to use. Use the [GET /scanners] (#scanners-list) endpoint to find the scanner UUID."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"launch":{"type":"string","description":"When to launch the scan. (i.e. DAILY, WEEKLY, MONTHLY, YEARLY)","enum":["DAILY","WEEKLY","MONTHLY","YEARLY"]},"starttime":{"type":"string","description":"The starting time and date for the scan (i.e. YYYYMMDDTHHMMSS).","example":"20140826T133000"},"rrules":{"type":"string","description":"Expects a semi-colon delimited string comprised of three values. The frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). To create a scan that runs every three weeks on Monday Wednesday and Friday the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`","example":"FREQ=DAILY;INTERVAL=1"},"timezone":{"type":"string","description":"The timezone for the scan schedule.","example":"America/New_York"},"target_groups":{"items":{"type":"string"},"description":"An array of target group IDs to scan.","type":"array","example":"[2, 8, 12]"},"agent_group_id":{"items":{"type":"string"},"description":"An array of agent group UUIDs to scan. Required if the scan is an agent scan.","type":"array"},"text_targets":{"type":"string","description":"Comma separated list of targets to scan. Required for non-agent scans if no target groups are provided.","example":"localhost"},"file_targets":{"type":"string","description":"The name of a file containing the list of targets to scan.","example":"targets.txt"},"tag_targets":{"type":"array","description":"The list of asset tag identifiers the scan uses to determine which assets it evaluates. For more information about tag-based scans, see [Manage Tag-Based Scans](/docs/manage-tag-based-scans-tio).","items":{"type":"string","description":"The UUID for an asset tag value. For more information about asset tags, see [Manage Asset Tags](/docs/manage-asset-tags-tio)."}},"emails":{"type":"string","description":"A comma-separated list of accounts that receive the email summary report.","example":"test1@test.com, test2@test.com"},"acls":{"items":{"type":"object","properties":{"permissions":{"type":"integer","description":"The scan permission. For more information, see [Permissions](/docs/permissions)."},"owner":{"type":"integer","description":"A value that indicates whether the user or user group specified in the object owns the scan. Possible values include: `null` (system-owned permissions), `0` (the user is not the owner of the scan), `1` (the user is the owner of the scan)."},"display_name":{"type":"string","description":"The name of the user or group granted the specified permissions, as it appears in the Tenable.io user interface."},"name":{"type":"string","description":"The name of the user or group granted the specified permissions."},"id":{"type":"integer","description":"A number representing the order in which the user or user groups display in the Permissions tab in the Tenable.io user interface."},"type":{"type":"string","description":"The type of scan permissions: `default` (default permissions for the scan), `user` (permissions for an individual user), or `group` (permissions for a user group)."}}},"description":"An array containing sharing permissions to apply to the scan.","type":"array","example":"[{\"type\": \"default\", \"permissions\": 16}, {\"type\": \"user\", \"permissions\": 64, \"name\": \"admin\", \"id\": 1, \"owner\": 1}]"}},"required":["text_targets","file_targets","target_groups"]}}}}}},"responses":{"200":{"description":"Returned if Tenable.io updates the scan configuration as specified.","content":{"application/json":{"schema":{"type":"object","properties":{"container_id":{"type":"string","description":"The unique ID of your Tenable.io instance."},"owner_uuid":{"type":"string","description":"The unique ID of the scan owner."},"uuid":{"type":"string","description":"The UUID of the schedule for the scan."},"name":{"type":"string","description":"The user-defined scan name."},"description":{"type":"string","description":"A brief user-defined description of the scan."},"policy_id":{"type":"integer","description":"The unique ID of the policy associated with the scan."},"scanner_id":{"type":"integer","description":"The unique ID of the scanner that the scan is configured to use.","example":"1","format":"int32"},"scanner_uuid":{"type":"string","description":"The UUID of the scanner that the scan is configured to use."},"emails":{"type":"string","description":"A comma-separated list of accounts that receive the email summary report.","example":"test1@test.com, test2@test.com"},"sms":{"type":"string","description":"A comma-separated list of mobile phone numbers that receive notification of the scan."},"enabled":{"type":"boolean","description":"A value indicating whether the scan schedule is active (`true`) or inactive (`false`)."},"dashboard_file":{"type":"string","description":"The name of the dashboard file associated with the scan."},"include_aggregate":{"type":"boolean","description":"A value indicating whether the scan results appear in dashboards."},"scan_time_window":{"type":"string","description":"The time frame during which agents must report in order to be included and visible in vulnerability reports. For non-agent scans, this attribute is `null`."},"custom_targets":{"type":"string","description":"Targets you specify in the text_targets parameter of the request message that creates or modifies the scan configuration."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"rrules":{"type":"string","description":"The interval at which the scan repeats. The interval is formatted as a string of three values delimited by semi-colons. These values are: the frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). For a scan that runs every three weeks on Monday Wednesday and Friday, the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`. If the scan is not scheduled to recur, this attribute is `null`. "},"timezone":{"type":"string","description":"The timezone for the scan."},"notification_filters":{"type":"array","description":"A list of filters that Tenable.io applies to determine whether it sends a notification email on scan completion to the recipients specified in the `emails` attribute.","items":{"type":"object","properties":{"value":{"type":"string","description":"The attribute value Tenable.io filters on. For example, when filtering on severity, this attribute might specify `Critical`."},"quality":{"type":"string","description":"The operator Tenable.io applies to the filter value, for example, `eq`."},"filter":{"type":"string","description":"The attribute name. For example, use the `risk_factor` attribute if you want to filter on vulnerability severity."}}}},"tag_targets":{"type":"array","description":"The list of asset tag identifiers the scan uses to determine which assets it evaluates. For more information about tag-based scans, see [Manage Tag-Based Scans](/docs/manage-tag-based-scans-tio).","items":{"type":"string","description":"The UUID for an asset tag value. For more information about asset tags, see [Manage Asset Tags](/docs/manage-asset-tags-tio)."}},"shared":{"type":"boolean","description":"If `1`, the scan is shared with users other than the scan owner. The level of sharing is specified in the `acls` attribute of the scan details."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan.","format":"int32"},"default_permissions":{"type":"integer","description":"The default permissions for the scan.","format":"int32"},"owner":{"type":"string","description":"The owner of the scan."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scan."},"last_modification_date":{"type":"integer","description":"For newly-created scans, the date on which the scan configuration was created. For scans that have been launched at least once, this attribute does not represent the date on which the scan configuration was last modified. Instead, it represents the date on which the scan was last launched, in Unix time format. Tenable.io updates this attribute each time the scan launches.","format":"int32"},"creation_date":{"type":"integer","description":"For newly-created scans, the date on which the scan configuration was originally created. For scans that have been launched at least once, this attribute does not represent the date on which the scan configuration was originally created. Instead, it represents the date on which the scan was first launched, in Unix time format.","format":"int32"},"type":{"type":"string","description":"The type of scan."},"id":{"type":"integer","description":"The unique ID of the scan.","format":"int32"}}},"examples":{"response":{"value":{"container_id":"40ac4662-6af3-4a0b-b422-93387ec0f298","owner_uuid":"50f84b7f-d1d3-4182-bb46-79cf5c51806e","uuid":"template-c311aa94-82d0-7827-6c71-72978df15544f4f660ba792e0b0f","name":"Basic Daily Network Scan","description":"Scan all hosts daily","policy_id":16,"scanner_id":null,"scanner_uuid":"00000000-0000-0000-0000-00000000000000000000000000001","emails":"","sms":"","enabled":false,"dashboard_file":null,"include_aggregate":true,"scan_time_window":null,"custom_targets":"host1.example.com\r\nhost2.example.com\r\nhost3.example.com\r\nhost4.example.com\r\nhost5.example.com\r\n","starttime":null,"rrules":null,"timezone":"US-Central","notification_filters":[{"value":"Critical","quality":"eq","filter":"risk_factor"}],"tag_targets":["6fb39d03-fc3d-470d-96f1-6dbb7bef1f51","715f11cc-c503-4a73-9dc2-cbfb1089616d"],"shared":0,"user_permissions":128,"default_permissions":0,"owner":"user2@example.com","owner_id":2,"last_modification_date":1544207231,"creation_date":1544145190,"type":"public","id":26}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encounters an error while attempting to save the configuration.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete scan","description":"Deletes a scan.\n**Note:** You cannot delete scans in running, paused, or stopping states.

      Requires SCAN MANAGER [40] user permissions and CAN CONFIGURE [64] scan permissions. See Permissions.

      ","operationId":"scans-delete","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to delete. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the specified scan configuration.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the scan.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/launch":{"post":{"summary":"Launch scan","description":"Launches a scan.

      Requires SCAN OPERATOR [24] user permissions and CAN CONTROL [32] scan permissions. See Permissions.

      ","operationId":"scans-launch","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to launch. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"alt_targets":{"items":{"type":"string"},"description":"If you include this parameter, Tenable.io scans these targets instead of the default. Value can be an array where each index is a target, or an array with a single index of comma-separated targets.","type":"array"}}}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully launches the scan.","content":{"application/json":{"schema":{"type":"object","properties":{"scan_uuid":{"type":"string"}}},"examples":{"response":{"value":{"scan_uuid":"e7f6c3f2-1718-4451-b459-1e8aa2ec6cdf"}}}}}},"403":{"description":"Returned if Tenable.io cannot launch the scan because the scan is disabled."},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/pause":{"post":{"summary":"Pause scan","description":"Pauses a scan.

      Requires SCAN OPERATOR [24] user permissions and CAN CONTROL [32] scan permissions. See Permissions.

      ","operationId":"scans-pause","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to pause. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully queues the scan to pause.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"409":{"description":"Returned if the scan is not currently active."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/resume":{"post":{"summary":"Resume scan","description":"Resumes a scan.

      Requires SCAN OPERATOR [24] user permissions and CAN CONTROL [32] scan permissions. See Permissions.

      ","operationId":"scans-resume","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to resume. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully queues the scan to resume.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"409":{"description":"Returned if the scan is not currently active."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/stop":{"post":{"summary":"Stop scan","description":"Stops a scan.

      Requires SCAN OPERATOR [24] user permissions and CAN CONTROL [32] scan permissions. See Permissions.

      ","operationId":"scans-stop","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to stop. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully queues the scan to stop.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"409":{"description":"Returned if the scan is not currently active."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/schedule":{"put":{"summary":"Enable schedule","description":"Enables or disables a scan schedule.

      Requires SCAN OPERATOR [24] user permisisons and CAN CONTROL [32] scan permissions. See Permissions.

      ","operationId":"scans-schedule","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to schedule. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enables or disables the scan schedule."}},"required":["enabled"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully enabled or disabled the scan schedule.","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"rrules":{"type":"string","description":"Indicates whether the schedule is enabled."},"starttime":{"type":"string","description":"Indicates whether the schedule is enabled."},"timezone":{"type":"string","description":"Indicates whether the schedule is enabled."}}},"examples":{"response":{"value":{"control":true,"enabled":false,"rrules":"FREQ=DAILY;INTERVAL=1","timezone":"US/Central","starttime":"20181206T230000"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if the scan does not have a schedule to enable.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/latest-status":{"get":{"summary":"Get latest scan status","description":"Returns the latest status for a scan. Status values can include:
      • aborted—Tenable.io or the scanner encountered problems during the latest run and aborted the scan. The scan results associated with the run reflect only the completed tasks.
      • canceled—At user request, Tenable.io successfully stopped the latest scan run.
      • completed—The latest run of the scan completed.
      • empty—The scan configuration is new or has yet to run.
      • imported—A user imported the scan. You cannot run imported scans. Scan history is unavailable for imported scans.
      • pausing—A user paused a running scan, and Tenable.io is in the process of terminating tasks for the scan.
      • paused—At user request, Tenable.io successfully paused active tasks related to the scan. The paused tasks continue to fill the task capacity of the scanner that the tasks were assigned to. Tenable.io does not dispatch new tasks from a paused scan job. If the scan remains in a paused state for more than 14 days, the scan times out. Tenable.io then aborts the related tasks on the scanner and categorizes the scan as aborted.
      • processing—Tenable.io is processing tasks for the scan. For example, Tenable.io may be importing scan results from the scanner that performed the latest run of the scan.
      • resuming—Tenable.io is restarting tasks for a paused scan. When you resume a scan, Tenable.io instructs the scanner to start the tasks from the point at which the scan was paused. If Tenable.io or the scanner encounters problems when resuming the scan, the scan fails, and Tenable.io updates the scan status to aborted.
      • running—The scan is currently running.
      • stopping—A user stopped a pending, running, or paused scan, and Tenable.io is in the process of terminating tasks for the scan.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-get-latest-status","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully retrieves the scan status.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"The latest status of the scan."}}},"examples":{"response":{"value":{"status":"imported"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encounters an error while attempting to retrieve the status.","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/status":{"put":{"summary":"Update scan status","description":"Changes the status of a scan.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-read-status","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean","description":"If `true`, the scan has been read."}},"required":["read"]}}}},"responses":{"200":{"description":"Returned if Tenable.io updates the scan status.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/copy":{"post":{"summary":"Copy scan","description":"Copies the specified scan.

      Requires SCAN OPERATOR [24] user permissions and CAN CONFIGURE [64] scan permissions. See Permissions.

      ","operationId":"scans-copy","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to copy. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"folder_id":{"type":"integer","description":"The ID of the destination folder. If you don't specify a folder ID, Tenable.io creates the copy in the same folder as the original.","format":"int32"},"name":{"type":"string","description":"The name of the copied scan. If you don't specify a name, Tenable.io uses the same name as the original with \"Copy of\" prefix."}}}}}},"responses":{"200":{"description":"Returns the copied scan object.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the scan."},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"id":{"type":"integer","description":"The unique ID of the scan."},"last_modification_date":{"type":"integer","description":"For newly-created scans, the date on which the scan configuration was created. For scans that have been launched at least once, this attribute does not represent the date on which the scan configuration was last modified. Instead, it represents the date on which the scan was last launched, in Unix time format. Tenable.io updates this attribute each time the scan launches.","format":"int32"},"uuid":{"type":"string","description":"The UUID of the scan."},"type":{"type":"string","description":"The type of scan (local, remote, or agent)."},"owner":{"type":"string","description":"The owner of the scan."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"read":{"type":"boolean","description":"If `true`, the scan has been read."},"shared":{"type":"boolean","description":"If `1`, the scan is shared with users other than the scan owner. The level of sharing is specified in the `acls` attribute of the scan details."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan."},"creation_date":{"type":"integer","description":"The creation date for the scan in Unix time."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"timezone":{"type":"string","description":"The timezone for the scan."},"rrules":{"type":"string","description":"The interval at which the scan repeats. The interval is formatted as a string of three values delimited by semi-colons. These values are: the frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). For a scan that runs every three weeks on Monday Wednesday and Friday, the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`. If the scan is not scheduled to recur, this attribute is `null`. "},"schedule_uuid":{"type":"string","description":"The UUID for a specific instance in the scan schedule."}}},"examples":{"response":{"value":{"timezone":"US-Central","enabled":false,"last_modification_date":1544207231,"id":28,"status":"empty","user_permissions":128,"owner":"user@example.com","starttime":null,"control":true,"uuid":"6240fb62-e950-5a07-90fa-9a82634a851615c4912d35e1c64c","rrules":null,"creation_date":1544145190,"read":false,"shared":false,"name":"Copy of Basic Network Scan - Daily"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encounters an error while attempting to copy.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scans/import":{"post":{"summary":"Import uploaded scan","description":"Import an existing scan uploaded using [Upload File] (#file-upload) endpoint.\n**Note:** You cannot import results from scans run more than 15 months ago.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"scans-import","tags":["Scans"],"parameters":[{"description":"Specifies whether to include the imported scan data in the vulnerabilities dashboard views. To include, use `1`. To exclude, use `0`. If you don't specify the include_aggregate parameter, the data does not appear in the dashboard.","required":false,"name":"include_aggregate","in":"query","schema":{"type":"integer","enum":[0,1]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"The name of the file to import as provided by the response from [Upload File] (#file-upload) endpoint."},"folder_id":{"type":"integer","description":"The ID of the destination folder. If you omit this parameter, Tenable.io stores the imported scan in the default folder.","format":"int32"},"password":{"type":"string","description":"The password for the file to import (required for nessus.db).","format":"password"}},"required":["file"]}}}},"responses":{"200":{"description":"Returns the scan object.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the scan."},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"id":{"type":"integer","description":"The unique ID of the scan."},"last_modification_date":{"type":"integer","description":"For newly-created scans, the date on which the scan configuration was created. For scans that have been launched at least once, this attribute does not represent the date on which the scan configuration was last modified. Instead, it represents the date on which the scan was last launched, in Unix time format. Tenable.io updates this attribute each time the scan launches.","format":"int32"},"uuid":{"type":"string","description":"The UUID of the scan."},"type":{"type":"string","description":"The type of scan (local, remote, or agent)."},"owner":{"type":"string","description":"The owner of the scan."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"read":{"type":"boolean","description":"If `true`, the scan has been read."},"shared":{"type":"boolean","description":"If `1`, the scan is shared with users other than the scan owner. The level of sharing is specified in the `acls` attribute of the scan details."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan."},"creation_date":{"type":"integer","description":"The creation date for the scan in Unix time."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"timezone":{"type":"string","description":"The timezone for the scan."},"rrules":{"type":"string","description":"The interval at which the scan repeats. The interval is formatted as a string of three values delimited by semi-colons. These values are: the frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). For a scan that runs every three weeks on Monday Wednesday and Friday, the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`. If the scan is not scheduled to recur, this attribute is `null`. "},"schedule_uuid":{"type":"string","description":"The UUID for a specific instance in the scan schedule."}}},"examples":{"response":{"value":{"scan":{"timezone":null,"id":38,"last_modification_date":1544219402,"status":"imported","user_permissions":128,"folder_id":null,"owner":"user2@example.com","control":null,"starttime":null,"uuid":"25f3b839-3e4b-aa38-252f-f4614dbe5b170e3fa8eea4c7cb27","rrules":null,"creation_date":1544219402,"read":false,"name":"KitchenSinkScan","shared":false}}}}}}},"409":{"description":"Returned if you attempt to import results from scans run more than 15 months ago.","content":{"text/html":{"examples":{"response":{"value":{"error":"{\"error\":\"Scans older than 06/01/2018 cannot be imported.\"}"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to import the scan.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/export":{"post":{"summary":"Export scan","description":"Export the specified scan. To see the status of the requested export, submit an export status request. On receiving a \"ready\" status from the export-status request, download the export file using the export download method.\n\n**Note:** If you request a scan export in the `nessus` file format, but do not specify filters for the export, Tenable.io truncates the plugins output data in the export file at 5 MB or 5,000,000 characters, and appends `TRUNCATED` (bracketed by three asterisks) at the end of the output in the export file. You can obtain the full plugins output by exporting the scan in any other file format than `nessus`.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-export-request","tags":["Scans"],"parameters":[{"description":"The identifier for the scan you want to export. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}},{"description":"The ID of the historical data that should be exported.","required":false,"name":"history_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the historical data that should be returned.","required":false,"name":"history_uuid","in":"query","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string","description":"The file format to use (Nessus, HTML, PDF, CSV, or DB).","enum":["nessus","html","pdf","csv","db"]},"password":{"type":"string","description":"The password used to encrypt database exports (\\*Required when exporting as DB).","format":"password"},"chapters":{"type":"string","description":"The chapters to include in the export (expecting a semi-colon delimited string comprised of some combination of the following options: vuln\\_hosts\\_summary, vuln\\_by\\_host, compliance\\_exec, remediations, vuln\\_by\\_plugin, compliance)"},"filter.0.filter":{"type":"string","description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. If you specify the name of the filter, you must specify the operator as the `filter.0.quality` parameter and the value as the `filter.0.value` parameter. To use multiple filters, increment the `` portion of `filter..filter`, for example, `filter.1.filter`. For more information about using this parameter, see [Scan Export Filters](/docs/scan-export-filters-tio)."},"filter.0.quality":{"type":"string","description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. To use multiple filters, increment the `` portion of `filter..quality`, for example, `filter.1.quality`. For more information about using this parameter, see [Scan Export Filters](/docs/scan-export-filters-tio)."},"filter.0.value":{"type":"string","description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the `control` attribute of the objects returned by the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. To use multiple filters, increment the `` portion of `filter..value`, for example, `filter.1.value`. For more information about using this parameter, see [Scan Export Filters](/docs/scan-export-filters-tio)."},"filter.search_type":{"type":"string","description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about using this parameter, see [Scan Export Filters](/docs/scan-export-filters-tio).","enum":["and","or"]},"asset_id":{"type":"integer","description":"The ID of the asset scanned.","format":"int32"}},"required":["format","chapters","asset_id"]}}}},"responses":{"200":{"description":"Returned if Tenable.io queues the export successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string"},"temp_token":{"type":"string"}}},"examples":{"response":{"value":{"file":778874546,"temp_token":"995bdb656fc6dc5d76e18ccafe7fbd390618fcd0257e0e1aa121f4412a6f7ecc"}}}}}},"400":{"description":"Returned if your request message is missing a required parameter."},"404":{"description":"Returned if Tenable.io cannot find the specified scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/export/{file_id}/status":{"get":{"summary":"Check scan export status","description":"Check the file status of an exported scan. When an export has been requested, it is necessary to poll this endpoint until a \"ready\" status is returned, at which point the file is complete and can be downloaded using the export download endpoint.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-export-status","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}},{"description":"The ID of the file to poll (Included in response from /scans/{scan\\_id}/export).","required":true,"name":"file_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the status of the file. A status of `ready` indicates the file can be downloaded.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"The export status."}}},"examples":{"response":{"value":{"status":"ready"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified file."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/export/{file_id}/download":{"get":{"summary":"Download exported scan","description":"Download an exported scan.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-export-download","tags":["Scans"],"parameters":[{"description":"The identifier for the exported scan you want to download. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}},{"description":"The ID of the file to download (Included in response from /scans/{scan\\_id}/export).","required":true,"name":"file_id","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the content of the file as an attachment.","content":{"application/octet-stream":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified file."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/history":{"get":{"summary":"Get scan history","description":"Returns a scan's history records.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-history","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}},{"description":"Maximum number of objects requested (or service imposed limit if not in request). The max limit value allowed is 50. Must be in the int32 format.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Offset from request (or zero). Must be in the int32 format.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns a scan's history records.","content":{"application/json":{"schema":{"type":"object","properties":{"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of objects matching your search criteria. Must be in the int32 format."},"limit":{"type":"integer","description":"Maximum number of objects requested (or service imposed limit if not in request). Must be in the int32 format."},"offset":{"type":"integer","description":"Offset from request (or zero). Must be in the int32 format."},"sort":{"description":"An array of objects representing the fields you specified as sort fields in the request message, which Tenable.io uses to sort the returned data.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The field on which Tenable.io sorts the results."},"order":{"type":"string","description":"The direction of the sort order. Supported values are `asc` (ascending) and `desc` (descending)."}}}}}},"history":{"type":"array","items":{"type":"object","properties":{"time_end":{"type":"integer","description":"The date the scan completed in Unix time."},"scan_uuid":{"type":"string","description":"The scan history's UUID."},"time_start":{"type":"integer","description":"The date the scan started in Unix time."},"visibility":{"type":"string","description":"The visibility of the scan in workbenches (public or private)."},"targets":{"type":"object","description":"The target parameters used to launch the scan.","properties":{"custom":{"type":"boolean","description":"If `true`, custom parameters were used to launch the scan."},"default":{"type":"boolean","description":"If `true`, default parameters were used to launch the scan.."}}},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."}}}}}},"examples":{"response":{"value":{"pagination":{"offset":0,"total":8,"sort":[{"order":"DESC","name":"start_date"}],"limit":50},"history":[{"time_end":1545945607,"scan_uuid":"1732621d-a7c3-4295-bbc9-37035112ff0a","id":10535512,"time_start":1545945482,"visibility":"public","targets":{"custom":false,"default":null},"status":"canceled"},{"time_end":1545945457,"scan_uuid":"cd5c32e9-0b66-4c31-b61a-8d1bdd8a67ad","id":10535505,"time_start":1545945321,"visibility":"public","targets":{"custom":false,"default":null},"status":"completed"},{"time_end":1545944767,"scan_uuid":"34e04696-2abf-4767-86cb-c51eb26a3511","id":10535496,"time_start":1545944637,"visibility":"public","targets":{"custom":false,"default":null},"status":"completed"},{"time_end":1545877987,"scan_uuid":"47ee2c49-9422-4082-9b5b-48d0883bc76e","id":10534608,"time_start":1545877843,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"},{"time_end":1545877717,"scan_uuid":"0a20f6f1-cb6e-4947-ab71-8121dddff8e9","id":10534601,"time_start":1545877590,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"},{"time_end":1545877057,"scan_uuid":"2b346502-d769-452e-9c2e-0c50033852d2","id":10534598,"time_start":1545876907,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"},{"time_end":1545871897,"scan_uuid":"e80fa271-8a6e-44e8-bdcf-dc75274d4b25","id":10534540,"time_start":1545871758,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"},{"time_end":1545871177,"scan_uuid":"36984557-946d-4858-8af7-ded422fee78b","id":10534536,"time_start":1545871035,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"}]}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified `scan_id`."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/history/{history_uuid}":{"get":{"summary":"Get scan history details","description":"Returns the details of a previous result of a scan.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-history-details","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}},{"description":"The UUID of the historical scan result to return details about. You can use either the `history_uuid` or the numeric `history_id` attribute. You can find the ID values by examining the scan details object returned by the [GET /scans/{scan_id}](#scans-details) endpoint.","required":true,"name":"history_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns details of the historical scan result.","content":{"application/json":{"schema":{"type":"object","properties":{"alt_targets_used":{"type":"boolean","description":"If `true`, Tenable.io did not not launched with a target list. This parameter is `true` for agent scans."},"scheduler":{"type":"integer","description":"If `true`, Tenable.io launched the scan automatically from a schedule."},"status":{"type":"string","description":"The status of the historical data."},"type":{"type":"string","description":"The type of scan: local, remote, or agent."},"uuid":{"type":"string","description":"The UUID of the historical data."},"last_modification_date":{"type":"integer","description":"The last modification date for the historical data in Unix time."},"creation_date":{"type":"integer","description":"The creation date for the historical data in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scan."},"history_id":{"type":"integer","description":"The unique ID of the historical data."}}},"examples":{"response":{"value":{"owner_id":2,"schedule_uuid":"template-c47a2aec-516d-20fc-6128-e8cbbb9864d1c98ab9a2d2e18e78","status":"aborted","scan_start":1543870842,"owner_uuid":"6ee44fda-eced-400b-a574-503884bdafa5","owner":"user2@example.com","targets":"","object_id":10509951,"uuid":"7cf03b42-ee6b-4d89-aa83-6c726509b369","scan_end":null,"scan_type":"remote","name":"Advanced Windows Servers Scan"}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified `scan_id` or `history_uuid`."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/history/{history_id}":{"delete":{"summary":"Delete scan history","description":"Deletes historical results from a scan.

      Requires SCAN OPERATOR [24] user permissions and CAN CONFIGURE [64] scan permissions. See Permissions.

      ","operationId":"scans-delete-history","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}},{"description":"The ID of the results to delete.","required":true,"name":"history_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deleted the specified scan results.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified scan results."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the scan results.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"501":{"description":"Returned if Tenable.io does not support deleting historical scan results.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":501,"error":"Not Implemented","message":"This feature is not yet implemented."}}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_uuid}/hosts/{host_id}":{"get":{"summary":"Get host details","description":"Returns details for the specified host.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-host-details","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_uuid","in":"path","schema":{"type":"string"}},{"description":"The ID of the host to retrieve.","required":true,"name":"host_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the historical data that should be returned.","name":"history_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the historical data that should be returned.","name":"history_uuid","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the host details.","content":{"application/json":{"schema":{"type":"object","properties":{"info":{"type":"object"},"compliance":{"type":"array","items":{"type":"object","properties":{"host_id":{"type":"integer","description":"The unique ID of the host."},"hostname":{"type":"string","description":"The name of the host."},"plugin_id":{"type":"integer","description":"The unique ID of the vulnerability plugin."},"plugin_name":{"type":"string","description":"The name of the vulnerability plugin."},"plugin_family":{"type":"string","description":"The parent family of the vulnerability plugin."},"count":{"type":"integer","description":"The number of vulnerabilities found."},"severity_index":{"type":"integer","description":"The severity index order of the plugin."},"severity":{"type":"integer","description":"The severity of plugin."}}}},"vulnerabilities":{"type":"array","items":{"type":"object","properties":{"host_id":{"type":"integer","description":"The unique ID of the host."},"hostname":{"type":"string","description":"The name of the host."},"plugin_id":{"type":"integer","description":"The unique ID of the vulnerability plugin."},"plugin_name":{"type":"string","description":"The name of the vulnerability plugin."},"plugin_family":{"type":"string","description":"The parent family of the vulnerability plugin."},"count":{"type":"integer","description":"The number of vulnerabilities found."},"vuln_index":{"type":"integer","description":"The index of the vulnerability plugin."},"severity_index":{"type":"integer","description":"The severity index order of the plugin."},"severity":{"type":"integer","description":"The severity of plugin."}}}}}},"examples":{"response":{"value":{"info":{"mac-address":null,"host-fqdn":"matrixcentos5_matrix","host-ip":"172.204.81.57","operating-system":["Linux Kernel 2.6.18-274.el5PAE on CentOS release 5.7 (Final)"],"host_end":"Fri Dec 7 21:46:59 2018","host_start":"Fri Dec 7 21:46:59 2018"},"vulnerabilities":[{"count":7,"host_id":9,"hostname":"172.204.81.57","plugin_family":"Port scanners","plugin_id":14272,"plugin_name":"Netstat Portscanner (SSH)","severity":0,"severity_index":0,"vuln_index":0},{"count":7,"host_id":9,"hostname":"172.204.81.57","plugin_family":"Service detection","plugin_id":25221,"plugin_name":"Remote listeners enumeration (Linux / AIX)","severity":0,"severity_index":0,"vuln_index":0},{"count":4,"host_id":9,"hostname":"172.204.81.57","plugin_family":"Service detection","plugin_id":11111,"plugin_name":"RPC Services Enumeration","severity":0,"severity_index":0,"vuln_index":0}],"compliance":[{"count":1,"host_id":9,"hostname":"172.204.81.57","plugin_family":"Unix Compliance Checks","plugin_id":"0042cf05a8358f531c68c8bd249a4874","plugin_name":"BSI-100-2: S 4.105: Telnet should be replaced by SSH.","severity":1,"severity_index":0},{"count":1,"host_id":9,"hostname":"172.204.81.57","plugin_family":"Unix Compliance Checks","plugin_id":"0483f10c66f3ecf1f2ec55cf47365416","plugin_name":"BSI-100-2: S 4.105: /usr/X11R6/bin/startx - `xhost +` should never be used.","severity":3,"severity_index":1},{"count":1,"host_id":9,"hostname":"172.204.81.57","plugin_family":"Unix Compliance Checks","plugin_id":"04a999b7e2dbe28fc1c76b15237a39a2","plugin_name":"Red Hat 6 is not installed on target","severity":2,"severity_index":2}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_uuid}/hosts/{host_id}/plugins/{plugin_id}":{"get":{"summary":"Get plugin output","description":"Returns the output for a specified plugin.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-plugin-output","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_uuid","in":"path","schema":{"type":"string"}},{"description":"The ID of the host to retrieve.","required":true,"name":"host_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the plugin to retrieve.","required":true,"name":"plugin_id","in":"path","schema":{"type":"string"}},{"description":"The ID of the historical data that should be returned.","name":"history_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the historical data that should be returned.","name":"history_uuid","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the plugin output.","content":{"application/json":{"schema":{"type":"object","properties":{"output":{"type":"array","items":{"type":"object","properties":{"ports":{"type":"object","properties":{}},"has_attachment":{"type":"integer","description":"If the value is 1, the plugin output contains files that may be exported."},"custom_description":{"type":"string","description":"A custom description of the plugin."},"plugin_output":{"type":"string","description":"The text of the plugin output."},"hosts":{"type":"string","description":"Other hosts with the same output."},"severity":{"type":"integer","description":"The severity the output."}}}},"info":{"type":"object","properties":{"host-fqdn":{"type":"string","description":"The host's fully qualified domain name; optional."},"host_fqdn":{"type":"string","description":"The FQDN of the host. Normally, this is populated with the value used to scan the host in the target list of the last scan that was ran where it was seen; always present."},"host-ip":{"type":"string","description":"The host's IPv4 address; optional."},"host-uuid":{"type":"string","description":"The host's UUID generated by Tenable.io for identification purposes; always present."},"host_start":{"type":"string","description":"The last time a scan was started for this host as an ISO 8601 timestamp; always present."},"host_end":{"type":"string","description":"The last time a scan was completed for this host as an ISO 8601 timestamp; always present."},"mac-address":{"type":"string","description":"The hosts's mac addresses in a newline-separated list; optional."}}}}},"examples":{"response":{"value":{"outputs":[{"ports":{"445 / tcp / cifs":[{"hostname":"172.204.81.57"}]},"has_attachment":0,"severity":0,"plugin_output":"\nThe following DCERPC services are available remotely :\n\nObject UUID : 765294ba-60bc-48b8-92e9-89fd77769d91\nUUID : d95afe70-a6d5-4259-822e-2c84da1ddb0d, version 1.0\nDescription : Unknown RPC service\nType : Remote RPC service\nNamed pipe : \\PIPE\\InitShutdown\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : b08669ee-8cb5-43a5-a017-84fe00000000\nUUID : 76f226c3-ec14-4325-8a99-6a46348418af, version 1.0\nDescription : Unknown RPC service\nType : Remote RPC service\nNamed pipe : \\PIPE\\InitShutdown\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 0767a036-0d22-48aa-ba69-b619480f38cb, version 1.0\nDescription : Unknown RPC service\nAnnotation : PcaSvc\nType : Remote RPC service\nNamed pipe : \\pipe\\trkwks\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Remote RPC service\nNamed pipe : \\pipe\\lsass\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Remote RPC service\nNamed pipe : \\PIPE\\protected_storage\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3473dd4d-2e88-4006-9cba-22570909dd10, version 5.0\nDescription : Unknown RPC service\nAnnotation : WinHttp Auto-Proxy Service\nType : Remote RPC service\nNamed pipe : \\PIPE\\W32TIME_ALT\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 1ff70682-0a51-30e8-076d-740be8cee98b, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 378e52b0-c0a9-11cf-822d-00aa0051e40f, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 86d35949-83c9-4044-b424-db363231fd0c, version 1.0\nDescription : Unknown RPC service\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : a398e520-d59a-4bdd-aa7a-3c1e0303a511, version 1.0\nDescription : Unknown RPC service\nAnnotation : IKE/Authip API\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 552d076a-cb29-4e44-8b6a-d15e59e2c0af, version 1.0\nDescription : Unknown RPC service\nAnnotation : IP Transition Configuration endpoint\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 98716d03-89ac-44c7-bb8c-285824e51c4a, version 1.0\nDescription : Unknown RPC service\nAnnotation : XactSrv service\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 201ef99a-7fa0-444c-9399-19ba84f12a1a, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 201ef99a-7fa0-444c-9399-19ba84f12a1a, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\srvsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 201ef99a-7fa0-444c-9399-19ba84f12a1a, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\browser\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 5f54ce7d-5b79-4175-8584-cb65313a0e98, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 5f54ce7d-5b79-4175-8584-cb65313a0e98, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\srvsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 5f54ce7d-5b79-4175-8584-cb65313a0e98, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\browser\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : fd7a0523-dc70-43dd-9b2e-9c5ed48225b1, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : fd7a0523-dc70-43dd-9b2e-9c5ed48225b1, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\srvsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : fd7a0523-dc70-43dd-9b2e-9c5ed48225b1, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\browser\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 58e604e8-9adb-4d2e-a464-3b0683fb1480, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\atsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 58e604e8-9adb-4d2e-a464-3b0683fb1480, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\srvsvc\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 58e604e8-9adb-4d2e-a464-3b0683fb1480, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nNamed pipe : \\PIPE\\browser\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : f6beaff7-1e19-4fbb-9f8f-b89e2018337c, version 1.0\nDescription : Unknown RPC service\nAnnotation : Event log TCPIP\nType : Remote RPC service\nNamed pipe : \\pipe\\eventlog\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 30adc50c-5cbc-46ce-9a0e-91914789e23c, version 1.0\nDescription : Unknown RPC service\nAnnotation : NRP server endpoint\nType : Remote RPC service\nNamed pipe : \\pipe\\eventlog\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d5, version 1.0\nDescription : DHCP Client Service\nWindows process : svchost.exe\nAnnotation : DHCP Client LRPC Endpoint\nType : Remote RPC service\nNamed pipe : \\pipe\\eventlog\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d6, version 1.0\nDescription : Unknown RPC service\nAnnotation : DHCPv6 Client LRPC Endpoint\nType : Remote RPC service\nNamed pipe : \\pipe\\eventlog\nNetbios name : \\\\WIN764-UTIL\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 06bba54a-be05-49f9-b0a0-30f790261023, version 1.0\nDescription : Unknown RPC service\nAnnotation : Security Center\nType : Remote RPC service\nNamed pipe : \\pipe\\eventlog\nNetbios name : \\\\WIN764-UTIL\n\n","hosts":null,"custom_description":null},{"ports":{"135 / tcp / epmap":[{"hostname":"172.204.81.57"}]},"has_attachment":0,"severity":0,"plugin_output":"\nThe following DCERPC services are available locally :\n\nObject UUID : 765294ba-60bc-48b8-92e9-89fd77769d91\nUUID : d95afe70-a6d5-4259-822e-2c84da1ddb0d, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : WindowsShutdown\n\nObject UUID : 765294ba-60bc-48b8-92e9-89fd77769d91\nUUID : d95afe70-a6d5-4259-822e-2c84da1ddb0d, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : WMsgKRpc080D30\n\nObject UUID : b08669ee-8cb5-43a5-a017-84fe00000000\nUUID : 76f226c3-ec14-4325-8a99-6a46348418af, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : WindowsShutdown\n\nObject UUID : b08669ee-8cb5-43a5-a017-84fe00000000\nUUID : 76f226c3-ec14-4325-8a99-6a46348418af, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : WMsgKRpc080D30\n\nObject UUID : 6d726574-7273-0076-0000-000000000000\nUUID : c9ac6db5-82b7-4e55-ae8a-e464ed7b4277, version 1.0\nDescription : Unknown RPC service\nAnnotation : Impl friendly name\nType : Local RPC service\nNamed pipe : LRPC-1c3977acb41cea1124\n\nObject UUID : 52ef130c-08fd-4388-86b3-6edf00000001\nUUID : 12e65dd8-887f-41ef-91bf-8d816c42c2e7, version 1.0\nDescription : Unknown RPC service\nAnnotation : Secure Desktop LRPC interface\nType : Local RPC service\nNamed pipe : WMsgKRpc080F61\n\nObject UUID : b08669ee-8cb5-43a5-a017-84fe00000001\nUUID : 76f226c3-ec14-4325-8a99-6a46348418af, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : WMsgKRpc080F61\n\nObject UUID : 8d6696ea-1fe0-42cf-89e9-788c0c0bed65\nUUID : 906b0ce0-c70b-1067-b317-00dd010662da, version 1.0\nDescription : Distributed Transaction Coordinator\nWindows process : msdtc.exe\nType : Local RPC service\nNamed pipe : LRPC-1f8c246f740d47d8e2\n\nObject UUID : 36833313-dcb3-4e11-901d-4d9bbd1c431e\nUUID : 906b0ce0-c70b-1067-b317-00dd010662da, version 1.0\nDescription : Distributed Transaction Coordinator\nWindows process : msdtc.exe\nType : Local RPC service\nNamed pipe : LRPC-1f8c246f740d47d8e2\n\nObject UUID : 3e594dc9-6140-44a0-96f3-c5fe867c50d0\nUUID : 906b0ce0-c70b-1067-b317-00dd010662da, version 1.0\nDescription : Distributed Transaction Coordinator\nWindows process : msdtc.exe\nType : Local RPC service\nNamed pipe : LRPC-1f8c246f740d47d8e2\n\nObject UUID : 5ee96290-1a81-45db-b4e6-8f26c5b72870\nUUID : 906b0ce0-c70b-1067-b317-00dd010662da, version 1.0\nDescription : Distributed Transaction Coordinator\nWindows process : msdtc.exe\nType : Local RPC service\nNamed pipe : LRPC-1f8c246f740d47d8e2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345678-1234-abcd-ef00-0123456789ab, version 1.0\nDescription : IPsec Services (Windows XP & 2003)\nWindows process : lsass.exe\nAnnotation : IPSec Policy agent endpoint\nType : Local RPC service\nNamed pipe : LRPC-b5787f2c3745e80e78\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 0767a036-0d22-48aa-ba69-b619480f38cb, version 1.0\nDescription : Unknown RPC service\nAnnotation : PcaSvc\nType : Local RPC service\nNamed pipe : OLE9B946BCEF2704168BDBCB83E70EF\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 0767a036-0d22-48aa-ba69-b619480f38cb, version 1.0\nDescription : Unknown RPC service\nAnnotation : PcaSvc\nType : Local RPC service\nNamed pipe : trkwks\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Local RPC service\nNamed pipe : LRPC-055bf0579030efacc0\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Local RPC service\nNamed pipe : audit\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Local RPC service\nNamed pipe : securityevent\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Local RPC service\nNamed pipe : LSARPC_ENDPOINT\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Local RPC service\nNamed pipe : lsapolicylookup\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Local RPC service\nNamed pipe : lsasspirpc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Local RPC service\nNamed pipe : protected_storage\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Local RPC service\nNamed pipe : samss lpc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : dd490425-5325-4565-b774-7e27d6c09c24, version 1.0\nDescription : Unknown RPC service\nAnnotation : Base Firewall Engine API\nType : Local RPC service\nNamed pipe : LRPC-1bee010807d4fce7f5\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 7f9d11bf-7fb9-436b-a812-b2d50c5d4c03, version 1.0\nDescription : Unknown RPC service\nAnnotation : Fw APIs\nType : Local RPC service\nNamed pipe : LRPC-1bee010807d4fce7f5\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 2fb92682-6599-42dc-ae13-bd2ca89bd11c, version 1.0\nDescription : Unknown RPC service\nAnnotation : Fw APIs\nType : Local RPC service\nNamed pipe : LRPC-1bee010807d4fce7f5\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 0b6edbfa-4a24-4fc6-8a23-942b1eca65d1, version 1.0\nDescription : Unknown RPC service\nAnnotation : Spooler function endpoint\nType : Local RPC service\nNamed pipe : spoolss\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : ae33069b-a2a8-46ee-a235-ddfd339be281, version 1.0\nDescription : Unknown RPC service\nAnnotation : Spooler base remote object endpoint\nType : Local RPC service\nNamed pipe : spoolss\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 4a452661-8290-4b36-8fbe-7f4093a94978, version 1.0\nDescription : Unknown RPC service\nAnnotation : Spooler function endpoint\nType : Local RPC service\nNamed pipe : spoolss\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 7ea70bcf-48af-4f6a-8968-6a440754d5fa, version 1.0\nDescription : Unknown RPC service\nAnnotation : NSI server endpoint\nType : Local RPC service\nNamed pipe : OLE1555EBBE48654F7BBACABDD0A0E0\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 7ea70bcf-48af-4f6a-8968-6a440754d5fa, version 1.0\nDescription : Unknown RPC service\nAnnotation : NSI server endpoint\nType : Local RPC service\nNamed pipe : LRPC-1195853e231b3360c7\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3473dd4d-2e88-4006-9cba-22570909dd10, version 5.0\nDescription : Unknown RPC service\nAnnotation : WinHttp Auto-Proxy Service\nType : Local RPC service\nNamed pipe : OLE1555EBBE48654F7BBACABDD0A0E0\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3473dd4d-2e88-4006-9cba-22570909dd10, version 5.0\nDescription : Unknown RPC service\nAnnotation : WinHttp Auto-Proxy Service\nType : Local RPC service\nNamed pipe : LRPC-1195853e231b3360c7\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3473dd4d-2e88-4006-9cba-22570909dd10, version 5.0\nDescription : Unknown RPC service\nAnnotation : WinHttp Auto-Proxy Service\nType : Local RPC service\nNamed pipe : W32TIME_ALT\n\nObject UUID : 666f7270-6c69-7365-0000-000000000000\nUUID : c9ac6db5-82b7-4e55-ae8a-e464ed7b4277, version 1.0\nDescription : Unknown RPC service\nAnnotation : Impl friendly name\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 6c637067-6569-746e-0000-000000000000\nUUID : c9ac6db5-82b7-4e55-ae8a-e464ed7b4277, version 1.0\nDescription : Unknown RPC service\nAnnotation : Impl friendly name\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 24d1f7c7-76af-4f28-9ccd-7f6cb6468601\nUUID : 2eb08e3e-639f-4fba-97b1-14f878961076, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 736e6573-0000-0000-0000-000000000000\nUUID : c9ac6db5-82b7-4e55-ae8a-e464ed7b4277, version 1.0\nDescription : Unknown RPC service\nAnnotation : Impl friendly name\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 736e6573-0000-0000-0000-000000000000\nUUID : c9ac6db5-82b7-4e55-ae8a-e464ed7b4277, version 1.0\nDescription : Unknown RPC service\nAnnotation : Impl friendly name\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 736e6573-0000-0000-0000-000000000000\nUUID : c9ac6db5-82b7-4e55-ae8a-e464ed7b4277, version 1.0\nDescription : Unknown RPC service\nAnnotation : Impl friendly name\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 0a74ef1c-41a4-4e06-83ae-dc74fb1cdd53, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 0a74ef1c-41a4-4e06-83ae-dc74fb1cdd53, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 0a74ef1c-41a4-4e06-83ae-dc74fb1cdd53, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 1ff70682-0a51-30e8-076d-740be8cee98b, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 1ff70682-0a51-30e8-076d-740be8cee98b, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 1ff70682-0a51-30e8-076d-740be8cee98b, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 378e52b0-c0a9-11cf-822d-00aa0051e40f, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 378e52b0-c0a9-11cf-822d-00aa0051e40f, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 378e52b0-c0a9-11cf-822d-00aa0051e40f, version 1.0\nDescription : Scheduler Service\nWindows process : svchost.exe\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 86d35949-83c9-4044-b424-db363231fd0c, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 86d35949-83c9-4044-b424-db363231fd0c, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 86d35949-83c9-4044-b424-db363231fd0c, version 1.0\nDescription : Unknown RPC service\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : a398e520-d59a-4bdd-aa7a-3c1e0303a511, version 1.0\nDescription : Unknown RPC service\nAnnotation : IKE/Authip API\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : a398e520-d59a-4bdd-aa7a-3c1e0303a511, version 1.0\nDescription : Unknown RPC service\nAnnotation : IKE/Authip API\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : a398e520-d59a-4bdd-aa7a-3c1e0303a511, version 1.0\nDescription : Unknown RPC service\nAnnotation : IKE/Authip API\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 552d076a-cb29-4e44-8b6a-d15e59e2c0af, version 1.0\nDescription : Unknown RPC service\nAnnotation : IP Transition Configuration endpoint\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 552d076a-cb29-4e44-8b6a-d15e59e2c0af, version 1.0\nDescription : Unknown RPC service\nAnnotation : IP Transition Configuration endpoint\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 552d076a-cb29-4e44-8b6a-d15e59e2c0af, version 1.0\nDescription : Unknown RPC service\nAnnotation : IP Transition Configuration endpoint\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 98716d03-89ac-44c7-bb8c-285824e51c4a, version 1.0\nDescription : Unknown RPC service\nAnnotation : XactSrv service\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 98716d03-89ac-44c7-bb8c-285824e51c4a, version 1.0\nDescription : Unknown RPC service\nAnnotation : XactSrv service\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 98716d03-89ac-44c7-bb8c-285824e51c4a, version 1.0\nDescription : Unknown RPC service\nAnnotation : XactSrv service\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 201ef99a-7fa0-444c-9399-19ba84f12a1a, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 201ef99a-7fa0-444c-9399-19ba84f12a1a, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 201ef99a-7fa0-444c-9399-19ba84f12a1a, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 5f54ce7d-5b79-4175-8584-cb65313a0e98, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 5f54ce7d-5b79-4175-8584-cb65313a0e98, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 5f54ce7d-5b79-4175-8584-cb65313a0e98, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : fd7a0523-dc70-43dd-9b2e-9c5ed48225b1, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : fd7a0523-dc70-43dd-9b2e-9c5ed48225b1, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : fd7a0523-dc70-43dd-9b2e-9c5ed48225b1, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 58e604e8-9adb-4d2e-a464-3b0683fb1480, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : IUserProfile2\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 58e604e8-9adb-4d2e-a464-3b0683fb1480, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : OLEE4DBCACEC38F463A8D7319C3ED20\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 58e604e8-9adb-4d2e-a464-3b0683fb1480, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Local RPC service\nNamed pipe : senssvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : f6beaff7-1e19-4fbb-9f8f-b89e2018337c, version 1.0\nDescription : Unknown RPC service\nAnnotation : Event log TCPIP\nType : Local RPC service\nNamed pipe : eventlog\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 30adc50c-5cbc-46ce-9a0e-91914789e23c, version 1.0\nDescription : Unknown RPC service\nAnnotation : NRP server endpoint\nType : Local RPC service\nNamed pipe : eventlog\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 30adc50c-5cbc-46ce-9a0e-91914789e23c, version 1.0\nDescription : Unknown RPC service\nAnnotation : NRP server endpoint\nType : Local RPC service\nNamed pipe : AudioClientRpc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 30adc50c-5cbc-46ce-9a0e-91914789e23c, version 1.0\nDescription : Unknown RPC service\nAnnotation : NRP server endpoint\nType : Local RPC service\nNamed pipe : Audiosrv\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d5, version 1.0\nDescription : DHCP Client Service\nWindows process : svchost.exe\nAnnotation : DHCP Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : eventlog\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d5, version 1.0\nDescription : DHCP Client Service\nWindows process : svchost.exe\nAnnotation : DHCP Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : AudioClientRpc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d5, version 1.0\nDescription : DHCP Client Service\nWindows process : svchost.exe\nAnnotation : DHCP Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : Audiosrv\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d5, version 1.0\nDescription : DHCP Client Service\nWindows process : svchost.exe\nAnnotation : DHCP Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : dhcpcsvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d6, version 1.0\nDescription : Unknown RPC service\nAnnotation : DHCPv6 Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : eventlog\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d6, version 1.0\nDescription : Unknown RPC service\nAnnotation : DHCPv6 Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : AudioClientRpc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d6, version 1.0\nDescription : Unknown RPC service\nAnnotation : DHCPv6 Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : Audiosrv\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d6, version 1.0\nDescription : Unknown RPC service\nAnnotation : DHCPv6 Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : dhcpcsvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d6, version 1.0\nDescription : Unknown RPC service\nAnnotation : DHCPv6 Client LRPC Endpoint\nType : Local RPC service\nNamed pipe : dhcpcsvc6\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 06bba54a-be05-49f9-b0a0-30f790261023, version 1.0\nDescription : Unknown RPC service\nAnnotation : Security Center\nType : Local RPC service\nNamed pipe : eventlog\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 06bba54a-be05-49f9-b0a0-30f790261023, version 1.0\nDescription : Unknown RPC service\nAnnotation : Security Center\nType : Local RPC service\nNamed pipe : AudioClientRpc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 06bba54a-be05-49f9-b0a0-30f790261023, version 1.0\nDescription : Unknown RPC service\nAnnotation : Security Center\nType : Local RPC service\nNamed pipe : Audiosrv\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 06bba54a-be05-49f9-b0a0-30f790261023, version 1.0\nDescription : Unknown RPC service\nAnnotation : Security Center\nType : Local RPC service\nNamed pipe : dhcpcsvc\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 06bba54a-be05-49f9-b0a0-30f790261023, version 1.0\nDescription : Unknown RPC service\nAnnotation : Security Center\nType : Local RPC service\nNamed pipe : dhcpcsvc6\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 06bba54a-be05-49f9-b0a0-30f790261023, version 1.0\nDescription : Unknown RPC service\nAnnotation : Security Center\nType : Local RPC service\nNamed pipe : OLE4F91526C693C4FC7944E94E9DEC2\n\n","hosts":null,"custom_description":null},{"ports":{"49155 / tcp / dce-rpc":[{"hostname":"172.204.81.57"}]},"has_attachment":0,"severity":0,"plugin_output":"\nThe following DCERPC services are available on TCP port 49155 :\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345778-1234-abcd-ef00-0123456789ac, version 1.0\nDescription : Security Account Manager\nWindows process : lsass.exe\nType : Remote RPC service\nTCP Port : 49155\nIP : 172.204.81.57\n\n","hosts":null,"custom_description":null},{"ports":{"49152 / tcp / dce-rpc":[{"hostname":"172.204.81.57"}]},"has_attachment":0,"severity":0,"plugin_output":"\nThe following DCERPC services are available on TCP port 49152 :\n\nObject UUID : 765294ba-60bc-48b8-92e9-89fd77769d91\nUUID : d95afe70-a6d5-4259-822e-2c84da1ddb0d, version 1.0\nDescription : Unknown RPC service\nType : Remote RPC service\nTCP Port : 49152\nIP : 172.204.81.57\n\n","hosts":null,"custom_description":null},{"ports":{"49157 / tcp / dce-rpc":[{"hostname":"172.204.81.57"}]},"has_attachment":0,"severity":0,"plugin_output":"\nThe following DCERPC services are available on TCP port 49157 :\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 6b5bdd1e-528c-422c-af8c-a4079be4fe48, version 1.0\nDescription : Unknown RPC service\nAnnotation : Remote Fw APIs\nType : Remote RPC service\nTCP Port : 49157\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 12345678-1234-abcd-ef00-0123456789ab, version 1.0\nDescription : IPsec Services (Windows XP & 2003)\nWindows process : lsass.exe\nAnnotation : IPSec Policy agent endpoint\nType : Remote RPC service\nTCP Port : 49157\nIP : 172.204.81.57\n\n","hosts":null,"custom_description":null},{"ports":{"49156 / tcp / dce-rpc":[{"hostname":"172.204.81.57"}]},"has_attachment":0,"severity":0,"plugin_output":"\nThe following DCERPC services are available on TCP port 49156 :\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 367abb81-9844-35f1-ad32-98f038001003, version 2.0\nDescription : Service Control Manager\nWindows process : svchost.exe\nType : Remote RPC service\nTCP Port : 49156\nIP : 172.204.81.57\n\n","hosts":null,"custom_description":null},{"ports":{"49154 / tcp / dce-rpc":[{"hostname":"172.204.81.57"}]},"has_attachment":0,"severity":0,"plugin_output":"\nThe following DCERPC services are available on TCP port 49154 :\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 86d35949-83c9-4044-b424-db363231fd0c, version 1.0\nDescription : Unknown RPC service\nType : Remote RPC service\nTCP Port : 49154\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : a398e520-d59a-4bdd-aa7a-3c1e0303a511, version 1.0\nDescription : Unknown RPC service\nAnnotation : IKE/Authip API\nType : Remote RPC service\nTCP Port : 49154\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 552d076a-cb29-4e44-8b6a-d15e59e2c0af, version 1.0\nDescription : Unknown RPC service\nAnnotation : IP Transition Configuration endpoint\nType : Remote RPC service\nTCP Port : 49154\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 98716d03-89ac-44c7-bb8c-285824e51c4a, version 1.0\nDescription : Unknown RPC service\nAnnotation : XactSrv service\nType : Remote RPC service\nTCP Port : 49154\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 201ef99a-7fa0-444c-9399-19ba84f12a1a, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nTCP Port : 49154\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 5f54ce7d-5b79-4175-8584-cb65313a0e98, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nTCP Port : 49154\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : fd7a0523-dc70-43dd-9b2e-9c5ed48225b1, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nTCP Port : 49154\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 58e604e8-9adb-4d2e-a464-3b0683fb1480, version 1.0\nDescription : Unknown RPC service\nAnnotation : AppInfo\nType : Remote RPC service\nTCP Port : 49154\nIP : 172.204.81.57\n\n","hosts":null,"custom_description":null},{"ports":{"49153 / tcp / dce-rpc":[{"hostname":"172.204.81.57"}]},"has_attachment":0,"severity":0,"plugin_output":"\nThe following DCERPC services are available on TCP port 49153 :\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : f6beaff7-1e19-4fbb-9f8f-b89e2018337c, version 1.0\nDescription : Unknown RPC service\nAnnotation : Event log TCPIP\nType : Remote RPC service\nTCP Port : 49153\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 30adc50c-5cbc-46ce-9a0e-91914789e23c, version 1.0\nDescription : Unknown RPC service\nAnnotation : NRP server endpoint\nType : Remote RPC service\nTCP Port : 49153\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d5, version 1.0\nDescription : DHCP Client Service\nWindows process : svchost.exe\nAnnotation : DHCP Client LRPC Endpoint\nType : Remote RPC service\nTCP Port : 49153\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 3c4728c5-f0ab-448b-bda1-6ce01eb0a6d6, version 1.0\nDescription : Unknown RPC service\nAnnotation : DHCPv6 Client LRPC Endpoint\nType : Remote RPC service\nTCP Port : 49153\nIP : 172.204.81.57\n\nObject UUID : 00000000-0000-0000-0000-000000000000\nUUID : 06bba54a-be05-49f9-b0a0-30f790261023, version 1.0\nDescription : Unknown RPC service\nAnnotation : Security Center\nType : Remote RPC service\nTCP Port : 49153\nIP : 172.204.81.57\n\n","hosts":null,"custom_description":null}],"info":{"plugindescription":{"severity":0,"pluginname":"DCE Services Enumeration","pluginattributes":{"risk_information":{"risk_factor":"None"},"plugin_information":{"plugin_version":"$Revision: 1.51 $","plugin_id":10736,"plugin_type":"local","plugin_publication_date":"2001-08-26T00:00:00Z","plugin_family":"Windows","plugin_modification_date":"2014-05-12T00:00:00Z"},"solution":null,"has_patch":false,"description":"By sending a Lookup request to the portmapper (TCP 135 or epmapper PIPE) it was possible to enumerate the Distributed Computing Environment (DCE) services running on the remote port. Using this information it is possible to connect and bind to each service by sending an RPC request to the remote port/pipe.","synopsis":"A DCE/RPC service is running on the remote host."},"pluginfamily":"Windows","pluginid":"10736"}}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/attachments/{attachment_id}":{"get":{"summary":"Get scan attachment file","description":"Gets the requested scan attachment file.

      Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

      ","operationId":"scans-attachments","tags":["Scans"],"parameters":[{"description":"The identifier for the scan containing the attachment. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}},{"description":"The ID of the scan attachment.","required":true,"name":"attachment_id","in":"path","schema":{"type":"string"}},{"description":"The attachment access token.","required":true,"name":"key","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the attachment file.","content":{"application/octet-stream":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified attachment file."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/timezones":{"get":{"summary":"Get timezones","description":"Returns the timezones list for creating a scan.

      Requires SCAN OPERATOR [24] user permissions. See Permissions.

      ","operationId":"scans-timezones","tags":["Scans"],"responses":{"200":{"description":"Returns the timezone list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The readable name of the timezone."},"value":{"type":"string","description":"The system value for the timezone."}}}},"examples":{"response":{"value":{"timezones":[{"name":"Africa/Abidjan","value":"Africa/Abidjan"},{"name":"Europe/Tiraspol","value":"Europe/Tiraspol"},{"name":"UCT","value":"UCT"},{"name":"US/Alaska","value":"US/Alaska"},{"name":"US/Aleutian","value":"US/Aleutian"},{"name":"US/Arizona","value":"US/Arizona"},{"name":"US/Central","value":"US/Central"},{"name":"US/East-Indiana","value":"US/East-Indiana"},{"name":"US/Eastern","value":"US/Eastern"},{"name":"US/Hawaii","value":"US/Hawaii"},{"name":"US/Indiana-Starke","value":"US/Indiana-Starke"},{"name":"US/Michigan","value":"US/Michigan"},{"name":"US/Mountain","value":"US/Mountain"},{"name":"US/Pacific","value":"US/Pacific"},{"name":"US/Pacific-New","value":"US/Pacific-New"},{"name":"US/Samoa","value":"US/Samoa"},{"name":"UTC","value":"UTC"},{"name":"Universal","value":"Universal"},{"name":"W-SU","value":"W-SU"},{"name":"WET","value":"WET"},{"current":true,"name":"Zulu","value":"Zulu"}]}}}}}},"403":{"description":"Returned if you do not have permission to view timezones."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/server/status":{"get":{"summary":"Get server status","description":"Gets the server status.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"server-status","tags":["Server"],"responses":{"200":{"description":"Returns the server status. Status values can include:\n - loading\n - ready\n - corrupt-db\n - feed-expired\n - eval-expired\n - locked\n - register\n - register-locked\n - download-failed\n - feed-error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"code":{"type":"integer"}}},"examples":{"response":{"value":{"code":200,"status":"ready"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"503":{"description":"Returns the server status. Indicates a session destroy is required.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":503,"error":"Service Unavailable","message":"Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/server/properties":{"get":{"summary":"List server properties","description":"Lists the server version and other properties.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"server-properties","tags":["Server"],"responses":{"200":{"description":"Returns the server properties.","content":{"application/json":{"schema":{"type":"object","properties":{"capabilities":{"type":"object"},"enterprise":{"type":"boolean"},"expiration":{"type":"integer"},"expiration_time":{"type":"integer"},"idle_timeout":{"type":"integer"},"license":{"type":"object"},"loaded_plugin_set":{"type":"string"},"login_banner":{"type":"boolean"},"nessus_type":{"type":"string"},"nessus_ui_version":{"type":"string"},"notifications":{"type":"array","items":{"type":"string"}},"plugin_set":{"type":"string"},"scanner_boottime":{"type":"integer"},"server_version":{"type":"string"},"server_uuid":{"type":"string"},"update":{"type":"object","properties":{"href":{"type":"string"},"new_version":{"type":"integer"},"restart":{"type":"integer"}}},"analytics":{"type":"object"},"limitEnabled":{"type":"boolean"},"msp":{"type":"boolean"},"server_build":{"type":"string"},"force_ui_reload":{"type":"boolean"},"nessus_ui_build":{"type":"string"},"container_db_version":{"type":"string"}}},"examples":{"response":{"value":{"limitEnabled":false,"region":"US East","loaded_plugin_set":"201812271241","server_uuid":"417457f6-cd2b-727c-d94f-06bce34d74cf27789b39c292efc6","update":{"href":null,"new_version":0,"restart":0},"expiration":1551160800,"nessus_ui_version":"11.0.52","nessus_type":"Nessus Cloud","notifications":[],"expiration_time":60,"license":{"enterprise_pause":false,"expiration_date":1551160800,"ips":1024,"agents":512,"users":10,"scanners":2,"evaluation":false,"scanners_used":0,"agents_used":0,"apps":{"was":{"mode":"eval","expiration_date":1549299101}}},"enterprise":true,"analytics":{"key":"ddf4b2ef-ac5e-4ee7-701d-7fd1a95bc458","enabled":true,"site_id":"us-2b"},"msp":true,"server_build":"C20023","force_ui_reload":true,"capabilities":{"multi_user":"full","multi_scanner":true,"report_email_config":true,"two_factor":{"twilio":true,"smtp":false}},"plugin_set":"201812271241","idle_timeout":"30","nessus_ui_build":"161","scanner_boottime":1545191736,"evaluation":{"limitEnabled":true,"targets":25},"container_db_version":"10.43.0","login_banner":null,"server_version":"6.9.1"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/tags/categories":{"post":{"summary":"Create tag category","description":"Creates a new tag category.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-create-tag-category","tags":["Tags"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the tag category."},"description":{"type":"string","description":"The description of the tag category."}},"required":["name"]},"example":{"name":"Location","description":"The geographic location of the asset."}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates a category.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the category."},"name":{"type":"string","description":"The name of the category. The name must be unique within a Tenable.io instance."},"description":{"type":"string","description":"The description of the category."},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the category was created, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who created the category."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the category was last updated, for example, `2018-08-09T13:51:17.243Z`."},"updated_by":{"type":"string","description":"The name of the user who last updated the category."},"reserved":{"type":"boolean","description":"Indicates whether the tags in this category are reserved (cannot be updated). This is a read-only field set by the system."}}},"examples":{"response":{"value":{"uuid":"ce8e96b5-4e4a-4469-99a6-425479153dea","created_at":"2018-10-30T20:43:36.496Z","created_by":"user3@example.com","updated_at":"2018-10-30T20:43:36.496Z","updated_by":"user3@example.com","name":"Location","description":"The geographic location of the asset.","reserved":false}}}}}},"400":{"description":"Returned if Tenable.io encounters any of the following error conditions:\n - max_entries—your request exceeds the maximum number of categories (100 per container).\n - duplicate—a category with the name you specified already exists."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List tag categories","description":"Returns a list of tag categories.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-list-tag-categories","tags":["Tags"],"parameters":[{"description":"A filter condition in the `field:operator:value` format, for example, `f=name:match:location`. Filter conditions can include: \n* name:eq:<name> \n* name:match: \n* description:eq:<description> \n* description:match: \n* updated\\_at:date-eq: \n* updated\\_at:date-gt: \n* updated\\_at:date-lt: \n* created\\_at:date-eq: \n* created\\_at:date-gt: \n* created\\_at:date-lt: \n* updated\\_by:eq:","required":false,"name":"f","in":"query","schema":{"type":"string"}},{"description":"If multiple `f` parameters are present, specifies whether Tenable.io applies `AND` or `OR` to conditions. Supported values are `and` and `or`. If you omit this parameter when using multiple `f` parameters, Tenable.io applies `AND` by default.","required":false,"name":"ft","in":"query","schema":{"type":"string"}},{"description":"Maximum number of records requested (or service imposed limit if not in request). Must be in the int32 format.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The number of records to skip in the returned result set. Must be in the int32 format.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The fields to sort on and the sort order, for example, `sort=updated_at:desc,name`. Default sort order is `asc`. If you specify multiple fields, fields must be separated by commas.","required":false,"name":"sort","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of tag categories with pagination information.","content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"description":"A collection of category objects.","type":"array","items":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the category."},"name":{"type":"string","description":"The name of the category. The name must be unique within a Tenable.io instance."},"description":{"type":"string","description":"The description of the category."},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the category was created, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who created the category."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the category was last updated, for example, `2018-08-09T13:51:17.243Z`."},"updated_by":{"type":"string","description":"The name of the user who last updated the category."},"reserved":{"type":"boolean","description":"Indicates whether the tags in this category are reserved (cannot be updated). This is a read-only field set by the system."}}}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of records matching your search criteria. Must be in the int32 format."},"limit":{"type":"integer","description":"Maximum number of records requested (or service imposed limit if not in request). Must be in the int32 format."},"offset":{"type":"integer","description":"The number of skipped records in the returned result set. Must be in the int32 format."},"sort":{"description":"An array of objects representing the fields you specified as sort parameters in the request. This attribute is only present if your request message specifies sort parameters.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the sort field."},"order":{"type":"string","description":"The direction in which Tenable.io sorts on the field, `asc` for ascending or `desc` for descending.","enum":["asc","desc"]}}}}}}}},"examples":{"response":{"value":{"categories":[{"uuid":"4ae732b5-f34b-4ef4-b4c7-c2da6d01f49b","created_at":"2018-10-29T16:37:51.097Z","created_by":"user3@example.com","updated_at":"2018-10-29T16:37:51.097Z","updated_by":"user3@example.com","name":"asset_class","description":"Asset class","reserved":false},{"uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","created_at":"2018-09-20T16:10:21.412Z","created_by":"user3@example.com","updated_at":"2018-10-30T15:39:16.540Z","updated_by":"user3@example.com","name":"location","description":"Asset location","reserved":false}],"pagination":{"offset":0,"limit":5000,"total":4,"sort":[{"name":"name","order":"asc"}]}}}}}}},"400":{"description":"Returned if your request specifies invalid or malformed query parameters. Tenable.io can encounter the following error conditions:\n - `invalidvalue`—The query parameter format is incorrect, for example, uses an invalid operator as in this example: `f=name:invalid_operator:some_value`.\n- `unknownproperty`—The query parameter format is correct, but it references a field that does not exist, for example `sort=non_existing_property:desc`.","content":{"application/json":{"examples":{"response":{"value":{"errors":[{"property":"filter","rule":"invalidvalue","message":"'Filter' does not have a valid value"}],"error":"'Filter' does not have a valid value"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/tags/categories/{category_uuid}":{"get":{"summary":"Get category details","description":"Returns the details for the specified category.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-tag-category-details","tags":["Tags"],"parameters":[{"description":"The UUID of the tag category to return details for. For more information on determining this value, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","required":true,"name":"category_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the category details.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the category."},"name":{"type":"string","description":"The name of the category. The name must be unique within a Tenable.io instance."},"description":{"type":"string","description":"The description of the category."},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the category was created, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who created the category."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the category was last updated, for example, `2018-08-09T13:51:17.243Z`."},"updated_by":{"type":"string","description":"The name of the user who last updated the category."},"reserved":{"type":"boolean","description":"Indicates whether the tags in this category are reserved (cannot be updated). This is a read-only field set by the system."}}},"examples":{"response":{"value":{"uuid":"ce8e96b5-4e4a-4469-99a6-425479153dea","created_at":"2018-10-30T20:43:36.496Z","created_by":"user3@example.com","updated_at":"2018-10-30T20:43:36.496Z","updated_by":"user3@example.com","name":"Location","description":"The geographic location of the asset.","reserved":false}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified tag category."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update tag category","description":"Updates the specified tag category.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-edit-tag-category","tags":["Tags"],"parameters":[{"description":"The UUID of the category. For more information on determining this value, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","required":true,"name":"category_uuid","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the tag category."},"description":{"type":"string","description":"The description of the category."}},"required":["name"]},"example":{"name":"location","description":"The physical location of the asset."}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updates the tag category.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID of the category."},"name":{"type":"string","description":"The name of the category. The name must be unique within a Tenable.io instance."},"description":{"type":"string","description":"The description of the category."},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the category was created, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who created the category."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the category was last updated, for example, `2018-08-09T13:51:17.243Z`."},"updated_by":{"type":"string","description":"The name of the user who last updated the category."},"reserved":{"type":"boolean","description":"Indicates whether the tags in this category are reserved (cannot be updated). This is a read-only field set by the system."}}},"examples":{"response":{"value":{"uuid":"ce8e96b5-4e4a-4469-99a6-425479153dea","created_at":"2018-10-30T20:43:36.496Z","created_by":"user3@sample.org","updated_at":"2018-10-30T23:01:42.105Z","updated_by":"user3@sample.org","name":"operating_system","description":"The operating system of the asset","reserved":false}}}}}},"400":{"description":"Returned if your request message does not specify a value for the tag category name."},"404":{"description":"Returned if Tenable.io cannot find the specified tag category."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete tag category","description":"Deletes the specified category and any associated tag values. Deleting an asset tag category automatically deletes all tag values associated with that category and removes the tags from any assets where the tags were assigned.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-delete-tag-category","tags":["Tags"],"parameters":[{"description":"The UUID of the category to delete. For more information on determining this value, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","required":true,"name":"category_uuid","in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Returned if Tenable.io successfully deletes the specified tag category.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified tag category."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/tags/values":{"get":{"summary":"List tag values","description":"Returns a list of tag values.\n**Note:** The list can also include the tag categories that do not have any associated values.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-list-tag-values","tags":["Tags"],"parameters":[{"description":"A filter condition in the `field:operator:value` format, for example, `f=value:match:rhel`. Filters should match `field:op:value` format. Filter conditions can include: \n* value:eq:<value> \n* value:match:<value> \n* category\\_name:match: \n* category\\_name:eq: \n* category\\_name:match: \n* description:eq:<description> \n* description:match: \n* updated\\_at:date-eq: \n* updated\\_at:date-gt: \n* updated\\_at:date-lt: \n* updated\\_by:eq:","name":"f","in":"query","schema":{"type":"string"}},{"description":"If multiple `f` parameters are present, specifies whether Tenable.io applies `AND` or `OR` to conditions. Supported values are `and` and `or`. If you omit this parameter when using multiple `f` parameters, Tenable.io applies `AND` by default.","name":"ft","in":"query","schema":{"type":"string"}},{"description":"A comma-separated list of fields to include in the wildcard search. Provides the same functionality as a `match` condition in the `f` in parameter. For example, `f=value:match:Chi` returns the same results as `wf=value&w=Chi`. Wildcard fields include: \n* category\\_name \n* value \n* description \nUse the `w` parameter to specify the search value.","name":"wf","in":"query","schema":{"type":"string"}},{"description":"A single search value for the wildcard fields specified in the `wf` parameter.","required":false,"name":"w","in":"query","schema":{"type":"string"}},{"description":"Maximum number of records requested (or service imposed limit if not in request). Must be in the int32 format.","name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The number of records to skip in the returned result set. Must be in the int32 format.","name":"offset","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The fields to sort on, for example, `sort=updated_at:desc,value`. Default sort order is `asc`. If you specify multiple fields, fields must be separated by commas.","name":"sort","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of tags with pagination information.","content":{"application/json":{"schema":{"type":"object","properties":{"values":{"description":"An array of tag value objects.","type":"array","items":{"type":"object","properties":{"uuid":{"description":"The UUID of the tag value. Use this value to assign the tag to assets.","type":"string"},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the tag value was created, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who created the tag value."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the tag value was last updated, for example, `2018-08-09T13:51:17.243Z`. When you create a tag value, this date matches the `created_at` date."},"updated_by":{"type":"string","description":"The name of the user who last updated the tag value. When you create a tag value, this name matches the `created_by` name."},"category_uuid":{"type":"string","description":"The UUID of the category associated with the tag value. Use this value to create future tags in the same category."},"value":{"description":"The tag value. Must be unique within the category.","type":"string"},"description":{"type":"string","description":"The description of the tag value."},"type":{"type":"string","description":"The tag type:\n - static—A user must manually apply the tag to assets.\n - dynamic—Tenable.io automatically applies the tag based on asset attribute rules."},"category_name":{"type":"string","description":"The name of the category associated with the tag value."},"category_description":{"type":"string","description":"The description of the category associated with the tag value."}}}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of records matching your search criteria. Must be in the int32 format."},"limit":{"type":"integer","description":"Maximum number of records requested (or service imposed limit if not in request). Must be in the int32 format."},"offset":{"type":"integer","description":"The number of skipped records in the returned result set. Must be in the int32 format."},"sort":{"description":"An array of objects representing the fields you specified as sort parameters in the request. This attribute is only present if your request message specifies sort parameters.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the sort field."},"order":{"type":"string","description":"The direction in which Tenable.io sorts on the field, `asc` for ascending or `desc` for descending.","enum":["asc","desc"]}}}}}}}},"examples":{"response":{"value":{"values":[{"uuid":"0a6c1176-4c03-4776-a60a-048021a48799","created_at":"2018-10-30T15:39:16.687Z","created_by":"user3@example.com","updated_at":"2018-10-30T15:39:16.687Z","updated_by":"user3@example.com","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","value":"Seattle","description":"","type":"static","category_name":"location","category_description":"Asset location"},{"uuid":"18179e00-b0e0-4fd7-be91-e9e854fe66b9","created_at":"2018-09-20T16:10:21.710Z","created_by":"user3@example.com","updated_at":"2018-10-30T16:12:11.901Z","updated_by":"user3@example.com","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","value":"Chicago","description":"Chicago Office New","type":"static","category_name":"location","category_description":"Asset location"},{"uuid":"4a4627e8-6334-4ec7-a44d-414d6f510936","created_at":"2018-10-30T23:27:58.579Z","created_by":"user3@example.com","updated_at":"2018-10-30T23:27:58.579Z","updated_by":"user3@example.com","category_uuid":"ce8e96b5-4e4a-4469-99a6-425479153dea","value":"mac","type":"static","category_name":"operating_system","category_description":"The operating system of the asset"},{"uuid":"8e40f9c6-7a6a-4add-b92b-14f3de7f5c5e","created_at":"2018-10-30T15:40:49.110Z","created_by":"user3@example.com","updated_at":"2018-10-30T15:40:49.110Z","updated_by":"user3@example.com","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","value":"New York","type":"static","category_name":"location","category_description":"Asset location"},{"uuid":"9e65d586-94d8-4b44-b3fd-2765385dfb0b","created_at":"2018-10-30T15:34:57.099Z","created_by":"user3@example.com","updated_at":"2018-10-30T15:34:57.099Z","updated_by":"user3@example.com","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","value":"Austin","type":"static","category_name":"location","category_description":"Asset location"},{"uuid":"a270a97d-a5d4-400f-83fd-6f9e0bd44fc1","created_at":"2018-10-30T15:54:47.830Z","created_by":"user3@example.com","updated_at":"2018-10-30T15:54:47.830Z","updated_by":"user3@example.com","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","value":"Rochester","type":"static","category_name":"location","category_description":"Asset location"},{"uuid":"cb12b269-862a-486d-88d2-c20e85d499b3","created_at":"2018-10-30T15:34:57.113Z","created_by":"user3@example.com","updated_at":"2018-10-30T15:34:57.113Z","updated_by":"user3@example.com","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","value":"Portland","type":"static","category_name":"location","category_description":"Asset location"}],"pagination":{"offset":0,"limit":5000,"total":10,"sort":[{"name":"category_name","order":"asc"}]}}}}}}},"400":{"description":"Returned if your request specifies invalid or malformed query parameters. Tenable.io can encounter the following error conditions:\n - `invalidvalue`—The query parameter format is incorrect, for example, uses an invalid operator as in this example: `f=name:invalid_operator:some_value`.\n- `unknownproperty`—The query parameter format is correct, but it references a field that does not exist, for example `sort=non_existing_property:desc`.","content":{"application/json":{"examples":{"response":{"value":{"errors":[{"property":"filter","rule":"invalidvalue","message":"'Filter' does not have a valid value"}],"error":"'Filter' does not have a valid value"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"post":{"summary":"Create tag value","description":"Creates a tag value. The tag category can be specified by UUID or name. If Tenable.io cannot find a category you specify by name, the system creates a new category with the specified name. To automatically apply the tag to assets, specify the rules using the `filters` property.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-create-tag-value","tags":["Tags"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"category_name":{"type":"string","description":"The name of the tag category to associate with the new value.\n\nSpecify the name of a new category if you want to add both a new category and tag value.\n\nSpecify the name of an existing category if you want to add the tag value to the existing category.\n\nCaution: This value is case-sensitive. For example, Tenable.io considers \"location\" and \"Location\" to be separate categories.\n\nThe category_name can result in the following responses:\n\n - If the category_name you specify exists, and the tag value you specify already exists for that category, Tenable.io returns a 400 response code, instead of adding the tag.\n\n - If the category_name you specify exists, but the tag value you specify does not yet exist for that category, Tenable.io adds the tag value to the existing category.\n\n - If the category_name you specify does not exist, Tenable.io creates a new tag category and adds the new tag value to that category.\n\nThis parameter is required if category_uuid is not present in the request message."},"category_uuid":{"type":"string","description":"The UUID of the tag category to associate with the new value. For more information on determining this value, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).\n\nUse this parameter only if you want to add the tag value to an existing category. If the UUID you specify does not exist, Tenable.io does not create a new catgory. Instead, it returns a 400 (Bad Request) response code.\n\nThis parameter is required if category_name is not present in the request message."},"category_description":{"type":"string","description":"The description for the new tag category that Tenable.io creates if the category specified by name does not exist. Otherwise, Tenable.io ignores the description."},"value":{"type":"string","description":"The new tag value.\n\nCaution: This value is case-sensitive. For example, Tenable.io considers \"headquarters\" and \"Headquarters\" to be separate tag values."},"description":{"type":"string","description":"The new tag value description."},"filters":{"type":"object","description":"The filters (conditional sets of rules) for automatically applying the tag to assets. For more information, see [Apply Dynamic Tags](/docs/apply-dynamic-tags).","properties":{"asset":{"type":"object","description":"The object containing conditional sets of rules for applying the tags. \n\n**Note:** Tenable.io supports a maximum of 1,000 rules per tag. This limit means that you can specify a maximum of 1,000 `and` or `or` conditions for a single tag value. However, there is no limit on the number of values you can specify in a comma-delimited string for the `value` of an individual rule.","properties":{"and":{"type":"array","description":"To apply the tag to assets that match all of the rules, specify the rules inside the `and` object.","items":{"type":"object","description":"A rule for applying the tag to assets by matching asset properties or other tags. Includes a field or tag name, an operator, and a value.","properties":{"field":{"type":"string","description":"The asset attribute name or tag to match."},"operator":{"type":"string","description":"The operator to apply to the matched value, for example, equals, does not equal, or contains. To find supported operators, use the [GET /tags/assets/filters](/reference#tags-list-asset-filters) endpoint."},"value":{"type":"string","description":"The asset attribute value or tag to match. You can specify multiple values separated by commas, for example, \"172.204.81.57,172.82.157.177,172.156.65.8,172.207.124.176,172.106.217.225\"."}}}},"or":{"type":"array","description":"To apply the tag to assets that match any of the rules, specify the rules inside the `or` object.","items":{"type":"object","description":"A rule for applying the tag to assets by matching asset properties or other tags. Includes a field or tag name, an operator, and a value.","properties":{"field":{"type":"string","description":"The asset attribute name or tag to match."},"operator":{"type":"string","description":"The operator to apply to the matched value, for example, equals, does not equal, or contains. To find supported operators, use the [GET /tags/assets/filters](/reference#tags-list-asset-filters) endpoint."},"value":{"type":"string","description":"The asset attribute value or tag to match. You can specify multiple values separated by commas, for example, \"172.204.81.57,172.82.157.177,172.156.65.8,172.207.124.176,172.106.217.225\"."}}}}}}}}},"required":["value"]},"example":{"category_name":"Location","value":"San Diego","description":"San Diego - WFH"}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates a value.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"description":"The UUID of the tag value. Use this value to assign the tag to assets.","type":"string"},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the tag value was created, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who created the tag value."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the tag value was last updated, for example, `2018-08-09T13:51:17.243Z`. When you create a tag value, this date matches the `created_at` date."},"updated_by":{"type":"string","description":"The name of the user who last updated the tag value. When you create a tag value, this name matches the `created_by` name."},"category_uuid":{"type":"string","description":"The UUID of the category associated with the tag value. Use this value to create future tags in the same category."},"value":{"description":"The tag value. Must be unique within the category.","type":"string"},"description":{"type":"string","description":"The description of the tag value."},"type":{"type":"string","description":"The tag type:\n - static—A user must manually apply the tag to assets.\n - dynamic—Tenable.io automatically applies the tag based on asset attribute rules."},"category_name":{"type":"string","description":"The name of the category associated with the tag value."},"category_description":{"type":"string","description":"The description of the category associated with the tag value."},"filters":{"type":"object","description":"For dynamic tags, asset selection rules.","properties":{"asset":{"type":"string","description":"Tag rule definitions represented as a JSON-formatted string."}}}}},"examples":{"response":{"value":{"uuid":"fb7fae7d-8acb-48e4-928c-d93103e9e73f","created_at":"2018-11-08T19:39:40.473Z","created_by":"user3@example.com","updated_at":"2018-11-08T19:39:40.473Z","updated_by":"user3@example.com","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","value":"San Diego","description":"San Diego - WFH","type":"static","category_name":"location","category_description":"Asset location"}}}}}},"400":{"description":"Returned if Tenable.io encounters any of the following error conditions:\n - the combination of category and value you specified already exists (`duplicate`)\n - the category you specified does not exist (`not_found`)\n - your request exceeded a tag limit for your organization, which can be either the maximum of 100 categories or the maximum 100,000 tags for each category, or as configured for your organization\n - you attempted to specify more than 1,000 rules for an individual tag (`Filter expression cannot have more than 1,000 conditions`)\n - your request specified an invalid filter for the dynamic tag rule, for example, `ipv 4` (`The following filter types are not valid: {string}`)."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/tags/values/{value_uuid}":{"get":{"summary":"Get tag value details","description":"Returns the details for specified tag value.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-tag-value-details","tags":["Tags"],"parameters":[{"description":"The UUID of the tag value. For more information on determining this value, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","required":true,"name":"value_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the details for tag value.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"description":"The UUID of the tag value. Use this value to assign the tag to assets.","type":"string"},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the tag value was created, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who created the tag value."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the tag value was last updated, for example, `2018-08-09T13:51:17.243Z`. When you create a tag value, this date matches the `created_at` date."},"updated_by":{"type":"string","description":"The name of the user who last updated the tag value. When you create a tag value, this name matches the `created_by` name."},"category_uuid":{"type":"string","description":"The UUID of the category associated with the tag value. Use this value to create future tags in the same category."},"value":{"description":"The tag value. Must be unique within the category.","type":"string"},"description":{"type":"string","description":"The description of the tag value."},"type":{"type":"string","description":"The tag type:\n - static—A user must manually apply the tag to assets.\n - dynamic—Tenable.io automatically applies the tag based on asset attribute rules."},"category_name":{"type":"string","description":"The name of the category associated with the tag value."},"category_description":{"type":"string","description":"The description of the category associated with the tag value."},"filters":{"type":"object","description":"For dynamic tags, asset selection rules.","properties":{"asset":{"type":"string","description":"Tag rule definitions represented as a JSON-formatted string."}}}}},"examples":{"response":{"value":{"uuid":"2ffde3b7-85b4-4f01-b226-15ebc4551c4e","created_at":"2019-05-02T17:11:26.492Z","created_by":"user@example.com","updated_at":"2019-05-02T17:11:26.492Z","updated_by":"user@example.com","category_uuid":"7467cbe4-d6d3-4ffe-ae36-9f5ef4dac933","value":"freebsd","description":"FreeBSD hosts","type":"dynamic","category_name":"unix","category_description":"Unix hosts","filters":{"asset":"{\"and\":[{\"field\":\"operating_system\",\"operator\":\"match\",\"value\":\"FreeBSD\"}]}"}}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified tag value."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update tag value","description":"Updates the specified tag value.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-update-tag-value","tags":["Tags"],"parameters":[{"description":"The UUID of the value you want to update. For more information on determining this value, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","required":true,"name":"value_uuid","in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","description":"The new tag value."},"description":{"type":"string","description":"The new tag value description."},"filters":{"type":"object","description":"The filters (conditional sets of rules) for automatically applying the tag to assets. For more information, see [Apply Dynamic Tags](/docs/apply-dynamic-tags).","properties":{"asset":{"type":"object","description":"The object containing conditional sets of rules for applying the tags.\n\n**Note:** Tenable.io supports a maximum of 1,000 rules per tag. This limit means that you can specify a maximum of 1,000 `and` or `or` conditions for a single tag value. However, there is no limit on the number of values you can specify in a comma-delimited string for the `value` of an individual rule.","properties":{"and":{"type":"array","description":"To apply the tag to assets that match all of the rules, specify the rules inside the `and` object.","items":{"type":"object","description":"A rule for applying the tag to assets by matching asset properties or other tags. Includes a field or tag name, an operator, and a value.","properties":{"field":{"type":"string","description":"The asset attribute name or tag to match."},"operator":{"type":"string","description":"The operator to apply to the matched value, for example, equals, does not equal, or contains. To find supported operators, use the [GET /tags/assets/filters](/reference#tags-list-asset-filters) endpoint."},"value":{"type":"string","description":"The asset attribute value or tag to match. You can specify multiple values separated by commas, for example, \"172.204.81.57,172.82.157.177,172.156.65.8,172.207.124.176,172.106.217.225\"."}}}},"or":{"type":"array","description":"To apply the tag to assets that match any of the rules, specify the rules inside the `or` object.","items":{"type":"object","description":"A rule for applying the tag to assets by matching asset properties or other tags. Includes a field or tag name, an operator, and a value.","properties":{"field":{"type":"string","description":"The asset attribute name or tag to match."},"operator":{"type":"string","description":"The operator to apply to the matched value, for example, equals, does not equal, or contains. To find supported operators, use the [GET /tags/assets/filters](/reference#tags-list-asset-filters) endpoint."},"value":{"type":"string","description":"The asset attribute value or tag to match. You can specify multiple values separated by commas, for example, \"172.204.81.57,172.82.157.177,172.156.65.8,172.207.124.176,172.106.217.225\"."}}}}}}}}}},"example":{"description":"San Diego - Home Office"}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updates the tag value.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"description":"The UUID of the tag value. Use this value to assign the tag to assets.","type":"string"},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the tag value was created, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who created the tag value."},"updated_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the tag value was last updated, for example, `2018-08-09T13:51:17.243Z`. When you create a tag value, this date matches the `created_at` date."},"updated_by":{"type":"string","description":"The name of the user who last updated the tag value. When you create a tag value, this name matches the `created_by` name."},"category_uuid":{"type":"string","description":"The UUID of the category associated with the tag value. Use this value to create future tags in the same category."},"value":{"description":"The tag value. Must be unique within the category.","type":"string"},"description":{"type":"string","description":"The description of the tag value."},"type":{"type":"string","description":"The tag type:\n - static—A user must manually apply the tag to assets.\n - dynamic—Tenable.io automatically applies the tag based on asset attribute rules."},"category_name":{"type":"string","description":"The name of the category associated with the tag value."},"category_description":{"type":"string","description":"The description of the category associated with the tag value."},"filters":{"type":"object","description":"For dynamic tags, asset selection rules.","properties":{"asset":{"type":"string","description":"Tag rule definitions represented as a JSON-formatted string."}}}}},"examples":{"response":{"value":{"uuid":"fb7fae7d-8acb-48e4-928c-d93103e9e73f","created_at":"2018-11-08T19:39:40.473Z","created_by":"user3@example.com","updated_at":"2018-11-08T19:45:37.591Z","updated_by":"user3@example.com","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf","value":"San Diego","description":"San Diego - Home Office","type":"static","category_name":"location","category_description":"Asset location"}}}}}},"400":{"description":"Returned if Tenable.io encounters any of the following error conditions:\n - the combination of category and value you specified already exists (`duplicate`)\n - the category you specified does not exist (`not_found`)\n - your request exceeded a tag limit for your organization, which can be either the maximum of 100 categories or the maximum 100,000 tags for each category, or as configured for your organization\n - you attempted to specify more than 1,000 rules for an individual tag (`Filter expression cannot have more than 1,000 conditions`)\n - your request specified an invalid filter for the dynamic tag rule, for example, `ipv 4` (`The following filter types are not valid: {string}`)."},"404":{"description":"Returned if Tenable.io cannot find the specified tag value."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete tag value","description":"Deletes the specified tag value. If you delete an asset tag, Tenable.io also removes that tag from any assets where the tag was assigned.\n\n**Note:** If you delete all asset tags associated with a category, Tenable.io retains the category. You must [delete](/reference#tags-delete-tag-category) the category separately.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-delete-tag-value","tags":["Tags"],"parameters":[{"description":"The UUID of the tag value you want to delete.\n\n**Note:** A tag UUID is technically assigned to the tag value only (the second half of the category:value pair), but the API commands use this value to represent the whole `category:value` pair. For more information on determining this value, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","required":true,"name":"value_uuid","in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Returned if Tenable.io successfully deletes the specified tag value.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified tag value."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/tags/values/delete-requests":{"post":{"summary":"Bulk delete tag values","description":"Deletes tag values in bulk. If you delete an asset tag, Tenable.io also removes that tag from any assets where the tag was assigned. If you delete all asset tags associated with a category, Tenable.io retains the category. You must [delete](/reference#tags-delete-tag-category) the category separately.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-delete-tag-values-bulk","tags":["Tags"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"values":{"items":{"type":"string","format":"uuid"},"description":"The UUIDs of the tag values you want to delete.\n\n**Note:** A tag UUID is technically assigned to the tag value only (the second half of the category:value pair), but the API commands use this value to represent the whole `category:value` pair. For more information on determining this value, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","type":"array"}}},"example":{"values":["18179e00-b0e0-4fd7-be91-e9e854fe66b9","f45a48b4-50e7-41c3-afb9-2e01f5423698","fb7fae7d-8acb-48e4-928c-d93103e9e73f"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the specified tag values.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if you specify invalid UUIDs."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/tags/assets/{asset_uuid}/assignments":{"get":{"summary":"List tags for an asset","description":"Returns a list of assigned tags for an asset specified by UUID.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-list-asset-tags","tags":["Tags"],"parameters":[{"description":"The UUID of the asset.","required":true,"name":"asset_uuid","in":"path","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Returns a list of tags assigned to the specified asset. If no tags are assigned to the asset, Tenable.io returns an empty list. Also, the service does not validate the asset UUID.","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"description":"An array of asset assignment objects.","type":"array","items":{"type":"object","properties":{"asset_uuid":{"type":"string","description":"The UUID of the asset where the tag is assigned."},"value":{"type":"string","description":"The tag value (the second half of the category:value pair)."},"value_uuid":{"description":"The UUID of the tag value.\n\n**Note:** A tag UUID is technically assigned to the tag value only (the second half of the category:value pair), but the API commands use this value to represent the whole `category:value` pair.","type":"string"},"category_name":{"type":"string","description":"The tag category name (the first half of the category:value pair)."},"category_uuid":{"description":"The UUID of the tag category. Use this value to create future tags in the same category.","type":"string"},"created_at":{"type":"string","description":"An ISO timestamp indicating the date and time on which the was assigned to an asset, for example, `2018-08-09T13:51:17.243Z`."},"created_by":{"type":"string","description":"The name of the user who assigned the tag to the asset."},"source":{"type":"string","description":"The tag type:\n - static—A user must manually apply the tag to assets.\n - dynamic—Tenable.io automatically applies the tag based on asset attribute rules."}}}}}},"examples":{"response":{"value":{"tags":[{"value_uuid":"18179e00-b0e0-4fd7-be91-e9e854fe66b9","category_name":"location","asset_uuid":"842fa017-0141-4fdd-a53b-bcdd971ed1da","created_at":"2018-11-01T16:29:40.606Z","source":"static","value":"Chicago","created_by":"fa76f456-9a6f-4f63-8553-1cee233eb965","category_uuid":"8981f2d8-a043-4a74-ad78-e6a73b13ccaf"},{"value_uuid":"f45a48b4-50e7-41c3-afb9-2e01f5423698","category_name":"threat","asset_uuid":"842fa017-0141-4fdd-a53b-bcdd971ed1da","created_at":"2018-11-01T16:29:40.606Z","source":"static","value":"wannacry","created_by":"fa76f456-9a6f-4f63-8553-1cee233eb965","category_uuid":"a43054ec-87d7-4290-951f-2c489e848463"}]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/tags/assets/assignments":{"post":{"summary":"Add or remove asset tags","description":"Adds or removes tags to/from assets.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-assign-asset-tags","tags":["Tags"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","description":"Specifies whether to add or remove tags.","enum":["add","remove"]},"assets":{"items":{"type":"string","format":"uuid"},"description":"An array of asset UUIDs. For more information on determining values for this array, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","type":"array"},"tags":{"items":{"type":"string","format":"uuid"},"description":"An array of tag value UUIDs. For more information on determining values for this array, see [Determine Tag Identifiers](/docs/determine-tag-identifiers-tio).","type":"array"}},"required":["action","assets","tags"]},"example":{"action":"add","assets":["208d8f5f-73a9-47cd-8b04-4aa99f38af79","c2332afe-5bfd-41fe-9e2e-5462dd3df455","9166eea2-d4aa-4a99-99eb-fee1c36d6457","5fc79177-e820-4ff7-ac28-6a5a995fea8b","fc0a57cb-66fc-43a5-a628-13ef10664fe8"],"tags":["18179e00-b0e0-4fd7-be91-e9e854fe66b9","f45a48b4-50e7-41c3-afb9-2e01f5423698","fb7fae7d-8acb-48e4-928c-d93103e9e73f"]}}}},"responses":{"202":{"description":"Returns the UUID of the asynchronous asset update job.","content":{"application/json":{"schema":{"type":"object","properties":{"job_uuid":{"type":"string","format":"uuid","description":"The UUID of the asynchronous asset update job."}}},"examples":{"response":{"value":{"job_uuid":"62210d02a7056d0297f50a8ddfbd549eaef1d0bc94e1ea3fad09"}}}}}},"400":{"description":"Returned if Tenable.io cannot find the specified assets."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/tags/assets/filters":{"get":{"summary":"List asset tag filters","description":"Returns a list of filters that you can use to create the rules for applying dynamic tags. Includes the field or tag names to match, the operators that you can use with the filter, and the rules for matching the values ('control' field), for example, a list of valid values.\n\nFor definitions of asset attribute filters you might use in tag rules, see [Asset Attribute Definitions](/docs/common-asset-attributes#asset-attribute-definitions).

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"tags-list-asset-filters","tags":["Tags"],"responses":{"200":{"description":"Returns a list of filters.","content":{"application/json":{"schema":{"type":"array","description":"A list of available filters that you can use to define the rules for applying tags to assets.","items":{"type":"object","description":"A filter definition. Includes the field to be matched, the operators that you can use with the filter, and the rules for matching the values (`control` field), for example, a list of valid values.","properties":{"name":{"type":"string","description":"The name of the asset attribute or tag."},"readable_name":{"type":"string","description":"The asset attribute name displayed in the Tenable.io user interface."},"control":{"type":"object","properties":{"readable_regex":{"type":"string","description":"Provides a human-readable \"hint\" to guide users creating tag rules in the Tenable.io user interface."},"type":{"type":"string","description":"The type of UI control that represents the filter in the Tenable.io user interface."},"regex":{"type":"string","description":"A regular expression that Tenable.io UI uses to validate input."}}},"operators":{"description":"The comparison operators that can be used for the filter. To find supported operators, use the [GET /tags/assets/filters](/reference#tags-list-asset-filters) endpoint.","type":"array","items":{"type":"string"}}}}},"examples":{"response":{"value":{"filters":[{"control":{"readable_regex":"e.g. 123e4567e89b12d3a456426655440000","type":"entry","regex":".*"},"name":"tenable_uuid","readable_name":"Tenable UUID","operators":["eq","neq"]},{"control":{"readable_regex":"e.g. 01:23:45:67:89:AB","type":"entry","regex":"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"},"name":"mac_address","readable_name":"MAC Address","operators":["eq","neq"]},{"control":{"readable_regex":"e.g. mail01","type":"entry","regex":".*"},"name":"netbios_name","readable_name":"NetBIOS Name","operators":["eq","neq","match","nmatch"]},{"control":{"type":"dropdown_multi","list":[{"name":"US pacific","value":"US pacific"},{"name":"US Central","value":"US Central"},{"name":"US Pacific","value":"US Pacific"}]},"name":"tag.US Timezone","readable_name":"US Timezone","operators":["set-has","set-hasnot"]},{"control":{"type":"dropdown_multi","list":[{"name":"chicago","value":"chicago"}]},"name":"tag.city","readable_name":"city","operators":["set-has","set-hasnot"]},{"control":{"type":"dropdown_multi","list":[{"name":"FreeBSD","value":"FreeBSD"},{"name":"Linux Kernel","value":"Linux Kernel"},{"name":"Solaris","value":"Solaris"}]},"name":"tag.linux","readable_name":"linux","operators":["set-has","set-hasnot"]},{"control":{"type":"dropdown_multi","list":[{"name":"aix","value":"aix"}]},"name":"tag.unix","readable_name":"unix","operators":["set-has","set-hasnot"]}]}}}}}},"400":{"description":"Returned if Tenable.io cannot find the specified assets."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/target-groups":{"post":{"summary":"Create target group","description":"Creates a new target group for the current user.

      Requires SCAN OPERATOR [24] permissions to create user target groups, and ADMINISTRATOR [64] permissions to create system target groups. For information about target group types, see Tenable.io Vulnerability Management User Guide. For information about permissions, see Permissions.

      ","operationId":"target-groups-create","tags":["Target Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name for the group."},"members":{"type":"string","description":"The members of the group. A comma-separated list of FQDNs or IP address ranges that you want to scan."},"type":{"type":"string","description":"The group type (user or system).","enum":["user","system"]},"acls":{"items":{"type":"object","properties":{"owner":{"type":"integer","description":"The unique ID of the owner of the object."},"type":{"type":"string","description":"The type of permission (default, user, group).","enum":["default","user","group"]},"permissions":{"type":"integer","description":"The permission value to grant access as described in Permissions.","format":"int32"},"id":{"type":"integer","description":"The unique ID of the user if type is user."},"name":{"type":"string","description":"The name of the user or group."},"display_name":{"type":"string","description":"The display-friendly name of the user or group."}}},"description":"An array containing permissions to apply to the group.","type":"array","example":"[{\"type\": \"default\", \"permissions\": 16}, {\"type\": \"target-group\", \"permissions\": 64, \"name\": \"admin\", \"id\": 1, \"owner\": 1}]"}},"required":["name","members","type"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully creates the group.","content":{"application/json":{"schema":{"type":"object","properties":{"acls":{"description":"The Access Control Lists applicable to the group.","type":"array","items":{"type":"object","properties":{"owner":{"type":"integer","description":"The unique ID of the owner of the object."},"type":{"type":"string","description":"The type of permission (default, user, group).","enum":["default","user","group"]},"permissions":{"type":"integer","description":"The permission value to grant access as described in Permissions.","format":"int32"},"id":{"type":"integer","description":"The unique ID of the user if type is user."},"name":{"type":"string","description":"The name of the user or group."},"display_name":{"type":"string","description":"The display-friendly name of the user or group."}}}},"id":{"type":"integer","description":"The unique ID of the group."},"default_group":{"type":"boolean","description":"If true, this group is the default."},"name":{"type":"string","description":"The name of the group."},"members":{"type":"string","description":"The members of the group."},"type":{"type":"string","description":"The group type (user or system). Only administrators can create groups using the `system` type."},"owner":{"type":"string","description":"The name of the owner of the group. A user of `nessus_ms_agent` indicates it is a system target group."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the group."},"last_modification_date":{"type":"integer","description":"The last modification date for the group in unixtime."},"shared":{"type":"integer","description":"The shared status of the group."},"user_permissions":{"type":"integer","description":"The current user permissions for the group."}}},"examples":{"response":{"value":{"acls":[{"permissions":0,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"user2@example.com","name":"user2@example.com","id":2,"type":"user"}],"default_group":0,"members":"172.204.81.53, 172.204.81.54, 172.204.81.55, 172.204.81.56, 172.204.81.57","name":"RHEL_Hosts","owner":"user2@example.com","shared":0,"user_permissions":128,"last_modification_date":1543622674,"creation_date":1543622674,"owner_id":2,"id":18}}}}}},"400":{"description":"Returned if your response message is missing a required parameter or is otherwise invalid."},"403":{"description":"Returned if you do not have permission to create a group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to create the group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List target groups","description":"Returns the current target groups.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"target-groups-list","tags":["Target Groups"],"responses":{"200":{"description":"Returns the group.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"acls":{"description":"The Access Control Lists applicable to the group.","type":"array","items":{"type":"object","properties":{"owner":{"type":"integer","description":"The unique ID of the owner of the object."},"type":{"type":"string","description":"The type of permission (default, user, group).","enum":["default","user","group"]},"permissions":{"type":"integer","description":"The permission value to grant access as described in Permissions.","format":"int32"},"id":{"type":"integer","description":"The unique ID of the user if type is user."},"name":{"type":"string","description":"The name of the user or group."},"display_name":{"type":"string","description":"The display-friendly name of the user or group."}}}},"id":{"type":"integer","description":"The unique ID of the group."},"default_group":{"type":"boolean","description":"If true, this group is the default."},"name":{"type":"string","description":"The name of the group."},"members":{"type":"string","description":"The members of the group."},"type":{"type":"string","description":"The group type (user or system). Only administrators can create groups using the `system` type."},"owner":{"type":"string","description":"The name of the owner of the group. A user of `nessus_ms_agent` indicates it is a system target group."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the group."},"last_modification_date":{"type":"integer","description":"The last modification date for the group in unixtime."},"shared":{"type":"integer","description":"The shared status of the group."},"user_permissions":{"type":"integer","description":"The current user permissions for the group."}}}},"examples":{"response":{"value":{"target_groups":[{"acls":[{"permissions":0,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"user2@example.com","name":"user2@example.com","id":2,"type":"user"}],"default_group":0,"type":"user","members":"172.204.81.53, 172.204.81.54, 172.204.81.55, 172.204.81.56, 172.204.81.57","name":"Centos_Hosts","owner":"user2@example.com","shared":0,"user_permissions":128,"last_modification_date":1543622642,"creation_date":1543622642,"owner_id":2,"id":17},{"acls":[{"permissions":64,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"system","name":"nessus_ms_agent","id":1,"type":"user"}],"default_group":1,"type":"system","members":"*","name":"Default","owner":"nessus_ms_agent","shared":1,"user_permissions":64,"last_modification_date":1532459326,"creation_date":1532459326,"owner_id":1,"id":7},{"acls":[{"permissions":0,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"user2@example.com","name":"user2@example.com","id":2,"type":"user"}],"default_group":0,"type":"user","members":"172.204.81.53, 172.204.81.54, 172.204.81.55, 172.204.81.56, 172.204.81.57","name":"RHEL_Hosts","owner":"user2@example.com","shared":0,"user_permissions":128,"last_modification_date":1543622674,"creation_date":1543622674,"owner_id":2,"id":18}]}}}}}},"403":{"description":"Returned if you do not have permission to view the group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/target-groups/{group_id}":{"get":{"summary":"Get target group details","description":"Returns details for the specified target group.

      Requires BASIC [16] permissions. See Permissions.

      ","operationId":"target-groups-details","tags":["Target Groups"],"parameters":[{"description":"The ID of the group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the group details.","content":{"application/json":{"schema":{"type":"object","properties":{"acls":{"description":"The Access Control Lists applicable to the group.","type":"array","items":{"type":"object","properties":{"owner":{"type":"integer","description":"The unique ID of the owner of the object."},"type":{"type":"string","description":"The type of permission (default, user, group).","enum":["default","user","group"]},"permissions":{"type":"integer","description":"The permission value to grant access as described in Permissions.","format":"int32"},"id":{"type":"integer","description":"The unique ID of the user if type is user."},"name":{"type":"string","description":"The name of the user or group."},"display_name":{"type":"string","description":"The display-friendly name of the user or group."}}}},"id":{"type":"integer","description":"The unique ID of the group."},"default_group":{"type":"boolean","description":"If true, this group is the default."},"name":{"type":"string","description":"The name of the group."},"members":{"type":"string","description":"The members of the group."},"type":{"type":"string","description":"The group type (user or system). Only administrators can create groups using the `system` type."},"owner":{"type":"string","description":"The name of the owner of the group. A user of `nessus_ms_agent` indicates it is a system target group."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the group."},"last_modification_date":{"type":"integer","description":"The last modification date for the group in unixtime."},"shared":{"type":"integer","description":"The shared status of the group."},"user_permissions":{"type":"integer","description":"The current user permissions for the group."}}},"examples":{"response":{"value":{"acls":[{"permissions":0,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"user2@example.com","name":"user2@example.com","id":2,"type":"user"}],"default_group":0,"type":"user","members":"172.204.81.53, 172.204.81.54, 172.204.81.55, 172.204.81.56, 172.204.81.57","name":"Centos_Hosts","owner":"user2@example.com","shared":0,"user_permissions":128,"last_modification_date":1543622642,"creation_date":1543622642,"owner_id":2,"id":17}}}}}},"403":{"description":"Returned if you do not have permission to view the group."},"404":{"description":"Returned if Tenable.io cannot find the specified group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update target group","description":"Updates a target group.\n - User target groups: This request requires SCAN OPERATOR [24] user permissions.\n - System target groups: This request requires ADMINISTRATOR [64] user permissions.","operationId":"target-groups-edit","tags":["Target Groups"],"parameters":[{"description":"The ID of the group to edit.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the group."},"members":{"type":"string","description":"The members of the group. A comma-separated list of FQDNs or IP address ranges that you want to scan."},"type":{"type":"string","description":"The group type (user or system).","enum":["user","system"]},"acls":{"items":{"type":"object","properties":{"owner":{"type":"integer","description":"The unique ID of the owner of the object."},"type":{"type":"string","description":"The type of permission (default, user, group).","enum":["default","user","group"]},"permissions":{"type":"integer","description":"The permission value to grant access as described in Permissions.","format":"int32"},"id":{"type":"integer","description":"The unique ID of the user if type is user."},"name":{"type":"string","description":"The name of the user or group."},"display_name":{"type":"string","description":"The display-friendly name of the user or group."}}},"description":"An array containing permissions to apply to the group.","type":"array","example":"[{\"type\": \"default\", \"permissions\": 16}, {\"type\": \"target-group\", \"permissions\": 64, \"name\": \"admin\", \"id\": 1, \"owner\": 1}]"}},"required":["name","members","type"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully updated the target group.","content":{"application/json":{"schema":{"type":"object","properties":{"acls":{"description":"The Access Control Lists applicable to the group.","type":"array","items":{"type":"object","properties":{"owner":{"type":"integer","description":"The unique ID of the owner of the object."},"type":{"type":"string","description":"The type of permission (default, user, group).","enum":["default","user","group"]},"permissions":{"type":"integer","description":"The permission value to grant access as described in Permissions.","format":"int32"},"id":{"type":"integer","description":"The unique ID of the user if type is user."},"name":{"type":"string","description":"The name of the user or group."},"display_name":{"type":"string","description":"The display-friendly name of the user or group."}}}},"id":{"type":"integer","description":"The unique ID of the group."},"default_group":{"type":"boolean","description":"If true, this group is the default."},"name":{"type":"string","description":"The name of the group."},"members":{"type":"string","description":"The members of the group."},"type":{"type":"string","description":"The group type (user or system). Only administrators can create groups using the `system` type."},"owner":{"type":"string","description":"The name of the owner of the group. A user of `nessus_ms_agent` indicates it is a system target group."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the group."},"last_modification_date":{"type":"integer","description":"The last modification date for the group in unixtime."},"shared":{"type":"integer","description":"The shared status of the group."},"user_permissions":{"type":"integer","description":"The current user permissions for the group."}}},"examples":{"response":{"value":{"acls":[{"permissions":0,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"user2@example.com","name":"user2@example.com","id":2,"type":"user"}],"default_group":0,"type":"user","members":"172.204.81.53, 172.204.81.54, 172.204.81.55, 172.204.81.56, 172.204.81.57","name":"Centos_Hosts","owner":"user2@example.com","shared":0,"user_permissions":128,"last_modification_date":1543622642,"creation_date":1543622642,"owner_id":2,"id":17}}}}}},"403":{"description":"Returned if you do not have permission to update the group."},"404":{"description":"Returned if Tenable.io cannot find the specified group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to update the group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete target group","description":"Deletes a target group. \n - User target groups: This request requires SCAN OPERATOR [24] user permissions.\n - System target groups: This request requires ADMINISTRATOR [64] user permissions.","operationId":"target-groups-delete","tags":["Target Groups"],"parameters":[{"description":"The ID of the group to delete.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deletes the target group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to delete the group."},"404":{"description":"Returned if Tenable.io cannot find the specified group."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/import/vulnerabilities":{"post":{"summary":"Import vulnerabilities","description":"Imports a list of vulnerabilities in JSON format. The request cannot exceed 15 MB in total size. In addition, the request can contain a maximum of 50 asset objects. For request body examples, see [Add Vulnerability Data to Tenable.io](/docs/add-vulnerability-data-to-tenableio).

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"vulnerabilities-import","tags":["Vulnerabilities"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"source":{"type":"string","description":"The source of the scan that generated the vulnerability data. If you want to categorize the imported vulnerabilities in the same way that Tenable.io categorizes vulnerabilities detected in scans it manages, use the following values: \n - security_center—A Nessus scan identified the vulnerabilities you want to import. Use this value for all Nessus scans, regardless of the scan manager (Tenable.io, SecurityCenter, or Nessus Manager).\n - qualys—A Qualys scan identified the vulnerabilities you want to import.","example":"nessus"},"type":{"type":"string","description":"The type of scan that identified the vulnerabilities you want to import. If you want to categorize the imported vulnerabilities in the same way that Tenable.io categorizes vulnerabilities detected in scans it manages, use the following values: \n - vm—A Vulnerability Management scan identified the vulnerabilities.\n - was—A Web Application Scanning scan identified the vulnerabilities.\n - pc—A scan of a personal computer identified the vulnerabilities.","example":"vm"},"assets":{"type":"array","description":"An array of asset objects with vulnerabilities information. A valid asset record requires at least one valid network_interface object.\n\n**Note:** Tenable.io supports a maximum of 50 individual asset objects per request message. In addition, because Tenable.io supports a total size limit of 15 MB for the request message, you may want to limit the number of asset objects you include in an individual request, depending on the number of vulnerabilities identified on the assets and the size of the related vulnerability output.\n\n**Note:** This endpoint does not support the network_id attribute in asset objects for import. Tenable.io automatically assigns imported assets to the default network object. For more information about network objects, see [Manage Networks](/docs/manage-networks-tio).","items":{"type":"object","properties":{"network_interfaces":{"type":"array","description":"A valid network_interface object must contain at least one of the following parameters: ipv4, netbios_name, fqdn.","items":{"type":"object","properties":{"ipv4":{"type":"array","description":"A list of IPv4 address that the scan identified as associated with the network interface.","items":{"type":"string"}},"ipv6":{"type":"array","description":"A list of IPv6 addresses that the scan identified as associated with the network interface.","items":{"type":"string"}},"mac_address":{"type":"string","description":"The MAC address of the network interface."},"netbios_name":{"type":"string","description":"The NETBIOS name of the network interface."},"fqdn":{"type":"string","description":"The fully-qualified domain name (FQDN) of the network interface."}}}},"hostname":{"type":"string","description":"The asset's hostname."},"qualys_asset_id":{"type":"string","description":"The Asset ID of the asset in Qualys. For more information, see the Qualys documentation."},"qualys_host_id":{"type":"string","description":"The Host ID of the asset in Qualys. For more information, see the Qualys documentation."},"servicenow_sysid":{"type":"string","description":"The unique record identifier of the asset in ServiceNow. For more information, see the ServiceNow documentation."},"ssh_fingerprint":{"type":"string","description":"The SSH key fingerprint that the scan has associated with the asset."},"bios_uuid":{"type":"string","description":"The BIOS UUID of the asset."},"netbios_name":{"type":"string","description":"The NetBIOS name that the scan has associated with the asset."},"tenable_agent_id":{"type":"integer","description":"The unique ID of the Nessus agent installed on the asset. This parameter is supported only if the `source` parameter for the request is `security_center`."},"vulnerabilities":{"type":"array","description":"A valid vulnerability object must contain at least one of the following parameters: tenable_plugin_id, qualys_id, or cve.","items":{"type":"object","properties":{"tenable_plugin_id":{"type":"string","description":"The ID of the Nessus plugin that identified the vulnerability. This parameter is required if the vulnerability object does not specify either a qualys_id or cve value."},"qualys_id":{"type":"string","description":"The unique ID (QID) of the vulnerability in the Qualys system. For more information, see the Qualys documentation. This parameter is required if the vulnerability object does not specify either a tenable_plugin_id or cve value."},"cve":{"type":"string","description":"The Common Vulnerability and Exposure (CVE) ID for the vulnerability. This parameter is required if the vulnerability object does not specify either a tenable_plugin_id or qualys_id value."},"port":{"type":"integer","description":"The port the scanner used to communicate with the asset.","format":"int32"},"protocol":{"type":"string","description":"The protocol the scanner used to communicate with the asset."},"authenticated":{"type":"boolean","description":"A value specifying whether the scan that identified the vulnerability was an authenticated (credentialed) scan."},"first_found":{"type":"integer","description":"The date (in Unix time) when a scan first identified the vulnerability on the asset.","format":"int32"},"last_found":{"type":"integer","description":"The date (in Unix time) when a scan last identified the vulnerability on the asset.","format":"int32"},"last_fixed":{"type":"integer","description":"The date (in Unix time) when the vulnerability state was changed to `fixed`. Tenable.io updates the vulnerability state to fixed when a scan no longer detects a previously detected vulnerability on the asset.","format":"int32"},"output":{"type":"string","description":"(Required) The text output of the scanner, up to 2,000 maximum characters."}}}}}}},"checks_ran":{"type":"array","description":"An array of objects, each representing a check that the scan used to detect the vulnerabilities you are importing. This parameter supports Tenable plugin checks only. For more information, see [Plugins](https://www.tenable.com/plugins).","items":{"type":"object","properties":{"tenable_plugin_id":{"type":"string","description":"The Tenable plugin ID."},"port":{"type":"integer","description":"The port on the asset where the check ran."},"protocol":{"type":"string","description":"The protocol used to communicate with the asset while running the check."}}}}},"required":["source","type","assets"]}}}},"responses":{"200":{"description":"Returned if Tenable.io successfully imports the vulnerabilities.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"400":{"description":"Returned if you submitted an invalid request."},"403":{"description":"Returned if you do not have permission to import vulnerabilities."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an internal server error. Wait a moment, and try your request again."},"503":{"description":"Returned if a Tenable.io service is unavailable. Wait a moment, and try your request again."}},"security":[{"cloud":[]}]}},"/workbenches/vulnerabilities":{"get":{"summary":"List vulnerabilities","description":"Returns a list of recorded vulnerabilities. The list returned is limited to 5,000. To retrieve more than 5,000 vulnerabilities, use the export-request API.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-vulnerabilities","tags":["Workbenches"],"parameters":[{"description":"Lists only those vulnerabilities older than a certain number of days.","name":"age","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Lists only authenticated vulnerabilities.","name":"authenticated","in":"query","schema":{"type":"boolean"}},{"description":"The number of days of data prior to and including today that should be returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Lists only exploitable vulnerabilities.","name":"exploitable","in":"query","schema":{"type":"boolean"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}},{"description":"Lists only those vulnerabilities with a remediation path.","name":"resolvable","in":"query","schema":{"type":"boolean"}},{"description":"Lists only vulnerabilities of a specific severity (critical, high, medium or low)","name":"severity","in":"query","schema":{"type":"string","enum":["","critical","high","medium","low"]}}],"responses":{"200":{"description":"Returns a list of vulnerabilities.","content":{"application/json":{"schema":{"type":"object","description":"A list of vulnerabilities.","properties":{"vulnerabilities":{"type":"array","description":"A list of discovered vulnerabilities.","items":{"type":"object","properties":{"count":{"type":"integer","description":"The number of times that a scan detected the vulnerability on an asset."},"plugin_family":{"type":"string","description":"The plugin's family."},"plugin_id":{"type":"integer","description":"The unique plugin ID."},"plugin_name":{"type":"string","description":"The name of the plugin that detected the vulnerability."},"vulnerability_state":{"type":"string","description":"The current state of the reported plugin. Possible states include:\n - Active—The vulnerability is currently present on an asset.\n - New—The vulnerability is active on an asset, and was first detected within the last 14 days.\n - Fixed—A subsequent scan detected that the formerly-active vulnerability is no longer present on an asset.\n - Resurfaced—The vulnerability was previously marked as fixed on an asset, but a subsequent scan detected the vulnerability on the asset again."},"vpr_score":{"type":"integer","description":"The Vulnerability Priority Rating (VPR) for the vulnerability. If a plugin is designed to detect multiple vulnerabilities, the VPR represents the highest value calculated for a vulnerability associated with the plugin. For more information, see Severity vs. VPR in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"accepted_count":{"type":"integer","description":"The number of times that a user in the user interface created an accept rule for this vulnerability. For more information, see Recast Rules in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"recasted_count":{"type":"integer","description":"The number of times that a user in the user interface created a recast rule for this vulnerability. For more information, see Recast Rules in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"counts by severity":{"type":"array","description":"The number of times that a scan detected the vulnerability on an asset, grouped by severity level.","items":{"type":"object","properties":{"count":{"type":"integer","description":"The number of times that a scan detected the vulnerability on an asset while the vulnerability was assigned the specified severity level.","format":"int32"},"value":{"type":"integer","description":"The severity level of the vulnerabilities in the group."}}}},"severity":{"type":"integer","description":"The severity level of the vulnerability, as defined using the Common Vulnerability Scoring System (CVSS) base score. Possible values include: \n - 0—The vulnerability has a CVSS score of 0, which corresponds to the \"info\" severity level.\n - 1—The vulnerability has a CVSS score between 0.1 and 3.9, which corresponds to the \"low\" severity level.\n - 2—The vulnerability has a CVSS score between 4.0 and 6.9, which corresponds to the \"medium\" severity level.\n - 3—The vulnerability has a CVSS score between 7.0 and 9.9, which corresponds to the \"high\" severity level.\n - 4—The vulnerability has a CVSS score of 10.0, which corresponds to the \"critical\" severity level.","format":"int32"}}}},"total_vulnerability_count":{"type":"integer","description":"The total number of discovered vulnerabilities."},"total_asset_count":{"type":"integer","description":"The total number of assets."}}},"examples":{"response":{"value":{"vulnerabilities":[{"count":319,"plugin_family":"General","plugin_id":51192,"plugin_name":"SSL Certificate Cannot Be Trusted","vulnerability_state":"Active","vpr_score":2.4,"accepted_count":0,"recasted_count":0,"counts_by_severity":[{"count":319,"value":2}],"severity":2},{"count":215,"plugin_family":"Misc.","plugin_id":70658,"plugin_name":"SSH Server CBC Mode Ciphers Enabled","vulnerability_state":"Active","vpr_score":7.4,"accepted_count":0,"recasted_count":0,"counts_by_severity":[{"count":215,"value":1}],"severity":1},{"count":168,"plugin_family":"Misc.","plugin_id":71049,"plugin_name":"SSH Weak MAC Algorithms Enabled","vulnerability_state":"Active","vpr_score":5.5,"accepted_count":0,"recasted_count":0,"counts_by_severity":[{"count":168,"value":1}],"severity":1}],"total_vulnerability_count":3,"total_asset_count":0}}}}}},"403":{"description":"Returned if you do not have permission to list vulnerabilities."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/vulnerabilities/{plugin_id}/info":{"get":{"summary":"Get plugin details","description":"Retrieves the details for a plugin.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-vulnerability-info","tags":["Workbenches"],"parameters":[{"description":"The ID of the plugin. You can find the plugin ID by examining the output of the [GET /workbenches/vulnerabilities](#workbenches-vulnerabilities) endpoint.","required":true,"name":"plugin_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The number of days of data prior to and including today that should be returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}}],"responses":{"200":{"description":"Returns plugin details.","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer","format":"int32","description":"A count of the vulnerability occurrences."},"vuln_count":{"type":"integer","format":"int32","description":""},"description":{"type":"string","description":"The description of the vulnerability."},"synopsis":{"type":"string","description":"A brief summary of the vulnerability."},"solution":{"type":"string","description":"Information on how to fix the vulnerability."},"discovery":{"type":"object","properties":{"seen_first":{"type":"string","description":"The Unix timestamp of the scan that first detected the vulnerability on an asset."},"seen_last":{"type":"string","description":"The Unix timestamp of the scan that most recently detected the vulnerability on an asset."}}},"severity":{"type":"integer","description":"The severity level of the vulnerability."},"plugin_details":{"type":"object","properties":{"family":{"type":"string","description":"The plugin family."},"modification_date":{"type":"string","description":"The ISO timestamp when Tenable last updated the plugin definition."},"name":{"type":"string","description":"The name of the plugin."},"publication_date":{"type":"string","description":"The ISO timestamp when Tenable first published the plugin definition."},"type":{"type":"string","description":"The type of scan that uses the plugin, either a network scan (`remote`) or a credentialed scan (`local`)."},"version":{"type":"string","description":"The plugin version."},"severity":{"type":"integer","format":"int32","description":"The severity level of the plugin."}}},"reference_information":{"type":"array","description":"","items":{"type":"object","properties":{"name":{"type":"string","description":"The source of the reference information about the vulnerability. Possible values include:\n - bid—Bugtraq (Symantec Connect)\n - cert—CERT/CC Vulnerability Notes Database\n - cve—NIST National Vulnerability Database (NVD)\n - edb-id—The Exploit Database\n - iava—information assurance vulnerability alert\n - osvdb—Open Sourced Vulnerability Database"},"url":{"type":"string","description":"The URL of the source site, if available."},"values":{"type":"array","description":"The unique identifier(s) for the vulnerability at the source.","items":{"type":"string"}}}}},"risk_information":{"type":"object","properties":{"risk_factor":{"type":"string","description":"The risk factor associated with the plugin. Possible values are: `Low`, `Medium`, `High`, or `Critical`."},"cvss_vector":{"type":"string","description":"The raw CVSSv2 metrics for the vulnerability. For more information, see CVSSv2 documentation."},"cvss_base_score":{"type":"string","description":"The CVSSv2 base score (intrinsic and fundamental characteristics of a vulnerability that are constant over time and user environments)."},"cvss_temporal_vector":{"type":"string","description":"The raw CVSSv2 temporal metrics for the vulnerability."},"cvss_temporal_score":{"type":"string","description":"The CVSSv2 temporal score (characteristics of a vulnerability that change over time but not among user environments)."},"cvss3_vector":{"type":"string","description":"The raw CVSSv3 metrics for the vulnerability. For more information, see CVSSv3 documentation."},"cvss3_base_score":{"type":"string","description":"The CVSSv3 base score (intrinsic and fundamental characteristics of a vulnerability that are constant over time and user environments)."},"cvss3_temporal_vector":{"type":"string","description":"CVSSv3 temporal metrics for the vulnerability."},"cvss3_temporal_score":{"type":"string","description":"The CVSSv3 temporal score (characteristics of a vulnerability that change over time but not among user environments)."},"stig_severity":{"type":"string","description":"Security Technical Implementation Guide (STIG) severity code for the vulnerability."}}},"see_also":{"type":"array","description":"Links to external websites that contain helpful information about the vulnerability.","items":{"type":"string"}},"vulnerability_information":{"type":"object","properties":{"vulnerability_publication_date":{"type":"string","description":"The ISO timestamp for the first publication date of the plugin."},"exploited_by_malware":{"type":"boolean","description":"The vulnerability discovered by this plugin is known to be exploited by malware."},"patch_publication_date":{"type":"string","description":"The ISO timestamp for date on which the vendor published a patch for the vulnerability."},"exploit_available":{"type":"boolean","description":"A value specifying whether a public exploit exists for the vulnerability."},"exploitability_ease":{"type":"string","description":"Description of how easy it is to exploit the issue."},"asset_inventory":{"type":"string","description":""},"default_account":{"type":"string","description":""},"exploited_by_nessus":{"type":"boolean","description":"A value specifying whether Nessus exploited the vulnerability during the process of identification."},"in_the_news":{"type":"boolean","description":"A value specifying whether this plugin has received media attention (for example, ShellShock, Meltdown)."},"malware":{"type":"string","description":""},"unsupported_by_vendor":{"type":"boolean","description":"Software found by this plugin is unsupported by the software's vendor (for example, Windows 95 or Firefox 3)."},"cpe":{"type":"string","description":"The Common Platform Enumeration (CPE) number for the plugin."},"exploit_frameworks":{"type":"array","description":"A list of exploit frameworks that have identified the vulnerability.","items":{"type":"object","description":"Information about the vulnerability in a specific exploit framework.","properties":{"name":{"type":"string","description":"The name of the exploit framework."},"exploits":{"type":"array","description":"A list of exploits associated with the vulnerability in the specified exploit framework.","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the identified exploit."},"url":{"type":"string","description":"The URL for the exploit in the framework."}}}}}}}}},"vpr":{"type":"object","description":"Information about the Vulnerability Priority Rating (VPR) for the vulnerability.","properties":{"score":{"type":"integer","description":"The Vulnerability Priority Rating (VPR) for the vulnerability. If a plugin is designed to detect multiple vulnerabilities, the VPR represents the highest value calculated for a vulnerability associated with the plugin. For more information, see Severity vs. VPR in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"drivers":{"type":"object","description":"The key drivers Tenable uses to calculate a vulnerability's VPR. For more information, see Vulnerability Priority Rating Drivers.","properties":{}},"updated":{"type":"string","description":"The ISO timestamp when Tenable.io last imported the VPR for this vulnerability. Tenable.io imports a VPR value the first time you scan a vulnerability on your network. Then, Tenable.io automatically re-imports new and updated VPR values daily."}}}}},"examples":{"response":{"value":{"info":{"count":13,"vuln_count":14,"description":"The remote web server is affected by a command injection vulnerability in GNU Bash known as Shellshock. The vulnerability is due to the processing of trailing strings after function definitions in the values of environment variables. This allows a remote attacker to execute arbitrary code via environment variable manipulation depending on the configuration of the system.","synopsis":"The remote web server is affected by a remote code execution vulnerability.","solution":"Apply the referenced patch.","discovery":{"seen_first":"2019-03-08T17:15:52.000Z","seen_last":"2019-04-05T22:53:45.000Z"},"severity":4,"plugin_details":{"family":"CGI abuses","modification_date":"2017-04-25T00:00:00Z","name":"GNU Bash Environment Variable Handling Code Injection (Shellshock)","publication_date":"2014-09-24T00:00:00Z","type":"remote","version":null,"severity":4},"reference_information":[{"name":"bid","url":"http://www.securityfocus.com/bid/","values":[70103]},{"name":"cert","url":"http://www.kb.cert.org/vuls/id/","values":["252743"]},{"name":"cve","url":"http://web.nvd.nist.gov/view/vuln/detail?vulnId=","values":["CVE-2014-6271"]},{"name":"edb-id","url":"http://www.exploit-db.com/exploits/","values":["34766","34777","34765"]},{"name":"iava","values":["2014-A-0142"]},{"name":"osvdb","values":["112004"]}],"risk_information":{"risk_factor":"Critical","cvss_vector":"AV:N/AC:L/Au:N/C:C/I:C/A:C","cvss_base_score":"10.0","cvss_temporal_vector":"E:F/RL:OF/RC:ND","cvss_temporal_score":"8.3","cvss3_vector":null,"cvss3_base_score":null,"cvss3_temporal_vector":null,"cvss3_temporal_score":null,"stig_severity":null},"see_also":["http://seclists.org/oss-sec/2014/q3/650","http://www.nessus.org/u?dacf7829","https://www.invisiblethreat.ca/post/shellshock/"],"vulnerability_information":{"vulnerability_publication_date":"2014-09-24T00:00:00Z","exploited_by_malware":true,"patch_publication_date":"2014-09-24T00:00:00Z","exploit_available":true,"exploitability_ease":null,"asset_inventory":null,"default_account":null,"exploited_by_nessus":null,"in_the_news":true,"malware":null,"unsupported_by_vendor":null,"cpe":null,"exploit_frameworks":[{"name":"Core Impact"},{"name":"Metasploit","exploits":[{"name":"Apache mod_cgi Bash Environment Variable Code Injection (Shellshock)","url":null}]}]},"vpr":{"score":9.6,"drivers":{"age_of_vuln":{"lower_bound":731,"upper_bound":0},"exploit_code_maturity":"HIGH","cvss3_impact_score":5.9,"cvss_impact_score_predicted":true,"threat_intensity_last28":"HIGH","threat_recency":{"lower_bound":0,"upper_bound":7},"threat_sources_last28":["Others","Mainstream Media","Code Repo and Paste Bins"],"product_coverage":"LOW"},"updated":"2019-04-01T10:10:57Z"}}}}}}}},"403":{"description":"Returned if you do not have permission to view plugin details."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/vulnerabilities/{plugin_id}/outputs":{"get":{"summary":"List plugin outputs","description":"Retrieves the vulnerability outputs for a plugin. The list returned is limited to 5,000. To retrieve more than 5,000 vulnerability outputs, use the export-request API.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-vulnerability-output","tags":["Workbenches"],"parameters":[{"description":"The ID of the plugin. You can find the plugin ID by examining the output of the [GET /workbenches/vulnerabilities](#workbenches-vulnerabilities) endpoint.","required":true,"name":"plugin_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The number of days of data prior to and including today that should be returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/vulnerabilities](#workbenches-vulnerabilities-filters) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}}],"responses":{"200":{"description":"Returns plugin outputs.","content":{"application/json":{"schema":{"type":"array","description":"A list of vulnerabilities discovered by the plugin.","items":{"type":"object","properties":{"plugin_output":{"type":"string","description":"The plugin's output about the vulnerability. May be an empty string."},"states":{"type":"array","description":"Vulnerability state items.","items":{"type":"object","properties":{"name":{"type":"string","description":"The current state of the reported plugin (Active, Fixed, New, etc.)"},"results":{"type":"array","items":{"type":"object","properties":{"application_protocol":{"type":"string","description":"The application protocol where this vulnerability was found."},"port":{"type":"integer","description":"The port number where this vulnerability was found."},"transport_protocol":{"type":"string","description":"The transportation protocol (TCP or UDP) where this vulnerability was found."},"assets":{"type":"array","description":"A list of assets where this output was found.","items":{"type":"object","properties":{"hostname":{"type":"string","description":"The host name of the asset."},"id":{"type":"string","description":"The ID of the asset."},"uuid":{"type":"string","description":"The UUID of the asset."},"netbios_name":{"type":"string","description":"The NetBios name of the asset."},"fqdn":{"type":"string","description":"The FQDN of the asset."},"ipv4":{"type":"string","description":"The IPV4 of the asset."},"first_seen":{"type":"string","format":"date-time","description":"Indicates when the asset was discovered by a scan."},"last_seen":{"type":"string","format":"date-time","description":"Indicates when the asset was last observed by a scan."}}}},"severity":{"type":"integer","description":"Integer [0-4] indicating how severe the vulnerability is, where 0 is info only."}}}}}}}}}},"examples":{"response":{"value":{"outputs":[{"plugin_output":"\nThe following certificate was at the top of the certificate\nchain sent by the remote host, but it is signed by an unknown\ncertificate authority :\n\n|-Subject : O=LCE Users/OU=LCE Certification Authority/L=New York/C=US/ST=NY/CN=LCE Certification Authority\n|-Issuer : O=LCE Users/OU=LCE Certification Authority/L=New York/C=US/ST=NY/CN=LCE Certification Authority\n","states":[{"name":"Active","results":[{"application_protocol":"unknown","port":1243,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"484f06ae-c614-4a82-83f8-a8132b31ea37","uuid":"484f06ae-c614-4a82-83f8-a8132b31ea37","netbios_name":null,"fqdn":null,"ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"},{"hostname":"172.204.81.57","id":"73261e87-8a1a-4644-9e99-d97feb3bdf48","uuid":"73261e87-8a1a-4644-9e99-d97feb3bdf48","netbios_name":null,"fqdn":null,"ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"},{"hostname":"172.204.81.57","id":"9be6d0b1-33d2-4a29-86ea-69cd52642856","uuid":"9be6d0b1-33d2-4a29-86ea-69cd52642856","netbios_name":null,"fqdn":"benchlce2.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"},{"hostname":"172.204.81.57","id":"da2f6f2b-2a82-4fc4-865e-1a3d8b393b00","uuid":"da2f6f2b-2a82-4fc4-865e-1a3d8b393b00","netbios_name":null,"fqdn":null,"ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"},{"hostname":"172.204.81.57","id":"eb970c99-08c4-4e44-ae5f-01f062b4349d","uuid":"eb970c99-08c4-4e44-ae5f-01f062b4349d","netbios_name":null,"fqdn":"centos7.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"},{"hostname":"172.204.81.57","id":"f228fdad-2f82-40c0-bb68-ad3ff2a8bebc","uuid":"f228fdad-2f82-40c0-bb68-ad3ff2a8bebc","netbios_name":null,"fqdn":null,"ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":2}]}]},{"plugin_output":"\nThe following certificate was at the top of the certificate\nchain sent by the remote host, but it is signed by an unknown\ncertificate authority :\n\n|-Subject : O=VMware, Inc./OU=vCenterServer_2014.07.30_111246/CN=VMware default certificate/E=support@vmware.com\n|-Issuer : O=VMware, Inc./OU=vCenterServer_2014.07.30_111246/CN=172.204.81.57/E=support@vmware.com\n","states":[{"name":"Active","results":[{"application_protocol":"unknown","port":443,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":2},{"application_protocol":"unknown","port":8191,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":2},{"application_protocol":"unknown","port":8443,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":2},{"application_protocol":"unknown","port":31100,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":2},{"application_protocol":"unknown","port":32100,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":2}]}]},{"plugin_output":"\nThe following certificate was at the top of the certificate\nchain sent by the remote host, but it is signed by an unknown\ncertificate authority :\n\n|-Subject : CN=SSL_Self_Signed_Fallback\n|-Issuer : CN=SSL_Self_Signed_Fallback\n","states":[{"name":"Active","results":[{"application_protocol":"unknown","port":1433,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"159295bd-0942-4ce6-aeb2-25aa7f161ba1","uuid":"159295bd-0942-4ce6-aeb2-25aa7f161ba1","netbios_name":"SQL2014","fqdn":"sql2014.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"},{"hostname":"172.204.81.57","id":"64cd2363-849f-46cb-88cf-df1b78511d9c","uuid":"64cd2363-849f-46cb-88cf-df1b78511d9c","netbios_name":"SCCMHOST","fqdn":"sccmhost.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"},{"hostname":"172.204.81.57","id":"ca66c0ad-3058-4abc-8173-040f622d9820","uuid":"ca66c0ad-3058-4abc-8173-040f622d9820","netbios_name":"SHAREPOINT2013","fqdn":"sharepoint2013.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"},{"hostname":"sql.ad.demo.io","id":"f674a95a-ec3d-4401-88c9-2eef4be82fa8","uuid":"f674a95a-ec3d-4401-88c9-2eef4be82fa8","netbios_name":"SQL","fqdn":"sql.ad.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:57Z","last_seen":"2018-11-28T15:00:57Z"}],"severity":2},{"application_protocol":"unknown","port":51361,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":2}]}]}]}}}}}},"403":{"description":"Returned if you do not have permission to view plugin outputs."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/assets":{"get":{"summary":"List assets","description":"Retrieves a list of assets. The list can be modified using filters. The list returned is limited to 5,000. To retrieve more than 5,000 assets, use the export-request API.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-assets","tags":["Workbenches"],"parameters":[{"description":"The number of days of data prior to and including today that should be returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. If you specify the name of the filter, you must specify the operator as the filter.0.quality parameter and the value as the filter.0.value parameter. To use multiple filters, increment the `` portion of `filter..filter`, for example, `filter.0.filter`.","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}},{"description":"A value specifying whether you want the returned data to include all fields (`full`) or only the default fields (`default`). The schema for this endpoint defines the `default` fields only. For a definition of the `full` fields, see [Common Asset Attributes](/docs/common-asset-attributes).","required":false,"name":"all_fields","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns an array of asset objects.","content":{"application/json":{"schema":{"type":"object","properties":{"assets":{"type":"array","description":"An array of asset objects.","items":{"type":"object","properties":{"id":{"type":"string","description":"The UUID of the asset."},"has_agent":{"type":"boolean","description":"A value specifying whether a Nessus agent scan detected the asset (`true`)."},"last_seen":{"type":"string","description":"The ISO timestamp of the scan that most recently detected the asset."},"last_scan_target":{"type":"string","description":"The IPv4 address, IPv6 address, or FQDN that the scanner last used to evaluate the asset."},"sources":{"type":"array","description":"A list of sources for the asset record.","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the entity that reported the asset details. Sources can include sensors, connectors, and API imports. Source names can be customized by your organization (for example, you specify a name when you import asset records). If your organization does not customize source names, system-generated names include:\n - AWS—You obtained the asset data from an Amazon Web Services connector.\n - NESSUS_AGENT—You obtained the asset data obtained from a Nessus agent scan.\n - PVS—You obtained the asset data from a Nessus Network Monitor (NNM) scan.\n - NESSUS_SCAN—You obtained the asset data from a Nessus scan.\n - WAS—You obtained the asset data from a Web Application Scanning scan."},"first_seen":{"type":"string","description":"The ISO timestamp when the source first reported the asset."},"last_seen":{"type":"string","description":"The ISO timestamp when the source last reported the asset."}}}},"acr_score":{"type":"integer","description":"The Asset Criticality Rating (ACR) for the asset. Tenable assigns an ACR to each asset on your network to represent the asset's relative risk as an integer from 1 to 10. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*."},"acr_drivers":{"type":"array","description":"The key drivers that Tenable uses to calculate an asset's Tenable-provided ACR. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*.","items":{"type":"object","description":"Information about an asset characteristic that factored into the ACR score calculation.","properties":{"driver_name":{"type":"string","description":"The type of characteristic."},"driver_value":{"type":"array","description":"The characteristic value.","items":{"type":"string"}}}}},"exposure_score":{"type":"integer","description":"The Asset Exposure Score (AES) for the asset. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*."},"scan_frequency":{"type":"array","description":"Information about how often scans ran against the asset during specified intervals. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*.","items":{"type":"object","description":"Information about how often scans ran against asset during a specified interval.","properties":{"interval":{"type":"integer","description":"The number of days over which Tenable searches for scans involving the asset."},"frequency":{"type":"integer","description":"The number of times that a scan ran against the asset during the specified interval."},"licensed":{"type":"boolean","description":"Indicates whether the asset was licensed at the time of the identified scans."}}}},"ipv4":{"description":"A list of ipv4 addresses for the asset.","type":"array","items":{"type":"string"}},"ipv6":{"description":"A list of ipv6 addresses for the asset.","type":"array","items":{"type":"string"}},"fqdn":{"description":"A list of fully-qualified domain names (FQDNs) for the asset.","type":"array","items":{"type":"string"}},"netbios_name":{"type":"array","description":"The NetBIOS name for the asset.","items":{"type":"string"}},"operating_system":{"type":"array","description":"The operating system installed on the asset.","items":{"type":"string"}},"agent_name":{"type":"array","description":"The names of any Nessus agents that scanned and identified the asset.","items":{"type":"string"}},"aws_ec2_name":{"type":"array","description":"The name of the virtual machine instance in AWS EC2.","items":{"type":"string"}},"mac_address":{"type":"array","description":"A list of MAC addresses for the asset.","items":{"type":"string"}},"bigfix_asset_id":{"type":"array","items":{"type":"string"},"description":"The unique identifier of the asset in IBM BigFix. For more information, see the IBM BigFix documentation."}}}},"total":{"type":"integer","description":"The total count of returned assets."}}},"examples":{"response":{"value":{"assets":[{"id":"31e37f64-cf0f-4ba0-9359-f5094a92352b","has_agent":false,"last_seen":"2018-11-28T17:28:28.000Z","last_scan_target":"172.204.81.57","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z"}],"ipv4":["172.204.81.57"],"ipv6":[],"fqdn":[],"netbios_name":["S11C"],"operating_system":["Microsoft Windows Server 2008 R2 Datacenter Service Pack 1"],"agent_name":[],"aws_ec2_name":[],"mac_address":[],"bigfix_asset_id":[]},{"id":"7e35af00-a3f0-4d43-a354-0638ba2b05ae","has_agent":false,"last_seen":"2018-11-28T17:28:28.000Z","last_scan_target":"172.204.81.58","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z"}],"acr_score":8,"acr_drivers":[{"driver_name":"device_type","driver_value":["general_purpose"]},{"driver_name":"device_capability","driver_value":["pci"]},{"driver_name":"internet_exposure","driver_value":["internal"]}],"exposure_score":753,"scan_frequency":[{"interval":90,"frequency":3,"licensed":false},{"interval":30,"frequency":1,"licensed":false},{"interval":60,"frequency":1,"licensed":false}],"ipv4":["172.204.81.58"],"ipv6":[],"fqdn":["freebsd11.dc.demo.io"],"netbios_name":[],"operating_system":["FreeBSD 11.1"],"agent_name":[],"aws_ec2_name":[],"mac_address":[],"bigfix_asset_id":[]},{"id":"466934be-abb4-4fa9-b8b8-27ace85e5523","has_agent":false,"last_seen":"2018-11-28T17:28:28.000Z","last_scan_target":"172.204.81.59","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z"}],"ipv4":["172.204.81.59"],"ipv6":[],"fqdn":["fedorawork27.dc.demo.io"],"netbios_name":[],"operating_system":["Linux Kernel 4.13.9-300.fc27.x86_64 on Fedora release 27 (Twenty Seven)"],"agent_name":[],"aws_ec2_name":[],"mac_address":[],"bigfix_asset_id":[]}],"total":3}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/assets/vulnerabilities":{"get":{"summary":"List assets with vulnerabilities","description":"Returns a list of assets with vulnerabilities. The list is limited to 5,000. To retrieve more than 5,000 assets, use the export-request API.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-assets-vulnerabilities","tags":["Workbenches"],"parameters":[{"description":"The number of days of data prior to and including today that should be returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint.For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}}],"responses":{"200":{"description":"Returned an array of assets with vulnerabilities.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The UUID of the asset."},"severities":{"type":"array","description":"A count of vulnerabilities by severity.","items":{"type":"object","properties":{"count":{"type":"integer","description":"The number of vulnerabilities with the specified severity."},"level":{"type":"integer","description":"The code for the severity. Possible values include: \n - 0—The vulnerability has a CVSS score of 0, which corresponds to the \"info\" severity level.\n - 1—The vulnerability has a CVSS score between 0.1 and 3.9, which corresponds to the \"low\" severity level.\n - 2—The vulnerability has a CVSS score between 4.0 and 6.9, which corresponds to the \"medium\" severity level.\n - 3—The vulnerability has a CVSS score between 7.0 and 9.9, which corresponds to the \"high\" severity level.\n - 4—The vulnerability has a CVSS score of 10.0, which corresponds to the \"critical\" severity level."},"name":{"type":"string","description":"The severity of the vulnerability as defined using the Common Vulnerability Scoring System (CVSS) base score. Possible values include `info` (CVSS score of 0), `low` (CVSS score between 0.1 and 3.9), `medium` (CVSS score between 4.0 and 6.9), `high` (CVSS score between 7.0 and 9.9), and `critical` (CVSS score of 10.0)."}}}},"total":{"type":"integer","description":"The total number of vulnerabilities detected on the asset."},"fqdn":{"description":"A list of fully-qualified domain names (FQDNs) for the asset.","type":"array","items":{"type":"string"}},"ipv4":{"description":"A list of ipv4 addresses for the asset.","type":"array","items":{"type":"string"}},"ipv6":{"description":"A list of ipv6 addresses for the asset.","type":"array","items":{"type":"string"}},"last_seen":{"type":"string","description":"The ISO timestamp of the scan that most recently detected the asset."},"netbios_name":{"type":"array","description":"The NetBIOS name for the asset.","items":{"type":"string"}},"agent_name":{"type":"array","description":"The names of any Nessus agents that scanned and identified the asset.","items":{"type":"string"}}}}},"examples":{"response":{"value":{"assets":[{"id":"afa3a9cc-d615-4ac9-a1a4-8fd0686ffb04","severities":[{"count":0,"level":0,"name":"Info"},{"count":1,"level":1,"name":"Low"},{"count":0,"level":2,"name":"Medium"},{"count":0,"level":3,"name":"High"},{"count":0,"level":4,"name":"Critical"}],"total":1,"fqdn":[],"ipv4":["172.204.81.57"],"ipv6":[],"last_seen":"2018-11-28T17:28:28.000Z","netbios_name":[],"agent_name":[]},{"id":"bfcf0a96-cfc0-4299-8d56-bf7ea32daab1","severities":[{"count":0,"level":0,"name":"Info"},{"count":0,"level":1,"name":"Low"},{"count":1,"level":2,"name":"Medium"},{"count":0,"level":3,"name":"High"},{"count":0,"level":4,"name":"Critical"}],"total":1,"fqdn":["hpuxrisc.dc.demo.io"],"ipv4":["172.204.81.57"],"ipv6":[],"last_seen":"2018-11-28T17:28:28.000Z","netbios_name":[],"agent_name":[]},{"id":"e77201f2-d155-4bd7-b043-a60ad7561f07","severities":[{"count":0,"level":0,"name":"Info"},{"count":0,"level":1,"name":"Low"},{"count":1,"level":2,"name":"Medium"},{"count":0,"level":3,"name":"High"},{"count":0,"level":4,"name":"Critical"}],"total":1,"fqdn":["mint18.dc.demo.io"],"ipv4":["172.204.81.57"],"ipv6":[],"last_seen":"2018-11-28T17:28:28.000Z","netbios_name":[],"agent_name":[]}],"total_asset_count":3}}}}}},"403":{"description":"Returned if you do not have permission to view assets with vulnerabilities."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/assets/{asset_id}/info":{"get":{"summary":"Get asset information","description":"Returns information about the specified asset.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-asset-info","tags":["Workbenches"],"parameters":[{"description":"The UUID of the asset. You can find the UUID by examining the output of the [GET /workbenches/assets](#workbenches-assets) endpoint.","required":true,"name":"asset_id","in":"path","schema":{"type":"string"}},{"description":"A value specifying whether you want the returned data to include all fields (`full`) or only the default fields (`default`). The schema for this endpoint defines the `default` fields only. For a definition of the `full` fields, see [Common Asset Attributes](/docs/common-asset-attributes).","required":false,"name":"all_fields","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns asset information.","content":{"application/json":{"schema":{"type":"object","properties":{"info":{"type":"object","properties":{"id":{"type":"string","description":"The UUID of the asset."},"uuid":{"type":"string","description":"The UUID of the asset."},"operating_system":{"type":"array","description":"The operating systems that scans have associated with the asset record.","items":{"type":"string"}},"counts":{"type":"object","description":"Counts of vulnerabilities on the asset, as well as counts of audit checks performed on the asset. For more information about this object, see [Common Asset Attributes](/docs/common-asset-attributes).","properties":{}},"has_agent":{"type":"boolean","description":"A value specifying whether a Nessus agent scan detected the asset."},"created_at":{"type":"string","description":"The time and date when Tenable.io created the asset record."},"updated_at":{"type":"string","description":"The time and date when the asset record was last updated."},"first_seen":{"type":"string","description":"The time and date when a scan first identified the asset."},"last_seen":{"type":"string","description":"The time and date of the scan that most recently identified the asset."},"last_authenticated_scan_date":{"type":"string","description":"The time and date of the last credentialed scan run on the asset."},"last_licensed_scan_date":{"type":"string","description":"The time and date of the last scan that identified the asset as licensed. Tenable.io categorizes an asset as licensed if a scan of that asset has returned results from a non-discovery plugin within the last 90 days."},"last_scan_target":{"type":"string","description":"The IPv4 address, IPv6 address, or FQDN that the scanner last used to evaluate the asset."},"sources":{"type":"array","description":"The sources of the scans that identified the asset.","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the entity that reported the asset details. Sources can include sensors, connectors, and API imports. Source names can be customized by your organization (for example, you specify a name when you import asset records). If your organization does not customize source names, system-generated names include:\n - AWS—You obtained the asset data from an Amazon Web Services connector.\n - NESSUS_AGENT—You obtained the asset data obtained from a Nessus agent scan.\n - PVS—You obtained the asset data from a Nessus Network Monitor (NNM) scan.\n - NESSUS_SCAN—You obtained the asset data from a Nessus scan.\n - WAS—You obtained the asset data from a Web Application Scanning scan."},"first_seen":{"type":"string","description":"The ISO timestamp when the source first reported the asset."},"last_seen":{"type":"string","description":"The ISO timestamp when the source last reported the asset."}}}},"tags":{"type":"array","description":"Category tags assigned to the asset in Tenable.io.","items":{"type":"object","properties":{"tag_uuid":{"type":"string","description":"The UUID of the tag."},"tag_key":{"type":"string","description":"The tag category (the first half of the category:value pair)."},"tag_value":{"type":"string","description":"The tag value (the second half of the category:value pair)."},"added_by":{"type":"string","description":"The UUID of the user who assigned the tag to the asset."},"added_at":{"type":"string","description":"The ISO timestamp when the tag was assigned to the asset."},"source":{"type":"string","description":"The tag type:\n - static—A user manually applied the tag to an asset. You can use the Tenable.io API to create and assign static tags to assets.\n - dynamic—Tenable.io automatically applies the tag based on asset attribute rules. For more information, see [Apply Dynamic Tags](/docs/apply-dynamic-tags)."}}}},"acr_score":{"type":"integer","description":"The Asset Criticality Rating (ACR) for the asset. Tenable assigns an ACR to each asset on your network to represent the asset's relative risk as an integer from 1 to 10. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*."},"acr_drivers":{"type":"array","description":"The key drivers that Tenable uses to calculate an asset's Tenable-provided ACR. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*.","items":{"type":"object","description":"Information about an asset characteristic that factored into the ACR score calculation.","properties":{"driver_name":{"type":"string","description":"The type of characteristic."},"driver_value":{"type":"array","description":"The characteristic value.","items":{"type":"string"}}}}},"exposure_score":{"type":"integer","description":"The Asset Exposure Score (AES) for the asset. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*."},"scan_frequency":{"type":"array","description":"Information about how often scans ran against the asset during specified intervals. This attribute is only present in assets if Lumin is added to your Tenable.io instance. For more information, see [Lumin Metrics](https://docs.tenble.com/Content/Analysis/LuminMetrics.htm) in the *Tenable.io Vulnerability Management User Guide*.","items":{"type":"object","description":"Information about how often scans ran against asset during a specified interval.","properties":{"interval":{"type":"integer","description":"The number of days over which Tenable searches for scans involving the asset."},"frequency":{"type":"integer","description":"The number of times that a scan ran against the asset during the specified interval."},"licensed":{"type":"boolean","description":"Indicates whether the asset was licensed at the time of the identified scans."}}}},"ipv4":{"type":"array","description":"The IPv4 addresses that scans have associated with the asset record.","items":{"type":"string"}},"ipv6":{"type":"array","description":"The IPv6 addresses that scans have associated with the asset record.","items":{"type":"string"}},"fqdn":{"type":"array","description":"The fully-qualified domain names that scans have associated with the asset record.","items":{"type":"string"}},"mac_address":{"type":"array","description":"The MAC addresses that scans have associated with the asset record.","items":{"type":"string"}},"netbios_name":{"type":"array","description":"The NetBIOS names that scans have associated with the asset record.","items":{"type":"string"}},"system_type":{"type":"array","description":"The system types as reported by Plugin ID 54615. Possible values include `router`, `general-purpose`, `scan-host`, and `embedded`.","items":{"type":"string"}},"hostname":{"type":"array","description":"The hostnames that scans have associated with the asset record.","items":{"type":"string"}},"agent_name":{"type":"array","description":"The names of any Nessus agents that scanned and identified the asset.","items":{"type":"string"}},"bios_uuid":{"type":"array","description":"The BIOS UUID that scans have associated with the asset.","items":{"type":"string"}},"aws_ec2_instance_id":{"type":"array","description":"The unique identifier of the Linux instance in Amazon EC2. For more information, see the Amazon Elastic Compute Cloud Documentation.","items":{"type":"string"}},"aws_ec2_instance_ami_id":{"type":"array","description":"The unique identifier of the Linux AMI image in Amazon Elastic Compute Cloud (Amazon EC2). For more information, see the Amazon Elastic Compute Cloud Documentation.","items":{"type":"string"}},"aws_owner_id":{"type":"array","description":"The canonical user identifier for the AWS account associated with the virtual machine instance. For example, `79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be`. For more information, see AWS Account Identifiers in the AWS documentation.","items":{"type":"string"}},"aws_availability_zone":{"type":"array","description":"The availability zone where Amazon Web Services hosts the virtual machine instance, for example, `us-east-1a`. Availability zones are subdivisions of AWS regions. For more information, see Regions and Availability Zones in the AWS documentation.","items":{"type":"string"}},"aws_region":{"type":"array","description":"The region where AWS hosts the virtual machine instance, for example, `us-east-1`. For more information, see Regions and Availability Zones in the AWS documentation.","items":{"type":"string"}},"aws_vpc_id":{"type":"array","description":"The unique identifier for the public cloud that hosts the AWS virtual machine instance. For more information, see the Amazon Virtual Private Cloud User Guide.","items":{"type":"string"}},"aws_ec2_instance_group_name":{"type":"array","description":"The virtual machine instance's group in AWS.","items":{"type":"string"}},"aws_ec2_instance_state_name":{"type":"array","description":"The state of the virtual machine instance in AWS at the time of the scan.","items":{"type":"string"}},"aws_ec2_instance_type":{"type":"array","description":"The type of instance in AWS EC2.","items":{"type":"string"}},"aws_subnet_id":{"type":"array","description":"The unique identifier of the AWS subnet where the virtual machine instance was running at the time of the scan.","items":{"type":"string"}},"aws_ec2_product_code":{"type":"array","description":"The product code associated with the AMI used to launch the virtual machine instance in AWS EC2.","items":{"type":"string"}},"aws_ec2_name":{"type":"array","description":"The name of the virtual machine instance in AWS EC2.","items":{"type":"string"}},"azure_vm_id":{"type":"array","description":"The unique identifier of the Microsoft Azure virtual machine instance. For more information, see \"Accessing and Using Azure VM Unique ID\" in the Microsoft Azure documentation.","items":{"type":"string"}},"azure_resource_id":{"type":"array","description":"The unique identifier of the resource in the Azure Resource Manager. For more information, see the Azure Resource Manager Documentation.","items":{"type":"string"}},"gcp_project_id":{"type":"array","description":"The customized name of the project to which the virtual machine instance belongs in Google Cloud Platform (GCP). For more information, see \"Creating and Managing Projects\" in the GCP documentation.","items":{"type":"string"}},"gcp_zone":{"type":"array","description":"The zone where the virtual machine instance runs in GCP. For more information, see \"Regions and Zones\" in the GCP documentation.","items":{"type":"string"}},"gcp_instance_id":{"type":"array","description":"The unique identifier of the virtual machine instance in GCP.","items":{"type":"string"}},"ssh_fingerprint":{"type":"array","description":"The SSH key fingerprints that scans have associated with the asset record.","items":{"type":"string"}},"mcafee_epo_guid":{"type":"array","description":"The unique identifier of the asset in McAfee ePolicy Orchestrator (ePO). For more information, see the McAfee documentation.","items":{"type":"string"}},"mcafee_epo_agent_guid":{"type":"array","description":"The unique identifier of the McAfee ePO agent that identified the asset. For more information, see the McAfee documentation.","items":{"type":"string"}},"qualys_asset_id":{"type":"array","description":"The Asset ID of the asset in Qualys. For more information, see the Qualys documentation.","items":{"type":"string"}},"qualys_host_id":{"type":"array","description":"The Host ID of the asset in Qualys. For more information, see the Qualys documentation.","items":{"type":"string"}},"servicenow_sysid":{"type":"array","description":"The unique record identifier of the asset in ServiceNow. For more information, see the ServiceNow documentation.","items":{"type":"string"}},"installed_software":{"type":"array","description":"A list of Common Platform Enumeration (CPE) values that represent software applications a scan identified as present on an asset. This attribute supports the CPE 2.2 format. For more information, see the \"Component Syntax\" section of the [CPE Specification, Version 2.2](https://cpe.mitre.org/files/cpe-specification_2.2.pdf). For assets identified in Tenable scans, this attribute contains data only if a scan using [Nessus Plugin ID 45590](https://www.tenable.com/plugins/nessus/45590) has evaluated the asset.\n\n**Note:** If no scan detects an application within 30 days of the scan that originally detected the application, Tenable.io considers the detection of that application expired. As a result, the next time a scan evaluates the asset, Tenable.io removes the expired application from the installed_software attribute. This activity is logged as a `remove` type of `attribute_change` update in the asset activity log.","items":{"type":"string"}}}}}},"examples":{"response":{"value":{"info":{"time_end":"2018-11-28T15:00:25Z","time_start":"2018-11-28T15:00:25Z","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","operating_system":["Microsoft Windows Server 2008 R2 Standard Service Pack 1"],"fqdn":["vcenter.dc.demo.io"],"mac_address":["00:50:56:bd:4b:d8"],"counts":{"vulnerabilities":{"total":573,"severities":[{"count":392,"level":0,"name":"Info"},{"count":11,"level":1,"name":"Low"},{"count":122,"level":2,"name":"Medium"},{"count":316,"level":3,"name":"High"},{"count":27,"level":4,"name":"Critical"}]},"audits":{"total":0,"statuses":[{"count":0,"level":1,"name":"Passed"},{"count":0,"level":2,"name":"Warning"},{"count":0,"level":3,"name":"Failed"}]}},"has_agent":false,"created_at":"2018-11-28T15:00:45.684Z","updated_at":"2018-11-28T17:28:46.941Z","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z","last_authenticated_scan_date":"2018-11-28T17:28:28.000Z","last_licensed_scan_date":"2018-11-28T17:28:28.000Z","last_scan_target":"172.204.81.57","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z"}],"acr_score":8,"acr_drivers":[{"driver_name":"device_type","driver_value":["general_purpose"]},{"driver_name":"device_capability","driver_value":["pci"]},{"driver_name":"internet_exposure","driver_value":["internal"]}],"exposure_score":753,"scan_frequency":[{"interval":90,"frequency":3,"licensed":false},{"interval":30,"frequency":1,"licensed":false},{"interval":60,"frequency":1,"licensed":false}],"tags":[],"ipv4":["172.204.81.57"],"ipv6":[],"netbios_name":["VCENTER"],"system_type":["general-purpose"],"tenable_uuid":["02dfb8d5a8744a4d925dc8aec4c5dffb"],"hostname":["vcenter"],"agent_name":[],"bios_uuid":["32d83d42-beb4-69ee-131a-54ca30e6cc21"],"aws_ec2_instance_id":[],"aws_ec2_instance_ami_id":[],"aws_owner_id":[],"aws_availability_zone":[],"aws_region":[],"aws_vpc_id":[],"aws_ec2_instance_group_name":[],"aws_ec2_instance_state_name":[],"aws_ec2_instance_type":[],"aws_subnet_id":[],"aws_ec2_product_code":[],"aws_ec2_name":[],"azure_vm_id":[],"azure_resource_id":[],"gcp_project_id":[],"gcp_zone":[],"gcp_instance_id":[],"ssh_fingerprint":[],"mcafee_epo_guid":[],"mcafee_epo_agent_guid":[],"qualys_asset_id":[],"qualys_host_id":[],"servicenow_sysid":[],"installed_software":["cpe:/a:apple:itunes:12.8","cpe:/a:apple:quicktime:7.7.3","cpe:/a:openbsd:openssh:6.9","cpe:/a:google:chrome"],"bigfix_asset_id":[]}}}}}}},"403":{"description":"Returned if you do not have permission to view information for the specified asset."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/assets/{asset_uuid}/activity":{"get":{"summary":"Get asset activity log","description":"
        Returns the activity log for the specified asset. Event types include:
      • discovered—Asset created (for example, by a network scan or import).
      • seen—Asset observed by a network scan without any changes to its attributes.
      • tagging—Tag added to or removed from asset.
      • attribute_change—A scan identified new or changed attributes for the asset (for example, new software applications installed on the asset).
      • updated—Asset updated either manually by a user or automatically by a new scan.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-assets-activity","tags":["Workbenches"],"parameters":[{"description":"The UUID of the asset. You can find the UUID by examining the output of the [GET /workbenches/assets](#workbenches-assets) endpoint.","required":true,"name":"asset_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns activity for an asset.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"Event type:\n - discovered—Asset created (for example, by a network scan or import).\n - seen—Asset observed by a network scan without any changes to its attributes.\n - tagging—Tag added to or removed from asset.\n - attribute_change—A scan identified new or changed attributes for the asset (for example, new software applications installed on the asset).\n - updated—Asset updated either manually by a user or automatically by a new scan."},"timestamp":{"type":"integer","description":"The timestamp of the event. The timestamp is reported in ISO 8601 format in UTC time."},"scan_id":{"type":"string","description":"The UUID of the scan that logged the event."},"schedule_id":{"type":"string","description":"The ID of the scheduled scan associated with the event."},"source":{"type":"string","description":"The entity that logged the event, for example, NESSUS_AGENT, NESSUS_AGENT, PVS, or WAS."},"details":{"type":"object","properties":{"assetId":{"type":"string","description":"The UUID of the asset."},"containerId":{"type":"string","description":"The UUID of your Tenable.io instance."},"createdAt":{"type":"integer","description":"The timestamp of the asset creation. The timestamp is reported in ISO 8601 format in UTC time."},"updatedAt":{"type":"integer","description":"The timestamp of the asset update time. The timestamp is reported in ISO 8601 format in UTC time."},"hasAgent":{"type":"boolean","description":"Specifies whether the asset has an agent installed."},"hasPluginResults":{"type":"boolean","description":"Specifies whether or not any plugin results match this asset."},"firstScanTime":{"type":"integer","description":"The timestamp of the completion of the scan that discovered or observed the asset for the first time. The timestamp is reported in ISO 8601 format in UTC time."},"lastScanTime":{"type":"integer","description":"The timestamp of the completion of the last asset scan. The timestamp is reported in ISO 8601 format in UTC time."},"lastAuthenticatedScanTime":{"type":"integer","description":"The timestamp of the completion of the last authenticated scan of the asset. The timestamp is reported in ISO 8601 format in UTC time."},"lastLicensedScanTime":{"type":"integer","description":"The timestamp of the scan completion time when asset was last scanned and matched license v1 requirements. The timestamp is reported in ISO 8601 format in UTC time."},"lastLicensedScanTimeV2":{"type":"integer","description":"The timestamp of the scan completion time when asset was last scanned and matched license v2 requirements. The timestamp is reported in ISO 8601 format in UTC time."},"sources":{"description":"An array of source objects representing the entity that logged the event.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the entity that reported the asset details. Sources can include sensors, connectors, and API imports. Source names can be customized by your organization (for example, you specify a name when you import asset records). If your organization does not customize source names, system-generated names include:\n - AWS—You obtained the asset data from an Amazon Web Services connector.\n - NESSUS_AGENT—You obtained the asset data obtained from a Nessus agent scan.\n - PVS—You obtained the asset data from a Nessus Network Monitor (NNM) scan.\n - NESSUS_SCAN—You obtained the asset data from a Nessus scan.\n - WAS—You obtained the asset data from a Web Application Scanning scan."},"firstSeen":{"type":"string","description":"The ISO timestamp when the source first reported the asset."},"lastSeen":{"type":"string","description":"The ISO timestamp when the source last reported the asset."}}}},"terminatedAt":{"type":"integer","description":"If terminated, the timestamp of asset termination. The timestamp is reported in ISO 8601 format in UTC time."},"terminatedBy":{"type":"string","description":"The UUID of the user that terminated the asset."},"deletedAt":{"type":"integer","description":"If deleted, the timestamp of asset deletion. The timestamp is reported in ISO 8601 format in UTC time."},"deletedBy":{"type":"string","description":"The UUID of the user that deleted the asset."},"properties":{"type":"object","description":"Additional asset attributes. For attribute definitions, see [Common Asset Attributes](/docs/common-asset-attributes).","properties":{}}}},"updates":{"type":"array","description":"(attribute_change entries only) A list of updates to the asset's attributes.","items":{"type":"object","properties":{"method":{"type":"string","description":"The update method. Possible values include: \n - add—A scan identified a new software application installed on the asset.\n - remove—Tenable.io identified the specified application as expired and removed it from the installed_software attribute of the asset. Tenable.io considers an application detection expired if no scan detects the application within 30 days of the scan that originally detected the application."},"property":{"type":"string","description":"The name of the updated attribute."},"value":{"type":"string","description":"The updated value of the attribute."}}}}}}},"examples":{"response":{"value":{"activity":[{"type":"seen","timestamp":"2018-05-16T12:51:47.164Z","scan_id":"71243329-49b8-406c-87f9-64cf33ed4228","schedule_id":"template-027d96a5-d0ae-108b-de32-01ecf7f23d07423f49ea57344aef","source":"NESSUS_SCAN"},{"type":"seen","timestamp":"2018-05-09T11:43:55.975Z","scan_id":"bc9ea943-8273-431c-9113-f6827d1b4a06","schedule_id":"template-027d96a5-d0ae-108b-de32-01ecf7f23d07423f49ea57344aef","source":"NESSUS_SCAN"},{"type":"discovered","timestamp":"2018-05-04T16:50:55.418Z","scan_id":"63eca894-6da9-4eb7-9be6-b8a0510d29d4","schedule_id":"template-027d96a5-d0ae-108b-de32-01ecf7f23d07423f49ea57344aef","source":"NESSUS_SCAN","details":{"lastScanTime":"2018-05-04T16:50:55.418Z","createdAt":"2018-05-04T16:50:58.420Z","hasAgent":false,"sources":[{"name":"NESSUS_SCAN","firstSeen":"2018-05-04T16:50:55.418Z","lastSeen":"2018-05-04T16:50:55.418Z"}],"lastLicensedScanTime":"2018-05-04T16:50:55.418Z","firstScanTime":"2018-05-04T16:50:55.418Z","assetId":"39d6e01e-eaac-4951-a521-fe903f2c61bb","lastLicensedScanTimeV2":"2018-05-04T16:50:55.418Z","hasPluginResults":true,"containerId":"5043dfa2-7864-4785-aff7-80026f36efcb","properties":{"ipv4":{"lastObserved":"2018-05-04T16:50:55.418Z","values":["172.204.81.57"]}},"updatedAt":"2018-05-04T16:50:58.420Z"}},{"type":"attribute_change","timestamp":"2019-05-24T13:24:47.498Z","updates":[{"method":"add","property":"installed_software","value":"cpe:/a:apple:quicktime:7.7.1"},{"method":"add","property":"installed_software","value":"cpe:/a:apple:safari:12.0.2"},{"method":"add","property":"installed_software","value":"cpe:/a:apple:itunes:7.5"},{"method":"add","property":"installed_software","value":"cpe:/a:mariadb:mariadb:1.5"},{"method":"add","property":"installed_software","value":"cpe:/a:skype:skype:8.4"},{"method":"add","property":"installed_software","value":"cpe:/a:wireshark:wireshark:2.4.1"}]}]}}}}}},"401":{"description":"Returned if you do not have permission to view activity for an asset."},"404":{"description":"Returned if Tenable.io cannot find the specified asset."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to return activity for an asset.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/workbenches/assets/{asset_id}/vulnerabilities":{"get":{"summary":"List asset vulnerabilities","description":"Retrieves a list of the vulnerabilities recorded for a specified asset. By default, this list is sorted by vulnerability count in descending order. The list returned is limited to 5,000. To retrieve more than 5,000 vulnerabilities, use the export-request API.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-asset-vulnerabilities","tags":["Workbenches"],"parameters":[{"description":"The UUID of the asset. You can find the UUID by examining the output of the [GET /workbenches/assets](#workbenches-assets) endpoint.","required":true,"name":"asset_id","in":"path","schema":{"type":"string"}},{"description":"The number of days of data prior to and including today that should be returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}}],"responses":{"200":{"description":"Returns a list of vulnerabilities for the specified asset.","content":{"application/json":{"schema":{"type":"object","description":"A list of vulnerabilities.","properties":{"vulnerabilities":{"type":"array","description":"A list of discovered vulnerabilities.","items":{"type":"object","properties":{"count":{"type":"integer","description":"The number of times that a scan detected the vulnerability on an asset."},"plugin_family":{"type":"string","description":"The plugin's family."},"plugin_id":{"type":"integer","description":"The unique plugin ID."},"plugin_name":{"type":"string","description":"The name of the plugin that detected the vulnerability."},"vulnerability_state":{"type":"string","description":"The current state of the reported plugin. Possible states include:\n - Active—The vulnerability is currently present on an asset.\n - New—The vulnerability is active on an asset, and was first detected within the last 14 days.\n - Fixed—A subsequent scan detected that the formerly-active vulnerability is no longer present on an asset.\n - Resurfaced—The vulnerability was previously marked as fixed on an asset, but a subsequent scan detected the vulnerability on the asset again."},"vpr_score":{"type":"integer","description":"The Vulnerability Priority Rating (VPR) for the vulnerability. If a plugin is designed to detect multiple vulnerabilities, the VPR represents the highest value calculated for a vulnerability associated with the plugin. For more information, see Severity vs. VPR in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"accepted_count":{"type":"integer","description":"The number of times that a user in the user interface created an accept rule for this vulnerability. For more information, see Recast Rules in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"recasted_count":{"type":"integer","description":"The number of times that a user in the user interface created a recast rule for this vulnerability. For more information, see Recast Rules in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"counts by severity":{"type":"array","description":"The number of times that a scan detected the vulnerability on an asset, grouped by severity level.","items":{"type":"object","properties":{"count":{"type":"integer","description":"The number of times that a scan detected the vulnerability on an asset while the vulnerability was assigned the specified severity level.","format":"int32"},"value":{"type":"integer","description":"The severity level of the vulnerabilities in the group."}}}},"severity":{"type":"integer","description":"The severity level of the vulnerability, as defined using the Common Vulnerability Scoring System (CVSS) base score. Possible values include: \n - 0—The vulnerability has a CVSS score of 0, which corresponds to the \"info\" severity level.\n - 1—The vulnerability has a CVSS score between 0.1 and 3.9, which corresponds to the \"low\" severity level.\n - 2—The vulnerability has a CVSS score between 4.0 and 6.9, which corresponds to the \"medium\" severity level.\n - 3—The vulnerability has a CVSS score between 7.0 and 9.9, which corresponds to the \"high\" severity level.\n - 4—The vulnerability has a CVSS score of 10.0, which corresponds to the \"critical\" severity level.","format":"int32"}}}},"total_vulnerability_count":{"type":"integer","description":"The total number of discovered vulnerabilities."},"total_asset_count":{"type":"integer","description":"The total number of assets."}}},"examples":{"response":{"value":{"vulnerabilities":[{"count":55,"plugin_family":"Port scanners","plugin_id":34220,"plugin_name":"Netstat Portscanner (WMI)","vulnerability_state":"Active","vpr_score":2.4,"accepted_count":0,"recasted_count":0,"counts_by_severity":[{"count":55,"value":0}],"severity":0},{"count":54,"plugin_family":"Windows","plugin_id":34252,"plugin_name":"Microsoft Windows Remote Listeners Enumeration (WMI)","vulnerability_state":"Active","vpr_score":6.3,"accepted_count":0,"recasted_count":0,"counts_by_severity":[{"count":54,"value":0}],"severity":0},{"count":21,"plugin_family":"Service detection","plugin_id":22964,"plugin_name":"Service Detection","vulnerability_state":"Active","vpr_score":4.5,"accepted_count":0,"recasted_count":0,"counts_by_severity":[{"count":21,"value":0}],"severity":0},{"count":18,"plugin_family":"Web Servers","plugin_id":24260,"plugin_name":"HyperText Transfer Protocol (HTTP) Information","vulnerability_state":"Active","vpr_score":5.5,"accepted_count":0,"recasted_count":0,"counts_by_severity":[{"count":18,"value":0}],"severity":0}],"total_vulnerability_count":3,"total_asset_count":0}}}}}},"403":{"description":"Returned if you do not have permission to list vulnerabilities for the specified asset."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/assets/{asset_id}/vulnerabilities/{plugin_id}/info":{"get":{"summary":"Get asset vulnerability details","description":"Retrieves the details for a vulnerability recorded on a specified asset.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-asset-vulnerability-info","tags":["Workbenches"],"parameters":[{"description":"The UUID of the asset.","required":true,"name":"asset_id","in":"path","schema":{"type":"string"}},{"description":"The ID of the plugin.","required":true,"name":"plugin_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The number of days of data prior to and including today that should be returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object","properties":{"info":{"type":"object","properties":{"count":{"type":"integer","format":"int32","description":"A count of the vulnerability occurrences."},"vuln_count":{"type":"integer","format":"int32","description":""},"description":{"type":"string","description":"The description of the vulnerability."},"synopsis":{"type":"string","description":"A brief summary of the vulnerability."},"solution":{"type":"string","description":"Information on how to fix the vulnerability."},"discovery":{"type":"object","properties":{"seen_first":{"type":"string","description":"The Unix timestamp of the scan that first detected the vulnerability on an asset."},"seen_last":{"type":"string","description":"The Unix timestamp of the scan that most recently detected the vulnerability on an asset."}}},"severity":{"type":"integer","description":"The severity level of the vulnerability."},"plugin_details":{"type":"object","properties":{"family":{"type":"string","description":"The plugin family."},"modification_date":{"type":"string","description":"The ISO timestamp when Tenable last updated the plugin definition."},"name":{"type":"string","description":"The name of the plugin."},"publication_date":{"type":"string","description":"The ISO timestamp when Tenable first published the plugin definition."},"type":{"type":"string","description":"The type of scan that uses the plugin, either a network scan (`remote`) or a credentialed scan (`local`)."},"version":{"type":"string","description":"The plugin version."},"severity":{"type":"integer","format":"int32","description":"The severity level of the plugin."}}},"reference_information":{"type":"array","description":"","items":{"type":"object","properties":{"name":{"type":"string","description":"The source of the reference information about the vulnerability. Possible values include:\n - bid—Bugtraq (Symantec Connect)\n - cert—CERT/CC Vulnerability Notes Database\n - cve—NIST National Vulnerability Database (NVD)\n - edb-id—The Exploit Database\n - iava—information assurance vulnerability alert\n - osvdb—Open Sourced Vulnerability Database"},"url":{"type":"string","description":"The URL of the source site, if available."},"values":{"type":"array","description":"The unique identifier(s) for the vulnerability at the source.","items":{"type":"string"}}}}},"risk_information":{"type":"object","properties":{"risk_factor":{"type":"string","description":"The risk factor associated with the plugin. Possible values are: `Low`, `Medium`, `High`, or `Critical`."},"cvss_vector":{"type":"string","description":"The raw CVSSv2 metrics for the vulnerability. For more information, see CVSSv2 documentation."},"cvss_base_score":{"type":"string","description":"The CVSSv2 base score (intrinsic and fundamental characteristics of a vulnerability that are constant over time and user environments)."},"cvss_temporal_vector":{"type":"string","description":"The raw CVSSv2 temporal metrics for the vulnerability."},"cvss_temporal_score":{"type":"string","description":"The CVSSv2 temporal score (characteristics of a vulnerability that change over time but not among user environments)."},"cvss3_vector":{"type":"string","description":"The raw CVSSv3 metrics for the vulnerability. For more information, see CVSSv3 documentation."},"cvss3_base_score":{"type":"string","description":"The CVSSv3 base score (intrinsic and fundamental characteristics of a vulnerability that are constant over time and user environments)."},"cvss3_temporal_vector":{"type":"string","description":"CVSSv3 temporal metrics for the vulnerability."},"cvss3_temporal_score":{"type":"string","description":"The CVSSv3 temporal score (characteristics of a vulnerability that change over time but not among user environments)."},"stig_severity":{"type":"string","description":"Security Technical Implementation Guide (STIG) severity code for the vulnerability."}}},"see_also":{"type":"array","description":"Links to external websites that contain helpful information about the vulnerability.","items":{"type":"string"}},"vulnerability_information":{"type":"object","properties":{"vulnerability_publication_date":{"type":"string","description":"The ISO timestamp for the first publication date of the plugin."},"exploited_by_malware":{"type":"boolean","description":"The vulnerability discovered by this plugin is known to be exploited by malware."},"patch_publication_date":{"type":"string","description":"The ISO timestamp for date on which the vendor published a patch for the vulnerability."},"exploit_available":{"type":"boolean","description":"A value specifying whether a public exploit exists for the vulnerability."},"exploitability_ease":{"type":"string","description":"Description of how easy it is to exploit the issue."},"asset_inventory":{"type":"string","description":""},"default_account":{"type":"string","description":""},"exploited_by_nessus":{"type":"boolean","description":"A value specifying whether Nessus exploited the vulnerability during the process of identification."},"in_the_news":{"type":"boolean","description":"A value specifying whether this plugin has received media attention (for example, ShellShock, Meltdown)."},"malware":{"type":"string","description":""},"unsupported_by_vendor":{"type":"boolean","description":"Software found by this plugin is unsupported by the software's vendor (for example, Windows 95 or Firefox 3)."},"cpe":{"type":"string","description":"The Common Platform Enumeration (CPE) number for the plugin."},"exploit_frameworks":{"type":"array","description":"A list of exploit frameworks that have identified the vulnerability.","items":{"type":"object","description":"Information about the vulnerability in a specific exploit framework.","properties":{"name":{"type":"string","description":"The name of the exploit framework."},"exploits":{"type":"array","description":"A list of exploits associated with the vulnerability in the specified exploit framework.","items":{"type":"object","properties":{"name":{"type":"string","description":"The name of the identified exploit."},"url":{"type":"string","description":"The URL for the exploit in the framework."}}}}}}}}},"vpr":{"type":"object","description":"Information about the Vulnerability Priority Rating (VPR) for the vulnerability.","properties":{"score":{"type":"integer","description":"The Vulnerability Priority Rating (VPR) for the vulnerability. If a plugin is designed to detect multiple vulnerabilities, the VPR represents the highest value calculated for a vulnerability associated with the plugin. For more information, see Severity vs. VPR in the Tenable.io Vulnerability Management User Guide.","format":"int32"},"drivers":{"type":"object","description":"The key drivers Tenable uses to calculate a vulnerability's VPR. For more information, see Vulnerability Priority Rating Drivers.","properties":{}},"updated":{"type":"string","description":"The ISO timestamp when Tenable.io last imported the VPR for this vulnerability. Tenable.io imports a VPR value the first time you scan a vulnerability on your network. Then, Tenable.io automatically re-imports new and updated VPR values daily."}}}}}}},"examples":{"response":{"value":{"info":{"count":1,"vuln_count":1,"description":"The remote Windows host contains a version of the Microsoft Foundation Class (MFC) library affected by an insecure library loading vulnerability. The path used for loading external libraries is not securely restricted.\n\nAn attacker can exploit this by tricking a user into opening an MFC application in a directory that contains a malicious DLL, resulting in arbitrary code execution.","synopsis":"Arbitrary code can be executed on the remote host through the Microsoft Foundation Class library.","solution":"Microsoft has released a set of patches for Visual Studio .NET 2003, 2005, and 2008, as well as Visual C++ 2005, 2008, and 2010.","discovery":{"seen_first":"2019-03-08T17:15:52.000Z","seen_last":"2019-03-08T17:15:52.000Z"},"severity":3,"plugin_details":{"family":"Windows : Microsoft Bulletins","modification_date":"2016-05-06T00:00:00Z","name":"MS11-025: Vulnerability in Microsoft Foundation Class (MFC) Library Could Allow Remote Code Execution (2500212)","publication_date":"2011-04-13T00:00:00Z","type":"local","version":null,"severity":3},"reference_information":[{"name":"bid","url":"http://www.securityfocus.com/bid/","values":[42811]},{"name":"cve","url":"http://web.nvd.nist.gov/view/vuln/detail?vulnId=","values":["CVE-2010-3190"]},{"name":"iavb","values":["2011-B-0046"]},{"name":"msft","url":"http://technet.microsoft.com/en-us/security/bulletin/","values":["MS11-025"]},{"name":"osvdb","values":["67674"]},{"name":"secunia","url":"http://secunia.com/advisories/","values":["41212"]}],"risk_information":{"risk_factor":"High","cvss_vector":"AV:N/AC:M/Au:N/C:C/I:C/A:C","cvss_base_score":"9.3","cvss_temporal_vector":"E:F/RL:OF/RC:ND","cvss_temporal_score":"7.7","cvss3_vector":null,"cvss3_base_score":null,"cvss3_temporal_vector":null,"cvss3_temporal_score":null,"stig_severity":null},"see_also":["[\"https://technet.microsoft.com/library/security/ms11-025\"]"],"vulnerability_information":{"vulnerability_publication_date":"2010-08-27T00:00:00Z","exploited_by_malware":null,"patch_publication_date":"2011-04-12T00:00:00Z","exploit_available":true,"exploitability_ease":null,"asset_inventory":null,"default_account":null,"exploited_by_nessus":null,"in_the_news":null,"malware":null,"unsupported_by_vendor":null,"cpe":null,"exploit_frameworks":[]},"vpr":{"score":5.9,"drivers":{"age_of_vuln":{"lower_bound":731,"upper_bound":0},"exploit_code_maturity":"UNPROVEN","cvss_impact_score_predicted":true,"threat_intensity_last28":"VERY_LOW","threat_sources_last28":["No recorded events"],"product_coverage":"MEDIUM"},"updated":"2019-02-07T10:08:58Z"}}}}}}},"description":"Returns details for the specified vulnerability recorded on the specified asset."},"403":{"description":"Returned if you do not have permission to view vulnerability details for the specified asset."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/assets/{asset_id}/vulnerabilities/{plugin_id}/outputs":{"get":{"summary":"List asset vulnerabilties for plugin","description":"Retrieves the vulnerability outputs for a plugin recorded on a specified asset.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-asset-vulnerability-output","tags":["Workbenches"],"parameters":[{"description":"The UUID of the asset.","required":true,"name":"asset_id","in":"path","schema":{"type":"string"}},{"description":"The ID of the plugin.","required":true,"name":"plugin_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The number of days of data prior to and including today that should be returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}}],"responses":{"200":{"description":"Returns a list of asset vulnerabilties for the specified plugin.","content":{"application/json":{"schema":{"type":"object","properties":{"outputs":{"type":"array","description":"A list of vulnerabilities discovered by the plugin.","items":{"type":"object","properties":{"plugin_output":{"type":"string","description":"The plugin's output about the vulnerability. May be an empty string."},"states":{"type":"array","description":"Vulnerability state items.","items":{"type":"object","properties":{"name":{"type":"string","description":"The current state of the reported plugin (Active, Fixed, New, etc.)"},"results":{"type":"array","items":{"type":"object","properties":{"application_protocol":{"type":"string","description":"The application protocol where this vulnerability was found."},"port":{"type":"integer","description":"The port number where this vulnerability was found."},"transport_protocol":{"type":"string","description":"The transportation protocol (TCP or UDP) where this vulnerability was found."},"assets":{"type":"array","description":"A list of assets where this output was found.","items":{"type":"object","properties":{"hostname":{"type":"string","description":"The host name of the asset."},"id":{"type":"string","description":"The ID of the asset."},"uuid":{"type":"string","description":"The UUID of the asset."},"netbios_name":{"type":"string","description":"The NetBios name of the asset."},"fqdn":{"type":"string","description":"The FQDN of the asset."},"ipv4":{"type":"string","description":"The IPV4 of the asset."},"first_seen":{"type":"string","format":"date-time","description":"Indicates when the asset was discovered by a scan."},"last_seen":{"type":"string","format":"date-time","description":"Indicates when the asset was last observed by a scan."}}}},"severity":{"type":"integer","description":"Integer [0-4] indicating how severe the vulnerability is, where 0 is info only."}}}}}}}}}}}},"examples":{"response":{"value":{"outputs":[{"plugin_output":"Port 80/tcp was found to be open","states":[{"name":"Active","results":[{"application_protocol":"unknown","port":80,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":0}]}]},{"plugin_output":"Port 49153/tcp was found to be open","states":[{"name":"Active","results":[{"application_protocol":"unknown","port":49153,"transport_protocol":"tcp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":0}]}]},{"plugin_output":"Port 123/udp was found to be open","states":[{"name":"Active","results":[{"application_protocol":"unknown","port":123,"transport_protocol":"udp","assets":[{"hostname":"172.204.81.57","id":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","uuid":"7ee5f61c-e768-4dd7-baa6-f76381ca6970","netbios_name":"VCENTER","fqdn":"vcenter.dc.demo.io","ipv4":"172.204.81.57","first_seen":"2018-11-28T15:00:25Z","last_seen":"2018-11-28T15:00:25Z"}],"severity":0}]}]}]}}}}}},"403":{"description":"Returned if you do not have permission to list of asset vulnerabilties for the specified plugin."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/assets/{asset_uuid}":{"delete":{"summary":"Delete asset","description":"Deletes the specified asset. When you delete an asset, Tenable.io deletes vulnerability data associated with the asset. Deleting an asset does not immediately subtract the asset from your licensed assets count. Deleted assets continue to be included in the count until they automatically age out as inactive.

      Requires SCAN OPERATOR [24] user permissions. See Permissions.

      ","operationId":"workbenches-assets-delete","tags":["Workbenches"],"parameters":[{"description":"The UUID of the asset. You can find the UUID by examining the output of the [GET /workbenches/assets](#workbenches-assets) endpoint.","required":true,"name":"asset_uuid","in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":"Returned if Tenable.io successfully deletes the asset.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if you do not have permission to delete the asset."},"404":{"description":"Returned if Tenable.io cannot find the specified asset."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io fails to delete the specified asset.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/workbenches/export":{"get":{"summary":"Export workbench","description":"Exports the specified workbench to a file. Once requested, the file can be downloaded using the export download method upon receiving a \"ready\" status from the export status method. \nFor more information about workbench export files, see Export File Formats.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-export-request","tags":["Workbenches"],"parameters":[{"description":"The file format to use (Nessus, HTML, PDF, or CSV).","required":true,"name":"format","in":"query","schema":{"type":"string","enum":["nessus","html","pdf","csv"]}},{"description":"The type of workbench report to be exported","required":true,"name":"report","in":"query","schema":{"type":"string","enum":["vulnerabilities"]}},{"description":"The date (in unixtime) at which the exported results should begin to be included. Defaults to today.","name":"start_date","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Semicolon-separated list of chapters to include for vulnerabilities/hosts reports (vuln\\_by\\_plugin, vuln\\_by\\_asset, vuln\\_hosts\\_summary) or a single chapter for Executive Summary (exec\\_summary). Currently, only vuln\\_by\\_asset is supported for .nessus workbench exports.","required":true,"name":"chapter","in":"query","schema":{"type":"string"},"example":"vuln_by_asset"},{"description":"The number of days of data prior to and including start\\_date that should be returned. If not specified, data for all dates is returned.","name":"date_range","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The name of the filter to apply to the exported scan report. You can find available filters by using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.filter","in":"query","schema":{"type":"string"},"example":"?filter.0.filter=plugin.name"},{"description":"The operator of the filter to apply to the exported scan report. You can find the operators for the filter using the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.quality","in":"query","schema":{"type":"string"},"example":"&filter.0.quality=match"},{"description":"The value of the filter to apply to the exported scan report. You can find valid values for the filter in the 'control' attribute of the objects returned by the [GET /filters/workbenches/assets](#filters-assets-filter) endpoint. For more information about the format of this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.0.value","in":"query","schema":{"type":"string"},"example":"&filter.0.value=RHEL"},{"description":"For multiple filters, specifies whether to use the AND or the OR logical operator. The default is AND. For more information about this parameter, see [Workbench Filters](/docs/workbench-filters).","required":false,"name":"filter.search_type","in":"query","schema":{"type":"string","enum":["","and","or"]}},{"description":"When `true`, Tenable.io returns only a minimal subset of scan details for each result, excluding plugin attributes. In this case, only plugin\\_output and vulnerability\\_state fields are always returned; first\\_found, last\\_found and last\\_fixed are also returned if possible.","name":"minimum_vuln_info","in":"query","schema":{"type":"boolean","enum":["false","true"]}},{"description":"A plugin ID. Restricts the export data to vulnerabilities that only the specified plugin detects.","name":"plugin_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of an asset. Restricts the export data to findings on the specified asset only.","name":"asset_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully queues the export.","content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"integer","description":"The ID of the generated file."}}},"examples":{"response":{"value":{"file":"1279345678"}}}}}},"403":{"description":"Returned if you do not have permission to export the workbench."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/export/{file_id}/status":{"get":{"summary":"Check export status","description":"Returns the status of a pending export. When an export has been requested, it is necessary to poll this endpoint until a \"ready\" status is returned, at which point the file is complete and can be downloaded using the export download endpoint.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-export-status","tags":["Workbenches"],"parameters":[{"description":"The unique identifier of the workbench report being exported. The value for this parameter can be obtained from the response of the initial export request.","required":true,"name":"file_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the export status.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"The export processing status, for example, READY or LOADING."},"progress_total":{"type":"string","description":"The total number of items included in export."},"progress":{"type":"string","description":"The number of already processed items."}}},"examples":{"response":{"value":{"progress_total":"16666","progress":"4000","status":"loading"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/workbenches/export/{file_id}/download":{"get":{"summary":"Download export file","description":"Downloads a file that has been prepared for export.

      Requires BASIC [16] user permissions. See Permissions.

      ","operationId":"workbenches-export-download","tags":["Workbenches"],"parameters":[{"description":"The unique identifier of the workbench report being downloaded. The value for this parameter can be obtained from the response of the initial export request.","required":true,"name":"file_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the file downloaded successfully.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}}},"x-explorer-enabled":true,"x-proxy-enabled":true,"x-samples-enabled":true} \ No newline at end of file diff --git a/app_gen/openapi-parsers/other/TIO-API-Web-Application-Scanning.json b/app_gen/openapi-parsers/other/TIO-API-Web-Application-Scanning.json new file mode 100644 index 00000000..0ed03ccf --- /dev/null +++ b/app_gen/openapi-parsers/other/TIO-API-Web-Application-Scanning.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"Web Application Scanning","version":"1.0.0"},"security":[{"cloud":[]}],"servers":[{"url":"https://cloud.tenable.com"}],"components":{"securitySchemes":{"cloud":{"type":"apiKey","in":"header","name":"X-ApiKeys","description":"Format - accessKey=ACCESS_KEY;secretKey=SECRET_KEY"}}},"x-samples-languages":["python","curl","node","powershell","ruby","javascript","objectivec","java","php","csharp","go","swift","kotlin"],"paths":{"/assets":{"get":{"summary":"List assets","description":"Lists up to 5000 assets, including non-WAS assets.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"was-assets-list-assets","tags":["Assets"],"responses":{"200":{"description":"Returns a list of assets.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The unique ID of the asset."},"bios_uuid":{"type":"string","description":"BIOS UUID of the asset."},"ipv4":{"description":"A list of ipv4 addresses for the asset.","type":"array","items":{"type":"string"}},"ipv6":{"description":"A list of ipv6 addresses for the asset.","type":"array","items":{"type":"string"}},"hostname":{"description":"A list of hostnames for the asset.","type":"array","items":{"type":"string"}},"fqdn":{"description":"A list of FQDNs for the asset.","type":"array","items":{"type":"string"}},"ssh_fingerprint":{"type":"string","description":"The SSH fingerprint for the asset."},"mac_address":{"description":"A list of MAC addresses for the asset.","type":"array","items":{"type":"string"}},"netbios_name":{"type":"string","description":"The NetBIOS name for the asset."},"operating_system":{"type":"string","description":"The operating system installed on the asset."},"system_type":{"type":"string","description":"The system architecture (x86) of the asset."}}}},"examples":{"response":{"value":{"assets":[{"id":"31e37f64-cf0f-4ba0-9359-f5094a92352b","has_agent":false,"last_seen":"2018-11-28T17:28:28.000Z","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z"}],"ipv4":["172.204.81.57"],"ipv6":[],"fqdn":[],"netbios_name":["S11C"],"operating_system":["Microsoft Windows Server 2008 R2 Datacenter Service Pack 1"],"agent_name":[],"aws_ec2_name":[],"mac_address":[]},{"id":"7e35af00-a3f0-4d43-a354-0638ba2b05ae","has_agent":false,"last_seen":"2018-11-28T17:28:28.000Z","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z"}],"ipv4":["172.204.81.57"],"ipv6":[],"fqdn":["freebsd11.dc.demo.io"],"netbios_name":[],"operating_system":["FreeBSD 11.1"],"agent_name":[],"aws_ec2_name":[],"mac_address":[]},{"id":"466934be-abb4-4fa9-b8b8-27ace85e5523","has_agent":false,"last_seen":"2018-11-28T17:28:28.000Z","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:25.000Z","last_seen":"2018-11-28T17:28:28.000Z"}],"ipv4":["172.204.81.57"],"ipv6":[],"fqdn":["fedorawork27.dc.demo.io"],"netbios_name":[],"operating_system":["Linux Kernel 4.13.9-300.fc27.x86_64 on Fedora release 27 (Twenty Seven)"],"agent_name":[],"aws_ec2_name":[],"mac_address":[]}],"total":3}}}}}},"403":{"description":"Returned if the user does not have permission to list assets."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/assets/{asset_uuid}":{"get":{"summary":"Get asset information","description":"Gets information about the specified asset.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"was-assets-asset-info","tags":["Assets"],"parameters":[{"description":"The UUID of the asset.","required":true,"name":"asset_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns a list of assets.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The unique ID of the asset."},"bios_uuid":{"type":"string","description":"BIOS UUID of the asset."},"ipv4":{"description":"A list of ipv4 addresses for the asset.","type":"array","items":{"type":"string"}},"ipv6":{"description":"A list of ipv6 addresses for the asset.","type":"array","items":{"type":"string"}},"hostname":{"description":"A list of hostnames for the asset.","type":"array","items":{"type":"string"}},"fqdn":{"description":"A list of FQDNs for the asset.","type":"array","items":{"type":"string"}},"ssh_fingerprint":{"type":"string","description":"The SSH fingerprint for the asset."},"mac_address":{"description":"A list of MAC addresses for the asset.","type":"array","items":{"type":"string"}},"netbios_name":{"type":"string","description":"The NetBIOS name for the asset."},"operating_system":{"type":"string","description":"The operating system installed on the asset."},"system_type":{"type":"string","description":"The system architecture (x86) of the asset."}}},"examples":{"response":{"value":{"id":"f56168ed-b719-4273-b58c-a340a09ffbce","has_agent":false,"created_at":"2018-11-28T15:01:01.618Z","updated_at":"2018-11-28T15:01:01.618Z","first_seen":"2018-11-28T15:00:57.000Z","last_seen":"2018-11-28T15:00:57.000Z","last_authenticated_scan_date":"2018-11-28T15:00:57.000Z","last_licensed_scan_date":"2018-11-28T15:00:57.000Z","sources":[{"name":"NESSUS_SCAN","first_seen":"2018-11-28T15:00:57.000Z","last_seen":"2018-11-28T15:00:57.000Z"}],"tags":[],"network_id":["00000000-0000-0000-0000-000000000000"],"ipv4":["172.204.81.57"],"ipv6":[],"fqdn":["kubernetes.ad.demo.io"],"mac_address":["aa:e6:57:2f:b3:13","ca:0f:69:8b:c8:ff","8a:83:e4:13:69:96","1e:1f:92:66:12:63","02:42:97:86:cb:b3","46:eb:64:68:6d:7d","6a:7b:e7:9e:a2:0e","f6:27:2f:17:12:bd","00:50:56:a6:6a:a4","a6:8c:f1:b7:2d:1d","16:dd:64:f9:12:6e"],"netbios_name":["kubernetes.ad.demo.io"],"operating_system":["Linux Kernel 3.10.0-862.14.4.el7.x86_64 on CentOS Linux release 7.5.1804 (Core)"],"system_type":["general-purpose"],"tenable_uuid":["dea43dad16684f8a93e88bbd6b30a35a"],"hostname":["kubernetes.ad.demo.io"],"agent_name":[],"bios_uuid":["42263460-6fee-53cb-7e3e-44c8304da18e"],"aws_ec2_instance_id":[],"aws_ec2_instance_ami_id":[],"aws_owner_id":[],"aws_availability_zone":[],"aws_region":[],"aws_vpc_id":[],"aws_ec2_instance_group_name":[],"aws_ec2_instance_state_name":[],"aws_ec2_instance_type":[],"aws_subnet_id":[],"aws_ec2_product_code":[],"aws_ec2_name":[],"azure_vm_id":[],"azure_resource_id":[],"gcp_project_id":[],"gcp_zone":[],"gcp_instance_id":[],"ssh_fingerprint":[],"mcafee_epo_guid":[],"mcafee_epo_agent_guid":[],"qualys_asset_id":[],"qualys_host_id":[],"servicenow_sysid":[]}}}}}},"403":{"description":"Returned if the user does not have permission to view information about an asset."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/import/assets":{"post":{"summary":"Import asset list","description":"Imports a list of assets in JSON format. The request cannot exceed 5 MB. Each asset object requires a value for at least one of the following properties: fqdn, ipv4, netbios_name, mac_address.

      Requires CAN CONFIGURE [64] scan permissions. See Permissions.

      ","operationId":"was-assets-import","tags":["Assets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"assets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The unique ID of the asset."},"bios_uuid":{"type":"string","description":"BIOS UUID of the asset."},"ipv4":{"description":"A list of ipv4 addresses for the asset.","type":"array","items":{"type":"string"}},"ipv6":{"description":"A list of ipv6 addresses for the asset.","type":"array","items":{"type":"string"}},"hostname":{"description":"A list of hostnames for the asset.","type":"array","items":{"type":"string"}},"fqdn":{"description":"A list of FQDNs for the asset.","type":"array","items":{"type":"string"}},"ssh_fingerprint":{"type":"string","description":"The SSH fingerprint for the asset."},"mac_address":{"description":"A list of MAC addresses for the asset.","type":"array","items":{"type":"string"}},"netbios_name":{"type":"string","description":"The NetBIOS name for the asset."},"operating_system":{"type":"string","description":"The operating system installed on the asset."},"system_type":{"type":"string","description":"The system architecture (x86) of the asset."}}},"description":"An array of asset objects to import. Each asset object requires a value for at least one of the following properties: fqdn, ipv4, netbios\\_name, mac\\_address. Each asset object may also optionally contain additional properties for each property of was-assets. If any asset fails to be imported, an error message is returned indicating the cause of the failure.","example":"[{\"ipv4\":\"172.204.81.57\",\"operating_system\":\"Windows 7 x64\"}]"},"source":{"type":"string","description":"An identifier for the script used to upload the assets.","example":"Custom Import"}},"required":["assets","source"]}}}},"responses":{"200":{"description":"Returns the import job UUID.","content":{"application/json":{"schema":{"type":"object","properties":{"asset_import_job_uuid":{"type":"string","description":"The asset import job UUID."}}},"examples":{"response":{"value":{"asset_import_job_uuid":"fd7646b5-2c7a-433e-8f2b-f3281b7726ef"}}}}}},"400":{"description":"Returned if the user submitted a bad request."},"403":{"description":"Returned if the user does not have permission to import assets."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/import/asset-jobs":{"get":{"summary":"List asset import jobs","description":"Lists asset import jobs.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"was-assets-list-import-jobs","tags":["Assets"],"responses":{"200":{"description":"Returns a list of asset import jobs.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The unique ID of the job."},"user_uuid":{"type":"string","description":"The unique ID of the user who started the import job."},"start_time":{"type":"string","description":"The start time of the job."},"end_time":{"type":"string","description":"The end time of the job."},"asset_count":{"type":"integer","description":"The total number of assets uploaded for import."},"imported_asset_count":{"type":"integer","description":"The number of assets successfully imported."},"failed_asset_count":{"type":"integer","description":"The number of assets that failed to import."},"status":{"type":"string","description":"The status of the job."},"error_message":{"type":"string","description":"The description of why a job failed."}}}},"examples":{"response":{"value":{"asset_import_jobs":[{"job_id":"fd7646b5-2c7a-433e-8f2b-f3281b7726ef","container_id":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","source":"gabbytest","batches":1,"uploaded_assets":0,"failed_assets":0,"start_time":1544480303548,"last_update_time":1544484511492,"end_time":1544484511492,"status":"ERROR","status_message":"Job failed by exceeding time limit"},{"job_id":"15759fd1-3483-4467-b04f-1bff11141c37","container_id":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","source":"readmeio","batches":1,"uploaded_assets":0,"failed_assets":0,"start_time":1544480429785,"last_update_time":1544484511496,"end_time":1544484511496,"status":"ERROR","status_message":"Job failed by exceeding time limit"}]}}}}}},"403":{"description":"Returned if the user does not have permission to list asset import jobs."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/import/asset-jobs/{asset_import_job_uuid}":{"get":{"summary":"Get import job information","description":"Gets information about the specified import job.

      Requires ADMINISTRATOR [64] user permissions. See Permissions.

      ","operationId":"was-assets-import-job-info","tags":["Assets"],"parameters":[{"description":"The UUID of the asset import job.","required":true,"name":"asset_import_job_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns information about the specified import job.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The unique ID of the job."},"user_uuid":{"type":"string","description":"The unique ID of the user who started the import job."},"start_time":{"type":"string","description":"The start time of the job."},"end_time":{"type":"string","description":"The end time of the job."},"asset_count":{"type":"integer","description":"The total number of assets uploaded for import."},"imported_asset_count":{"type":"integer","description":"The number of assets successfully imported."},"failed_asset_count":{"type":"integer","description":"The number of assets that failed to import."},"status":{"type":"string","description":"The status of the job."},"error_message":{"type":"string","description":"The description of why a job failed."}}},"examples":{"response":{"value":{"job_id":"fd7646b5-2c7a-433e-8f2b-f3281b7726ef","container_id":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","source":"gabbytest","batches":1,"uploaded_assets":0,"failed_assets":0,"start_time":1544480303548,"last_update_time":1544484511492,"end_time":1544484511492,"status":"ERROR","status_message":"Job failed by exceeding time limit"}}}}}},"403":{"description":"Returned if the user does not have permission to list asset import jobs."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
      \n

      429 Too Many Requests

      \n
      \n
      \n
      nginx
      \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/editor/{type}/{id}":{"get":{"summary":"Get configuration details","description":"Gets the configuration details for the scan or policy.

      Requires STANDARD [32] user permissions. See Permissions.

      ","operationId":"was-editor-details","tags":["Editor"],"parameters":[{"description":"The type of object (scan or policy).","required":true,"name":"type","in":"path","schema":{"type":"string","enum":["scan","policy"]}},{"description":"The unique ID of the object.","required":true,"name":"id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the object data.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string"},"user_permissions":{"type":"integer"},"settings":{"type":"object"},"credentials":{"type":"object"},"plugins":{"type":"object"}}},"examples":{"response":{"value":{"credentials":{"data":[{"types":[{"inputs":[{"id":"username","placeholer":"admin","name":"Username","type":"entry","required":true},{"id":"password","name":"Password","type":"password","required":true},{"id":"type","name":"Authentication Type","type":"radio","default":"auto","options":["auto","ntlm"],"optionsLabels":["Basic / Digest","NTLM"]}],"max":1,"name":"HTTP Server Authentication","instances":[],"settings":null},{"inputs":[{"id":"was_auth_method","name":"Authentication method","type":"ui_radio","options":[{"inputs":[{"id":"login_page","name":"Login Page","type":"entry","required":true},{"id":"login_parameters","name":"Login Parameters","type":"entry"},{"id":"login_check","name":"Regex to verify successful auth","type":"entry","required":true},{"id":"login_check_url","name":"Page to verify active session","type":"entry","required":true},{"id":"login_check_pattern","name":"Regex to verify active session","type":"entry","required":true}],"name":"Login Form"},{"inputs":[{"id":"cookies","name":"Cookies","type":"entry"},{"id":"cookie_check_url","name":"Page to verify active session","type":"entry","required":true},{"id":"cookie_check_pattern","name":"Regex to verify active session","type":"entry","required":true}],"name":"Cookie Authentication"},{"inputs":[{"id":"selenium_script","name":"Selenium script (.side)","type":"file","required":true},{"id":"login_check_url","name":"Page to verify active session","type":"entry","required":true},{"id":"login_check_pattern","name":"Regex to verify active session","type":"entry","required":true}],"name":"Selenium Authentication"}],"default":"Login Form","required":true}],"max":1,"name":"Web Application Authentication","instances":[],"settings":null}],"name":"Web Authentication","default_expand":1}]},"is_was":true,"user_permissions":128,"owner":"admin@api.demo","title":"Web App Scan","is_agent":false,"uuid":"09805055-a034-4088-8986-aac5e1c57d5f0d44f09d736969bf","plugins":{"families":{"Injection":{"count":8,"id":8,"status":"enabled"},"File Inclusion":{"count":2,"id":10,"status":"enabled"},"Cross Site Request Forgery":{"count":1,"id":7,"status":"enabled"},"Data Exposure":{"count":7,"id":5,"status":"enabled"},"Cross Site Scripting":{"count":8,"id":6,"status":"enabled"},"Authentication & Session":{"count":4,"id":2,"status":"enabled"},"Web Servers":{"count":13,"id":4,"status":"enabled"},"Code Execution":{"count":5,"id":9,"status":"enabled"},"Component Vulnerability":{"count":502,"id":11,"status":"enabled"},"Web Applications":{"count":42,"id":3,"status":"enabled"}}},"filter_attributes":[],"settings":{"basic":{"inputs":[{"type":"entry","name":"Name","id":"name","default":"test","required":true},{"type":"textarea","name":"Description","id":"description","default":""},{"type":"select","id":"include_aggregate","name":"Scan results","default":true,"options":[{"name":"Keep private","value":"false"},{"name":"Show in dashboard","value":"true"}]},{"type":"select","id":"folder_id","name":"Folder","default":9,"options":[{"name":"My Scans","id":19},{"name":"Trash","id":18}]},{"type":"select","id":"scanner_id","name":"Scanner","default":"00000000-0000-0000-0000-00000000000000000000000000001","options":[{"id":"00000000-0000-0000-0000-00000000000000000000000000001","name":"US Cloud Scanner","type":"local","environment_name":null,"linked":true,"status":"on"},{"id":"1b895828-62a9-5084-8bc5-d4864a927fb10523d1e84e3fef44","name":"AP Singapore Cloud Scanners","type":"local","environment_name":null,"linked":true,"status":"on"},{"id":"cdf44a84-b547-b66c-d997-920aa1e897cc7165fe2e344196bb","name":"Demo Scanner","type":"local","environment_name":null,"linked":true,"status":"on"},{"id":"06ab826a-301d-7829-d2c4-37f400c0f949ea8cce60f523eeef","name":"EU Frankfurt Cloud Scanners","type":"local","environment_name":null,"linked":true,"status":"on"},{"id":"15e29fb5-c378-4803-37f7-67752912247e812e6cf942b4fd2e","name":"US East Cloud Scanners","type":"local","environment_name":null,"linked":true,"status":"on"},{"id":"37b315c1-f31f-cc8e-7e78-585c609fc1d7eba88f8d1e7d24b3","name":"US West Cloud Scanners","type":"local","environment_name":null,"linked":true,"status":"on"}]},{"type":"entry","id":"text_targets","name":"Target","default":"http://172.204.81.57","placeholder":"Example: https://www.tenable.com/","required":true,"regex":"^https?://\\S+$"}],"title":"Basic","groups":[{"title":"Schedule","name":"schedule","enabled":false,"rrules":null,"timezone":null,"starttime":null},{"filter_type":"and","inputs":[{"type":"textarea","name":"Email Recipient(s)","placeholder":"Example: me@example.com, you@example.com"}],"title":"Notifications","name":"email","emails":"","filters":null},{"title":"Permissions","name":"permissions","acls":[{"permissions":0,"owner":null,"display_name":null,"name":null,"id":null,"type":"default"},{"permissions":128,"owner":1,"display_name":"admin@api.demo","name":"admin@api.demo","id":2,"type":"user"}]}],"sections":[]},"assessment":{"inputs":null,"modes":{"id":"assessment_mode","name":"mode","type":"ui_radio","default":"Quick","options":[{"desc":"
      • No assessment will be conducted with this scan.
      ","name":"None"},{"desc":"
      • Elements to Audit
        • Cookies, Headers, Forms, Links
        • Parameter Values
        • DOM ...","name":"Quick"},{"desc":"
          • Elements to Audit
            • Cookies, Headers, Forms, Links
            • Parameter Names and Values
            • Choose your own assessment settings.
            ","name":"Custom"}]},"title":"Assessment","groups":[{"inputs":null,"title":"General","name":"general","sections":[{"inputs":[{"type":"checkbox","id":"was_audit_cookies","label":"Audit cookies","default":"yes"},{"type":"checkbox","id":"was_audit_forms","label":"Audit forms","default":"yes"},{"type":"checkbox","id":"was_audit_headers","label":"Audit headers","default":"yes"},{"type":"checkbox","id":"was_audit_links","label":"Audit links","default":"yes"},{"type":"checkbox","id":"was_audit_parameter_names","label":"Audit parameter names","default":"no"},{"type":"checkbox","id":"was_audit_parameter_values","label":"Audit parameter values","default":"yes"},{"type":"checkbox","id":"was_audit_jsons","label":"Audit JSON","default":"no"},{"type":"checkbox","id":"was_audit_xmls","label":"Audit XML","default":"no"},{"type":"checkbox","id":"was_audit_ui_forms","label":"Audit UI Forms","default":"yes"},{"type":"checkbox","id":"was_audit_ui_inputs","label":"Audit UI Inputs","default":"yes"}],"title":"Elements","name":"was_elements"},{"inputs":[{"type":"large-entry","name":"URL for Remote File Inclusion","id":"was_assessment_rfi_remote_url","default":"http://rfi.nessus.org/rfi.txt"}],"title":"Options","name":"was_options"}]}],"sections":[]},"advanced":{"inputs":null,"modes":{"id":"advanced_mode","name":"mode","type":"ui_radio","default":"Custom","options":[{"desc":"
            • Choose your own advanced settings.
            ","name":"Custom"}]},"title":"Advanced","groups":[{"inputs":null,"title":"General","name":"was_general","sections":[{"inputs":[{"type":"medium-entry","name":"Overall Scan max time (HH:MM:SS)","id":"was_timeout","default":"0000","required":true,"regex":"^[0-9]{2,}:[0-5][0-9]:[0-5][0-9]$"}],"title":"Scan Settings","name":"general"},{"inputs":[{"type":"medium-entry","name":"Number of URLs to Crawl and Browse","id":"was_scope_page_limit","default":"10000","required":true,"regex":"^\\d*$"},{"type":"medium-entry","name":"Path Directory Depth","id":"was_scope_directory_depth_limit","default":"10","regex":"^\\d+$"},{"type":"medium-entry","name":"Page DOM Element Depth","id":"was_scope_dom_depth_limit","default":"5","regex":"^\\d+$"},{"type":"medium-entry","name":"Maximum Response Size","id":"was_http_response_max_size","default":"500000","regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Request Redirect Limit","id":"was_http_request_redirect_limit","default":"1","regex":"^\\d+$","required":true}],"title":"Limits","name":"basic_limits"}]},{"inputs":null,"title":"Discovery","name":"was_discovery","sections":[{"inputs":[{"type":"large-entry","name":"User Agent","id":"was_http_user_agent","default":"Nessus WAS/%v","required":true},{"type":"textarea","name":"Custom Headers","id":"was_http_request_headers","default":""}],"title":"Crawl Settings","name":"general"},{"inputs":[{"type":"medium-entry","name":"Screen Width","id":"was_browser_cluster_screen_width","default":"1600","regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Screen Height","id":"was_browser_cluster_screen_height","default":"1200","regex":"^\\d+$","required":true},{"type":"checkbox","id":"was_browser_cluster_ignore_images","label":"Ignore Images","default":"yes","required":true}],"title":"Screen Settings","name":"browser"},{"inputs":[{"type":"medium-entry","id":"was_chrome_script_page_load_wait","name":"Page rendering delay","default":"10000","required":true,"regex":"^\\d+$","hint":"The number of milliseconds to let browser render the page"},{"type":"medium-entry","id":"was_chrome_script_command_wait","name":"Command execution delay","default":"500","required":true,"regex":"^\\d+$","hint":"The number of milliseconds to wait after processing a command before passing to the next one"},{"type":"medium-entry","id":"was_chrome_script_finish_wait","name":"Script completion delay","default":"5000","required":true,"regex":"^\\d+$","hint":"The number of milliseconds to wait once all commands are processed for rendering new contents."}],"title":"Selenium Settings","name":"selenium"}]},{"inputs":null,"title":"Performance","name":"was_performance","sections":[{"inputs":[{"type":"medium-entry","name":"Max number of concurrent HTTP connections","id":"was_http_request_concurrency","default":"10","regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Max number of HTTP requests per second","id":"was_plugins_rate_limiter_requests_per_second","default":"25","regex":"^\\d+$","required":true},{"type":"checkbox","id":"was_plugins_autothrottle","label":"Slow down the scan when network congestion is detected","default":"yes","required":true},{"type":"medium-entry","name":"Network timeout (in seconds)","id":"was_http_request_timeout","default":"5","regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Browser timeout (in seconds)","id":"was_browser_cluster_job_timeout","default":"10","regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Timeout threshold","id":"timeout_abort_threshold","default":"100","regex":"^\\d+$","required":true,"hint":"The number of consecutive timeouts before the scan is aborted (min 100)"}],"title":"Performance Settings","name":"general"}]}],"sections":[]},"scope":{"inputs":null,"title":"Scope","groups":[{"inputs":null,"title":"General Scope","name":"was_scope","sections":[{"inputs":[{"name":"List of URLs","type":"textarea","id":"was_scope_urls","placeholder":"Enter full absolute URLs only","default":""},{"type":"radio-group","id":"was_scope_option","label":"Specify how the scanner handles URL's found during the application crawl","default":"all","options":["all","all","paths","urls"],"optionsLabels":["","Crawl all URLs detected","Limit crawling to specified URLs and child paths","Limit crawling to specified URLs"],"required":true}],"desc":"Specify any URL's that you want to make sure are included in your scan in addition to your target UR ...","title":"Scan Inclusion","name":"scan_inclusion"},{"inputs":[{"name":"Regex for excluded URLs","id":"was_scope_exclude_path_patterns","type":"textarea","placeholder":"Enter patterns to identify URLs to exclude","default":"logout"},{"name":"File extensions to exclude","id":"was_scope_exclude_file_extensions","type":"textarea","placeholder":"Enter extensions separated by commas (css, png)","default":"css, js, png, jpeg, gif, pdf, csv"}],"desc":"Specify any URL's that you want to make sure are excluded from your scan.","title":"Scan Exclusion","name":"scan_exclusion"}]}],"sections":[]},"discovery":{"inputs":null,"modes":{"id":"discovery_mode","name":"mode","type":"ui_radio","default":"Custom","options":[{"desc":"
            • Choose your own discovery settings.
            ","name":"Custom"}]},"title":"Discovery","groups":[{"inputs":null,"title":"Path Discovery","name":"was_path_discovery","sections":[{"inputs":[{"type":"file","name":"Crawl Scripts","id":"was_plugins_selenium_crawl_script"}],"name":"general"}]}],"sections":[]}},"name":"was_scan"}}}}}},"403":{"description":"Returned if the user does not have permission to open the object."},"404":{"description":"Returned if the object does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/editor/{type}/templates":{"get":{"summary":"List templates","description":"Lists scan or policy templates, including non-WAS templates.

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-editor-list","tags":["Editor"],"parameters":[{"description":"The type of templates to retrieve (scan or policy).","required":true,"name":"type","in":"path","schema":{"type":"string","enum":["scan","policy"]}}],"responses":{"200":{"description":"Returns the template list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"Templates are used to create scans or policies with predefined parameters.","properties":{"unsupported":{"type":"boolean","description":"If true, template is not supported."},"cloud_only":{"type":"boolean","description":"If true, template is only available on the cloud."},"desc":{"type":"string","description":"The description of the template."},"subscription_only":{"type":"boolean","description":"If true, the template is only available for subscribers."},"is_was":{"type":"boolean","description":"If true, the template is for Web Application Scanning."},"title":{"type":"string","description":"The long name of the template."},"is_agent":{"type":"boolean","description":"If true, the template is for agent scans."},"uuid":{"type":"string","description":"The UUID for the template."},"manager_only":{"type":"boolean","description":"If true, can only be used by manager."},"name":{"type":"string","description":"The short name of the template."}}}},"examples":{"response":{"value":{"templates":[{"unsupported":false,"cloud_only":false,"desc":"A scan that checks a web application for vulnerabilities.","order":3,"subscription_only":false,"is_was":true,"title":"Web App Scan","is_agent":false,"uuid":"09805055-a034-4088-8986-aac5e1c57d5f0d44f09d736969bf","manager_only":false,"name":"was_scan"},{"unsupported":false,"cloud_only":false,"desc":"A full system scan suitable for any host.","order":null,"subscription_only":false,"is_was":null,"title":"Basic Network Scan","is_agent":null,"uuid":"731a8e52-3ea6-a291-ec0a-d2ff0619c19d7bd788d6be818b65","manager_only":false,"name":"basic"},{"unsupported":false,"cloud_only":false,"desc":"Audit systems connected via Nessus Agents.","order":null,"subscription_only":false,"is_was":null,"title":"Policy Compliance Auditing","is_agent":true,"uuid":"523c833f-e434-a05f-5a52-0c0c2c160b7cd9c901634c382c2d","manager_only":false,"name":"agent_compliance"}]}}}}}},"403":{"description":"Returned if the user does not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/editor/policy/{policy_id}/families/{family_id}/plugins/{plugin_id}":{"get":{"summary":"Get plugin details","description":"Gets the details of the plugin associated with the scan or policy.

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-editor-plugin-description","tags":["Editor"],"parameters":[{"description":"The ID of the policy to lookup.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the family to lookup within the policy.","required":true,"name":"family_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the plugin to lookup within the family.","required":true,"name":"plugin_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the plugin output.","content":{"application/json":{"schema":{"type":"object","properties":{"plugindescription":{"type":"object"}}},"examples":{"response":{"value":{"plugindescription":{"severity":null,"pluginname":"Ubuntu 10.04 LTS / 10.10 / 11.04 / 11.10 : clamav vulnerability (USN-1258-1)","pluginattributes":{"synopsis":"The remote Ubuntu host is missing a security-related patch.","description":"Stephane Chazelas discovered the bytecode engine of ClamAV improperly handled recursion under certain circumstances. This could allow a remote attacker to craft a file that could cause ClamAV to crash, resulting in a denial of service.\n\nNote that Tenable Network Security has extracted the preceding description block directly from the Ubuntu security advisory. Tenable has attempted to automatically clean and format it as much as possible without introducing additional issues.","risk_information":{"cvss_vector":"CVSS2#AV:N/AC:M/Au:N/C:N/I:N/A:P","risk_factor":"Medium","cvss_base_score":"4.3","cvss_temporal_score":"3.2","cvss_temporal_vector":"CVSS2#E:U/RL:OF/RC:C"},"ref_information":{"ref":[{"name":"bid","values":{"value":["50183"]},"url":"http://www.securityfocus.com/bid/"},{"name":"usn","values":{"value":["1258-1"]},"ext":"/","url":"http://www.ubuntu.com/usn/usn-"},{"name":"cve","values":{"value":["CVE-2011-3627"]},"url":"http://web.nvd.nist.gov/view/vuln/detail?vulnId="}]},"plugin_name":"Ubuntu 10.04 LTS / 10.10 / 11.04 / 11.10 : clamav vulnerability (USN-1258-1)","see_also":["https://usn.ubuntu.com/1258-1/"],"fname":"ubuntu_USN-1258-1.nasl","usn":"1258-1","plugin_information":{"plugin_version":"1.8","plugin_id":56777,"plugin_type":"local","plugin_publication_date":"2011/11/11","plugin_family":"Ubuntu Local Security Checks","plugin_modification_date":"2018/12/01"},"solution":"Update the affected libclamav6 package.","vuln_information":{"cpe":"cpe:/o:canonical:ubuntu_linux:10.04:-:lts\ncpe:/o:canonical:ubuntu_linux:10.10\ncpe:/o:canonical:ubuntu_linux:11.04\ncpe:/o:canonical:ubuntu_linux:11.10","exploitability_ease":"No known exploits are available","exploit_available":"false","patch_publication_date":"2011/11/10"}},"pluginfamily":"Ubuntu Local Security Checks","pluginid":"56777"}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/editor/{type}/templates/{template_uuid}":{"get":{"summary":"Get template details","description":"Gets details for the given template.

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-editor-template-details","tags":["Editor"],"parameters":[{"description":"The type of template to retrieve (scan or policy).","required":true,"name":"type","in":"path","schema":{"type":"string"}},{"description":"The UUID for the template.","required":true,"name":"template_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the template details.","content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string"},"is_agent":{"type":"boolean"},"settings":{"type":"object"},"credentials":{"type":"object"},"plugins":{"type":"object"}}},"examples":{"response":{"value":{"credentials":{"data":[{"types":[{"inputs":[{"id":"username","placeholer":"admin","name":"Username","type":"entry","required":true},{"id":"password","name":"Password","type":"password","required":true},{"id":"type","name":"Authentication Type","type":"radio","default":"auto","options":["auto","ntlm"],"optionsLabels":["Basic / Digest","NTLM"]}],"max":1,"name":"HTTP Server Authentication","instances":[],"settings":null},{"inputs":[{"id":"was_auth_method","name":"Authentication method","type":"ui_radio","options":[{"inputs":[{"id":"login_page","name":"Login Page","type":"entry","required":true},{"id":"login_parameters","name":"Login Parameters","type":"entry"},{"id":"login_check","name":"Regex to verify successful auth","type":"entry","required":true},{"id":"login_check_url","name":"Page to verify active session","type":"entry","required":true},{"id":"login_check_pattern","name":"Regex to verify active session","type":"entry","required":true}],"name":"Login Form"},{"inputs":[{"id":"cookies","name":"Cookies","type":"entry"},{"id":"cookie_check_url","name":"Page to verify active session","type":"entry","required":true},{"id":"cookie_check_pattern","name":"Regex to verify active session","type":"entry","required":true}],"name":"Cookie Authentication"},{"inputs":[{"id":"selenium_script","name":"Selenium script (.side)","type":"file","required":true},{"id":"login_check_url","name":"Page to verify active session","type":"entry","required":true},{"id":"login_check_pattern","name":"Regex to verify active session","type":"entry","required":true}],"name":"Selenium Authentication"}],"default":"Login Form","required":true}],"max":1,"name":"Web Application Authentication","instances":[],"settings":null}],"name":"Web Authentication","default_expand":1}]},"is_was":true,"user_permissions":128,"owner":"api@api.demo","title":"Web App Overview","is_agent":false,"uuid":"58323412-d521-9482-2224-bdf5e2d65e6a4c67d33d4322677f","plugins":{"families":{"Web Applications":{"count":15,"id":3,"status":"enabled"},"Data Exposure":{"count":4,"id":5,"status":"enabled"},"Authentication & Session":{"count":2,"id":2,"status":"enabled"},"Web Servers":{"count":5,"id":4,"status":"enabled"}}},"filter_attributes":[],"settings":{"basic":{"inputs":[{"type":"entry","name":"Name","id":"name","required":true},{"type":"textarea","name":"Description","id":"description"}],"title":"Basic","groups":[{"title":"Permissions","name":"permissions","acls":null}],"sections":[]},"advanced":{"inputs":null,"modes":{"id":"advanced_mode","name":"mode","type":"ui_radio","default":"Custom","options":[{"desc":"
            • Choose your own advanced settings.
            ","name":"Custom"}]},"title":"Advanced","groups":[{"inputs":null,"title":"General","name":"was_general","sections":[{"inputs":[{"type":"medium-entry","name":"Overall Scan max time (HH:MM:SS)","id":"was_timeout","default":"08:000","required":true,"regex":"^[0-9]{2,}:[0-5][0-9]:[0-5][0-9]$"}],"title":"Scan Settings","name":"general"},{"inputs":[{"type":"medium-entry","name":"Number of URLs to Crawl and Browse","id":"was_scope_page_limit","default":10000,"required":true,"regex":"^\\d*$"},{"type":"medium-entry","name":"Path Directory Depth","id":"was_scope_directory_depth_limit","default":10,"regex":"^\\d+$"},{"type":"medium-entry","name":"Page DOM Element Depth","id":"was_scope_dom_depth_limit","default":5,"regex":"^\\d+$"},{"type":"medium-entry","name":"Maximum Response Size","id":"was_http_response_max_size","default":500000,"regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Request Redirect Limit","id":"was_http_request_redirect_limit","default":1,"regex":"^\\d+$","required":true}],"title":"Limits","name":"basic_limits"}]},{"inputs":null,"title":"Discovery","name":"was_discovery","sections":[{"inputs":[{"type":"large-entry","name":"User Agent","id":"was_http_user_agent","default":"Nessus WAS/%v","required":true},{"type":"textarea","name":"Custom Headers","id":"was_http_request_headers","default":""}],"title":"Crawl Settings","name":"general"},{"inputs":[{"type":"medium-entry","name":"Screen Width","id":"was_browser_cluster_screen_width","default":1600,"regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Screen Height","id":"was_browser_cluster_screen_height","default":1200,"regex":"^\\d+$","required":true},{"type":"checkbox","id":"was_browser_cluster_ignore_images","label":"Ignore Images","default":"yes","required":true}],"title":"Screen Settings","name":"browser"},{"inputs":[{"type":"medium-entry","id":"was_chrome_script_page_load_wait","name":"Page rendering delay","default":10000,"required":true,"regex":"^\\d+$","hint":"The number of milliseconds to let browser render the page"},{"type":"medium-entry","id":"was_chrome_script_command_wait","name":"Command execution delay","default":500,"required":true,"regex":"^\\d+$","hint":"The number of milliseconds to wait after processing a command before passing to the next one"},{"type":"medium-entry","id":"was_chrome_script_finish_wait","name":"Script completion delay","default":5000,"required":true,"regex":"^\\d+$","hint":"The number of milliseconds to wait once all commands are processed for rendering new contents."}],"title":"Selenium Settings","name":"selenium"}]},{"inputs":null,"title":"Performance","name":"was_performance","sections":[{"inputs":[{"type":"medium-entry","name":"Max number of concurrent HTTP connections","id":"was_http_request_concurrency","default":10,"regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Max number of HTTP requests per second","id":"was_plugins_rate_limiter_requests_per_second","default":25,"regex":"^\\d+$","required":true},{"type":"checkbox","id":"was_plugins_autothrottle","label":"Slow down the scan when network congestion is detected","default":"yes","required":true},{"type":"medium-entry","name":"Network timeout (in seconds)","id":"was_http_request_timeout","default":5,"regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Browser timeout (in seconds)","id":"was_browser_cluster_job_timeout","default":10,"regex":"^\\d+$","required":true},{"type":"medium-entry","name":"Timeout threshold","id":"timeout_abort_threshold","default":100,"regex":"^\\d+$","required":true,"hint":"The number of consecutive timeouts before the scan is aborted (min 100)"}],"title":"Performance Settings","name":"general"}]}],"sections":[]},"scope":{"inputs":null,"title":"Scope","groups":[{"inputs":null,"title":"General Scope","name":"was_scope","sections":[{"inputs":[{"name":"List of URLs","type":"textarea","id":"was_scope_urls","placeholder":"Enter full absolute URLs only","default":""},{"type":"radio-group","id":"was_scope_option","label":"Specify how the scanner handles URL's found during the application crawl","default":"all","options":["all","all","paths","urls"],"optionsLabels":["","Crawl all URLs detected","Limit crawling to specified URLs and child paths","Limit crawling to specified URLs"],"required":true}],"desc":"Specify any URL's that you want to make sure are included in your scan in addition to your target URL.","title":"Scan Inclusion","name":"scan_inclusion"},{"inputs":[{"name":"Regex for excluded URLs","id":"was_scope_exclude_path_patterns","type":"textarea","placeholder":"Enter patterns to identify URLs to exclude","default":"logout"},{"name":"File extensions to exclude","id":"was_scope_exclude_file_extensions","type":"textarea","placeholder":"Enter extensions separated by commas (css, png)","default":"css, png, jpeg, gif, pdf, csv"}],"desc":"Specify any URL's that you want to make sure are excluded from your scan.","title":"Scan Exclusion","name":"scan_exclusion"}]}],"sections":[]},"discovery":{"inputs":null,"modes":{"id":"discovery_mode","name":"mode","type":"ui_radio","default":"Custom","options":[{"desc":"
            • Choose your own discovery settings.
            ","name":"Custom"}]},"title":"Discovery","groups":[{"inputs":null,"title":"Path Discovery","name":"was_path_discovery","sections":[{"inputs":[{"type":"file","name":"Crawl Scripts","id":"was_plugins_selenium_crawl_script"}],"name":"general"}]}],"sections":[]}},"name":"was_overview"}}}}}},"403":{"description":"Returned if the user does not have permission to open the template."},"404":{"description":"Returned if the template does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/folders":{"post":{"summary":"Create new folder","description":"Creates a new folder for the current user.

            Requires BASIC [16] user permissions. See Permissions.

            ","operationId":"was-folders-create","tags":["Folders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the folder."}},"required":["name"]}}}},"responses":{"200":{"description":"Returns the new folder ID.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"id":50}}}}}},"400":{"description":"Returned if the folder name is invalid."},"403":{"description":"Returned if the user does not have permission to create a folder."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to create the folder.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List scan folders","description":"Lists the current user's scan folders.

            Requires BASIC [16] user permissions. See Permissions.

            ","operationId":"was-folders-list","tags":["Folders"],"responses":{"200":{"description":"Returns the folder list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the folder."},"name":{"type":"string","description":"The name of the folder."},"type":{"type":"string","description":"The type of the folder (main, trash, custom)."},"default_tag":{"type":"integer","description":"Whether or not the folder is the default (1 or 0)."},"custom":{"type":"integer","description":"The custom status of the folder (1 or 0)."},"unread_count":{"type":"integer","description":"The number of unread scans in the folder."}}}},"examples":{"response":{"value":{"folders":[{"unread_count":0,"custom":0,"default_tag":0,"type":"trash","name":"Trash","id":18},{"unread_count":5,"custom":0,"default_tag":1,"type":"main","name":"My Scans","id":19},{"unread_count":0,"custom":1,"default_tag":0,"type":"custom","name":"fphaghh","id":34}]}}}}}},"403":{"description":"Returned if the user does not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/folders/{folder_id}":{"put":{"summary":"Rename folder","description":"Renames a folder for the current user.

            Requires BASIC [16] user permissions. See Permissions.

            ","operationId":"was-folders-edit","tags":["Folders"],"parameters":[{"description":"The ID of the folder to edit.","required":true,"name":"folder_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name of the folder."}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if the folder has been successfully renamed.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if the user tried to rename a system folder."},"404":{"description":"Returned if the folder does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to rename the folder.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete folder","description":"Deletes a folder.

            Requires BASIC [16] user permissions. See Permissions.

            ","operationId":"was-folders-delete","tags":["Folders"],"parameters":[{"description":"The ID of the folder to delete.","required":true,"name":"folder_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the folder has been successfully deleted.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if the user tried to delete a system folder."},"404":{"description":"Returned if the folder does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to delete the folder.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanner-groups":{"post":{"summary":"Create scanner group","description":"Creates a new scanner group.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanner-groups-create","tags":["Scanner Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The name for the new scanner group."},"type":{"type":"string","description":"The type of scanner group.","enum":["load_balancing"]}},"required":["name","type"]}}}},"responses":{"200":{"description":"Returned if the scanner group has been successfully created.","content":{"application/json":{"schema":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner group in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scanner group in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scanner group."},"owner":{"type":"string","description":"The username of the owner of the scanner group."},"owner_uuid":{"type":"string","description":"The UUID of the owner of the scacner group."},"default_permissions":{"type":"integer","description":"The access permissions for the Default group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scanner group."},"shared":{"type":"integer","description":"The shared status of the scanner group."},"scan_count":{"type":"integer","description":"The number of scans currently tasked to the scanner group."},"scanner_count":{"type":"string","description":"The number of scanners associated with this scanner group."},"uuid":{"type":"string","description":"The UUID of the scanner group."},"type":{"type":"string","description":"The type of scanner group. This is set to \"load_balancing\" by default."},"name":{"type":"string","description":"The name of the scanner group."},"id":{"type":"integer","description":"The unique ID of the scanner group."},"scanner_id":{"type":"integer","description":"The unique scanner ID of the scanner group."},"scanner_uuid":{"type":"string","description":"The UUID of the scanner group."},"owner_name":{"type":"string","description":"The name for the owner of the scanner group."}}},"examples":{"response":{"value":{"creation_date":1545331154,"last_modification_date":1545331154,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"scan_count":0,"uuid":"f00b532a-cbcd-4f9e-9292-9174083332df","type":"load_balancing","name":"Example Group","id":102825,"owner_name":"system"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to create the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List scanner groups","description":"Lists scanner groups within the current container.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanner-groups-list","tags":["Scanner Groups"],"responses":{"200":{"description":"Returns the scanner group list.","content":{"application/json":{"schema":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner group in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scanner group in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scanner group."},"owner":{"type":"string","description":"The username of the owner of the scanner group."},"owner_uuid":{"type":"string","description":"The UUID of the owner of the scacner group."},"default_permissions":{"type":"integer","description":"The access permissions for the Default group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scanner group."},"shared":{"type":"integer","description":"The shared status of the scanner group."},"scan_count":{"type":"integer","description":"The number of scans currently tasked to the scanner group."},"scanner_count":{"type":"string","description":"The number of scanners associated with this scanner group."},"uuid":{"type":"string","description":"The UUID of the scanner group."},"type":{"type":"string","description":"The type of scanner group. This is set to \"load_balancing\" by default."},"name":{"type":"string","description":"The name of the scanner group."},"id":{"type":"integer","description":"The unique ID of the scanner group."},"scanner_id":{"type":"integer","description":"The unique scanner ID of the scanner group."},"scanner_uuid":{"type":"string","description":"The UUID of the scanner group."},"owner_name":{"type":"string","description":"The name for the owner of the scanner group."}}},"examples":{"response":{"value":{"scanner_pools":[{"creation_date":1545326169,"last_modification_date":1545345793,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"user_permissions":128,"shared":1,"scan_count":0,"scanner_count":1,"uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","type":"load_balancing","name":"New Group Name","id":102823,"scanner_id":144057,"scanner_uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","owner_name":"system"},{"creation_date":1545326198,"last_modification_date":1545345902,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"user_permissions":128,"shared":1,"scan_count":0,"scanner_count":1,"uuid":"80ac7fcd-429b-4858-9d85-207577f6a35c","type":"load_balancing","name":"Group New Name","id":102824,"scanner_id":144058,"scanner_uuid":"80ac7fcd-429b-4858-9d85-207577f6a35c","owner_name":"system"},{"creation_date":1545331154,"last_modification_date":1545331154,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"user_permissions":128,"shared":1,"scan_count":0,"scanner_count":2,"uuid":"f00b532a-cbcd-4f9e-9292-9174083332df","type":"load_balancing","name":"test1","id":102825,"scanner_id":144059,"scanner_uuid":"f00b532a-cbcd-4f9e-9292-9174083332df","owner_name":"system"}]}}}}}},"403":{"description":"Returned if the user does not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanner-groups/{group_id}/scanners/{scanner_id}":{"post":{"summary":"Add scanner to scanner group","description":"Adds a scanner to the scanner group.

            Requires BASIC [16] user permissions. See Permissions.

            ","operationId":"was-scanner-groups-add-scanner","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the scanner to add to the scanner group.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the scanner has been successfully added to the scanner group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"To do":"Add response sample here"}}}}}},"400":{"description":"Returned if an attempt is made to add a scanner group to another scanner group."},"409":{"description":"Returned if you attempt to add a scanner to a scanner group that the scanner is already a member of.","content":{"text/html":{"examples":{"response":{"value":{"error":"Scanner 00000000-0000-0000-0000-00000000000000000000000000001 already exists in group f00b532a-cbcd-4f9e-9292-9174083332df"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to add the scanner to the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Remove scanner from scanner group","description":"Remove a scanner from the scanner group.

            Requires BASIC [16] user permissions. See Permissions.

            ","operationId":"was-scanner-groups-delete-scanner","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the scanner to remove from the scanner group.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the scanner has been successfully removed from the scanner group.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to remove the scanner from the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanner-groups/{group_id}/scanners":{"get":{"summary":"List scanners within scanner group","description":"Lists scanners associated with the scanner group.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanner-groups-list-scanners","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the list of scanners in the group.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner in Unix time."},"group":{"type":"boolean","description":"True if the scanner is in a group; false, otherwise."},"id":{"type":"integer","description":"The unique ID of the scanner."},"uuid":{"type":"string","description":"The UUID of the scanner."},"last_connect":{"type":"integer","description":"The last_connect time in Unix time."},"last_modification_date":{"type":"integer","description":"The last_modification_date in Unix time."},"linked":{"type":"integer","description":"1 if the scanner is linked; 0, otherwise."},"name":{"type":"string","description":"The user-defined name of the scanner."},"type":{"type":"string","description":"The type of scanner (managed_webapp)."},"status":{"type":"string","description":"The status of the scanner (on or off)."},"scan_count":{"type":"integer","description":"The current number of running scans on the scanner."},"engine_version":{"type":"string","description":"The version of the scanner."},"owner":{"type":"string","description":"The owner of the scanner."},"key":{"type":"string","description":"A alphanumeric sequence of characters used when linking a scanner to Tenable.io."}}}},"examples":{"response":{"value":{"scanners":[{"creation_date":1543416914,"group":true,"id":141484,"key":"70d1969c3a1a14697ad51f27f1ee4afe48ef535051d90f5481e32fd78005f05a","last_connect":null,"last_modification_date":1543416914,"license":null,"linked":1,"name":"US Cloud Scanner","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"source":"service","status":"on","timestamp":1543416914,"type":"local","uuid":"00000000-0000-0000-0000-00000000000000000000000000001"}]}}}}}},"403":{"description":"Returned if the user does not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanner-groups/{group_id}":{"get":{"summary":"List scanner group details","description":"Retruns details for the given scanner group.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanner-groups-details","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the scanner group details.","content":{"application/json":{"schema":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner group in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scanner group in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scanner group."},"owner":{"type":"string","description":"The username of the owner of the scanner group."},"owner_uuid":{"type":"string","description":"The UUID of the owner of the scacner group."},"default_permissions":{"type":"integer","description":"The access permissions for the Default group."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scanner group."},"shared":{"type":"integer","description":"The shared status of the scanner group."},"scan_count":{"type":"integer","description":"The number of scans currently tasked to the scanner group."},"scanner_count":{"type":"string","description":"The number of scanners associated with this scanner group."},"uuid":{"type":"string","description":"The UUID of the scanner group."},"type":{"type":"string","description":"The type of scanner group. This is set to \"load_balancing\" by default."},"name":{"type":"string","description":"The name of the scanner group."},"id":{"type":"integer","description":"The unique ID of the scanner group."},"scanner_id":{"type":"integer","description":"The unique scanner ID of the scanner group."},"scanner_uuid":{"type":"string","description":"The UUID of the scanner group."},"owner_name":{"type":"string","description":"The name for the owner of the scanner group."}}},"examples":{"response":{"value":{"creation_date":1545326169,"last_modification_date":1545326169,"owner_id":1,"owner":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","default_permissions":16,"user_permissions":128,"shared":1,"scan_count":0,"uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","type":"load_balancing","name":"New Scanner Group","network_name":"Default","id":102823,"scanner_id":144057,"scanner_uuid":"9b7b3d08-cc43-4e67-adc6-41b706c0b680","owner_name":"system"}}}}}},"403":{"description":"Returned if user does not have permission to view the scanner group."},"404":{"description":"Returned if the scanner group does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update scanner group","description":"Updates a scanner group.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanner-groups-edit","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The new name for the scanner group."}},"required":["name"]}}}},"responses":{"200":{"description":"Returned if the scanner group has been successfully updated.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","created":1545326198354,"modified":1545345902579,"container_uuid":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","uuid":"80ac7fcd-429b-4858-9d85-207577f6a35c","id":102824,"name":"Group New Name","type":"load_balancing","distributed":false,"default_permissions":16,"network_name":"Default","shared":1,"user_permissions":128,"created_in_seconds":1545326198,"modified_in_seconds":1545345902}}}}}},"404":{"description":"Returned if the scanner group does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to update the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete a scanner group","description":"Deletes a scanner group.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanner-groups-delete","tags":["Scanner Groups"],"parameters":[{"description":"The ID of the scanner group.","required":true,"name":"group_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the scanner group has been successfully deleted.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if the scanner group does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to delete the scanner group.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/policies":{"post":{"summary":"Create policy","description":"Creates a policy.

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-policies-create","tags":["Policies"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID for the editor template to use.","example":"ab4bacd2-05f6-425c-9d79-3ba3940ad1c24e51e1f403febe40"},"settings":{"type":"object","properties":{}}},"required":["uuid"]}}}},"responses":{"200":{"description":"Returned if Tenable.io saved the policy successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"policy_id":{"type":"integer"},"policy_name":{"type":"string"}}},"examples":{"response":{"value":{"policy_id":"integer","policy_name":"string"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an error while saving the policy.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List policies","description":"Returns a list of policies, including non-WAS policies.

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-policies-list","tags":["Policies"],"responses":{"200":{"description":"Returns the policy list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the policy."},"template_uuid":{"type":"string","description":"The UUID for the template the policy uses."},"name":{"type":"string","description":"The name of the policy."},"description":{"type":"string","description":"The description of the policy."},"owner_id":{"type":"string","description":"The unique ID of the owner of the policy."},"owner":{"type":"string","description":"The username for the owner of the policy."},"shared":{"type":"integer","description":"The shared status of the policy."},"user_permissions":{"type":"integer","description":"The sharing permissions for the policy."},"creation_date":{"type":"integer","description":"The creation date of the policy in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the policy in Unix time."},"visibility":{"type":"integer","description":"The visibility of the target (private or shared)."},"no_target":{"type":"boolean","description":"If `true`, the policy does not use targets."},"timeout_abort_threshold":{"type":"integer","description":"The number of consecutive timeouts before the scan aborts (minimum 100)."},"was_browser_cluster_ignore_images":{"type":"string","description":"Specifies whether images on web pages should be crawled or ignored by the virtual browser instance embedded into the scanner. Possible values are `yes` or `no`."},"was_browser_cluster_job_timeout":{"type":"integer","description":"The time that the scanner waits for a response from a browser, unless otherwise specified within a plugin. If you are scanning over a slow connection, you may wish to set this to a higher number of seconds."},"was_browser_cluster_screen_height":{"type":"integer","description":"The screen height, in pixels, of the virtual browser instance embedded into the scanner."},"was_browser_cluster_screen_width":{"type":"integer","description":"The screen width, in pixels, of the virtual browser instance embedded into the scanner."},"was_chrome_script_command_wait":{"type":"integer","description":"When running Selenium scripts for authentication and crawling, the number of milliseconds to wait after processing a command before passing to the next one."},"was_chrome_script_finish_wait":{"type":"integer","description":"When running Selenium scripts for authentication and crawling, the number of milliseconds to wait once all commands are processed for rendering new contents."},"was_chrome_script_page_load_wait":{"type":"integer","description":"When running Selenium scripts for authentication and crawling, the number of milliseconds to let the browser render the page."},"was_http_request_concurrency":{"type":"integer","description":"The maximum number of established HTTP sessions for a single host."},"was_http_request_headers":{"type":"string","description":"A list of custom headers injected into each HTTP request."},"was_http_request_redirect_limit":{"type":"integer","description":"The number of redirects the scan follows before it stops trying to crawl the page."},"was_http_request_timeout":{"type":"integer","description":"The time that the scanner waits for a response from a host, unless otherwise specified within a plugin. If you are scanning over a slow connection, you may wish to set this to a higher number of seconds."},"was_http_response_max_size":{"type":"integer","description":"The maximum load size of a page in order to be audited. If the scanner crawls a URL and the response exceeds the limit, then it is not audited and no vulnerability assessment is performed."},"was_http_user_agent":{"type":"string","description":"The user-agent header used by the scanner when sending an HTTP request. For example, `Nessus WAS/%v` where %v is the version of the scan engine."},"was_plugins_autothrottle":{"type":"string","description":"The plugins autothrottle setting (yes or no)."},"was_plugins_rate_limiter_requests_per_second":{"type":"integer","description":"The maximum number of HTTP requests for the entire scan for a single host."},"was_plugins_selenium_crawl_script":{"type":"string","description":"The name of the file containing Selenium scripts that Web Application Scanning uploads and uses to crawl during the scan. For more information, see Configure Selenium Authentication in the Tenable.io Web Application Scanning User Guide."},"was_scope_directory_depth_limit":{"type":"integer","description":"The maximum number of sub-directories the scanner crawls. For example, http://www.tenable.com/products/tenable-io has two sub-directories."},"was_scope_dom_depth_limit":{"type":"integer","description":"The maximum depth of HTML nested elements the scanner crawls."},"was_scope_exclude_file_extensions":{"type":"string","description":"A list of file types excluded from the scan. Possible values are: css, js, png, jpeg, gif, pdf, csv."},"was_scope_exclude_path_patterns":{"type":"string","description":"A regex specifying URLs excluded from the scan."},"was_scope_option":{"type":"string","description":"Specifies how the scanner handles URLs found during the application crawl. Possible values are:\n - all—Crawl all URLs detected.\n - urls—Limit crawling to specified URLs.\n - paths—Limit crawling to specified URLs and child paths."},"was_scope_page_limit":{"type":"integer","description":"The maximum number of URLs the scanner attempts to crawl and therefore audit."},"was_scope_urls":{"type":"string","description":"The list of URLs that are scanned."},"was_timeout":{"type":"string","description":"The maximum duration the scan runs before it stops automatically. This value uses the format, HH:MM:SS."}}}},"examples":{"response":{"value":{"policies":[{"no_target":"false","template_uuid":"ad629e16-03b6-8c1d-cef6-ef8c9dd3c658d24bd260ef5f9e66","description":"An example policy.","name":"Test Policy 1","owner":"api@api.demo","visibility":"shared","shared":1,"user_permissions":128,"last_modification_date":1545938690,"creation_date":1545938690,"owner_id":3,"id":43},{"no_target":"false","template_uuid":"09805055-a034-4088-8986-aac5e1c57d5f0d44f09d736969bf","description":"Policy for web app scans.","name":"Web App Scan Policy","owner":"api@api.demo","visibility":"private","shared":0,"user_permissions":128,"last_modification_date":1545947139,"creation_date":1545947139,"owner_id":3,"id":51}]}}}}}},"403":{"description":"Returned if the user does not have permission to view the policy list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/policies/{policy_id}":{"get":{"summary":"List policy details","description":"Returns the details for the given policy.

            Requires CAN USE [32] policy permissions. See Permissions.

            ","operationId":"was-policies-details","tags":["Policies"],"parameters":[{"description":"The ID of the policy to retrieve.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the policy details. This response can be edited and passed directly to the [PUT /policies/{policy_id}](/reference#was-policies-configure) endpoint.","content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string"},"credentials":{"type":"object"},"plugins":{"type":"object"},"settings":{"type":"object"}}},"examples":{"response":{"value":{"plugins":{"Injection":{"status":"enabled"},"File Inclusion":{"status":"enabled"},"Cross Site Request Forgery":{"status":"enabled"},"Data Exposure":{"status":"enabled"},"Cross Site Scripting":{"status":"enabled"},"Authentication & Session":{"status":"enabled"},"Web Servers":{"status":"enabled"},"Code Execution":{"status":"enabled"},"Component Vulnerability":{"status":"enabled"},"Web Applications":{"status":"enabled"}},"settings":{"was_chrome_script_command_wait":"500","was_scope_urls":"","description":"Policy for web app scans.","was_http_request_redirect_limit":"1","was_browser_cluster_screen_height":"1200","was_scope_dom_depth_limit":"5","was_http_user_agent":"Nessus WAS/%v","was_plugins_rate_limiter_requests_per_second":"25","was_scope_exclude_path_patterns":"logout","was_http_response_max_size":"500000","was_plugins_autothrottle":"yes","was_http_request_timeout":"5","was_plugins_selenium_crawl_script":"","was_http_request_concurrency":"10","assessment_mode":"Quick","was_timeout":"08:00:00","was_http_request_headers":"","was_scope_page_limit":"10000","was_browser_cluster_job_timeout":"10","was_scope_exclude_file_extensions":"css, png, jpeg, gif, pdf, csv","was_chrome_script_finish_wait":"5000","was_scope_directory_depth_limit":"10","was_chrome_script_page_load_wait":"10000","timeout_abort_threshold":"100","was_scope_option":"all","was_browser_cluster_ignore_images":"yes","was_browser_cluster_screen_width":"1600","name":"Web App Scan Policy"},"uuid":"09805055-a034-4088-8986-aac5e1c57d5f0d44f09d736969bf"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]},"put":{"summary":"Update policy","description":"Updates the parameters of a policy.

            Requires CAN EDIT [32] policy permissions. See Permissions.

            ","operationId":"was-policies-configure","tags":["Policies"],"parameters":[{"description":"The ID of the policy to change.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io changed the policy configuration.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if the policy does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io encountered an error while saving the configuration.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete policy","description":"Deletes a policy.

            Requires CAN EDIT [32] policy permissions. See Permissions.

            ","operationId":"was-policies-delete","tags":["Policies"],"parameters":[{"description":"The ID of the policy to delete.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io deleted the policy.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if the user does not have permission to delete the policy."},"404":{"description":"Returned if the policy does not exist."},"405":{"description":"Returned if the policy is in use by a scan."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/policies/{policy_id}/copy":{"post":{"summary":"Copy policy","description":"Copies a policy.

            Requires CAN EDIT [32] policy permissions. See Permissions.

            ","operationId":"was-policies-copy","tags":["Policies"],"parameters":[{"description":"The ID of the policy to copy.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the policy object with the ID and name properties set.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{"name":"Copy of Web App Scan Policy","id":52}}}}}},"403":{"description":"Returned if the user does not have permission to copy the policy."},"404":{"description":"Returned if the policy does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io failed to copy the policy.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/policies/import":{"post":{"summary":"Import policy","description":"Imports an existing policy uploaded using POST /file/upload (.nessus format only).

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-policies-import","tags":["Policies"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"The name of the file to import as provided by the response from [file: upload](/reference#file-upload)."}},"required":["file"]}}}},"responses":{"200":{"description":"Returns the policy object.","content":{"application/json":{"schema":{"type":"object","properties":{"private":{"type":"integer"},"no_target":{"type":"string"},"template_uuid":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"shared":{"type":"integer"},"user_permissions":{"type":"integer"},"last_modification_date":{"type":"integer"},"creation_date":{"type":"integer"},"owner_id":{"type":"integer"},"id":{"type":"integer"}}},"examples":{"response":{"value":{"private":"integer","no_target":"string","template_uuid":"string","description":"string","name":"string","owner":"string","shared":"integer","user_permissions":"integer","last_modification_date":"integer","creation_date":"integer","owner_id":"integer","id":"integer"}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to import the policy.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/policies/{policy_id}/export":{"get":{"summary":"Export policy","description":"Exports the given policy.

            Requires CAN EDIT [32] policy permissions. See Permissions.

            ","operationId":"was-policies-export","tags":["Policies"],"parameters":[{"description":"The ID of the policy to export.","required":true,"name":"policy_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the policy in nessus (XML) format.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if the user does not have permission to export the policy."},"404":{"description":"Returned if the policy does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners":{"get":{"summary":"List scanners","description":"Returns the scanner list.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanners-list","tags":["Scanners"],"responses":{"200":{"description":"Returns the scanner list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner in Unix time."},"group":{"type":"boolean","description":"True if the scanner is in a group; false, otherwise."},"id":{"type":"integer","description":"The unique ID of the scanner."},"uuid":{"type":"string","description":"The UUID of the scanner."},"last_connect":{"type":"integer","description":"The last_connect time in Unix time."},"last_modification_date":{"type":"integer","description":"The last_modification_date in Unix time."},"linked":{"type":"integer","description":"1 if the scanner is linked; 0, otherwise."},"name":{"type":"string","description":"The user-defined name of the scanner."},"type":{"type":"string","description":"The type of scanner (managed_webapp)."},"status":{"type":"string","description":"The status of the scanner (on or off)."},"scan_count":{"type":"integer","description":"The current number of running scans on the scanner."},"engine_version":{"type":"string","description":"The version of the scanner."},"owner":{"type":"string","description":"The owner of the scanner."},"key":{"type":"string","description":"A alphanumeric sequence of characters used when linking a scanner to Tenable.io."}}}},"examples":{"response":{"value":{"scanners":[{"creation_date":1543416914,"group":true,"id":141482,"key":"83520d0f4da8265cb52f7b558a3319ecb36d4b0ea490d4f9ec4ca9f2e1eee8b9","last_connect":null,"last_modification_date":1543416914,"license":{"agents":512,"ips":1024,"scanners":2,"users":10,"enterprise_pause":false,"expiration_date":1551160800,"evaluation":false,"apps":{"was":{"mode":"eval","expiration_date":1549299101}},"scanners_used":0,"agents_used":0},"linked":1,"name":"AP Singapore Cloud Scanners","network_name":"Default","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"shared":1,"source":"service","status":"on","timestamp":1543416914,"type":"local","user_permissions":64,"uuid":"1b895828-62a9-5084-8bc5-d4864a927fb10523d1e84e3fef44"},{"creation_date":1543416914,"group":true,"id":141483,"key":"e3eeefeacca0d998c466af126549d68ef0f4e0d0ba3ab04a6e59a1d8a8a57079","last_connect":null,"last_modification_date":1543416914,"license":{"agents":512,"ips":1024,"scanners":2,"users":10,"enterprise_pause":false,"expiration_date":1551160800,"evaluation":false,"apps":{"was":{"mode":"eval","expiration_date":1549299101}},"scanners_used":0,"agents_used":0},"linked":1,"name":"EU Frankfurt Cloud Scanners","network_name":"Default","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"shared":1,"source":"service","status":"on","timestamp":1543416914,"type":"local","user_permissions":64,"uuid":"06ab826a-301d-7829-d2c4-37f400c0f949ea8cce60f523eeef"},{"creation_date":1543416914,"group":true,"id":141484,"key":"70d1969c3a1a14697ad51f27f1ee4afe48ef535051d90f5481e32fd78005f05a","last_connect":null,"last_modification_date":1543416914,"license":{"agents":512,"ips":1024,"scanners":2,"users":10,"enterprise_pause":false,"expiration_date":1551160800,"evaluation":false,"apps":{"was":{"mode":"eval","expiration_date":1549299101}},"scanners_used":0,"agents_used":0},"linked":1,"name":"US Cloud Scanner","network_name":"Default","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"shared":1,"source":"service","status":"on","timestamp":1543416914,"type":"local","user_permissions":64,"uuid":"00000000-0000-0000-0000-00000000000000000000000000001"}]}}}}}},"403":{"description":"Returned if the user does not have permission to view the list."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/key":{"get":{"summary":"Get scanner key","description":"Gets the key of the requested scanner.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanners-get-scanner-key","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the scanner key.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"}}},"examples":{"response":{"value":{"key":"83520d0f4da8265cb52f7b558a3319ecb36d4b0ea490d4f9ec4ca9f2e1eee8b9"}}}}}},"403":{"description":"Returned if user does not have permission to view scanner data."},"404":{"description":"Returned if the scanner does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}":{"get":{"summary":"Get scanner details","description":"Gets details for the given scanner.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanners-details","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the scanner details.","content":{"application/json":{"schema":{"type":"object","properties":{"creation_date":{"type":"integer","description":"The creation date for the scanner in Unix time."},"group":{"type":"boolean","description":"True if the scanner is in a group; false, otherwise."},"id":{"type":"integer","description":"The unique ID of the scanner."},"uuid":{"type":"string","description":"The UUID of the scanner."},"last_connect":{"type":"integer","description":"The last_connect time in Unix time."},"last_modification_date":{"type":"integer","description":"The last_modification_date in Unix time."},"linked":{"type":"integer","description":"1 if the scanner is linked; 0, otherwise."},"name":{"type":"string","description":"The user-defined name of the scanner."},"type":{"type":"string","description":"The type of scanner (managed_webapp)."},"status":{"type":"string","description":"The status of the scanner (on or off)."},"scan_count":{"type":"integer","description":"The current number of running scans on the scanner."},"engine_version":{"type":"string","description":"The version of the scanner."},"owner":{"type":"string","description":"The owner of the scanner."},"key":{"type":"string","description":"A alphanumeric sequence of characters used when linking a scanner to Tenable.io."}}},"examples":{"response":{"value":{"creation_date":1543416914,"group":true,"id":141482,"key":"83520d0f4da8265cb52f7b558a3319ecb36d4b0ea490d4f9ec4ca9f2e1eee8b9","last_connect":null,"last_modification_date":1543416914,"license":null,"linked":1,"name":"AP Singapore Cloud Scanners","network_name":"Default","num_scans":0,"owner":"system","owner_id":1,"owner_name":"system","owner_uuid":"fe2e8b99-791a-429a-ab84-4226e62306ff","pool":true,"scan_count":0,"shared":1,"source":"service","status":"on","timestamp":1543416914,"type":"local","user_permissions":64,"uuid":"1b895828-62a9-5084-8bc5-d4864a927fb10523d1e84e3fef44"}}}}}},"403":{"description":"Returned if user does not have permission to view the scanner."},"404":{"description":"Returned if the scanner does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete scanner","description":"Deletes and unlinks a scanner from Tenable.io.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanners-delete","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if the server deleted/unlinked the scanner.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if an attempt is made to delete the local scanner."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the server failed to delete the scanner.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/scans":{"get":{"summary":"List running scans","description":"Lists scans running on the requested scanner.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanners-get-scans","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the list of scans running on the requested scanner.","content":{"application/json":{"schema":{"type":"object","properties":{"scanner_uuid":{"type":"string","description":"The UUID of the scanner the scan belongs to."},"name":{"type":"string","description":"The name of the scan."},"status":{"type":"string","description":"Scan status. Can be: pending, processing, stopping, pausing, paused, resuming, or running."},"id":{"type":"string","description":"The scan UUID."},"scan_id":{"type":"integer","description":"The ID of the scan."},"user":{"type":"string","description":"The username of the scan owner."},"last_modification_date":{"type":"integer","description":"The last time the scan was modified in Unix time."},"start_time":{"type":"integer","description":"The date and time when the scan was started."},"remote":{"type":"boolean","description":"True if the scan is running remotely; false, otherwise."}}},"examples":{"response":{"value":{"scans":[{"scan_id":53,"scanner_uuid":"00000000-0000-0000-0000-00000000000000000000000000001","name":"Web App Scan","status":"running","id":"0224cbf3-144b-415a-96e5-526afdbd6bec","user":"API Demo User","user_uuid":"394a4be9-782d-406a-9d0a-695188260f0b","last_modification_date":1545948401,"start_time":1545948381}]}}}}}},"403":{"description":"Returned if user does not have permission to view scanner data."},"404":{"description":"Returned if the scanner does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scanners/{scanner_id}/link":{"put":{"summary":"Toggle scanner link state","description":"Enables or disables the link state of the scanner identified by `scanner_id`.

            Requires ADMINISTRATOR [64] user permissions. See Permissions.

            ","operationId":"was-scanners-toggle-link-state","tags":["Scanners"],"parameters":[{"description":"The ID of the scanner.","required":true,"name":"scanner_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"link":{"type":"integer","description":"Pass 1 enable the link. Pass 0 to disable.","format":"int32"}},"required":["link"]}}}},"responses":{"200":{"description":"Returned if updating the scanner was successful.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"403":{"description":"Returned if the scanner is a cloud scanner and the user doesn’t have permission to edit it."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans":{"post":{"summary":"Create scan","description":"Creates a scan.

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-scans-create","tags":["Scans"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID for the editor template to use.","example":"ab4bacd2-05f6-425c-9d79-3ba3940ad1c24e51e1f403febe40"},"settings":{"type":"object","properties":{"name":{"type":"string","description":"The name of the scan."},"description":{"type":"string","description":"The description of the scan."},"policy_id":{"type":"integer","description":"The unique ID of the policy to use.","format":"int32"},"folder_id":{"type":"integer","description":"The unique ID of the destination folder for the scan.","format":"int32"},"scanner_id":{"type":"integer","description":"The unique ID of the scanner to use.","example":"1","format":"int32"},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"launch":{"type":"string","description":"When to launch the scan. Valid values are: ON\\_DEMAND, DAILY, WEEKLY, MONTHLY, YEARLY.","enum":["ON_DEMAND","DAILY","WEEKLY","MONTHLY","YEARLY"]},"starttime":{"type":"string","description":"The starting time and date for the scan in the following format: YYYYMMDDTHHMMSS.","example":"20140826T133000"},"rrules":{"type":"string","description":"Expects a string of three values separated by semi-colons. The frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). To create a scan that runs every three weeks on Monday Wednesday and Friday the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`","example":"FREQ=DAILY;INTERVAL=1"},"timezone":{"type":"string","description":"The timezone for the scan schedule.","example":"America/New_York"},"text_targets":{"type":"string","description":"A single URL to scan.","example":"localhost"},"emails":{"type":"string","description":"A comma-separated list of accounts who will receive the email summary report.","example":"test1@test.com, test2@test.com"},"acls":{"items":{"type":"string"},"description":"An array containing permissions to apply to the scan.","type":"array","example":"[{\"type\": \"default\", \"permissions\": 16}, {\"type\": \"user\", \"permissions\": 64, \"name\": \"admin\", \"id\": 1, \"owner\": 1}]"}},"required":["name","enabled","text_targets"]}},"required":["uuid"]}}}},"responses":{"200":{"description":"Returned if the scan was saved successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scan."},"uuid":{"type":"string","description":"The UUID for the scan."},"name":{"type":"string","description":"The name of the scan."},"type":{"type":"string","description":"The type of scan (local, remote, webapp, or agent). WAS scans will always have the type set to webapp."},"owner":{"type":"string","description":"The owner of the scan."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"read":{"type":"boolean","description":"If `true`, the scan has been read."},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"shared":{"type":"boolean","description":"If `true`, the scan is shared."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan."},"creation_date":{"type":"integer","description":"The creation date for the scan in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scan in Unix time."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"timezone":{"type":"string","description":"The timezone for the scan."},"rrules":{"type":"string","description":"The rules for repeating the scan."},"schedule_uuid":{"type":"string","description":"The schedule_uuid of the scan that should be returned."}}},"examples":{"response":{"value":{"scan":{"container_id":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","owner_uuid":"394a4be9-782d-406a-9d0a-695188260f0b","uuid":"template-6950fa18-56c2-fe8b-5f3c-3a6d7b2c406485debdf5bb0ba8ef","name":"Basic WebApp Scan","description":null,"policy_id":37,"scanner_id":null,"scanner_uuid":"00000000-0000-0000-0000-00000000000000000000000000001","emails":null,"sms":"","enabled":true,"dashboard_file":null,"include_aggregate":true,"scan_time_window":null,"custom_targets":"172.204.81.57:3030","starttime":"20190101T000000","rrules":null,"timezone":null,"notification_filters":null,"shared":0,"user_permissions":128,"default_permissions":0,"owner":"api@api.demo","owner_id":3,"last_modification_date":1545869117,"creation_date":1545869117,"type":"public","id":38}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if an error occurred while saving the scan.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"get":{"summary":"List scans","description":"Returns the scan list. Scans of all types are returned.

            Requires BASIC [16] user permissions. See Permissions.

            ","operationId":"was-scans-list","tags":["Scans"],"parameters":[{"description":"The ID of the folder whose scans should be listed.","required":false,"name":"folder_id","in":"query","schema":{"type":"integer"}},{"description":"Limit the results to those that have only changed since this time.","required":false,"name":"last_modification_date","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Returns the scan list.","content":{"application/json":{"schema":{"type":"object","properties":{"folders":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the folder."},"name":{"type":"string","description":"The name of the folder."},"type":{"type":"string","description":"The type of the folder (main, trash, custom)."},"default_tag":{"type":"integer","description":"Whether or not the folder is the default (1 or 0)."},"custom":{"type":"integer","description":"The custom status of the folder (1 or 0)."},"unread_count":{"type":"integer","description":"The number of unread scans in the folder."}}}},"scans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scan."},"uuid":{"type":"string","description":"The UUID for the scan."},"name":{"type":"string","description":"The name of the scan."},"type":{"type":"string","description":"The type of scan (local, remote, webapp, or agent). WAS scans will always have the type set to webapp."},"owner":{"type":"string","description":"The owner of the scan."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"read":{"type":"boolean","description":"If `true`, the scan has been read."},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"shared":{"type":"boolean","description":"If `true`, the scan is shared."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan."},"creation_date":{"type":"integer","description":"The creation date for the scan in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scan in Unix time."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"timezone":{"type":"string","description":"The timezone for the scan."},"rrules":{"type":"string","description":"The rules for repeating the scan."},"schedule_uuid":{"type":"string","description":"The schedule_uuid of the scan that should be returned."}}}},"timestamp":{"type":"integer"}}},"examples":{"response":{"value":{"folders":[{"unread_count":0,"custom":0,"default_tag":0,"type":"trash","name":"Trash","id":18},{"unread_count":0,"custom":0,"default_tag":1,"type":"main","name":"My Scans","id":19},{"unread_count":0,"custom":1,"default_tag":0,"type":"custom","name":"User3","id":34}],"scans":[{"legacy":false,"permissions":128,"type":null,"read":false,"last_modification_date":1543417257,"creation_date":1543417257,"status":"imported","uuid":"9968ccf5-dbbe-efe0-cc4c-a1b348e4eb841b20953abd11a344","shared":false,"user_permissions":128,"owner":"admin@api.demo","schedule_uuid":"428cba1c-8b40-5e3d-bdbb-e74472fade60e4282a12cd401b0b","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":false,"name":"Office - Network Scan - Auth","id":15},{"legacy":false,"permissions":128,"type":null,"read":false,"last_modification_date":1543417225,"creation_date":1543417225,"status":"imported","uuid":"09b50432-e029-201e-89b2-8b733f8a3efa8e53637fa729b116","shared":false,"user_permissions":128,"owner":"admin@api.demo","schedule_uuid":"49e7fc46-ecf4-fbc4-fa85-47c94c41023621febad27bd16e6b","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":false,"name":"DataCenters Scan","id":13},{"legacy":false,"permissions":128,"type":null,"read":false,"last_modification_date":1543417163,"creation_date":1543417163,"status":"imported","uuid":"efce931f-1f65-ac49-b022-78abeefdc085896b4f12aa7e823a","shared":false,"user_permissions":128,"owner":"admin@api.demo","schedule_uuid":"75528521-c3f5-9e31-3a40-c9e6b60d3164a486252826efc36c","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":false,"name":"Office - Agent Scan","id":11},{"legacy":false,"permissions":128,"type":null,"read":false,"last_modification_date":1543426108,"creation_date":1543426108,"status":"imported","uuid":"3a1332e9-71ef-3310-1302-ac82958eaf29423df53aecd40878","shared":false,"user_permissions":128,"owner":"admin@api.demo","schedule_uuid":"b98d669f-350d-a1e4-90e0-f6c4c11858385a3910706fdda295","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":false,"name":"DataCenters Scan","id":21},{"legacy":false,"permissions":128,"type":null,"read":false,"last_modification_date":1543417268,"creation_date":1543417268,"status":"imported","uuid":"920bd8d4-0715-1786-e8d8-58e8073c7aa2d115de514e664c2f","shared":false,"user_permissions":128,"owner":"admin@api.demo","schedule_uuid":"e688787c-0fed-b31c-6152-70478d436ed41ea88435b632b080","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":false,"name":"Cloud - PreAuth - Network Scan - Auth","id":17},{"permissions":128,"type":null,"read":true,"last_modification_date":0,"creation_date":0,"status":"empty","uuid":null,"shared":false,"user_permissions":128,"owner":"api@api.demo","schedule_uuid":"template-6950fa18-56c2-fe8b-5f3c-3a6d7b2c406485debdf5bb0ba8ef","timezone":null,"rrules":null,"starttime":"20190101T000000","enabled":true,"control":true,"name":"Basic WebApp Scan","id":38},{"permissions":128,"type":null,"read":true,"last_modification_date":0,"creation_date":0,"status":"empty","uuid":null,"shared":false,"user_permissions":128,"owner":"admin@api.demo","schedule_uuid":"template-76bd081d-e946-d72b-99e5-beb5e746e53859260f2708c44a96","timezone":null,"rrules":null,"starttime":null,"enabled":false,"control":true,"name":"test","id":29},{"permissions":128,"type":null,"read":true,"last_modification_date":0,"creation_date":0,"status":"empty","uuid":null,"shared":false,"user_permissions":128,"owner":"api@api.demo","schedule_uuid":"template-e7421587-770f-9cf1-a4b8-4dc90bd0f18887f3bd19e6c05c67","timezone":null,"rrules":null,"starttime":null,"enabled":true,"control":true,"name":"Basic Scan","id":36}],"timestamp":1545869219}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_uuid}":{"get":{"summary":"Get scan details","description":"Returns details for the specified scan.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-details","tags":["Scans"],"parameters":[{"description":"The UUID of the scan to retrieve. While scan UUID is preferred, scan ID is supported.","required":true,"name":"scan_uuid","in":"path","schema":{"type":"string"}},{"description":"The ID of the historical data that should be returned.","name":"history_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the historical data that should be returned.","name":"history_uuid","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the scan details.","content":{"application/json":{"schema":{"type":"object","properties":{"info":{"type":"object"},"hosts":{"type":"array","items":{"type":"object","properties":{"totalchecksconsidered":{"type":"integer","description":"The total number of checks considered on the host."},"numchecksconsidered":{"type":"integer","description":"The number of checks considered on the host."},"scanprogresstotal":{"type":"integer","description":"The total scan progress for the host."},"scanprogresscurrent":{"type":"integer","description":"The current scan progress for the host."},"host_index":{"type":"string","description":"The index for the host."},"score":{"type":"integer","description":"The overall score for the host."},"severitycount":{"type":"object","properties":{}},"progress":{"type":"string","description":"The scan progress of the host."},"critical":{"type":"integer","description":"The percentage of critical findings on the host."},"high":{"type":"integer","description":"The percentage of high findings on the host."},"medium":{"type":"integer","description":"The percentage of medium findings on the host."},"low":{"type":"integer","description":"The percentage of low findings on the host."},"info":{"type":"integer","description":"The percentage of info findings on the host."},"host_id":{"type":"integer","description":"The unique ID of the host."},"hostname":{"type":"string","description":"The name of the host."}}}},"notes":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"The title of the note."},"message":{"type":"string","description":"The specific message of the note."},"severity":{"type":"integer","description":"The severity of the note."}}}},"vulnerabilities":{"type":"array","items":{"type":"object","properties":{"count":{"type":"integer","description":"The number of vulnerabilities found."},"plugin_name":{"type":"string","description":"The name of the vulnerability plugin."},"vuln_index":{"type":"integer","description":"The index of the vulnerability plugin."},"severity":{"type":"integer","description":"The severity rating of the plugin."},"plugin_id":{"type":"integer","description":"The unique ID of the vulnerability plugin."},"severity_index":{"type":"integer","description":"The severity index order of the plugin."},"plugin_family":{"type":"string","description":"The parent family of the vulnerability plugin."}}}},"filters":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The short name of the filter."},"readable_name":{"type":"string","description":"The long name of the filter."},"operators":{"description":"The comparison options for the filter.","type":"array","items":{"type":"string"}},"control":{"type":"object","properties":{"type":{"type":"string","description":"The input type (entry or dropdown)."},"readable_regest":{"type":"string","description":"The placeholder for the input."},"regex":{"type":"string","description":"A regex for checking the value of the input."},"options":{"description":"A list of options if the input is a dropdown.","type":"array","items":{"type":"string"}}}}}}},"history":{"type":"array","items":{"type":"object","properties":{"alt_targets_used":{"type":"boolean","description":"If `true`, the scan was not launched with a target list. This parameter is `true` for agent scans."},"scheduler":{"type":"integer","description":"If `true`, the scan was launched automatically from a schedule."},"status":{"type":"string","description":"The status of the historical data."},"type":{"type":"string","description":"The type of scan: local, remote, or agent."},"uuid":{"type":"string","description":"The UUID of the historical data."},"last_modification_date":{"type":"integer","description":"The last modification date for the historical data in Unix time."},"creation_date":{"type":"integer","description":"The creation date for the historical data in Unix time."},"owner_id":{"type":"integer","description":"The unique ID of the owner of the scan."},"history_id":{"type":"integer","description":"The unique ID of the historical data."}}}}}},"examples":{"response":{"value":{"info":{"owner":"admin@api.demo","name":"Office - Network Scan - Auth","no_target":false,"folder_id":9,"control":false,"user_permissions":128,"schedule_uuid":"428cba1c-8b40-5e3d-bdbb-e74472fade60e4282a12cd401b0b","edit_allowed":false,"scanner_name":null,"policy":null,"shared":true,"object_id":null,"acls":null,"hostcount":36,"uuid":"9968ccf5-dbbe-efe0-cc4c-a1b348e4eb841b20953abd11a344","status":"imported","scan_type":null,"targets":null,"alt_targets_used":null,"pci-can-upload":null,"scan_start":1543417257,"timestamp":1543417257,"scan_end":1543417257,"haskb":false,"hasaudittrail":false,"scanner_start":null,"scanner_end":null},"history":[{"history_id":10503372,"owner_id":2,"creation_date":1543417257,"last_modification_date":1543417257,"uuid":"9968ccf5-dbbe-efe0-cc4c-a1b348e4eb841b20953abd11a344","type":null,"status":"imported","scheduler":0,"alt_targets_used":false}],"hosts":[{"asset_id":19,"host_id":19,"hostname":"shane.ad.demo.io","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":183,"severitylevel":0},{"count":3,"severitylevel":1},{"count":16,"severitylevel":2},{"count":91,"severitylevel":3},{"count":7,"severitylevel":4}]},"severity":300,"score":162813,"info":183,"low":3,"medium":16,"high":91,"critical":7,"host_index":0},{"asset_id":32,"host_id":32,"hostname":"sam.ad.demo.io","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":151,"severitylevel":0},{"count":2,"severitylevel":1},{"count":11,"severitylevel":2},{"count":100,"severitylevel":3},{"count":8,"severitylevel":4}]},"severity":272,"score":181271,"info":151,"low":2,"medium":11,"high":100,"critical":8,"host_index":2},{"asset_id":33,"host_id":33,"hostname":"alice.ad.demo.io","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":158,"severitylevel":0},{"count":2,"severitylevel":1},{"count":11,"severitylevel":2},{"count":90,"severitylevel":3},{"count":7,"severitylevel":4}]},"severity":268,"score":161278,"info":158,"low":2,"medium":11,"high":90,"critical":7,"host_index":4},{"asset_id":15,"host_id":15,"hostname":"brian.ad.demo.io","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":154,"severitylevel":0},{"count":2,"severitylevel":1},{"count":11,"severitylevel":2},{"count":90,"severitylevel":3},{"count":7,"severitylevel":4}]},"severity":264,"score":161274,"info":154,"low":2,"medium":11,"high":90,"critical":7,"host_index":7},{"asset_id":8,"host_id":8,"hostname":"rita.ad.demo.io","progress":"100-100/200-200","scanprogresscurrent":100,"scanprogresstotal":100,"numchecksconsidered":100,"totalchecksconsidered":100,"severitycount":{"item":[{"count":153,"severitylevel":0},{"count":2,"severitylevel":1},{"count":11,"severitylevel":2},{"count":90,"severitylevel":3},{"count":7,"severitylevel":4}]},"severity":263,"score":161273,"info":153,"low":2,"medium":11,"high":90,"critical":7,"host_index":8}],"vulnerabilities":[{"count":355,"plugin_id":34220,"plugin_name":"Netstat Portscanner (WMI)","severity":0,"plugin_family":"Port scanners","vuln_index":1},{"count":339,"plugin_id":34252,"plugin_name":"Microsoft Windows Remote Listeners Enumeration (WMI)","severity":0,"plugin_family":"Windows","vuln_index":2},{"count":157,"plugin_id":10736,"plugin_name":"DCE Services Enumeration","severity":0,"plugin_family":"Windows","vuln_index":3},{"count":87,"plugin_id":14272,"plugin_name":"Netstat Portscanner (SSH)","severity":0,"plugin_family":"Port scanners","vuln_index":4},{"count":82,"plugin_id":25221,"plugin_name":"Remote listeners enumeration (Linux / AIX)","severity":0,"plugin_family":"Service detection","vuln_index":5}],"comphosts":[],"compliance":[],"filters":[{"name":"host.id","readable_name":"Asset ID","control":{"type":"entry","regex":"[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}(,[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})*","readable_regex":"01234567-abcd-ef01-2345-6789abcdef01"},"operators":["eq","neq","match","nmatch"],"group_name":"vulnerability"},{"name":"plugin.attributes.bid","readable_name":"Bugtraq ID","control":{"type":"entry","regex":"^[0-9]+(,[0-9]+)*","readable_regex":"NUMBER","maxlength":18},"operators":["eq","neq","match","nmatch"],"group_name":"vulnerability"},{"name":"plugin.attributes.exploit_framework_canvas","readable_name":"CANVAS Exploit Framework","control":{"type":"dropdown","list":["true","false"]},"operators":["eq","neq"],"group_name":"vulnerability"},{"name":"plugin.attributes.canvas_package","readable_name":"CANVAS Package","control":{"type":"dropdown","list":["CANVAS","D2ExploitPack","White_Phosphorus"]},"operators":["eq","neq"],"group_name":"vulnerability"},{"name":"plugin.attributes.xref:CERT-CC","readable_name":"CERT Advisory ID","control":{"type":"entry","regex":"^CA-[0-9]+-[0-9]+(,CA-[0-9]+-[0-9]+)*$","readable_regex":"CA-YYYY-ID (ie: CA-2003-08)"},"operators":["eq","neq","match","nmatch"],"group_name":"vulnerability"}],"notes":[],"remediations":{"num_cves":0,"num_hosts":37,"num_remediated_cves":0,"num_impacted_hosts":0,"remediations":[]}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}":{"put":{"summary":"Update scan","description":"Updates the scan configuration. For example, you can enable or disable a scan, change the scan name, description, folder, scanner, targets, and schedule parameters.\nNote: You can specify scan targets as text, input file, or target groups.

            Requires CAN CONFIGURE [64] scan permissions. See Permissions.

            ","operationId":"was-scans-configure","tags":["Scans"],"parameters":[{"description":"The ID of the scan to change.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uuid":{"type":"string","description":"The UUID for the editor template to use."},"settings":{"type":"object","properties":{"name":{"type":"string","description":"The name of the scan."},"description":{"type":"string","description":"The description of the scan."},"policy_id":{"type":"integer","description":"The unique ID of the policy to use.","format":"int32"},"folder_id":{"type":"integer","description":"The unique ID of the destination folder for the scan.","format":"int32"},"scanner_id":{"type":"integer","description":"The unique ID of the scanner to use.","example":"1","format":"int32"},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"launch":{"type":"string","description":"When to launch the scan. (Valid values are DAILY, WEEKLY, MONTHLY, YEARLY.)","enum":["DAILY","WEEKLY","MONTHLY","YEARLY"]},"starttime":{"type":"string","description":"The starting time and date for the scan in the following format: YYYYMMDDTHHMMSS.","example":"20140826T133000"},"rrules":{"type":"string","description":"Expects a string of three values separated by semi-colons. The frequency (FREQ=ONETIME or DAILY or WEEKLY or MONTHLY or YEARLY), the interval (INTERVAL=1 or 2 or 3 ... x), and the days of the week (BYDAY=SU,MO,TU,WE,TH,FR,SA). To create a scan that runs every three weeks on Monday Wednesday and Friday the string would be `FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE,FR`","example":"FREQ=DAILY;INTERVAL=1"},"timezone":{"type":"string","description":"The timezone for the scan schedule.","example":"America/New_York"},"text_targets":{"type":"string","description":"A single URL to scan. Required for non-agent scans if no target groups are provided.","example":"localhost"},"emails":{"type":"string","description":"A comma-separated list of accounts who will receive the email summary report.","example":"test1@test.com, test2@test.com"},"acls":{"items":{"type":"string"},"description":"An array containing permissions to apply to the scan.","type":"array","example":"[{\"type\": \"default\", \"permissions\": 16}, {\"type\": \"user\", \"permissions\": 64, \"name\": \"admin\", \"id\": 1, \"owner\": 1}]"}},"required":["enabled","text_targets"]}}}}}},"responses":{"200":{"description":"Returned if the configuration was changed.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scan."},"uuid":{"type":"string","description":"The UUID for the scan."},"name":{"type":"string","description":"The name of the scan."},"type":{"type":"string","description":"The type of scan (local, remote, webapp, or agent). WAS scans will always have the type set to webapp."},"owner":{"type":"string","description":"The owner of the scan."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"read":{"type":"boolean","description":"If `true`, the scan has been read."},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"shared":{"type":"boolean","description":"If `true`, the scan is shared."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan."},"creation_date":{"type":"integer","description":"The creation date for the scan in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scan in Unix time."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"timezone":{"type":"string","description":"The timezone for the scan."},"rrules":{"type":"string","description":"The rules for repeating the scan."},"schedule_uuid":{"type":"string","description":"The schedule_uuid of the scan that should be returned."}}},"examples":{"response":{"value":{"container_id":"36f234c4-4ae3-4353-9324-8ad3dcc7fcc5","owner_uuid":"394a4be9-782d-406a-9d0a-695188260f0b","uuid":"template-6950fa18-56c2-fe8b-5f3c-3a6d7b2c406485debdf5bb0ba8ef","name":"Basic WebApp Scan","description":null,"policy_id":37,"scanner_id":null,"scanner_uuid":"00000000-0000-0000-0000-00000000000000000000000000001","emails":null,"sms":"","enabled":false,"dashboard_file":null,"include_aggregate":true,"scan_time_window":null,"custom_targets":"172.204.81.57:3030","starttime":"20181228T000000","rrules":null,"timezone":null,"notification_filters":null,"shared":0,"user_permissions":128,"default_permissions":0,"owner":"api@api.demo","owner_id":3,"last_modification_date":1545870368,"creation_date":1545869117,"type":"public","id":38}}}}}},"404":{"description":"Returned if the scan does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if an error occurred while saving the configuration.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete scan","description":"Deletes a scan.\n**Note:** You cannot delete scans in running, paused, or stopping states.

            Requires CAN CONFIGURE [64] scan permissions. See Permissions.

            ","operationId":"was-scans-delete","tags":["Scans"],"parameters":[{"description":"The ID of the scan to delete.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deleted the scan.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io failed to delete the scan.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/launch":{"post":{"summary":"Launch scan","description":"Launches a scan.

            Requires CAN CONTROL [32] scan permissions. See Permissions.

            ","operationId":"was-scans-launch","tags":["Scans"],"parameters":[{"description":"The ID of the scan to launch.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"alt_targets":{"items":{"type":"string"},"description":"If specified, the target will be scanned instead of the default. Value shall be an array with a single URL.","type":"array"}}}}}},"responses":{"200":{"description":"Returned if the scan was successfully launched.","content":{"application/json":{"schema":{"type":"object","properties":{"scan_uuid":{"type":"string","description":"The UUID of the scan."}}},"examples":{"response":{"value":{"scan_uuid":"36984557-946d-4858-8af7-ded422fee78c"}}}}}},"403":{"description":"Returned if the scan is disabled."},"404":{"description":"Returned if the scan does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/stop":{"post":{"summary":"Stop scan","description":"Stops a scan.

            Requires CAN CONTROL [32] scan permissions. See Permissions.

            ","operationId":"was-scans-stop","tags":["Scans"],"parameters":[{"description":"The ID of the scan to stop.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully queued the scan to stop.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if the scan does not exist."},"409":{"description":"Returned if the scan is not active."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/schedule":{"put":{"summary":"Enable schedule","description":"Enables or disables a scan schedule.

            Requires CAN CONTROL [32] scan permissions. See Permissions.

            ","operationId":"was-scans-schedule","tags":["Scans"],"parameters":[{"description":"The ID of the scan.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enables or disables the scan schedule."}},"required":["enabled"]}}}},"responses":{"200":{"description":"Returned if Tenable.io enabled or disabled the scan schedule.","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"control":{"type":"boolean"},"rrules":{"type":"string"},"starttime":{"type":"string"},"timezone":{"type":"string"}}},"examples":{"response":{"value":{"control":true,"enabled":false,"rrules":"FREQ=DAILY;INTERVAL=1","timezone":"US/Central","starttime":"20181206T230000"}}}}}},"404":{"description":"Returned if the scan does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if the scan does not have a schedule to enable.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/was-query/scans/{scan_uuid}/progress":{"get":{"summary":"Check scan status","description":"Retrieve the status of a web application scan that is currently running. Data returned includes scan statistics and the number of plugins identified so far.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-progress","tags":["Scans"],"parameters":[{"description":"The UUID of the scan for which you want to view the progress.","required":true,"name":"scan_uuid","in":"path","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Returns the progress of the scan.","content":{"application/json":{"schema":{"type":"object","properties":{"crawled_urls":{"type":"integer"},"queued_urls":{"type":"integer"},"audited_pages":{"type":"integer"},"queued_pages":{"type":"integer"},"request_count":{"type":"integer"},"response_time":{"type":"string"},"assets":{"type":"array","items":{"type":"object"}},"vulnerabilities":{"type":"array","items":{"type":"object"}}}},"examples":{"response":{"value":{"crawled_urls":"integer","queued_urls":"integer","audited_pages":"integer","queued_pages":"integer","request_count":"integer","response_time":"string","assets":[{"name":"string","severities":{"0":{"level":0,"name":"Info","count":"integer"},"1":{"level":1,"name":"Low","count":"integer"},"2":{"level":2,"name":"Medium","count":"integer"},"3":{"level":3,"name":"High","count":"integer"},"4":{"level":4,"name":"Critical","count":"integer"}}}],"vulnerabilities":[{"count":"integer","plugin_family":"string","plugin_id":"integer","plugin_name":"string","severity":"integer"},{"count":"integer","plugin_family":"string","plugin_id":"integer","plugin_name":"string","severity":"integer"},{"count":"integer","plugin_family":"string","plugin_id":"integer","plugin_name":"string","severity":"integer"}]}}}}}},"404":{"description":"Returned if the scan does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/status":{"put":{"summary":"Update scan status","description":"Changes the status of a scan.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-read-status","tags":["Scans"],"parameters":[{"description":"The ID of the scan to change.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean","description":"If `true`, the scan has been read."}},"required":["read"]}}}},"responses":{"200":{"description":"Returned if Tenable.io changed the status.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if the scan does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/copy":{"post":{"summary":"Copy scan","description":"Copies the given scan.

            Requires CAN CONFIGURE [64] scan permissions. See Permissions.

            ","operationId":"was-scans-copy","tags":["Scans"],"parameters":[{"description":"The ID of the scan to copy.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"folder_id":{"type":"integer","description":"The ID of the destination folder.","format":"int32"},"name":{"type":"string","description":"The name of the copied scan."}}}}}},"responses":{"200":{"description":"Returns the copied scan object.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scan."},"uuid":{"type":"string","description":"The UUID for the scan."},"name":{"type":"string","description":"The name of the scan."},"type":{"type":"string","description":"The type of scan (local, remote, webapp, or agent). WAS scans will always have the type set to webapp."},"owner":{"type":"string","description":"The owner of the scan."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"read":{"type":"boolean","description":"If `true`, the scan has been read."},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"shared":{"type":"boolean","description":"If `true`, the scan is shared."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan."},"creation_date":{"type":"integer","description":"The creation date for the scan in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scan in Unix time."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"timezone":{"type":"string","description":"The timezone for the scan."},"rrules":{"type":"string","description":"The rules for repeating the scan."},"schedule_uuid":{"type":"string","description":"The schedule_uuid of the scan that should be returned."}}},"examples":{"response":{"value":{"timezone":null,"enabled":true,"last_modification_date":1545877843,"id":42,"status":"empty","user_permissions":128,"owner":"api@api.demo","starttime":null,"control":true,"uuid":"f09066da-3817-6e3f-7244-8c10c3e0850e3c25cfc9e660ef09","rrules":null,"creation_date":1545868722,"read":false,"shared":false,"name":"Copy of Basic Scan"}}}}}},"404":{"description":"Returned if the scan does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if an error occurred while copying.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scans/import":{"post":{"summary":"Import scan","description":"Import an existing scan uploaded using file: upload.

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-scans-import","tags":["Scans"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"The name of the file to import as provided by the response from [file: upload](/reference#file-upload)."},"folder_id":{"type":"integer","description":"The ID of the destination folder. If not specified, the default folder will be used.","format":"int32"},"password":{"type":"string","description":"The password for the file to import (required for nessus.db).","format":"password"}},"required":["file"]}}}},"responses":{"200":{"description":"Returns the scan object.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The unique ID of the scan."},"uuid":{"type":"string","description":"The UUID for the scan."},"name":{"type":"string","description":"The name of the scan."},"type":{"type":"string","description":"The type of scan (local, remote, webapp, or agent). WAS scans will always have the type set to webapp."},"owner":{"type":"string","description":"The owner of the scan."},"enabled":{"type":"boolean","description":"If `true`, the schedule for the scan is enabled."},"read":{"type":"boolean","description":"If `true`, the scan has been read."},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."},"shared":{"type":"boolean","description":"If `true`, the scan is shared."},"user_permissions":{"type":"integer","description":"The sharing permissions for the scan."},"creation_date":{"type":"integer","description":"The creation date for the scan in Unix time."},"last_modification_date":{"type":"integer","description":"The last modification date for the scan in Unix time."},"control":{"type":"boolean","description":"If `true`, the scan has a schedule and can be launched."},"starttime":{"type":"string","description":"The scheduled start time for the scan."},"timezone":{"type":"string","description":"The timezone for the scan."},"rrules":{"type":"string","description":"The rules for repeating the scan."},"schedule_uuid":{"type":"string","description":"The schedule_uuid of the scan that should be returned."}}},"examples":{"response":{"value":{"scan":{"timezone":null,"id":38,"last_modification_date":1544219402,"status":"imported","user_permissions":128,"folder_id":null,"owner":"user2@example.com","control":null,"starttime":null,"uuid":"25f3b839-3e4b-aa38-252f-f4614dbe5b170e3fa8eea4c7cb27","rrules":null,"creation_date":1544219402,"read":false,"name":"Basic Scan","shared":false}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io failed to import the scan.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/export":{"post":{"summary":"Export scan","description":"Export the given scan. To see the status of the requested export, submit a scan export status request. On receiving a \"ready\" status from the was-export-status request, download the export file using the scan export download method.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-export-request","tags":["Scans"],"parameters":[{"description":"The ID of the scan to export.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the historical data that should be exported.","required":false,"name":"history_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the historical data that should be returned.","required":false,"name":"history_uuid","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The value `web-app`. This parameter is required only when using the API with Web Application Scanning.","required":true,"name":"type","in":"query","schema":{"type":"string","enum":["web-app"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string","description":"The file format to use. For Web Application Scanning, supported export formats are Nessus, CSV, and DB.","enum":["nessus","csv","db"]},"password":{"type":"string","description":"The password used to encrypt database exports (\\*Required when exporting as DB).","format":"password"},"chapters":{"type":"string","description":"The chapters to include in the export (expecting a semi-colon delimited string comprised of some combination of the following options: vuln\\_hosts\\_summary, vuln\\_by\\_host, compliance\\_exec, remediations, vuln\\_by\\_plugin, compliance)"}},"required":["format"]}}}},"responses":{"200":{"description":"Returned if the export was queued successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string"},"temp_token":{"type":"string"}}},"examples":{"response":{"value":{"file":"a50d608e-8a05-4521-9896-3f99b2558d30"}}}}}},"400":{"description":"Returned if a required parameter is missing."},"404":{"description":"Returned if the scan does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/export/{file_uuid}/status":{"get":{"summary":"Check scan export status","description":"Check the file status of an exported scan. After you request an export, you must poll this endpoint until a \"ready\" status is returned, at which point the file is complete and can be downloaded using the was-export-download endpoint.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-export-status","tags":["Scans"],"parameters":[{"description":"The ID of the scan to export.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the file to poll (Included in response from /was-scans/{scan\\_id}/export).","required":true,"name":"file_uuid","in":"path","schema":{"type":"string"}},{"description":"The value `web-app`. This parameter is required only when using the API with Web Application Scanning.","required":true,"name":"type","in":"query","schema":{"type":"string","enum":["web-app"]}}],"responses":{"200":{"description":"Returns the status of the file. A status of `ready` indicates the file can be downloaded.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"The export status."}}},"examples":{"response":{"value":{"status":"ready"}}}}}},"404":{"description":"Returned if the file does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/export/{file_uuid}/download":{"get":{"summary":"Download exported scan","description":"Download an exported scan.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-export-download","tags":["Scans"],"parameters":[{"description":"The ID of the scan to export.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the file to download (Included in response from /scans/{scan\\_id}/export).","required":true,"name":"file_uuid","in":"path","schema":{"type":"string"}},{"description":"The value `web-app`. This parameter is required only when using the API with Web Application Scanning.","required":false,"name":"type","in":"query","schema":{"type":"string","enum":["web-app"]}}],"responses":{"200":{"description":"Returns the content of the file as an attachment.","content":{"application/octet-stream":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if the file does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/history":{"get":{"summary":"Get scan history","description":"Returns a scan's history records.

            Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-history","tags":["Scans"],"parameters":[{"description":"The identifier for the scan. This identifier can be the either the `schedule_uuid` or the numeric `id` attribute for the scan. We recommend that you use `schedule_uuid`.","required":true,"name":"scan_id","in":"path","schema":{"type":"string"}},{"description":"Maximum number of objects requested (or service imposed limit if not in request). The max limit value allowed is 50. Must be in the int32 format.","required":false,"name":"limit","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"Offset from request (or zero). Must be in the int32 format.","required":false,"name":"offset","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns a scan's history records.","content":{"application/json":{"schema":{"type":"object","properties":{"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"The total number of objects matching your search criteria. Must be in the int32 format."},"limit":{"type":"integer","description":"Maximum number of objects requested (or service imposed limit if not in request). Must be in the int32 format."},"offset":{"type":"integer","description":"Offset from request (or zero). Must be in the int32 format."},"sort":{"description":"An array of objects representing the fields you specified as sort fields in the request message, which Tenable.io uses to sort the returned data.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The field on which Tenable.io sorts the results."},"order":{"type":"string","description":"The direction of the sort order. Supported values are `asc` (ascending) and `desc` (descending)."}}}}}},"history":{"type":"array","items":{"type":"object","properties":{"time_end":{"type":"integer","description":"The date the scan completed in Unix time."},"scan_uuid":{"type":"string","description":"The scan history's UUID."},"time_start":{"type":"integer","description":"The date the scan started in Unix time."},"visibility":{"type":"string","description":"The visibility of the scan in workbenches (public or private)."},"targets":{"type":"object","description":"The target parameters used to launch the scan.","properties":{"custom":{"type":"boolean","description":"If `true`, then custom parameters were used to launch the scan."},"default":{"type":"boolean","description":"If `true`, then default parameters were used to launch the scan.."}}},"status":{"type":"string","description":"The status of the scan (completed, aborted, imported, pending, running, resuming, canceling, canceled, pausing, paused, stopping, stopped)."}}}}}},"examples":{"response":{"value":{"pagination":{"offset":0,"total":8,"sort":[{"order":"DESC","name":"start_date"}],"limit":50},"history":[{"time_end":1545945607,"scan_uuid":"1732621d-a7c3-4295-bbc9-37035112ff0a","id":10535512,"time_start":1545945482,"visibility":"public","targets":{"custom":false,"default":null},"status":"canceled"},{"time_end":1545945457,"scan_uuid":"cd5c32e9-0b66-4c31-b61a-8d1bdd8a67ad","id":10535505,"time_start":1545945321,"visibility":"public","targets":{"custom":false,"default":null},"status":"completed"},{"time_end":1545944767,"scan_uuid":"34e04696-2abf-4767-86cb-c51eb26a3511","id":10535496,"time_start":1545944637,"visibility":"public","targets":{"custom":false,"default":null},"status":"completed"},{"time_end":1545877987,"scan_uuid":"47ee2c49-9422-4082-9b5b-48d0883bc76e","id":10534608,"time_start":1545877843,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"},{"time_end":1545877717,"scan_uuid":"0a20f6f1-cb6e-4947-ab71-8121dddff8e9","id":10534601,"time_start":1545877590,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"},{"time_end":1545877057,"scan_uuid":"2b346502-d769-452e-9c2e-0c50033852d2","id":10534598,"time_start":1545876907,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"},{"time_end":1545871897,"scan_uuid":"e80fa271-8a6e-44e8-bdcf-dc75274d4b25","id":10534540,"time_start":1545871758,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"},{"time_end":1545871177,"scan_uuid":"36984557-946d-4858-8af7-ded422fee78b","id":10534536,"time_start":1545871035,"visibility":"public","targets":{"custom":false,"default":null},"status":"aborted"}]}}}}}},"404":{"description":"Returned if Tenable.io cannot find the specified `scan_id`."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/history/{history_uuid}":{"get":{"summary":"Get scan history details","description":"Returns the details of a previous result of a scan.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-history-details","tags":["Scans"],"parameters":[{"description":"The ID of the scan to retrieve.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the historical scan result to return details about.","required":true,"name":"history_uuid","in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns details of the historical scan result.","content":{"application/json":{"schema":{"type":"object","properties":{"owner_id":{"type":"integer"},"schedule_uuid":{"type":"string"},"scan_start":{"type":"integer"},"scan_end":{"type":"integer"},"owner_uuid":{"type":"string"},"owner":{"type":"string"},"targets":{"type":"string"},"object_id":{"type":"integer"},"uuid":{"type":"string"},"scan_type":{"type":"string"},"name":{"type":"string"}}},"examples":{"response":{"value":{"owner_id":2,"schedule_uuid":"e688787c-0fed-b31c-6152-70478d436ed41ea88435b632b080","status":"imported","scan_start":1543417268,"owner_uuid":"7a676323-47bb-4838-9cec-c9f01448bb2d","owner":"admin@api.demo","targets":null,"object_id":10503373,"uuid":"920bd8d4-0715-1786-e8d8-58e8073c7aa2d115de514e664c2f","scan_end":null,"scan_type":null,"name":"Cloud - PreAuth - Network Scan - Auth"}}}}}},"404":{"description":"Returned if scan_id or history_uuid are not found."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]},"delete":{"summary":"Delete scan history","description":"Deletes historical results from a scan.

            Requires CAN CONFIGURE [64] scan permissions. See Permissions.

            ","operationId":"was-scans-delete-history","tags":["Scans"],"parameters":[{"description":"The ID of the scan.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the results to delete.","required":true,"name":"history_uuid","in":"path","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Returned if Tenable.io successfully deleted the results.","content":{"application/json":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if Tenable.io could not find the results."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}},"500":{"description":"Returned if Tenable.io failed to delete the results.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":500,"error":"Internal Server Error","message":"An internal server error occurred. Please wait a moment and try your request again."}}}}}},"501":{"description":"Returned if Tenable.io does not support deleting historical scan results.","content":{"application/json":{"examples":{"response":{"value":{"statusCode":501,"error":"Not Implemented","message":"This feature is not yet implemented."}}}}}}},"security":[{"cloud":[]}]}},"/was-query/scans/{scan_uuid}/hosts/{host_id}":{"get":{"summary":"Return host details","description":"Returns details for the specified host.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-host-details","tags":["Scans"],"parameters":[{"description":"The UUID of the scan to retrieve. While scan UUID is preferred, scan ID is supported.","required":true,"name":"scan_uuid","in":"path","schema":{"type":"string"}},{"description":"The ID of the host to retrieve.","required":true,"name":"host_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the historical data that should be returned.","name":"history_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the historical data that should be returned.","name":"history_uuid","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the host details.","content":{"application/json":{"schema":{"type":"object","properties":{"info":{"type":"object"},"vulnerabilities":{"type":"array","items":{"type":"object","properties":{"host_id":{"type":"integer","description":"The unique ID of the host where the scan identified the vulnerability."},"hostname":{"type":"string","description":"The name of the host where the scan identified the vulnerability."},"plugin_id":{"type":"integer","description":"The unique ID of the vulnerability plugin."},"plugin_name":{"type":"string","description":"The name of the vulnerability plugin."},"plugin_family":{"type":"string","description":"The parent family of the vulnerability plugin."},"count":{"type":"integer","description":"The number of vulnerabilities found."},"vuln_index":{"type":"integer","description":"The index of the vulnerability plugin."},"severity_index":{"type":"integer","description":"The severity index order of the plugin."},"severity":{"type":"integer","description":"The severity of plugin."}}}}}},"examples":{"response":{"value":{"info":{"mac-address":null,"host-fqdn":"ip-10-10-2-49.ec2.internal","host-ip":"10.10.2.49","operating-system":["Linux Kernel 3.16.0-4-amd64 on Debian 8.7"],"host_end":"Wed Nov 28 15:01:08 2018","host_start":"Wed Nov 28 15:01:08 2018"},"vulnerabilities":[{"count":1,"host_id":8,"hostname":"ip-10-10-2-49.ec2.internal","plugin_family":"General","plugin_id":10114,"plugin_name":"ICMP Timestamp Request Remote Date Disclosure","severity":0,"severity_index":0,"vuln_index":0},{"count":1,"host_id":8,"hostname":"ip-10-10-2-49.ec2.internal","plugin_family":"Service detection","plugin_id":10267,"plugin_name":"SSH Server Type and Version Information","severity":0,"severity_index":0,"vuln_index":0},{"count":1,"host_id":8,"hostname":"ip-10-10-2-49.ec2.internal","plugin_family":"General","plugin_id":10287,"plugin_name":"Traceroute Information","severity":0,"severity_index":0,"vuln_index":0},{"count":1,"host_id":8,"hostname":"ip-10-10-2-49.ec2.internal","plugin_family":"General","plugin_id":10881,"plugin_name":"SSH Protocol Versions Supported","severity":0,"severity_index":0,"vuln_index":0}],"compliance":[]}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/was-query/scans/{scan_uuid}/hosts/{host_id}/plugins/{plugin_id}":{"get":{"summary":"Get plugin output","description":"Returns the output for a given plugin.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-plugin-output","tags":["Scans"],"parameters":[{"description":"The UUID of the scan to retrieve. While scan UUID is preferred, scan ID is supported.","required":true,"name":"scan_uuid","in":"path","schema":{"type":"string"}},{"description":"The ID of the host to retrieve.","required":true,"name":"host_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the plugin to retrieve.","required":true,"name":"plugin_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the historical data that should be returned.","name":"history_id","in":"query","schema":{"type":"integer","format":"int32"}},{"description":"The UUID of the historical data that should be returned.","name":"history_uuid","in":"query","schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Returns the plugin output.","content":{"application/json":{"schema":{"type":"object","properties":{"output":{"type":"array","items":{"type":"object","properties":{"ports":{"type":"object","properties":{}},"has_attachment":{"type":"integer","description":"If the value is `1`, the plugin output contains files that may be exported."},"custom_description":{"type":"string","description":"A custom description of the plugin."},"plugin_output":{"type":"string","description":"The text of the plugin output."},"hosts":{"type":"string","description":"Other hosts with the same output."},"severity":{"type":"integer","description":"The severity of the output."}}}},"info":{"type":"object"}}},"examples":{"response":{"value":{"outputs":[{"ports":{"0 / tcp":[{"hostname":"ip-10-10-2-49.ec2.internal"}]},"has_attachment":0,"severity":0,"plugin_output":"\nHere is the list of packages installed on the remote Debian Linux system : \n\n ii acl 2.2.52-2 amd64 Access control list utilities\n ii adduser 3.113+nmu3 all add and remove users and groups\n ii apt 1.0.9.8.4 amd64 commandline package manager\n ii apt-transport-https 1.0.9.8.4 amd64 https download transport for APT\n ii apt-utils 1.0.9.8.4 amd64 package management related utility programs\n ii awscli 1.4.2-1 all Universal Command Line Environment for AWS\n ii base-files 8+deb8u7 amd64 Debian base system miscellaneous files\n ii base-passwd 3.5.37 amd64 Debian base system master password and group files\n ii bash 4.3-11+deb8u1 amd64 GNU Bourne Again SHell\n ii binutils 2.25-5 amd64 GNU assembler, linker and binary utilities\n ii bsdmainutils 9.0.6 amd64 collection of more utilities from FreeBSD\n ii bsdutils 1:2.25.2-6 amd64 basic utilities from 4.4BSD-Lite\n ii ca-certificates 20141019+deb8u2 all Common CA certificates\n ii cloud-guest-utils 0.29-1~bpo8+1 all cloud guest utilities\n ii cloud-image-utils 0.29-1~bpo8+1 all cloud image management utilities\n ii cloud-init 0.7.7~bzr1156-1~bpo8+1 all initialization system for infrastructure cloud instances\n ii cloud-utils 0.29-1~bpo8+1 all metapackage for installation of upstream cloud-utils source\n ii coreutils 8.23-4 amd64 GNU core utilities\n ii cpio 2.11+dfsg-4.1+deb8u1 amd64 GNU cpio -- a program to manage archives of files\n ii cpp 4:4.9.2-2 amd64 GNU C preprocessor (cpp)\n ii cpp-4.8 4.8.4-1 amd64 GNU C preprocessor\n ii cpp-4.9 4.9.2-10 amd64 GNU C preprocessor\n ii cron 3.0pl1-127+deb8u1 amd64 process scheduling daemon\n ii dash 0.5.7-4+b1 amd64 POSIX-compliant shell\n ii debconf 1.5.56 all Debian configuration management system\n ii debconf-i18n 1.5.56 all full internationalization support for debconf\n ii debian-archive-keyring 2014.3 all GnuPG archive keys of the Debian archive\n ii debianutils 4.4+b1 amd64 Miscellaneous utilities specific to Debian\n ii dh-python 1.20141111-2 all Debian helper tools for packaging Python libraries and applications\n ii diffutils 1:3.3-1+b1 amd64 File comparison utilities\n ii dkms 2.2.0.3-2 all Dynamic Kernel Module Support Framework\n ii dmeventd 2:1.02.90-2.2+deb8u1 amd64 Linux Kernel Device Mapper event daemon\n ii dmidecode 2.12-3 amd64 SMBIOS/DMI table decoder\n ii dmsetup 2:1.02.90-2.2+deb8u1 amd64 Linux Kernel Device Mapper userspace library\n ii docutils-common 0.12+dfsg-1 all text processing system for reStructuredText - common data\n ii dpkg 1.17.27 amd64 Debian package management system\n ii e2fslibs 1.42.12-2+b1 amd64 ext2/ext3/ext4 file system libraries\n ii e2fsprogs 1.42.12-2+b1 amd64 ext2/ext3/ext4 file system utilities\n ii file 1:5.22+15-2+deb8u3 amd64 Determines file type using \"magic\" numbers\n ii findutils 4.4.2-9+b1 amd64 utilities for finding files--find, xargs\n ii gcc 4:4.9.2-2 amd64 GNU C compiler\n ii gcc-4.8 4.8.4-1 amd64 GNU C compiler\n ii gcc-4.8-base 4.8.4-1 amd64 GCC, the GNU Compiler Collection (base package)\n ii gcc-4.9 4.9.2-10 amd64 GNU C compiler\n ii gcc-4.9-base 4.9.2-10 amd64 GCC, the GNU Compiler Collection (base package)\n ii gdisk 0.8.10-2 amd64 GPT fdisk text-mode partitioning tool\n ii genisoimage 9:1.1.11-3 amd64 Creates ISO-9660 CD-ROM filesystem images\n ii gettext-base 0.19.3-2 amd64 GNU Internationalization utilities for the base system\n ii gnupg 1.4.18-7+deb8u3 amd64 GNU privacy guard - a free PGP replacement\n ii gpgv 1.4.18-7+deb8u3 amd64 GNU privacy guard - signature verification tool\n ii grep 2.20-4.1 amd64 GNU grep, egrep and fgrep\n ii groff-base 1.22.2-8 amd64 GNU troff text-formatting system (base system components)\n ii grub-common 2.02~beta2-22+deb8u1 amd64 GRand Unified Bootloader (common files)\n ii grub-pc 2.02~beta2-22+deb8u1 amd64 GRand Unified Bootloader, version 2 (PC/BIOS version)\n ii grub-pc-bin 2.02~beta2-22+deb8u1 amd64 GRand Unified Bootloader, version 2 (PC/BIOS binaries)\n ii grub2-common 2.02~beta2-22+deb8u1 amd64 GRand Unified Bootloader (common files for version 2)\n ii gzip 1.6-4 amd64 GNU compression utilities\n ii hostname 3.15 amd64 utility to set/show the host name or domain name\n ii ifupdown 0.7.53.1 amd64 high level tools to configure network interfaces\n ii init 1.22 amd64 System-V-like init utilities - metapackage\n ii init-system-helpers 1.22 all helper tools for all init systems\n ii initramfs-tools 0.120+deb8u2 all generic modular initramfs generator\n ii initscripts 2.88dsf-59 amd64 scripts for initializing and shutting down the system\n ii insserv 1.14.0-5 amd64 boot sequence organizer using LSB init.d script dependency information\n ii iproute2 3.16.0-2 amd64 networking and traffic control tools\n ii iptables 1.4.21-2+b1 amd64 administration tools for packet filtering and NAT\n ii iputils-ping 3:20121221-5+b2 amd64 Tools to test the reachability of network hosts\n ii isc-dhcp-client 4.3.1-6+deb8u2 amd64 DHCP client for automatically obtaining an IP address\n ii isc-dhcp-common 4.3.1-6+deb8u2 amd64 common files used by all of the isc-dhcp packages\n ii klibc-utils 2.0.4-2 amd64 small utilities built with klibc for early boot\n ii kmod 18-3 amd64 tools for managing Linux kernel modules\n ii less 458-3 amd64 pager program similar to more\n ii libacl1 2.2.52-2 amd64 Access control list shared library\n ii libaio1 0.3.110-1 amd64 Linux kernel AIO access library - shared library\n ii libapt-inst1.5 1.0.9.8.4 amd64 deb package format runtime library\n ii libapt-pkg4.12 1.0.9.8.4 amd64 package management runtime library\n ii libasan0 4.8.4-1 amd64 AddressSanitizer -- a fast memory error detector\n ii libasan1 4.9.2-10 amd64 AddressSanitizer -- a fast memory error detector\n ii libasprintf0c2 0.19.3-2 amd64 GNU library to use fprintf and friends in C++\n ii libatomic1 4.9.2-10 amd64 support library providing __atomic built-in functions\n ii libattr1 1:2.4.47-2 amd64 Extended attribute shared library\n ii libaudit-common 1:2.4-1 all Dynamic library for security auditing - common files\n ii libaudit1 1:2.4-1+b1 amd64 Dynamic library for security auditing\n ii libblkid1 2.25.2-6 amd64 block device id library\n ii libboost-iostreams1.55.0 1.55.0+dfsg-3 amd64 Boost.Iostreams Library\n ii libboost-system1.55.0 1.55.0+dfsg-3 amd64 Operating system (e.g. diagnostics support) library\n ii libboost-thread1.55.0 1.55.0+dfsg-3 amd64 portable C++ multi-threading\n ii libbsd0 0.7.0-2 amd64 utility functions from BSD systems - shared library\n ii libbz2-1.0 1.0.6-7+b3 amd64 high-quality block-sorting file compressor library - runtime\n ii libc-bin 2.19-18+deb8u7 amd64 GNU C Library: Binaries\n ii libc6 2.19-18+deb8u7 amd64 GNU C Library: Shared libraries\n ii libcap2 1:2.24-8 amd64 POSIX 1003.1e capabilities (library)\n ii libcap2-bin 1:2.24-8 amd64 POSIX 1003.1e capabilities (utilities)\n ii libcilkrts5 4.9.2-10 amd64 Intel Cilk Plus language extensions (runtime)\n ii libcloog-isl4 0.18.2-1+b2 amd64 Chunky Loop Generator (runtime library)\n ii libcomerr2 1.42.12-2+b1 amd64 common error description library\n ii libcryptsetup4 2:1.6.6-5 amd64 disk encryption support - shared library\n ii libcurl3-gnutls 7.38.0-4+deb8u5 amd64 easy-to-use client-side URL transfer library (GnuTLS flavour)\n ii libdb5.3 5.3.28-9 amd64 Berkeley v5.3 Database Libraries [runtime]\n ii libdebconfclient0 0.192 amd64 Debian Configuration Management System (C-implementation library)\n ii libdevmapper-event1.02.1 2:1.02.90-2.2+deb8u1 amd64 Linux Kernel Device Mapper event support library\n ii libdevmapper1.02.1 2:1.02.90-2.2+deb8u1 amd64 Linux Kernel Device Mapper userspace library\n ii libdns-export100 1:9.9.5.dfsg-9+deb8u9 amd64 Exported DNS Shared Library\n ii libedit2 3.1-20140620-2 amd64 BSD editline and history libraries\n ii libestr0 0.1.9-1.1 amd64 Helper functions for handling strings (lib)\n ii libexpat1 2.1.0-6+deb8u3 amd64 XML parsing C library - runtime library\n ii libffi6 3.1-2+b2 amd64 Foreign Function Interface library runtime\n ii libfreetype6 2.5.2-3+deb8u1 amd64 FreeType 2 font engine, shared library files\n ii libfuse2 2.9.3-15+deb8u2 amd64 Filesystem in Userspace (library)\n ii libgcc-4.8-dev 4.8.4-1 amd64 GCC support library (development files)\n ii libgcc-4.9-dev 4.9.2-10 amd64 GCC support library (development files)\n ii libgcc1 1:4.9.2-10 amd64 GCC support library\n ii libgcrypt20 1.6.3-2+deb8u2 amd64 LGPL Crypto library - runtime library\n ii libgdbm3 1.8.3-13.1 amd64 GNU dbm database routines (runtime version)\n ii libglib2.0-0 2.42.1-1+b1 amd64 GLib library of C routines\n ii libgmp10 2:6.0.0+dfsg-6 amd64 Multiprecision arithmetic library\n ii libgnutls-deb0-28 3.3.8-6+deb8u4 amd64 GNU TLS library - main runtime library\n ii libgnutls-openssl27 3.3.8-6+deb8u4 amd64 GNU TLS library - OpenSSL wrapper\n ii libgomp1 4.9.2-10 amd64 GCC OpenMP (GOMP) support library\n ii libgpg-error0 1.17-3 amd64 library for common error values and messages in GnuPG components\n ii libgssapi-krb5-2 1.12.1+dfsg-19+deb8u2 amd64 MIT Kerberos runtime libraries - krb5 GSS-API Mechanism\n ii libhogweed2 2.7.1-5+deb8u2 amd64 low level cryptographic library (public-key cryptos)\n ii libicu52 52.1-8+deb8u4 amd64 International Components for Unicode\n ii libidn11 1.29-1+deb8u2 amd64 GNU Libidn library, implementation of IETF IDN specifications\n ii libirs-export91 1:9.9.5.dfsg-9+deb8u9 amd64 Exported IRS Shared Library\n ii libisc-export95 1:9.9.5.dfsg-9+deb8u9 amd64 Exported ISC Shared Library\n ii libisccfg-export90 1:9.9.5.dfsg-9+deb8u9 amd64 Exported ISC CFG Shared Library\n ii libiscsi2 1.12.0-2 amd64 iSCSI client shared library\n ii libisl10 0.12.2-2 amd64 manipulating sets and relations of integer points bounded by linear constraints\n ii libitm1 4.9.2-10 amd64 GNU Transactional Memory Library\n ii libjson-c2 0.11-4 amd64 JSON manipulation library - shared library\n ii libk5crypto3 1.12.1+dfsg-19+deb8u2 amd64 MIT Kerberos runtime libraries - Crypto Library\n ii libkeyutils1 1.5.9-5+b1 amd64 Linux Key Management Utilities (library)\n ii libklibc 2.0.4-2 amd64 minimal libc subset for use with initramfs\n ii libkmod2 18-3 amd64 libkmod shared library\n ii libkrb5-3 1.12.1+dfsg-19+deb8u2 amd64 MIT Kerberos runtime libraries\n ii libkrb5support0 1.12.1+dfsg-19+deb8u2 amd64 MIT Kerberos runtime libraries - Support library\n ii libldap-2.4-2 2.4.40+dfsg-1+deb8u2 amd64 OpenLDAP libraries\n ii liblocale-gettext-perl 1.05-8+b1 amd64 module using libc functions for internationalization in Perl\n ii liblogging-stdlog0 1.0.4-1 amd64 easy to use and lightweight logging library\n ii liblognorm1 1.0.1-3 amd64 Log normalizing library\n ii liblsan0 4.9.2-10 amd64 LeakSanitizer -- a memory leak detector (runtime)\n ii liblvm2cmd2.02 2.02.111-2.2+deb8u1 amd64 LVM2 command library\n ii liblzma5 5.1.1alpha+20120614-2+b3 amd64 XZ-format compression library\n ii libmagic1 1:5.22+15-2+deb8u3 amd64 File type determination library using \"magic\" numbers\n ii libmnl0 1.0.3-5 amd64 minimalistic Netlink communication library\n ii libmount1 2.25.2-6 amd64 device mounting library\n ii libmpc3 1.0.2-1 amd64 multiple precision complex floating-point library\n ii libmpdec2 2.4.1-1 amd64 library for decimal floating point arithmetic (runtime library)\n ii libmpfr4 3.1.2-2 amd64 multiple precision floating-point computation\n ii libncurses5 5.9+20140913-1+b1 amd64 shared libraries for terminal handling\n ii libncursesw5 5.9+20140913-1+b1 amd64 shared libraries for terminal handling (wide character support)\n ii libnetfilter-acct1 1.0.2-1.1 amd64 Netfilter acct library\n ii libnettle4 2.7.1-5+deb8u2 amd64 low level cryptographic library (symmetric and one-way cryptos)\n ii libnewt0.52 0.52.17-1+b1 amd64 Not Erik's Windowing Toolkit - text mode windowing with slang\n ii libnfnetlink0 1.0.1-3 amd64 Netfilter netlink library\n ii libnspr4 2:4.12-1+debu8u1 amd64 NetScape Portable Runtime Library\n ii libnss3 2:3.26-1+debu8u1 amd64 Network Security Service libraries\n ii libp11-kit0 0.20.7-1 amd64 Library for loading and coordinating access to PKCS#11 modules - runtime\n ii libpam-modules 1.1.8-3.1+deb8u2 amd64 Pluggable Authentication Modules for PAM\n ii libpam-modules-bin 1.1.8-3.1+deb8u2 amd64 Pluggable Authentication Modules for PAM - helper binaries\n ii libpam-runtime 1.1.8-3.1+deb8u2 all Runtime support for the PAM library\n ii libpam0g 1.1.8-3.1+deb8u2 amd64 Pluggable Authentication Modules library\n ii libparted2 3.2-7 amd64 disk partition manipulator - shared library\n ii libpcre3 2:8.35-3.3+deb8u4 amd64 Perl 5 Compatible Regular Expression Library - runtime files\n ii libpipeline1 1.4.0-1 amd64 pipeline manipulation library\n ii libpng12-0 1.2.50-2+deb8u3 amd64 PNG library - runtime\n ii libpopt0 1.16-10 amd64 lib for parsing cmdline parameters\n ii libprocps3 2:3.3.9-9 amd64 library for accessing process information from /proc\n ii libpsl0 0.5.1-1 amd64 Library for Public Suffix List (shared libraries)\n ii libpython-stdlib 2.7.9-1 amd64 interactive high-level object-oriented language (default python version)\n ii libpython2.7-minimal 2.7.9-2+deb8u1 amd64 Minimal subset of the Python language (version 2.7)\n ii libpython2.7-stdlib 2.7.9-2+deb8u1 amd64 Interactive high-level object-oriented language (standard library, version 2.7)\n ii libpython3-stdlib 3.4.2-2 amd64 interactive high-level object-oriented language (default python3 version)\n ii libpython3.4-minimal 3.4.2-1 amd64 Minimal subset of the Python language (version 3.4)\n ii libpython3.4-stdlib 3.4.2-1 amd64 Interactive high-level object-oriented language (standard library, version 3.4)\n ii libquadmath0 4.9.2-10 amd64 GCC Quad-Precision Math Library\n ii librados2 0.80.7-2+deb8u2 amd64 RADOS distributed object store client library\n ii librbd1 0.80.7-2+deb8u2 amd64 RADOS block device client library\n ii libreadline5 5.2+dfsg-2 amd64 GNU readline and history libraries, run-time libraries\n ii libreadline6 6.3-8+b3 amd64 GNU readline and history libraries, run-time libraries\n ii librtmp1 2.4+20150115.gita107cef-1 amd64 toolkit for RTMP streams (shared library)\n ii libsasl2-2 2.1.26.dfsg1-13+deb8u1 amd64 Cyrus SASL - authentication abstraction library\n ii libsasl2-modules-db 2.1.26.dfsg1-13+deb8u1 amd64 Cyrus SASL - pluggable authentication modules (DB)\n ii libselinux1 2.3-2 amd64 SELinux runtime shared libraries\n ii libsemanage-common 2.3-1 all Common files for SELinux policy management libraries\n ii libsemanage1 2.3-1+b1 amd64 SELinux policy management library\n ii libsepol1 2.3-2 amd64 SELinux library for manipulating binary security policies\n ii libsigc++-2.0-0c2a 2.4.0-1 amd64 type-safe Signal Framework for C++ - runtime\n ii libslang2 2.3.0-2 amd64 S-Lang programming library - runtime version\n ii libsmartcols1 2.25.2-6 amd64 smart column output alignment library\n ii libsqlite3-0 3.8.7.1-1+deb8u2 amd64 SQLite 3 shared library\n ii libss2 1.42.12-2+b1 amd64 command-line interface parsing library\n ii libssh2-1 1.4.3-4.1+deb8u1 amd64 SSH2 client-side library\n ii libssl1.0.0 1.0.1t-1+deb8u5 amd64 Secure Sockets Layer toolkit - shared libraries\n ii libstdc++6 4.9.2-10 amd64 GNU Standard C++ Library v3\n ii libsystemd0 215-17+deb8u6 amd64 systemd utility library\n ii libtasn1-6 4.2-3+deb8u2 amd64 Manage ASN.1 structures (runtime)\n ii libtext-charwidth-perl 0.04-7+b3 amd64 get display widths of characters on the terminal\n ii libtext-iconv-perl 1.7-5+b2 amd64 converts between character sets in Perl\n ii libtext-wrapi18n-perl 0.06-7 all internationalized substitute of Text::Wrap\n ii libtinfo5 5.9+20140913-1+b1 amd64 shared low-level terminfo library for terminal handling\n ii libtsan0 4.9.2-10 amd64 ThreadSanitizer -- a Valgrind-based detector of data races (runtime)\n ii libubsan0 4.9.2-10 amd64 UBSan -- undefined behaviour sanitizer (runtime)\n ii libudev1 215-17+deb8u6 amd64 libudev shared library\n ii libusb-0.1-4 2:0.1.12-25 amd64 userspace USB programming library\n ii libustr-1.0-1 1.0.4-3+b2 amd64 Micro string library: shared library\n ii libuuid-perl 0.05-1+b1 amd64 Perl extension for using UUID interfaces as defined in e2fsprogs\n ii libuuid1 2.25.2-6 amd64 Universally Unique ID library\n ii libwrap0 7.6.q-25 amd64 Wietse Venema's TCP wrappers library\n ii libxtables10 1.4.21-2+b1 amd64 netfilter xtables library\n ii libyaml-0-2 0.1.6-3 amd64 Fast YAML 1.1 parser and emitter library\n ii linux-base 3.5 all Linux image base package\n ii linux-compiler-gcc-4.8-x86 3.16.39-1 amd64 Compiler for Linux on x86 (meta-package)\n ii linux-headers-3.16.0-4-amd64 3.16.39-1 amd64 Header files for Linux 3.16.0-4-amd64\n ii linux-headers-3.16.0-4-common 3.16.39-1 amd64 Common header files for Linux 3.16.0-4\n ii linux-headers-amd64 3.16+63 amd64 Header files for Linux amd64 configuration (meta-package)\n ii linux-image-3.16.0-4-amd64 3.16.39-1 amd64 Linux 3.16 for 64-bit PCs\n ii linux-image-amd64 3.16+63 amd64 Linux for 64-bit PCs (meta-package)\n ii linux-kbuild-3.16 3.16.7-ckt20-1 amd64 Kbuild infrastructure for Linux 3.16\n ii locales 2.19-18+deb8u7 all GNU C Library: National Language (locale) data [support]\n ii login 1:4.2-3+deb8u1 amd64 system login tools\n ii logrotate 3.8.7-1+b1 amd64 Log rotation utility\n ii lsb-base 4.1+Debian13+nmu1 all Linux Standard Base 4.1 init script functionality\n ii lsb-release 4.1+Debian13+nmu1 all Linux Standard Base version reporting utility\n ii lvm2 2.02.111-2.2+deb8u1 amd64 Linux Logical Volume Manager\n ii make 4.0-8.1 amd64 utility for directing compilation\n ii man-db 2.7.0.2-5 amd64 on-line manual pager\n ii manpages 3.74-1 all Manual pages about using a GNU/Linux system\n ii mawk 1.3.3-17 amd64 a pattern scanning and text processing language\n ii mime-support 3.58 all MIME files `mime.types` & `mailcap`, and support programs\n ii mount 2.25.2-6 amd64 Tools for mounting and manipulating filesystems\n ii multiarch-support 2.19-18+deb8u7 amd64 Transitional package to ensure multiarch compatibility\n ii nano 2.2.6-3 amd64 small, friendly text editor inspired by Pico\n ii ncurses-base 5.9+20140913-1 all basic terminal type definitions\n ii ncurses-bin 5.9+20140913-1+b1 amd64 terminal-related programs and man pages\n ii ncurses-term 5.9+20140913-1 all additional terminal type definitions\n ii net-tools 1.60-26+b1 amd64 NET-3 networking toolkit\n ii netbase 5.3 all Basic TCP/IP networking system\n ii netcat-traditional 1.10-41 amd64 TCP/IP swiss army knife\n ii nfacct 1.0.1-1.1 amd64 netfilter accounting object tool\n ii openssh-client 1:6.7p1-5+deb8u3 amd64 secure shell (SSH) client, for secure access to remote machines\n ii openssh-server 1:6.7p1-5+deb8u3 amd64 secure shell (SSH) server, for secure access from remote machines\n ii openssh-sftp-server 1:6.7p1-5+deb8u3 amd64 secure shell (SSH) sftp server module, for SFTP access from remote machines\n ii openssl 1.0.1t-1+deb8u5 amd64 Secure Sockets Layer toolkit - cryptographic utility\n ii parted 3.2-7 amd64 disk partition manipulator\n ii passwd 1:4.2-3+deb8u1 amd64 change and administer password and group data\n ii patch 2.7.5-1 amd64 Apply a diff file to an original\n ii perl 5.20.2-3+deb8u6 amd64 Larry Wall's Practical Extraction and Report Language\n ii perl-base 5.20.2-3+deb8u6 amd64 minimal Perl system\n ii perl-modules 5.20.2-3+deb8u6 all Core Perl modules\n ii procps 2:3.3.9-9 amd64 /proc file system utilities\n ii python 2.7.9-1 amd64 interactive high-level object-oriented language (default version)\n ii python-boto 2.34.0-2 all Python interface to Amazon's Web Services - Python 2.x\n ii python-chardet 2.3.0-1 all universal character encoding detector for Python2\n ii python-minimal 2.7.9-1 amd64 minimal subset of the Python language (default version)\n ii python-pkg-resources 5.5.1-1 all Package Discovery and Resource Access using pkg_resources\n ii python-requests 2.4.3-6 all elegant and simple HTTP library for Python2, built for human beings\n ii python-six 1.8.0-1 all Python 2 and 3 compatibility library (Python 2 interface)\n ii python-urllib3 1.9.1-3 all HTTP library with thread-safe connection pooling for Python\n ii python2.7 2.7.9-2+deb8u1 amd64 Interactive high-level object-oriented language (version 2.7)\n ii python2.7-minimal 2.7.9-2+deb8u1 amd64 Minimal subset of the Python language (version 2.7)\n ii python3 3.4.2-2 amd64 interactive high-level object-oriented language (default python3 version)\n ii python3-bcdoc 0.12.2-1 all ReST document generation tools for botocore (Python 3)\n ii python3-boto 2.34.0-2 all Python interface to Amazon's Web Services - Python 3.x\n ii python3-botocore 0.62.0-1 all Low-level, data-driven core of boto 3 (Python 3)\n ii python3-chardet 2.3.0-1 all universal character encoding detector for Python3\n ii python3-colorama 0.3.2-1 all Cross-platform colored terminal text in Python - Python 3.x\n ii python3-configobj 5.0.6-1 all simple but powerful config file reader and writer for Python 3\n ii python3-crypto 2.6.1-5+deb8u1 amd64 cryptographic algorithms and protocols for Python 3\n ii python3-dateutil 2.2-2 all powerful extensions to the standard datetime module\n ii python3-docutils 0.12+dfsg-1 all text processing system for reStructuredText (implemented in Python 3)\n ii python3-jinja2 2.7.3-1 all small but fast and easy to use stand-alone template engine\n ii python3-jmespath 0.4.1-1 all JSON Matching Expressions (Python 3)\n ii python3-json-pointer 1.0-2 all resolve JSON pointers - python 3.x\n ii python3-jsonpatch 1.3-5 all library to apply JSON patches - python 3.x\n ii python3-jwt 0.2.1-1+deb8u1 all Python 3 implementation of JSON Web Token\n ii python3-markupsafe 0.23-1+b1 amd64 HTML/XHTML/XML string library for Python 3\n ii python3-minimal 3.4.2-2 amd64 minimal subset of the Python language (default python3 version)\n ii python3-oauthlib 0.6.3-1 all generic, spec-compliant implementation of OAuth for Python3\n ii python3-pkg-resources 5.5.1-1 all Package Discovery and Resource Access using pkg_resources\n ii python3-prettytable 0.7.2-3 all library to represent tabular data in visually appealing ASCII tables (Python3)\n ii python3-requests 2.4.3-6 all elegant and simple HTTP library for Python3, built for human beings\n ii python3-roman 2.0.0-1 all module for generating/analyzing Roman numerals for Python 3\n ii python3-rsa 3.1.4-1+deb8u1 all Pure-Python RSA implementation (Python 3)\n ii python3-serial 2.6-1.1 all pyserial - module encapsulating access for the serial port\n ii python3-six 1.8.0-1 all Python 2 and 3 compatibility library (Python 3 interface)\n ii python3-urllib3 1.9.1-3 all HTTP library with thread-safe connection pooling for Python3\n ii python3-yaml 3.11-2 amd64 YAML parser and emitter for Python3\n ii python3.4 3.4.2-1 amd64 Interactive high-level object-oriented language (version 3.4)\n ii python3.4-minimal 3.4.2-1 amd64 Minimal subset of the Python language (version 3.4)\n ii qemu-utils 1:2.1+dfsg-12+deb8u6 amd64 QEMU utilities\n ii readline-common 6.3-8 all GNU readline and history libraries, common files\n ii rsyslog 8.4.2-1+deb8u2 amd64 reliable system and kernel logging daemon\n ii sed 4.2.2-4+deb8u1 amd64 The GNU sed stream editor\n ii sensible-utils 0.0.9 all Utilities for sensible alternative selection\n ii sgml-base 1.26+nmu4 all SGML infrastructure and SGML catalog file support\n ii startpar 0.59-3 amd64 run processes in parallel and multiplex their output\n ii sudo 1.8.10p3-1+deb8u3 amd64 Provide limited super user privileges to specific users\n ii systemd 215-17+deb8u6 amd64 system and service manager\n ii systemd-sysv 215-17+deb8u6 amd64 system and service manager - SysV links\n ii sysv-rc 2.88dsf-59 all System-V-like runlevel change mechanism\n ii sysvinit-utils 2.88dsf-59 amd64 System-V-like utilities\n ii tar 1.27.1-2+deb8u1 amd64 GNU version of the tar archiving utility\n ii tasksel 3.31+deb8u1 all tool for selecting tasks for installation on Debian systems\n ii tasksel-data 3.31+deb8u1 all official tasks used for installation of Debian systems\n ii traceroute 1:2.0.20-2+b1 amd64 Traces the route taken by packets over an IPv4/IPv6 network\n ii tzdata 2016j-0+deb8u1 all time zone and daylight-saving time data\n ii ucf 3.0030 all Update Configuration File(s): preserve user changes to config files\n ii udev 215-17+deb8u6 amd64 /dev/ and hotplug management daemon\n ii util-linux 2.25.2-6 amd64 Miscellaneous system utilities\n ii vim-common 2:7.4.488-7+deb8u1 amd64 Vi IMproved - Common files\n ii vim-tiny 2:7.4.488-7+deb8u1 amd64 Vi IMproved - enhanced vi editor - compact version\n ii wget 1.16-1+deb8u1 amd64 retrieves files from the web\n ii whiptail 0.52.17-1+b1 amd64 Displays user-friendly dialog boxes from shell scripts\n ii xml-core 0.13+nmu2 all XML infrastructure and XML catalog file support\n ii zlib1g 1:1.2.8.dfsg-2+b1 amd64 compression library - runtime\n","hosts":null,"custom_description":null}],"info":{"plugindescription":{"severity":0,"pluginname":"Software Enumeration (SSH)","pluginattributes":{"risk_information":{"risk_factor":"None"},"plugin_information":{"plugin_version":"$Revision: 1.24 $","plugin_id":22869,"plugin_type":"remote","plugin_publication_date":"2006-10-15T00:00:00Z","plugin_family":"General","plugin_modification_date":"2017-07-28T00:00:00Z"},"solution":"Remove any software that is not in compliance with your organization's acceptable use and security policies.","has_patch":false,"description":"Nessus was able to list the software installed on the remote host by calling the appropriate command (e.g., `rpm -qa` on RPM-based Linux distributions, qpkg, dpkg, etc.).","synopsis":"It was possible to enumerate installed software on the remote host via SSH."},"pluginfamily":"General","pluginid":"22869"}}}}}}}},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/{scan_id}/attachments/{attachment_id}":{"get":{"summary":"Get scan attachment file","description":"Gets the requested scan attachment file.

            Requires CAN VIEW [16] scan permissions. See Permissions.

            ","operationId":"was-scans-attachments","tags":["Scans"],"parameters":[{"description":"The ID of the scan containing the attachment.","required":true,"name":"scan_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The ID of the scan attachment.","required":true,"name":"attachment_id","in":"path","schema":{"type":"integer","format":"int32"}},{"description":"The attachment access token.","required":true,"name":"key","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns the attachment file.","content":{"application/octet-stream":{"schema":{},"examples":{"response":{"value":{}}}}}},"404":{"description":"Returned if the attachment file does not exist."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}},"/scans/timezones":{"get":{"summary":"Get timezones","description":"Returns the timezones list for creating a scan.

            Requires STANDARD [32] user permissions. See Permissions.

            ","operationId":"was-scans-timezones","tags":["Scans"],"responses":{"200":{"description":"Returns the timezone list.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The readable name of the timezone."},"value":{"type":"string","description":"The system value for the timezone."}}}},"examples":{"response":{"value":{"timezones":[{"name":"Africa/Abidjan","value":"Africa/Abidjan"},{"name":"Europe/Tiraspol","value":"Europe/Tiraspol"},{"name":"UCT","value":"UCT"},{"name":"US/Alaska","value":"US/Alaska"},{"name":"US/Aleutian","value":"US/Aleutian"},{"name":"US/Arizona","value":"US/Arizona"},{"name":"US/Central","value":"US/Central"},{"name":"US/East-Indiana","value":"US/East-Indiana"},{"name":"US/Eastern","value":"US/Eastern"},{"name":"US/Hawaii","value":"US/Hawaii"},{"name":"US/Indiana-Starke","value":"US/Indiana-Starke"},{"name":"US/Michigan","value":"US/Michigan"},{"name":"US/Mountain","value":"US/Mountain"},{"name":"US/Pacific","value":"US/Pacific"},{"name":"US/Pacific-New","value":"US/Pacific-New"},{"name":"US/Samoa","value":"US/Samoa"},{"name":"UTC","value":"UTC"},{"name":"Universal","value":"Universal"},{"name":"W-SU","value":"W-SU"},{"name":"WET","value":"WET"},{"current":true,"name":"Zulu","value":"Zulu"}]}}}}}},"403":{"description":"Returned if the user does not have permission to view timezones."},"429":{"description":"Returned if you attempt to send too many requests in a specific period of time. For more information, see [Rate Limiting](/docs/rate-limiting).","content":{"text/html":{"examples":{"response":{"value":"\n\n\n 429 Too Many Requests\n\n\n\n
            \n

            429 Too Many Requests

            \n
            \n
            \n
            nginx
            \n\n\n"}}}}}},"security":[{"cloud":[]}]}}},"x-explorer-enabled":true,"x-proxy-enabled":true,"x-samples-enabled":true} \ No newline at end of file diff --git a/app_gen/openapi-parsers/swimlane.py b/app_gen/openapi-parsers/swimlane.py new file mode 100644 index 00000000..4926c690 --- /dev/null +++ b/app_gen/openapi-parsers/swimlane.py @@ -0,0 +1,131 @@ +import requests +import yaml + +def parse_data(data): + openapi = { + "openapi": "3.0.2", + "info": { + "title": "", + "description": "", + "version": "1.0.0", + "contact": { + "name": "@frikkylikeme", + "url": "https://twitter.com/frikkylikeme", + "email": "frikky@shuffler.io" + } + }, + "paths": {}, + "components": { + "schemas": {}, + "securitySchemes": {}, + } + } + + category = data["category"] + + filename = "%s.yaml" % data["title"].replace(" ", "_").lower() + openapi["info"]["title"] = data["title"] + openapi["info"]["description"] = "Automated generation of %s" % data["title"] + # data["description"] + + cnt = 0 + for task in data["tasks"]: + method = "post" + + openapi["paths"]["tmp%d" % cnt] = {} + openapi["paths"]["tmp%d" % cnt][method] = { + "summary": task["name"], + "description": task["description"], + "parameters": [], + "responses": { + "200": { + "description": "Successful request", + + } + }, + } + + #taskname = task["name"] + #taskdescription = task["description"] + taskcategory = task["family"] + + # This doesn't really do much except build the return value structures + for parameter in task["input_parameters"]: + example = parameter["example"] + + inVar = "query" + + if parameter["type"] == 6: + inVar = "body" + + schema = "string" + schemaset = False + if parameter["type"] != 1: + if (parameter["type"] == 7): + schema = "boolean" + schemaset = True + + if schema == "string" and schemaset: + print("Should change type: %d" % parameter["type"]) + print(task["name"]) + print(parameter["name"]) + print() + + + if len(example) == 1: + print("Change to number?") + if example.startswith("{"): + print("Change to json object?") + if example.startswith("["): + print("Change to array object?") + + # Not sure how to tackle this + openapi["paths"]["tmp%d" % cnt][method]["parameters"].append({ + "in": inVar, + "name": parameter["name"], + "required": parameter["required"], + "description": parameter["description"], + "schema": {"type": schema} + }) + + if len(task["available_output_variables"]) > 0: + openapi["paths"]["tmp%d" % cnt][method]["responses"]["200"]["content"]: { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tmp%d" % cnt + } + } + } + + openapi["components"]["schemas"]["tmp%d" % cnt] = { + "type": "object", + "properties": {}, + } + + for output in task["available_output_variables"]: + openapi["components"]["schemas"]["tmp%d" % cnt]["properties"][output["key"]] = {"type": "string"} + + + cnt += 1 + + #print(openapi) + #print(filename) + + return filename, openapi + +def dump_data(filename, openapi): + generatedfile = "generated/%s" % filename + with open(generatedfile, "w+") as tmp: + tmp.write(yaml.dump(openapi)) + + print("Generated %s" % generatedfile) + +if __name__ == "__main__": + url = "https://apphub.swimlane.com/api/v1/bundles/cjuspytpz00rh0hpjo5chqg10" + url = "https://apphub.swimlane.com/api/v1/bundles/cjyoy62ch04920lr26id5sr0e" + url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdc2yr02rs0fli6jrosiqb" + url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdat0u01ux0flipb68a0a0" + url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdhbwp07nf0fli23lyb52h" + data = requests.get(url).json() + filename, openapi = parse_data(data) + dump_data(filename, openapi) diff --git a/app_gen/openapi/README.md b/app_gen/openapi/README.md new file mode 100644 index 00000000..7476d394 --- /dev/null +++ b/app_gen/openapi/README.md @@ -0,0 +1,7 @@ +# OpenAPI generator +This contains test code that's been moved to shaffuru/backend/go-app/codegen.go + +## Todo: +1. Don't use filesystem, but rather store in GCP +2. Add swagger 2.0 to 3.0 converter +3. Fix body / data parsing diff --git a/app_gen/openapi/baseline/Dockerfile b/app_gen/openapi/baseline/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/app_gen/openapi/baseline/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/app_gen/openapi/baseline/requirements.txt b/app_gen/openapi/baseline/requirements.txt new file mode 100644 index 00000000..dfad3eb9 --- /dev/null +++ b/app_gen/openapi/baseline/requirements.txt @@ -0,0 +1,3 @@ +# No extra requirements needed +requests +urllib3 diff --git a/app_gen/openapi/test.go b/app_gen/openapi/test.go new file mode 100644 index 00000000..b7cde4f4 --- /dev/null +++ b/app_gen/openapi/test.go @@ -0,0 +1,387 @@ +package main + +import ( + "crypto/md5" + "encoding/hex" + "errors" + "fmt" + "github.com/getkin/kin-openapi/openapi3" + "gopkg.in/yaml.v2" + "io" + "io/ioutil" + "log" + "os" + "strings" +) + +type WorkflowApp struct { + Name string `json:"name" yaml:"name" required:true datastore:"name"` + IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` + ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"` + Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"` + AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"` + Description string `json:"description" datastore:"description" required:false yaml:"description"` + Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"` + SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"` + LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false` + ContactInfo struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Url string `json:"url" datastore:"url" yaml:"url"` + } `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false` + Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"` + Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"` +} + +type AuthenticationParams struct { + Description string `json:"description" datastore:"description" yaml:"description"` + ID string `json:"id" datastore:"id" yaml:"id"` + Name string `json:"name" datastore:"name" yaml:"name"` + Example string `json:"example" datastore:"example" yaml:"example"s` + Value string `json:"value,omitempty" datastore:"value" yaml:"value"` + Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` + Required bool `json:"required" datastore:"required" yaml:"required"` +} + +type Authentication struct { + Required bool `json:"required" datastore:"required" yaml:"required" ` + Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"` +} + +type AuthenticationStore struct { + Key string `json:"key" datastore:"key"` + Value string `json:"value" datastore:"value"` +} + +type WorkflowAppActionParameter struct { + Description string `json:"description" datastore:"description" yaml:"description"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Name string `json:"name" datastore:"name" yaml:"name"` + Example string `json:"example" datastore:"example" yaml:"example"` + Value string `json:"value" datastore:"value" yaml:"value,omitempty"` + Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` + ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"` + Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"` + Required bool `json:"required" datastore:"required" yaml:"required"` + Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"` +} + +type SchemaDefinition struct { + Type string `json:"type" datastore:"type"` +} + +type WorkflowAppAction struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Name string `json:"name" datastore:"name"` + NodeType string `json:"node_type" datastore:"node_type"` + Environment string `json:"environment" datastore:"environment"` + Authentication []AuthenticationStore `json:"authentication" datastore:"authentication" yaml:"authentication,omitempty"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"` + Returns struct { + Description string `json:"description" datastore:"returns" yaml:"description,omitempty"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"returns" datastore:"returns"` +} + +func copyFile(fromfile, tofile string) error { + from, err := os.Open(fromfile) + if err != nil { + return err + } + defer from.Close() + + to, err := os.OpenFile(tofile, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return err + } + defer to.Close() + + _, err = io.Copy(to, from) + if err != nil { + return err + } + + return nil +} + +// Builds the base structure for the app that we're making +// Returns error if anything goes wrong. This has to work if +// the python code is supposed to be generated +func buildStructure(swagger *openapi3.Swagger, curHash string) (string, error) { + //log.Printf("%#v", swagger) + + // adding md5 based on input data to not overwrite earlier data. + generatedPath := "generated" + identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash) + appPath := fmt.Sprintf("%s/%s", generatedPath, identifier) + + os.MkdirAll(appPath, os.ModePerm) + os.Mkdir(fmt.Sprintf("%s/src", appPath), os.ModePerm) + + err := copyFile("baseline/Dockerfile", fmt.Sprintf("%s/%s", appPath, "Dockerfile")) + if err != nil { + log.Println("Failed to move Dockerfile") + return appPath, err + } + + err = copyFile("baseline/requirements.txt", fmt.Sprintf("%s/%s", appPath, "requirements.txt")) + if err != nil { + log.Println("Failed to move requrements.txt") + return appPath, err + } + + return appPath, nil +} + +func makePythoncode(name, url, method string, parameters, optionalQueries []string) string { + method = strings.ToLower(method) + queryString := "" + queryData := "" + + // FIXME - this might break - need to check if ? or & should be set as query + parameterData := "" + if len(optionalQueries) > 0 { + queryString += ", " + for _, query := range optionalQueries { + queryString += fmt.Sprintf("%s=\"\"", query) + queryData += fmt.Sprintf(` + if %s: + url += f"&%s={%s}"`, query, query, query) + } + } + + if len(parameters) > 0 { + parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", ")) + } + + // FIXME - add checks for query data etc + data := fmt.Sprintf(` async def %s_%s(self%s%s): + url=f"%s" + %s + return requests.%s(url).text + `, name, method, parameterData, queryString, url, queryData, method) + + return data +} + +func generateYaml(swagger *openapi3.Swagger) (WorkflowApp, []string, error) { + api := WorkflowApp{} + log.Printf("%#v", swagger.Info) + + if len(swagger.Info.Title) == 0 { + return WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.") + } + + if len(swagger.Servers) == 0 { + return WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'") + } + + api.Name = swagger.Info.Title + api.Description = swagger.Info.Description + api.IsValid = true + api.Link = swagger.Servers[0].URL // host doesnt exist lol + api.AppVersion = "1.0.0" + api.Environment = "cloud" + api.ID = "" + api.SmallImage = "" + api.LargeImage = "" + + // This is the python code to be generated + // Could just as well be go at this point lol + pythonFunctions := []string{} + + for actualPath, path := range swagger.Paths { + //log.Printf("%#v", path) + //log.Printf("%#v", actualPath) + // Find the path name and add it to makeCode() param + + firstQuery := true + if path.Get != nil { + // What to do with this, hmm + functionName := strings.ReplaceAll(path.Get.Summary, " ", "_") + functionName = strings.ToLower(functionName) + + action := WorkflowAppAction{ + Description: path.Get.Description, + Name: path.Get.Summary, + NodeType: "action", + Environment: api.Environment, + Parameters: []WorkflowAppActionParameter{}, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + parameters := []string{} + optionalParameters := []WorkflowAppActionParameter{} + if len(path.Get.Parameters) > 0 { + for _, param := range path.Get.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + curCode := makePythoncode(functionName, baseUrl, "get", parameters, optionalQueries) + pythonFunctions = append(pythonFunctions, curCode) + + api.Actions = append(api.Actions, action) + } + } + + return api, pythonFunctions, nil +} + +func verifyApi(api WorkflowApp) WorkflowApp { + if api.AppVersion == "" { + api.AppVersion = "1.0.0" + } + + return api +} + +func dumpPython(basePath, name, version string, pythonFunctions []string) error { + //log.Printf("%#v", api) + log.Printf(strings.Join(pythonFunctions, "\n")) + + parsedCode := fmt.Sprintf(`import requests +import asyncio +import json + +from walkoff_app_sdk.app_base import AppBase + +class %s(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "%s" + app_name = "%s" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + +%s + +if __name__ == "__main__": + asyncio.run(CarbonBlack.run(), debug=True) +`, name, version, name, strings.Join(pythonFunctions, "\n")) + + err := ioutil.WriteFile(fmt.Sprintf("%s/src/app.py", basePath), []byte(parsedCode), os.ModePerm) + if err != nil { + return err + } + fmt.Println(parsedCode) + + //log.Println(string(data)) + return nil +} + +func dumpApi(basePath string, api WorkflowApp) error { + //log.Printf("%#v", api) + data, err := yaml.Marshal(api) + if err != nil { + log.Printf("Error with yaml marshal: %s", err) + return err + } + + err = ioutil.WriteFile(fmt.Sprintf("%s/api.yaml", basePath), []byte(data), os.ModePerm) + if err != nil { + return err + } + + //log.Println(string(data)) + return nil +} + +func main() { + data := []byte(`{"swagger":"3.0","info":{"title":"hi","description":"you","version":"1.0"},"servers":[{"url":"https://shuffler.io/api/v1"}],"host":"shuffler.io","basePath":"/api/v1","schemes":["https:"],"paths":{"/workflows":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflows","description":"Get workflows","parameters":[]}},"/workflows/{id}":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflow","description":"Get workflow","parameters":[{"in":"query","name":"forgetme","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"query","name":"anotherone","description":"Generated by shuffler.io OpenAPI","required":false,"schema":{"type":"string"}},{"in":"query","name":"hi","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}}]}}},"securityDefinitions":{}}`) + + hasher := md5.New() + hasher.Write(data) + newmd5 := hex.EncodeToString(hasher.Sum(nil)) + + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(data) + if err != nil { + log.Printf("Swagger validation error: %s", err) + os.Exit(3) + } + + if strings.Contains(swagger.Info.Title, " ") { + strings.ReplaceAll(swagger.Info.Title, " ", "") + } + + basePath, err := buildStructure(swagger, newmd5) + if err != nil { + log.Printf("Failed to build base structure: %s", err) + os.Exit(3) + } + + api, pythonfunctions, err := generateYaml(swagger) + if err != nil { + log.Printf("Failed building and generating yaml: %s", err) + os.Exit(3) + } + + err = dumpApi(basePath, api) + if err != nil { + log.Printf("Failed dumping yaml: %s", err) + os.Exit(3) + } + + err = dumpPython(basePath, swagger.Info.Title, swagger.Info.Version, pythonfunctions) + if err != nil { + log.Printf("Failed dumping python: %s", err) + os.Exit(3) + } +} diff --git a/app_gen/openapi/testGCP.go b/app_gen/openapi/testGCP.go new file mode 100644 index 00000000..da5510b8 --- /dev/null +++ b/app_gen/openapi/testGCP.go @@ -0,0 +1,499 @@ +package main + +/* + Code used to generate apps from OpenAPI JSON data + Any function ending with GCP doesn't use local fileIO, but rather + google cloud storage, and also has a normal filesystem version of the + same code (doesn't required client as first argument). + + This code is used in the backend to generate apps on the fly for users. + All new code is appended to backend/go-app/codegen.go +*/ + +import ( + "context" + "crypto/md5" + "encoding/hex" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "strings" + + "cloud.google.com/go/storage" + "github.com/getkin/kin-openapi/openapi3" + "gopkg.in/yaml.v2" +) + +var bucketName = "shuffler.appspot.com" + +type WorkflowApp struct { + Name string `json:"name" yaml:"name" required:true datastore:"name"` + IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` + ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"` + Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"` + AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"` + Description string `json:"description" datastore:"description" required:false yaml:"description"` + Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"` + SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"` + LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false` + ContactInfo struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Url string `json:"url" datastore:"url" yaml:"url"` + } `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false` + Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"` + Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"` +} + +type AuthenticationParams struct { + Description string `json:"description" datastore:"description" yaml:"description"` + ID string `json:"id" datastore:"id" yaml:"id"` + Name string `json:"name" datastore:"name" yaml:"name"` + Example string `json:"example" datastore:"example" yaml:"example"s` + Value string `json:"value,omitempty" datastore:"value" yaml:"value"` + Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` + Required bool `json:"required" datastore:"required" yaml:"required"` +} + +type Authentication struct { + Required bool `json:"required" datastore:"required" yaml:"required" ` + Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"` +} + +type AuthenticationStore struct { + Key string `json:"key" datastore:"key"` + Value string `json:"value" datastore:"value"` +} + +type WorkflowAppActionParameter struct { + Description string `json:"description" datastore:"description" yaml:"description"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Name string `json:"name" datastore:"name" yaml:"name"` + Example string `json:"example" datastore:"example" yaml:"example"` + Value string `json:"value" datastore:"value" yaml:"value,omitempty"` + Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` + ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"` + Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"` + Required bool `json:"required" datastore:"required" yaml:"required"` + Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"` +} + +type SchemaDefinition struct { + Type string `json:"type" datastore:"type"` +} + +type WorkflowAppAction struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Name string `json:"name" datastore:"name"` + NodeType string `json:"node_type" datastore:"node_type"` + Environment string `json:"environment" datastore:"environment"` + Authentication []AuthenticationStore `json:"authentication" datastore:"authentication" yaml:"authentication,omitempty"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"` + Returns struct { + Description string `json:"description" datastore:"returns" yaml:"description,omitempty"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"returns" datastore:"returns"` +} + +func copyFile(fromfile, tofile string) error { + from, err := os.Open(fromfile) + if err != nil { + return err + } + defer from.Close() + + to, err := os.OpenFile(tofile, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return err + } + defer to.Close() + + _, err = io.Copy(to, from) + if err != nil { + return err + } + + return nil +} + +func buildStructureGCP(client *storage.Client, swagger *openapi3.Swagger, curHash string) (string, error) { + ctx := context.Background() + + // 1. Have baseline in bucket/generated_apps/baseline + // 2. Copy the baseline to a new folder with identifier name + + basePath := "generated_apps" + identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash) + appPath := fmt.Sprintf("%s/%s", basePath, identifier) + fileNames := []string{"Dockerfile", "requirements.txt"} + for _, file := range fileNames { + src := client.Bucket(bucketName).Object(fmt.Sprintf("%s/baseline/%s", basePath, file)) + dst := client.Bucket(bucketName).Object(fmt.Sprintf("%s/%s", appPath, file)) + if _, err := dst.CopierFrom(src).Run(ctx); err != nil { + return "", err + } + } + + return appPath, nil +} + +// Builds the base structure for the app that we're making +// Returns error if anything goes wrong. This has to work if +// the python code is supposed to be generated +func buildStructure(swagger *openapi3.Swagger, curHash string) (string, error) { + //log.Printf("%#v", swagger) + + // adding md5 based on input data to not overwrite earlier data. + generatedPath := "generated" + identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash) + appPath := fmt.Sprintf("%s/%s", generatedPath, identifier) + + os.MkdirAll(appPath, os.ModePerm) + os.Mkdir(fmt.Sprintf("%s/src", appPath), os.ModePerm) + + err := copyFile("baseline/Dockerfile", fmt.Sprintf("%s/%s", appPath, "Dockerfile")) + if err != nil { + log.Println("Failed to move Dockerfile") + return appPath, err + } + + err = copyFile("baseline/requirements.txt", fmt.Sprintf("%s/%s", appPath, "requirements.txt")) + if err != nil { + log.Println("Failed to move requrements.txt") + return appPath, err + } + + return appPath, nil +} + +func makePythoncode(name, url, method string, parameters, optionalQueries []string) string { + method = strings.ToLower(method) + queryString := "" + queryData := "" + + // FIXME - this might break - need to check if ? or & should be set as query + parameterData := "" + if len(optionalQueries) > 0 { + queryString += ", " + for _, query := range optionalQueries { + queryString += fmt.Sprintf("%s=\"\"", query) + queryData += fmt.Sprintf(` + if %s: + url += f"&%s={%s}"`, query, query, query) + } + } + + if len(parameters) > 0 { + parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", ")) + } + + // FIXME - add checks for query data etc + data := fmt.Sprintf(` async def %s_%s(self%s%s): + url=f"%s" + %s + return requests.%s(url).text + `, name, method, parameterData, queryString, url, queryData, method) + + return data +} + +func generateYaml(swagger *openapi3.Swagger) (WorkflowApp, []string, error) { + api := WorkflowApp{} + log.Printf("%#v", swagger.Info) + + if len(swagger.Info.Title) == 0 { + return WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.") + } + + if len(swagger.Servers) == 0 { + return WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'") + } + + api.Name = swagger.Info.Title + api.Description = swagger.Info.Description + api.IsValid = true + api.Link = swagger.Servers[0].URL // host doesnt exist lol + api.AppVersion = "1.0.0" + api.Environment = "cloud" + api.ID = "" + api.SmallImage = "" + api.LargeImage = "" + + // This is the python code to be generated + // Could just as well be go at this point lol + pythonFunctions := []string{} + + for actualPath, path := range swagger.Paths { + //log.Printf("%#v", path) + //log.Printf("%#v", actualPath) + // Find the path name and add it to makeCode() param + + firstQuery := true + if path.Get != nil { + // What to do with this, hmm + functionName := strings.ReplaceAll(path.Get.Summary, " ", "_") + functionName = strings.ToLower(functionName) + + action := WorkflowAppAction{ + Description: path.Get.Description, + Name: path.Get.Summary, + NodeType: "action", + Environment: api.Environment, + Parameters: []WorkflowAppActionParameter{}, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + parameters := []string{} + optionalParameters := []WorkflowAppActionParameter{} + if len(path.Get.Parameters) > 0 { + for _, param := range path.Get.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + curCode := makePythoncode(functionName, baseUrl, "get", parameters, optionalQueries) + pythonFunctions = append(pythonFunctions, curCode) + + api.Actions = append(api.Actions, action) + } + } + + return api, pythonFunctions, nil +} + +func verifyApi(api WorkflowApp) WorkflowApp { + if api.AppVersion == "" { + api.AppVersion = "1.0.0" + } + + return api +} + +func dumpPythonGCP(client *storage.Client, basePath, name, version string, pythonFunctions []string) error { + //log.Printf("%#v", api) + log.Printf(strings.Join(pythonFunctions, "\n")) + + parsedCode := fmt.Sprintf(`import requests +import asyncio +import json + +from walkoff_app_sdk.app_base import AppBase + +class %s(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "%s" + app_name = "%s" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + +%s + +if __name__ == "__main__": + asyncio.run(CarbonBlack.run(), debug=True) +`, name, version, name, strings.Join(pythonFunctions, "\n")) + + // Create bucket handle + ctx := context.Background() + bucket := client.Bucket(bucketName) + obj := bucket.Object(fmt.Sprintf("%s/src/app.py", basePath)) + w := obj.NewWriter(ctx) + if _, err := fmt.Fprintf(w, parsedCode); err != nil { + return err + } + // Close, just like writing a file. + if err := w.Close(); err != nil { + return err + } + + return nil +} + +func dumpPython(basePath, name, version string, pythonFunctions []string) error { + //log.Printf("%#v", api) + log.Printf(strings.Join(pythonFunctions, "\n")) + + parsedCode := fmt.Sprintf(`import requests +import asyncio +import json + +from walkoff_app_sdk.app_base import AppBase + +class %s(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "%s" + app_name = "%s" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + +%s + +if __name__ == "__main__": + asyncio.run(CarbonBlack.run(), debug=True) +`, name, version, name, strings.Join(pythonFunctions, "\n")) + + err := ioutil.WriteFile(fmt.Sprintf("%s/src/app.py", basePath), []byte(parsedCode), os.ModePerm) + if err != nil { + return err + } + fmt.Println(parsedCode) + + //log.Println(string(data)) + return nil +} + +func dumpApiGCP(client *storage.Client, basePath string, api WorkflowApp) error { + //log.Printf("%#v", api) + data, err := yaml.Marshal(api) + if err != nil { + log.Printf("Error with yaml marshal: %s", err) + return err + } + + // Create bucket handle + ctx := context.Background() + bucket := client.Bucket(bucketName) + obj := bucket.Object(fmt.Sprintf("%s/app.yaml", basePath)) + w := obj.NewWriter(ctx) + if _, err := fmt.Fprintf(w, string(data)); err != nil { + return err + } + // Close, just like writing a file. + if err := w.Close(); err != nil { + return err + } + + //log.Println(string(data)) + return nil +} + +func dumpApi(basePath string, api WorkflowApp) error { + //log.Printf("%#v", api) + data, err := yaml.Marshal(api) + if err != nil { + log.Printf("Error with yaml marshal: %s", err) + return err + } + + err = ioutil.WriteFile(fmt.Sprintf("%s/api.yaml", basePath), []byte(data), os.ModePerm) + if err != nil { + return err + } + + //log.Println(string(data)) + return nil +} + +func main() { + data := []byte(`{"swagger":"3.0","info":{"title":"hi","description":"you","version":"1.0"},"servers":[{"url":"https://shuffler.io/api/v1"}],"host":"shuffler.io","basePath":"/api/v1","schemes":["https:"],"paths":{"/workflows":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflows","description":"Get workflows","parameters":[]}},"/workflows/{id}":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflow","description":"Get workflow","parameters":[{"in":"query","name":"forgetme","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"query","name":"anotherone","description":"Generated by shuffler.io OpenAPI","required":false,"schema":{"type":"string"}},{"in":"query","name":"hi","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}}]}}},"securityDefinitions":{}}`) + + ctx := context.Background() + client, err := storage.NewClient(ctx) + if err != nil { + log.Printf("Failed to create client: %v", err) + os.Exit(3) + } + + hasher := md5.New() + hasher.Write(data) + newmd5 := hex.EncodeToString(hasher.Sum(nil)) + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(data) + if err != nil { + log.Printf("Swagger validation error: %s", err) + os.Exit(3) + } + + if strings.Contains(swagger.Info.Title, " ") { + strings.ReplaceAll(swagger.Info.Title, " ", "") + } + + basePath, err := buildStructureGCP(client, swagger, newmd5) + if err != nil { + log.Printf("Failed to build base structure: %s", err) + os.Exit(3) + } + + api, pythonfunctions, err := generateYaml(swagger) + if err != nil { + log.Printf("Failed building and generating yaml: %s", err) + os.Exit(3) + } + + err = dumpApiGCP(client, basePath, api) + if err != nil { + log.Printf("Failed dumping yaml: %s", err) + os.Exit(3) + } + + err = dumpPythonGCP(client, basePath, swagger.Info.Title, swagger.Info.Version, pythonfunctions) + if err != nil { + log.Printf("Failed dumping python: %s", err) + os.Exit(3) + } +} diff --git a/app_gen/python-lib/README.md b/app_gen/python-lib/README.md new file mode 100644 index 00000000..da096b0f --- /dev/null +++ b/app_gen/python-lib/README.md @@ -0,0 +1,5 @@ +# Generators +This folder contains an attempt at creating apps & similar from python libraries + +## Howto + diff --git a/app_gen/python-lib/baseline/Dockerfile b/app_gen/python-lib/baseline/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/app_gen/python-lib/baseline/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/app_gen/python-lib/baseline/docker-compose.yml b/app_gen/python-lib/baseline/docker-compose.yml new file mode 100644 index 00000000..04fab667 --- /dev/null +++ b/app_gen/python-lib/baseline/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.4' +services: + thehive4py: + build: + context: . + dockerfile: Dockerfile + env_file: + - env.txt + restart: "no" + deploy: + mode: replicated + replicas: 10 + restart_policy: + condition: none diff --git a/app_gen/python-lib/baseline/env.txt b/app_gen/python-lib/baseline/env.txt new file mode 100644 index 00000000..452a148c --- /dev/null +++ b/app_gen/python-lib/baseline/env.txt @@ -0,0 +1,4 @@ +REDIS_URI=redis://redis +REDIS_ACTION_RESULT_CH=action-results +REDIS_ACTION_RESULTS_GROUP=action-results-group +APP_NAME= diff --git a/app_gen/python-lib/baseline/requirements.txt b/app_gen/python-lib/baseline/requirements.txt new file mode 100644 index 00000000..f76ae497 --- /dev/null +++ b/app_gen/python-lib/baseline/requirements.txt @@ -0,0 +1 @@ +# No extra requirements needed diff --git a/app_gen/python-lib/generator.py b/app_gen/python-lib/generator.py new file mode 100644 index 00000000..2af39cdc --- /dev/null +++ b/app_gen/python-lib/generator.py @@ -0,0 +1,525 @@ +# Read a directory +# Find python functions +# Generate yaml + +# FIXME: +# Position, default_value and function in params + + + +# TO ADD: +# from walkoff_app_sdk.app_base import AppBase +# class TheHive(AppBase): <-- Add appbase +# __version__ = version within class +# app_name = app_name in class +# if __name__ == "__main__": +# asyncio.run(TheHive.run(), debug=True) <-- APPEND SHIT HERE +# async infront of every function? +# Add async library to imports + +# Make wrapper class? <-- within app.py + + +# 1. Generate app.yaml (functions with returns etc) +# 2. Generate app.py (with imports to the original function etc +# 3. Build requirements.txt based on the items necessary +# 4. Check whether it runs? + +import os +import yaml +import jedi +import shutil + +# Testing generator +entrypoint_directory = "thehive4py" +include_requirements = False +if not os.path.exists(entrypoint_directory): + include_requirements = True + print("Requires library in requirements") + + +source = ''' +import %s +%s. +''' % (entrypoint_directory, entrypoint_directory) +splitsource = source.split("\n") + +# Find modules AKA files +def get_modules(): + curline = splitsource[-2] + print(splitsource, curline) + entrypoint = jedi.Script(source, line=len(splitsource)-1, column=len(curline)) + + modules = [] + completions = entrypoint.completions() + for item in completions: + if item.type != "module": + continue + + + modules.append(item.name) + + return modules + +def loop_modules(modules, data): +# Loop modules AKA files - this is garbage but works lmao + for module in modules: + modulesplit = list(splitsource) + modulesplit[2] = "%s%s." % (modulesplit[2], module) + + #print(modulesplit) + source = "\n".join(modulesplit) + entrypoint = jedi.Script(source, line=len(modulesplit)-1, column=len(modulesplit[2])) + + # Loop classes in the files + for classcompletion in entrypoint.completions(): + if classcompletion.type != "class": + continue + + if not classcompletion.full_name.startswith(modulesplit[2]): + continue + + # Same thing again, but for functions within classes + # CBA with subclasses etc atm + + #print(classcompletion.full_name, modulesplit[2]) + + classplit = list(modulesplit) + classplit[2] = "%s." % (classcompletion.full_name) + + #print(modulesplit) + source = "\n".join(classplit) + entrypoint = jedi.Script(source, line=len(classplit)-1, column=len(classplit[2])) + + # List of functions sorted by their name + nameinternalfunctions = [] + for functioncompletion in entrypoint.completions(): + if functioncompletion.type != "function": + continue + + if not functioncompletion.full_name.startswith(classplit[2]): + continue + + nameinternalfunctions.append(functioncompletion) + + #print(nameinternalfunctions) + + # List of functions sorted by their line in the file (reversed) + # CODE USED TO ACTUALLY PRINT THE CODE + + #prevnumber = 0 + #numberinternalfunctions = sorted(nameinternalfunctions, key=lambda k: k.line, reverse=True) + numberinternalfunctions = sorted(nameinternalfunctions, key=lambda k: k.line) + prevnumber = 0 + + origparent = "TheHiveApi" + # Predefined functions? - maybe skip: __init__ + skip_functions = ["__init__"] + skip_parameters = [""] + cnt = 0 + for item in numberinternalfunctions: + if item.parent().name != origparent: + continue + + # FIXME - prolly wrong + if item.name in skip_functions or (item.name.startswith("__") and item.name.endswith("__")): + continue + + # FIXME - remove + #print(item.get_line_code()) + #if "=" not in item.get_line_code(): + # continue + + #if item.docstring() in item.get_line_code(): + # print("NO DOCSTRING FOR: %s. Skipping!" % item.name) + # cnt += 1 + # continue + + curfunction = { + "name": item.name, + "description": "HEY", + } + + params = [] + curreturn = {} + + function = item.docstring().split("\n")[0] + for line in item.docstring().split("\n"): + if not line: + continue + + linesplit = line.split(" ") + try: + curname = linesplit[1][:-1] + except IndexError as e: + print("IndexError: %s. Line: %s" % (e, line)) + continue + + paramfound = False + foundindex = 0 + cnt = 0 + for param in params: + #print(param["name"], curname) + if param["name"] == curname: + #print("ALREADY EXISTS: %s" % curname) + paramfound = True + foundindex = cnt + break + + cnt += 1 + + # CBA finding a good parser, as that seemed impossible :( + # Skipped :return + if line.startswith(":param"): + if not paramfound: + #print("HERE!: %s" % line) + + curparam = {} + #print(line) + curparam["name"] = curname + curparam["description"] = " ".join(linesplit[2:]) + #print(curparam["description"]) + if "\r\n" in curparam["description"]: + curparam["description"] = " ".join(curparam["description"].split("\r\n")) + if "\n" in curparam["description"]: + curparam["description"] = " ".join(curparam["description"].split("\n")) + + curparam["function"] = function + + #curparam["docstring"] = item.docstring() + params.append(curparam) + elif line.startswith(":type"): + if paramfound: + params[foundindex]["schema"] = {} + params[foundindex]["schema"]["type"] = " ".join(linesplit[2:]) + #print(params) + + #print(line) + elif line.startswith(":rtype"): + curreturn["type"] = " ".join(linesplit[1:]) + + + # Check whether param is required + # FIXME - remove + #if len(params) != 0: + # print(params) + # continue + + #print(function) + #print(params) + + # FIXME - this might crash when missing docstrings + # FIXME - is also bad splitt (can be written without e.g. spaces + # This should maybe be done first? idk + fields = function.split("(")[1][:-1].split(", ") + if len(params) == 0: + # Handle missing docstrings + params = [] + for item in fields: + params.append({ + "name": item, + "description": "", + "schema": {}, + "function": function, + }) + + cnt = 0 + for param in params: + found = False + + for field in fields: + if param["name"] in field: + if "=" in field: + param["required"] = False + param["default_value"] = field + else: + param["required"] = True + + found = True + break + + if not param.get("schema"): + #print("Defining object schema for %s" % param["name"]) + param["schema"] = {} + param["schema"]["type"] = "object" + + param["position"] = cnt + + if not found: + # FIXME - waht here? + pass + #print("HANDLE NOT FOUND") + #print(param) + #print(fields) + + cnt += 1 + + if len(params) > 0: + curfunction["parameters"] = params + + if not curfunction.get("returns"): + curfunction["returns"] = {} + curfunction["returns"]["schema"] = {} + curfunction["returns"]["schema"]["type"] = "object" + + #print(curfunction) + try: + print("Finished prepping %s with %d parameters and return %s" % (item.name, len(curfunction["parameters"]), curfunction["returns"]["schema"]["type"])) + except KeyError as e: + print("Error: %s" % e) + #print("Finished prepping %s with 0 parameters and return %s" % (item.name, curfunction["returns"]["schema"]["type"])) + curfunction["parameters"] = [] + except AttributeError as e: + pass + + try: + data["actions"].append(curfunction) + except KeyError: + data["actions"] = [] + data["actions"].append(curfunction) + + #return data + + # FIXME + #if cnt == breakcnt: + # break + + #cnt += 1 + + # Check if + + + # THIS IS TO GET READ THE ACTUAL CODE + #functioncode = item.get_line_code(after=prevnumber-item.line-1) + #prevnumber = item.line + + # break + return data + +# Generates the base information necessary to make an api.yaml file +def generate_base_yaml(filename, version, appname): + print("Generating base app for library %s with version %s" % (appname, version)) + data = { + "walkoff_version": "0.0.1", + "app_version": version, + "name": appname, + "description": "Autogenerated yaml with @Frikkylikeme's generator", + "contact_info": { + "name": "@frikkylikeme", + "url": "https://github.com/frikky", + } + } + + return data + +def generate_app(filepath, data): + + tbd = [ + "library_path", + "import_class", + "required_init" + ] + + # FIXME - add to data dynamically and remove + data["library_path"] = "thehive4py.api" + data["import_class"] = "TheHiveApi" + data["required_init"] = {"url": "http://localhost:9000", "principal": "asd"} + + wrapperstring = "" + cnt = 0 + # FIXME - only works for strings currently + for key, value in data["required_init"].items(): + if cnt != len(data["required_init"]): + wrapperstring += "%s=\"%s\", " % (key, value) + + cnt += 1 + + wrapperstring = wrapperstring[:-2] + wrapper = "self.wrapper = %s(%s)" % (data["import_class"], wrapperstring) + + name = data["name"] + if ":" in data["name"]: + name = data["name"].split(":")[0] + + if not data.get("actions"): + print("No actions found for %s in path %s" % (entrypoint_directory, data["library_path"])) + print("Folder might be missing (or unexported (__init__.py), library not installed (pip) or library action missing") + exit() + + functions = [] + for action in data["actions"]: + internalparamstring = "" + paramstring = "" + try: + for param in action["parameters"]: + if param["required"] == False: + paramstring += "%s, " % (param["default_value"]) + else: + paramstring += "%s, " % param["name"] + except KeyError: + action["parameters"] = [] + + #internalparamstring += "%s, " % param["name"] + + paramstring = paramstring[:-2] + #internalparamstring = internalparamstring[:-2] + + functionstring = ''' async def %s(%s): + return self.wrapper.%s(%s) + ''' % (action["name"], paramstring, action["name"], paramstring) + + functions.append(functionstring) + + filedata = '''from walkoff_app_sdk.app_base import AppBase +import asyncio + +from %s import %s + +class %sWrapper(AppBase): + + __version__ = "%s" + app_name = "%s" + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + + super().__init__(redis, logger, console_logger) + %s + +%s + +if __name__ == "__main__": + asyncio.run(%sWrapper.run(), debug=True) +''' % ( \ + data["library_path"], + data["import_class"], + name, + data["app_version"], + name, + wrapper, + "\n".join(functions), + name + ) + + # Simple key cleanup + for item in tbd: + try: + del data[item] + except KeyError: + pass + + + tbd_action = [] + + tbd_param = [ + "position", + "default_value", + "function" + ] + + for action in data["actions"]: + for param in action["parameters"]: + for item in tbd_param: + try: + del param[item] + except KeyError: + pass + + for item in tbd_action: + try: + del action[item] + except KeyError: + pass + + # FIXME - add how to initialize the class + with open(filepath, "w") as tmp: + tmp.write(filedata) + + return data + +def dump_yaml(filename, data): + with open(filename, 'w') as outfile: + yaml.dump(data, outfile, default_flow_style=False) + +def build_base_structure(appname, version): + outputdir = "generated" + app_path = "%s/%s" % (outputdir, appname) + filepath = "%s/%s" % (app_path, version) + srcdir_path = "%s/src" % (filepath) + + directories = [ + outputdir, + app_path, + filepath, + srcdir_path + ] + + for directory in directories: + try: + os.mkdir(directory) + except FileExistsError: + print("%s already exists. Skipping." % directory) + + # "docker-compose.yml", + # "env.txt", + filenames = [ + "Dockerfile", + "requirements.txt" + ] + + #if strings. + # include_requirements = False + + for filename in filenames: + ret = shutil.copyfile("baseline/%s" % filename, "%s/%s" % (filepath, filename)) + print("Copied baseline/%s." % filename) + +def move_files(appname, version): + applocation = "../../functions/apps/%s" % appname + if not os.path.exists("../../functions/apps"): + os.mkdir("../../functions/apps") + + if not os.path.exists(applocation): + os.mkdir(applocation) + + versionlocation = "%s/%s" % (applocation, version) + if not os.path.exists(versionlocation): + os.mkdir(versionlocation) + + shutil.rmtree(versionlocation) + shutil.move("generated/%s/%s" % (appname, version), versionlocation) + + print("\nMoved files to %s" % versionlocation) + + +def main(): + appname = entrypoint_directory + version = "0.0.1" + output_path = "generated/%s/%s" % (appname, version) + api_yaml_path = "%s/api.yaml" % (output_path) + app_python_path = "%s/src/app.py" % (output_path) + + # Builds the directory structure for the app + build_base_structure(appname, version) + + # Generates the yaml based on input library etc + data = generate_base_yaml(api_yaml_path, version, appname) + modules = get_modules() + data = loop_modules(modules, data) + + # Generates app file + data = generate_app(app_python_path, data) + + # Dumps the yaml to specified directory + dump_yaml(api_yaml_path, data) + + # Move the file to functions/apps repository + move_files(appname, version) + +if __name__ == "__main__": + main() diff --git a/app_gen/python-lib/requirements.txt b/app_gen/python-lib/requirements.txt new file mode 100644 index 00000000..e0f980fc --- /dev/null +++ b/app_gen/python-lib/requirements.txt @@ -0,0 +1,2 @@ +jedi +pyyaml diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..36e3c0f2 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,26 @@ +from golang as builder + +# Add files +RUN mkdir /app +WORKDIR /app +ADD ./go-app/main.go /app +ADD ./go-app/walkoff.go /app +ADD ./go-app/docker.go /app +ADD ./go-app/codegen.go /app + +ADD ./go-app/go.mod /app + +RUN go get -v + +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webapp . + +# Certificate build +FROM alpine:latest as certs +RUN apk --update add ca-certificates + +from scratch +COPY --from=builder /app/ / +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt + +EXPOSE 5001 +CMD ["./webapp"] diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..8cea3c80 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,61 @@ +# Backend setup +1 Go to https://console.cloud.google.com/apis/credentials?project=shuffle-241517&folder&organizationId and get credentials +2. Move the file to current folder (or make step 3 be your download folder or w/e) +3. export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/Shuffle-2a19ff64af66.json + +# Backend run testserver (appengine) +1. Set up gcloud locally +```bash +dev_appserver.py go-app/ --port=5001 --host=0.0.0.0 --enable_host_checking=false +``` + +# Backend deploy +* I created a simple script that moves the data into your GOPATH and deploys for you. This will require more tests in the future. + +# OpenAPI spec checks +Paths: +* /path/{variablename}?queryvar= <-- variable +* ^variablename needs to be part of parameters too. +* ^queryvar needs to be part of parameters too. +``` +parameters: +- name: variablename + in: path + description: Blah blah + required: true/false + schema: + type: string + enum: [a, b, c] # <-- not necessary, but could be great +- name: queryvar + in: query + description: blah blah + required: true/false + schema: + type: string + enum: [a, b, c] +``` +* requestBody? Not in GET, DELETE & HEAD. Can consume JSON, XML, form data, plai ntext & others. Can use markdown for the description. +* Do I care about the response? Maybe :o + +``` +requestBody: + description: Optional kind of description + required: false/true + content: + application/json: + schema: + type: object + additionalProperties: true + properties: + name: + type: string + fav_number: + type: integer + required: + - name + - email + encoding: + color: + style: form + explode: false +``` diff --git a/backend/database/Dockerfile b/backend/database/Dockerfile new file mode 100644 index 00000000..2b1758f7 --- /dev/null +++ b/backend/database/Dockerfile @@ -0,0 +1,5 @@ +# 2. docker run -p 8000:8000 +FROM google/cloud-sdk + +EXPOSE 8000 +CMD ["gcloud", "beta", "emulators", "datastore", "start", "--project=shuffle", "--host-port", "0.0.0.0:8000", "--data-dir=/etc/shuffle"] diff --git a/backend/deploy-backend.sh b/backend/deploy-backend.sh new file mode 100644 index 00000000..bb51e6de --- /dev/null +++ b/backend/deploy-backend.sh @@ -0,0 +1,8 @@ +# Deploys to backend +echo "Deploying to appengine." +mkdir -p $GOPATH/src/github.com/frikky/shuffle +cp -r go-app/* $GOPATH/src/github.com/frikky/shuffle +cd $GOPATH/src/github.com/frikky/shuffle +go build +go test +gcloud app deploy $GOPATH/src/github.com/frikky/shuffle/app.yaml diff --git a/backend/go-app/README.md b/backend/go-app/README.md new file mode 100644 index 00000000..c882ced5 --- /dev/null +++ b/backend/go-app/README.md @@ -0,0 +1,6 @@ +## RUN +```bash +cd shaffuru/ +dev_appserver.py --port=5001 --host=0.0.0.0 --enable_host_checking=false . +``` + diff --git a/backend/go-app/app.yaml b/backend/go-app/app.yaml new file mode 100644 index 00000000..59f28320 --- /dev/null +++ b/backend/go-app/app.yaml @@ -0,0 +1,98 @@ +runtime: go111 + +env_variables: + +automatic_scaling: + max_instances: 1 + min_instances: 1 + +handlers: +- url: /api/(.*) + script: auto + secure: always +- url: /static/js/(.*) + static_files: build/static/js/\1 + upload: build/static/js/(.*) + secure: always +- url: /static/css/(.*) + static_files: build/static/css/\1 + upload: build/static/css/(.*) + secure: always +- url: /images/(.*) + static_files: build/images/\1 + upload: build/images/(.*) + secure: always +- url: /(.*\.(json|ico))$ + static_files: build/\1 + upload: build/.*\.(json|ico)$ + secure: always +- url: /manifest.json + static_files: build/manifest.json + upload: build/manifest.json + secure: always + +# lol.. wildcard doesn't work with /api/(.*) for some reason +- url: / + static_files: build/index.html + upload: build/index.html + secure: always +- url: /home + static_files: build/index.html + upload: build/index.html + secure: always +- url: /passwordreset + static_files: build/index.html + upload: build/index.html + secure: always +- url: /login + static_files: build/index.html + upload: build/index.html + secure: always +- url: /register + static_files: build/index.html + upload: build/index.html + secure: always +- url: /workflows + static_files: build/index.html + upload: build/index.html + secure: always +- url: /workflows/(.*) + static_files: build/index.html + upload: build/index.html + secure: always +- url: /info/(.*) + static_files: build/index.html + upload: build/index.html + secure: always +- url: /docs/(.*) + static_files: build/index.html + upload: build/index.html + secure: always +- url: /docs + static_files: build/index.html + upload: build/index.html + secure: always +- url: /settings + static_files: build/index.html + upload: build/index.html + secure: always +- url: /apps + static_files: build/index.html + upload: build/index.html + secure: always +- url: /contact + static_files: build/index.html + upload: build/index.html + secure: always +- url: /apps/(.*) + static_files: build/index.html + upload: build/index.html + secure: always +- url: /register/(.*) + static_files: build/index.html + upload: build/index.html + secure: always +- url: /passwordreset/(.*) + static_files: build/index.html + upload: build/index.html + secure: always diff --git a/backend/go-app/codegen.go b/backend/go-app/codegen.go new file mode 100644 index 00000000..56786024 --- /dev/null +++ b/backend/go-app/codegen.go @@ -0,0 +1,1279 @@ +package main + +import ( + "archive/zip" + "context" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "strings" + + "cloud.google.com/go/storage" + "github.com/getkin/kin-openapi/openapi3" + "github.com/satori/go.uuid" + "gopkg.in/yaml.v2" +) + +func copyFile(fromfile, tofile string) error { + from, err := os.Open(fromfile) + if err != nil { + return err + } + defer from.Close() + + to, err := os.OpenFile(tofile, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return err + } + defer to.Close() + + _, err = io.Copy(to, from) + if err != nil { + return err + } + + return nil +} + +func formatAppfile(filedata string) (string, string) { + lines := strings.Split(filedata, "\n") + + newfile := []string{} + classname := "" + for _, line := range lines { + if strings.Contains(line, "walkoff_app_sdk") { + continue + } + + // Remap logging. CBA this right now + // This issue also persists in onprem apps because of await thingies.. :( + // FIXME + if strings.Contains(line, "console_logger") && strings.Contains(line, "await") { + continue + //line = strings.Replace(line, "console_logger", "logger", -1) + //log.Println(line) + } + + // Might not work with different import names + // Could be fucked up with spaces everywhere? Idk + if strings.Contains(line, "class") && strings.Contains(line, "(AppBase)") { + items := strings.Split(line, " ") + if len(items) > 0 && strings.Contains(items[1], "(AppBase)") { + classname = strings.Split(items[1], "(")[0] + } else { + // This could break something.. + classname = "TMP" + } + } + + if strings.Contains(line, "if __name__ ==") { + break + } + + // asyncio.run(HelloWorld.run(), debug=True) + + newfile = append(newfile, line) + } + + filedata = strings.Join(newfile, "\n") + return classname, filedata +} + +// Streams the data into a zip to be used for a cloud function +func streamZipdata(ctx context.Context, client *storage.Client, identifier, pythoncode, requirements string) (string, error) { + bucket := client.Bucket(bucketName) + + filename := fmt.Sprintf("generated_cloudfunctions/%s.zip", identifier) + + obj := bucket.Object(filename) + storageWriter := obj.NewWriter(ctx) + defer storageWriter.Close() + + zipWriter := zip.NewWriter(storageWriter) + + zipFile, err := zipWriter.Create("main.py") + if err != nil { + log.Printf("Packing failed to create zip file from bucket: %v", err) + return filename, err + } + + // Have to use Fprintln otherwise it tries to parse all strings etc. + if _, err := fmt.Fprintln(zipFile, pythoncode); err != nil { + return filename, err + } + + zipFile, err = zipWriter.Create("requirements.txt") + if err != nil { + log.Printf("Packing failed to create zip file from bucket: %v", err) + return filename, err + } + if _, err := fmt.Fprintln(zipFile, requirements); err != nil { + return filename, err + } + + err = zipWriter.Close() + if err != nil { + log.Printf("Packing failed to close zip file writer from bucket: %v", err) + return filename, err + } + + return filename, nil +} + +func getAppbase(ctx context.Context, client *storage.Client) ([]byte, []byte, error) { + // 1. Have baseline in bucket/generated_apps/baseline + // 2. Copy the baseline to a new folder with identifier name + static := "../../functions/static_baseline.py" + appbase := "../../functions/onprem/app_sdk/app_base.py" + + staticData, err := ioutil.ReadFile(static) + if err != nil { + return []byte{}, []byte{}, err + } + + appbaseData, err := ioutil.ReadFile(appbase) + if err != nil { + return []byte{}, []byte{}, err + } + + return appbaseData, staticData, nil +} + +// Builds the structure for the new generated app in storage (copying baseline files) +func getAppbaseGCP(ctx context.Context, client *storage.Client) ([]byte, []byte, error) { + // 1. Have baseline in bucket/generated_apps/baseline + // 2. Copy the baseline to a new folder with identifier name + basePath := "generated_apps/baseline" + static, err := client.Bucket(bucketName).Object(fmt.Sprintf("%s/static_baseline.py", basePath)).NewReader(ctx) + if err != nil { + return []byte{}, []byte{}, err + } + appbase, err := client.Bucket(bucketName).Object(fmt.Sprintf("%s/app_base.py", basePath)).NewReader(ctx) + if err != nil { + return []byte{}, []byte{}, err + } + + defer static.Close() + defer appbase.Close() + + staticData, err := ioutil.ReadAll(static) + if err != nil { + return []byte{}, []byte{}, err + } + + appbaseData, err := ioutil.ReadAll(appbase) + if err != nil { + return []byte{}, []byte{}, err + } + + return appbaseData, staticData, nil +} + +func fixAppbase(appbase []byte) []string { + record := false + validLines := []string{} + for _, line := range strings.Split(string(appbase), "\n") { + if strings.Contains(line, "#STOPCOPY") { + //log.Println("Stopping copy") + break + } + + if record { + validLines = append(validLines, line) + } + + if strings.Contains(line, "#STARTCOPY") { + //log.Println("Starting copy") + record = true + } + } + + return validLines +} + +// Builds the structure for the new generated app in storage (copying baseline files) +func buildStructureGCP(ctx context.Context, client *storage.Client, swagger *openapi3.Swagger, curHash string) (string, error) { + // 1. Have baseline in bucket/generated_apps/baseline + // 2. Copy the baseline to a new folder with identifier name + + basePath := "generated_apps" + identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash) + appPath := fmt.Sprintf("%s/%s", basePath, identifier) + fileNames := []string{"Dockerfile", "requirements.txt"} + for _, file := range fileNames { + src := client.Bucket(bucketName).Object(fmt.Sprintf("%s/baseline/%s", basePath, file)) + dst := client.Bucket(bucketName).Object(fmt.Sprintf("%s/%s", appPath, file)) + if _, err := dst.CopierFrom(src).Run(ctx); err != nil { + return "", err + } + } + + return appPath, nil +} + +// Builds the base structure for the app that we're making +// Returns error if anything goes wrong. This has to work if +// the python code is supposed to be generated +func buildStructure(swagger *openapi3.Swagger, curHash string) (string, error) { + //log.Printf("%#v", swagger) + + // adding md5 based on input data to not overwrite earlier data. + generatedPath := "generated" + subpath := "../../app_gen/openapi/" + identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash) + appPath := fmt.Sprintf("%s/%s", generatedPath, identifier) + + os.MkdirAll(appPath, os.ModePerm) + os.Mkdir(fmt.Sprintf("%s/src", appPath), os.ModePerm) + + err := copyFile(fmt.Sprintf("%sbaseline/Dockerfile", subpath), fmt.Sprintf("%s/%s", appPath, "Dockerfile")) + if err != nil { + log.Println("Failed to move Dockerfile") + return appPath, err + } + + err = copyFile(fmt.Sprintf("%sbaseline/requirements.txt", subpath), fmt.Sprintf("%s/%s", appPath, "requirements.txt")) + if err != nil { + log.Println("Failed to move requrements.txt") + return appPath, err + } + + return appPath, nil +} + +func makePythoncode(swagger *openapi3.Swagger, name, url, method string, parameters, optionalQueries []string) (string, string) { + method = strings.ToLower(method) + queryString := "" + queryData := "" + + // FIXME - this might break - need to check if ? or & should be set as query + parameterData := "" + if len(optionalQueries) > 0 { + queryString += ", " + for _, query := range optionalQueries { + queryString += fmt.Sprintf("%s=\"\", ", query) + queryData += fmt.Sprintf(` + if %s: + url += f"&%s={%s}"`, query, query, query) + } + } + + // How to add authentication? + // I think it should be like: + // async def(self, auth, baseurl, data): + // api.Authentication.Parameters[0].Value = "BearerAuth" + authenticationParameter := "" + authenticationSetup := "" + authenticationAddin := "" + // Python configuration code that should work :) + if swagger.Components.SecuritySchemes != nil { + if swagger.Components.SecuritySchemes["BearerAuth"] != nil { + authenticationParameter = ", apikey" + authenticationSetup = "headers[\"Authorization\"] = f\"Bearer {apikey}\"" + } else if swagger.Components.SecuritySchemes["BasicAuth"] != nil { + authenticationParameter = ", username, password" + authenticationAddin = ", auth=(username, password)" + } else if swagger.Components.SecuritySchemes["ApiKeyAuth"] != nil { + authenticationParameter = ", apikey" + if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "header" { + authenticationSetup = fmt.Sprintf("headers[\"%s\"] = apikey", swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name) + } else if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "query" { + authenticationSetup = fmt.Sprintf("url+=f\"?%s={apikey}\"", swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name) + } + } + } + + //baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + // This is a quickfix for onpremises stuff. Does work, but should really be + // part of the authentication scheme from openapi3 + urlParameter := "" + urlInline := "" + //log.Printf("URL: %s", url) + if !strings.HasPrefix(strings.ToLower(url), "http") { + urlParameter = ", baseurl" + urlInline = "{baseurl}" + } + + if len(parameters) > 0 { + parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", ")) + } + + // FIXME - add checks for query data etc + + functionname := strings.ToLower(fmt.Sprintf("%s_%s", method, name)) + if strings.Contains(strings.ToLower(name), strings.ToLower(method)) { + functionname = strings.ToLower(name) + } + + bodyParameter := "" + bodyAddin := "" + postParameters := []string{"post", "patch", "put"} + for _, item := range postParameters { + if method == item { + bodyParameter = ", body=\"\"" + bodyAddin = ", json=body" + break + } + } + + data := fmt.Sprintf(` async def %s(self%s%s%s%s%s): + headers={} + url=f"%s%s" + %s + %s + return requests.%s(url, headers=headers%s%s).text + `, functionname, authenticationParameter, urlParameter, parameterData, queryString, bodyParameter, urlInline, url, authenticationSetup, queryData, method, authenticationAddin, bodyAddin) + + //log.Println(data) + //log.Println(functionname) + return functionname, data +} + +func generateYaml(swagger *openapi3.Swagger, newmd5 string) (WorkflowApp, []string, error) { + api := WorkflowApp{} + //log.Printf("%#v", swagger.Info) + + if len(swagger.Info.Title) == 0 { + return WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.") + } + + if len(swagger.Servers) == 0 { + return WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'") + } + + api.Name = swagger.Info.Title + api.Description = swagger.Info.Description + api.ID = uuid.NewV4().String() + api.IsValid = true + api.Link = swagger.Servers[0].URL // host doesnt exist lol + if strings.HasSuffix(api.Link, "/") { + api.Link = api.Link[:len(api.Link)-1] + } + + api.AppVersion = "1.0.0" + api.Environment = "cloud" + api.SmallImage = "" + api.LargeImage = "" + api.Sharing = false + api.Verified = false + api.Tested = false + api.PrivateID = newmd5 + api.Generated = true + // Setting up security schemes + extraParameters := []WorkflowAppActionParameter{} + + securitySchemes := swagger.Components.SecuritySchemes + if securitySchemes != nil { + log.Printf("%#v", securitySchemes) + + api.Authentication = Authentication{ + Required: true, + Parameters: []AuthenticationParams{ + AuthenticationParams{ + Multiline: false, + Required: true, + }, + }, + } + + // Used for python code generation lol + // Not sure how this should work with oauth + if securitySchemes["BearerAuth"] != nil { + api.Authentication.Parameters[0].Value = "BearerAuth" + api.Authentication.Parameters[0].Description = securitySchemes["BearerAuth"].Value.Description + api.Authentication.Parameters[0].Name = securitySchemes["BearerAuth"].Value.Name + api.Authentication.Parameters[0].In = securitySchemes["BearerAuth"].Value.In + api.Authentication.Parameters[0].Scheme = securitySchemes["BearerAuth"].Value.Scheme + extraParameters = append(extraParameters, WorkflowAppActionParameter{ + Name: "apikey", + Description: "The apikey to use", + Multiline: false, + Required: true, + Schema: SchemaDefinition{ + Type: "string", + }, + }) + } else if securitySchemes["ApiKeyAuth"] != nil { + api.Authentication.Parameters[0].Value = "ApiKeyAuth" + api.Authentication.Parameters[0].Description = securitySchemes["ApiKeyAuth"].Value.Description + api.Authentication.Parameters[0].Name = securitySchemes["ApiKeyAuth"].Value.Name + api.Authentication.Parameters[0].In = securitySchemes["ApiKeyAuth"].Value.In + api.Authentication.Parameters[0].Scheme = securitySchemes["ApiKeyAuth"].Value.Scheme + extraParameters = append(extraParameters, WorkflowAppActionParameter{ + Name: "apikey", + Description: "The apikey to use", + Multiline: false, + Required: true, + Schema: SchemaDefinition{ + Type: "string", + }, + }) + } else if securitySchemes["BasicAuth"] != nil { + api.Authentication.Parameters[0].Value = "BasicAuth" + api.Authentication.Parameters[0].Description = securitySchemes["BasicAuth"].Value.Description + api.Authentication.Parameters[0].Name = securitySchemes["BasicAuth"].Value.Name + api.Authentication.Parameters[0].In = securitySchemes["BasicAuth"].Value.In + api.Authentication.Parameters[0].Scheme = securitySchemes["BasicAuth"].Value.Scheme + extraParameters = append(extraParameters, WorkflowAppActionParameter{ + Name: "username", + Description: "The username to use", + Multiline: false, + Required: true, + Schema: SchemaDefinition{ + Type: "string", + }, + }) + extraParameters = append(extraParameters, WorkflowAppActionParameter{ + Name: "password", + Description: "The password to use", + Multiline: false, + Required: true, + Schema: SchemaDefinition{ + Type: "string", + }, + }) + } + } + if len(api.Link) == 0 { + extraParameters = append(extraParameters, WorkflowAppActionParameter{ + Name: "url", + Description: "The URL of the app", + Multiline: false, + Required: true, + Schema: SchemaDefinition{ + Type: "string", + }, + }) + } + + // This is the python code to be generated + // Could just as well be go at this point lol + pythonFunctions := []string{} + + for actualPath, path := range swagger.Paths { + //log.Printf("%#v", path) + //log.Printf("%#v", actualPath) + + // FIXME: Add everything from here: + // https://godoc.org/github.com/getkin/kin-openapi/openapi3#PathItem + firstQuery := true + if path.Get != nil { + action, curCode := handleGet(swagger, api, extraParameters, path, actualPath, firstQuery) + api.Actions = append(api.Actions, action) + pythonFunctions = append(pythonFunctions, curCode) + } + if path.Connect != nil { + action, curCode := handleConnect(swagger, api, extraParameters, path, actualPath, firstQuery) + api.Actions = append(api.Actions, action) + pythonFunctions = append(pythonFunctions, curCode) + } + if path.Head != nil { + action, curCode := handleHead(swagger, api, extraParameters, path, actualPath, firstQuery) + api.Actions = append(api.Actions, action) + pythonFunctions = append(pythonFunctions, curCode) + } + if path.Delete != nil { + action, curCode := handleDelete(swagger, api, extraParameters, path, actualPath, firstQuery) + api.Actions = append(api.Actions, action) + pythonFunctions = append(pythonFunctions, curCode) + } + if path.Post != nil { + action, curCode := handlePost(swagger, api, extraParameters, path, actualPath, firstQuery) + api.Actions = append(api.Actions, action) + pythonFunctions = append(pythonFunctions, curCode) + } + if path.Patch != nil { + action, curCode := handlePatch(swagger, api, extraParameters, path, actualPath, firstQuery) + api.Actions = append(api.Actions, action) + pythonFunctions = append(pythonFunctions, curCode) + } + if path.Put != nil { + action, curCode := handlePut(swagger, api, extraParameters, path, actualPath, firstQuery) + api.Actions = append(api.Actions, action) + pythonFunctions = append(pythonFunctions, curCode) + } + } + + return api, pythonFunctions, nil +} + +// FIXME - have this give a real version? +func verifyApi(api WorkflowApp) WorkflowApp { + if api.AppVersion == "" { + api.AppVersion = "1.0.0" + } + + return api +} + +func getBasePython() string { + baseString := `import requests +import asyncio +import json +import urllib3 + +from walkoff_app_sdk.app_base import AppBase + +class %s(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "%s" + app_name = "%s" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + +%s + +if __name__ == "__main__": + asyncio.run(%s.run(), debug=True) +` + return baseString +} + +func dumpPythonGCP(ctx context.Context, client *storage.Client, basePath, name, version string, pythonFunctions []string) (string, error) { + parsedCode := fmt.Sprintf(getBasePython(), name, version, name, strings.Join(pythonFunctions, "\n"), name) + + // Create bucket handle + bucket := client.Bucket(bucketName) + obj := bucket.Object(fmt.Sprintf("%s/src/app.py", basePath)) + w := obj.NewWriter(ctx) + if _, err := fmt.Fprintf(w, parsedCode); err != nil { + return "", err + } + // Close, just like writing a file. + if err := w.Close(); err != nil { + return "", err + } + + return parsedCode, nil +} + +func dumpPython(basePath, name, version string, pythonFunctions []string) (string, error) { + //log.Printf("%#v", api) + //log.Printf(strings.Join(pythonFunctions, "\n")) + + parsedCode := fmt.Sprintf(getBasePython(), name, version, name, strings.Join(pythonFunctions, "\n"), name) + + err := ioutil.WriteFile(fmt.Sprintf("%s/src/app.py", basePath), []byte(parsedCode), os.ModePerm) + if err != nil { + return "", err + } + //fmt.Println(parsedCode) + //log.Println(string(data)) + return parsedCode, nil +} + +func dumpApiGCP(ctx context.Context, client *storage.Client, swagger *openapi3.Swagger, basePath string, api WorkflowApp) error { + //log.Printf("%#v", api) + data, err := yaml.Marshal(api) + if err != nil { + log.Printf("Error with yaml marshal: %s", err) + return err + } + + // Create bucket handle + bucket := client.Bucket(bucketName) + obj := bucket.Object(fmt.Sprintf("%s/app.yaml", basePath)) + w := obj.NewWriter(ctx) + if _, err := fmt.Fprintln(w, string(data)); err != nil { + return err + } + // Close, just like writing a file. + if err := w.Close(); err != nil { + return err + } + + openapidata, err := yaml.Marshal(swagger) + if err != nil { + log.Printf("Error with yaml marshal: %s", err) + return err + } + obj = bucket.Object(fmt.Sprintf("%s/openapi.yaml", basePath)) + //log.Println(string(openapidata)) + w = obj.NewWriter(ctx) + if _, err := fmt.Fprintln(w, string(openapidata)); err != nil { + return err + } + // Close, just like writing a file. + if err := w.Close(); err != nil { + return err + } + + //log.Println(string(data)) + return nil +} + +func dumpApi(basePath string, api WorkflowApp) error { + //log.Printf("%#v", api) + data, err := yaml.Marshal(api) + if err != nil { + log.Printf("Error with yaml marshal: %s", err) + return err + } + + err = ioutil.WriteFile(fmt.Sprintf("%s/api.yaml", basePath), []byte(data), os.ModePerm) + if err != nil { + return err + } + + //log.Println(string(data)) + return nil +} + +func getRunner(classname string) string { + return fmt.Sprintf(` +# Run the actual thing after we've checked params +def run(request): + action = request.get_json() + print(action) + print(type(action)) + authorization_key = action.get("authorization") + current_execution_id = action.get("execution_id") + + if action and "name" in action and "app_name" in action: + asyncio.run(%s.run(action), debug=True) + return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' + else: + return f'Invalid action' + + `, classname) +} + +func deployAppToDatastore(ctx context.Context, workflowapp WorkflowApp) error { + err := setWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID) + if err != nil { + log.Printf("Failed setting workflowapp: %s", err) + return err + } else { + log.Printf("Added %s:%s to the database", workflowapp.Name, workflowapp.AppVersion) + } + + return nil +} + +func fixFunctionName(functionName, actualPath string) string { + if len(functionName) == 0 { + functionName = actualPath + } + //log.Printf("Fixing function name for %s", functionName) + functionName = strings.Replace(functionName, " ", "_", -1) + functionName = strings.Replace(functionName, ".", "", -1) + functionName = strings.Replace(functionName, ".", "", -1) + functionName = strings.Replace(functionName, "/", "", -1) + functionName = strings.Replace(functionName, "\\", "", -1) + functionName = strings.ToLower(functionName) + + return functionName +} + +func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { + // What to do with this, hmm + functionName := fixFunctionName(path.Connect.Summary, actualPath) + + action := WorkflowAppAction{ + Description: path.Connect.Description, + Name: fmt.Sprintf("%s %s", "Connect", path.Connect.Summary), + NodeType: "action", + Environment: api.Environment, + Parameters: extraParameters, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + parameters := []string{} + optionalParameters := []WorkflowAppActionParameter{} + if len(path.Connect.Parameters) > 0 { + for _, param := range path.Connect.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + //log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + //log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "connect", parameters, optionalQueries) + + if len(functionname) > 0 { + action.Name = functionname + } + + return action, curCode +} + +func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { + // What to do with this, hmm + functionName := fixFunctionName(path.Get.Summary, actualPath) + + action := WorkflowAppAction{ + Description: path.Get.Description, + Name: fmt.Sprintf("%s %s", "Get", path.Get.Summary), + NodeType: "action", + Environment: api.Environment, + Parameters: extraParameters, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + + // FIXME - remove this when authentication is properly introduced + parameters := []string{} + + optionalParameters := []WorkflowAppActionParameter{} + if len(path.Get.Parameters) > 0 { + for _, param := range path.Get.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + //log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + //log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "get", parameters, optionalQueries) + + if len(functionname) > 0 { + action.Name = functionname + } + + return action, curCode +} + +func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { + // What to do with this, hmm + functionName := fixFunctionName(path.Head.Summary, actualPath) + + action := WorkflowAppAction{ + Description: path.Head.Description, + Name: fmt.Sprintf("%s %s", "Head", path.Head.Summary), + NodeType: "action", + Environment: api.Environment, + Parameters: extraParameters, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + parameters := []string{} + optionalParameters := []WorkflowAppActionParameter{} + if len(path.Head.Parameters) > 0 { + for _, param := range path.Head.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + //log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + //log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "head", parameters, optionalQueries) + + if len(functionname) > 0 { + action.Name = functionname + } + + return action, curCode +} + +func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { + // What to do with this, hmm + functionName := fixFunctionName(path.Delete.Summary, actualPath) + + action := WorkflowAppAction{ + Description: path.Delete.Description, + Name: fmt.Sprintf("%s %s", "Delete", path.Delete.Summary), + NodeType: "action", + Environment: api.Environment, + Parameters: extraParameters, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + parameters := []string{} + optionalParameters := []WorkflowAppActionParameter{} + if len(path.Delete.Parameters) > 0 { + for _, param := range path.Delete.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + //log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + //log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "delete", parameters, optionalQueries) + + if len(functionname) > 0 { + action.Name = functionname + } + + return action, curCode +} + +func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { + // What to do with this, hmm + log.Printf("PATH: %s", actualPath) + functionName := fixFunctionName(path.Post.Summary, actualPath) + + action := WorkflowAppAction{ + Description: path.Post.Description, + Name: fmt.Sprintf("%s %s", "Post", path.Post.Summary), + NodeType: "action", + Environment: api.Environment, + Parameters: extraParameters, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + parameters := []string{} + optionalParameters := []WorkflowAppActionParameter{ + WorkflowAppActionParameter{ + Name: "body", + Description: "The body to use", + Multiline: true, + Required: false, + Example: `{"username": "test"}`, + Schema: SchemaDefinition{ + Type: "string", + }, + }, + } + if len(path.Post.Parameters) > 0 { + for _, param := range path.Post.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + //log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + //log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "post", parameters, optionalQueries) + + if len(functionname) > 0 { + action.Name = functionname + } + + return action, curCode +} + +func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { + // What to do with this, hmm + functionName := fixFunctionName(path.Patch.Summary, actualPath) + + action := WorkflowAppAction{ + Description: path.Patch.Description, + Name: fmt.Sprintf("%s %s", "Patch", path.Patch.Summary), + NodeType: "action", + Environment: api.Environment, + Parameters: extraParameters, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + parameters := []string{} + optionalParameters := []WorkflowAppActionParameter{ + WorkflowAppActionParameter{ + Name: "body", + Description: "The body to use", + Multiline: true, + Required: false, + Example: `{"username": "test"}`, + Schema: SchemaDefinition{ + Type: "string", + }, + }, + } + if len(path.Patch.Parameters) > 0 { + for _, param := range path.Patch.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + //log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + //log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "patch", parameters, optionalQueries) + + if len(functionname) > 0 { + action.Name = functionname + } + + return action, curCode +} + +func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { + // What to do with this, hmm + functionName := fixFunctionName(path.Put.Summary, actualPath) + + action := WorkflowAppAction{ + Description: path.Put.Description, + Name: fmt.Sprintf("%s %s", "Put", path.Put.Summary), + NodeType: "action", + Environment: api.Environment, + Parameters: extraParameters, + } + + action.Returns.Schema.Type = "string" + baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) + + //log.Println(path.Parameters) + + // Parameters: []WorkflowAppActionParameter{}, + // FIXME - add data for POST stuff + firstQuery = true + optionalQueries := []string{} + parameters := []string{} + optionalParameters := []WorkflowAppActionParameter{ + WorkflowAppActionParameter{ + Name: "body", + Description: "The body to use", + Multiline: true, + Required: false, + Example: `{"username": "test"}`, + Schema: SchemaDefinition{ + Type: "string", + }, + }, + } + if len(path.Put.Parameters) > 0 { + for _, param := range path.Put.Parameters { + curParam := WorkflowAppActionParameter{ + Name: param.Value.Name, + Description: param.Value.Description, + Multiline: false, + Required: param.Value.Required, + Schema: SchemaDefinition{ + Type: param.Value.Schema.Value.Type, + }, + } + + if param.Value.Required { + action.Parameters = append(action.Parameters, curParam) + } else { + optionalParameters = append(optionalParameters, curParam) + } + + if param.Value.In == "path" { + //log.Printf("PATH!: %s", param.Value.Name) + parameters = append(parameters, param.Value.Name) + //baseUrl = fmt.Sprintf("%s%s", baseUrl) + } else if param.Value.In == "query" { + //log.Printf("QUERY!: %s", param.Value.Name) + if !param.Value.Required { + optionalQueries = append(optionalQueries, param.Value.Name) + continue + } + + parameters = append(parameters, param.Value.Name) + + if firstQuery { + baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } else { + baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) + firstQuery = false + } + } + + } + } + + // ensuring that they end up last in the specification + // (order is ish important for optional params) - they need to be last. + for _, optionalParam := range optionalParameters { + action.Parameters = append(action.Parameters, optionalParam) + } + + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "put", parameters, optionalQueries) + + if len(functionname) > 0 { + action.Name = functionname + } + + return action, curCode +} diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go new file mode 100644 index 00000000..1c95006a --- /dev/null +++ b/backend/go-app/docker.go @@ -0,0 +1,644 @@ +package main + +// Docker +import ( + "archive/tar" + "path/filepath" + + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + network "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + natting "github.com/docker/go-connections/nat" + "github.com/go-git/go-billy/v5" + + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + //"google.golang.org/appengine" +) + +// Parses a directory with a Dockerfile into a tar for Docker images.. +func getParsedTar(tw *tar.Writer, baseDir, extra string) error { + return filepath.Walk(baseDir, func(file string, fi os.FileInfo, err error) error { + if file == baseDir { + return nil + } + + //log.Printf("File: %s", file) + //log.Printf("Fileinfo: %#v", fi) + switch mode := fi.Mode(); { + case mode.IsDir(): + // do directory recursion + //log.Printf("DIR: %s", file) + + // Append "src" as extra here + filenamesplit := strings.Split(file, "/") + filename := fmt.Sprintf("%s%s/", extra, filenamesplit[len(filenamesplit)-1]) + + tmpExtra := fmt.Sprintf(filename) + //log.Printf("TmpExtra: %s", tmpExtra) + err = getParsedTar(tw, file, tmpExtra) + if err != nil { + log.Printf("Directory parse issue: %s", err) + return err + } + case mode.IsRegular(): + // do file stuff + //log.Printf("FILE: %s", file) + + fileReader, err := os.Open(file) + if err != nil { + return err + } + + // Read the actual Dockerfile + readFile, err := ioutil.ReadAll(fileReader) + if err != nil { + log.Printf("Not file: %s", err) + return err + } + + filenamesplit := strings.Split(file, "/") + filename := fmt.Sprintf("%s%s", extra, filenamesplit[len(filenamesplit)-1]) + //log.Printf("Filename: %s", filename) + tarHeader := &tar.Header{ + Name: filename, + Size: int64(len(readFile)), + } + + //Writes the header described for the TAR file + err = tw.WriteHeader(tarHeader) + if err != nil { + return err + } + + // Writes the dockerfile data to the TAR file + _, err = tw.Write(readFile) + if err != nil { + return err + } + } + return nil + }) +} + +// Custom TAR builder in memory for Docker images +func getParsedTarMemory(fs billy.Filesystem, tw *tar.Writer, baseDir, extra string) error { + // This one has to use baseDir + Extra + newBase := fmt.Sprintf("%s%s", baseDir, extra) + dir, err := fs.ReadDir(newBase) + if err != nil { + return err + } + + for _, file := range dir { + // Folder? + switch mode := file.Mode(); { + case mode.IsDir(): + filename := file.Name() + filenamesplit := strings.Split(filename, "/") + + tmpExtra := fmt.Sprintf("%s%s/", extra, filenamesplit[len(filenamesplit)-1]) + //log.Printf("EXTRA: %s", tmpExtra) + err = getParsedTarMemory(fs, tw, baseDir, tmpExtra) + if err != nil { + log.Printf("Directory parse issue: %s", err) + return err + } + case mode.IsRegular(): + filenamesplit := strings.Split(file.Name(), "/") + filename := fmt.Sprintf("%s%s", extra, filenamesplit[len(filenamesplit)-1]) + // Newbase + path := fmt.Sprintf("%s%s", newBase, file.Name()) + + fileReader, err := fs.Open(path) + if err != nil { + return err + } + + readFile, err := ioutil.ReadAll(fileReader) + if err != nil { + log.Printf("Not file: %s", err) + return err + } + + //log.Printf("Filename: %s", filename) + // FIXME - might need the folder from EXTRA here + // Name has to be e.g. just "requirements.txt" + tarHeader := &tar.Header{ + Name: filename, + Size: int64(len(readFile)), + } + + //Writes the header described for the TAR file + err = tw.WriteHeader(tarHeader) + if err != nil { + return err + } + + // Writes the dockerfile data to the TAR file + _, err = tw.Write(readFile) + if err != nil { + return err + } + } + } + + return nil +} + +// Custom Docker image builder wrapper in memory +func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string) error { + ctx := context.Background() + client, err := client.NewEnvClient() + if err != nil { + log.Printf("Unable to create docker client: %s", err) + return err + } + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + defer tw.Close() + + log.Printf("Setting up memory build structure for folder: %s", dockerfileFolder) + err = getParsedTarMemory(fs, tw, dockerfileFolder, "") + if err != nil { + log.Printf("Tar issue: %s", err) + return err + } + + dockerFileTarReader := bytes.NewReader(buf.Bytes()) + + // Dockerfile is inside the TAR itself. Not local context + buildOptions := types.ImageBuildOptions{ + Remove: true, + Tags: tags, + } + + // Build the actual image + imageBuildResponse, err := client.ImageBuild( + ctx, + dockerFileTarReader, + buildOptions, + ) + + log.Printf("IMAGERESPONSE: %#v", imageBuildResponse.Body) + + if err != nil { + return err + } + + // Read the STDOUT from the build process + defer imageBuildResponse.Body.Close() + _, err = io.Copy(os.Stdout, imageBuildResponse.Body) + if err != nil { + return err + } + + return nil +} + +func buildImage(tags []string, dockerfileFolder string) error { + ctx := context.Background() + client, err := client.NewEnvClient() + if err != nil { + log.Printf("Unable to create docker client: %s", err) + return err + } + + log.Printf("Tags: %s", tags) + dockerfileSplit := strings.Split(dockerfileFolder, "/") + + // Create a buffer + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + defer tw.Close() + baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/") + + // Builds the entire folder into buf + err = getParsedTar(tw, baseDir, "") + if err != nil { + log.Printf("Tar issue: %s", err) + } + + dockerFileTarReader := bytes.NewReader(buf.Bytes()) + buildOptions := types.ImageBuildOptions{ + Remove: true, + Tags: tags, + } + + // Build the actual image + imageBuildResponse, err := client.ImageBuild( + ctx, + dockerFileTarReader, + buildOptions, + ) + + if err != nil { + return err + } + + // Read the STDOUT from the build process + defer imageBuildResponse.Body.Close() + _, err = io.Copy(os.Stdout, imageBuildResponse.Body) + if err != nil { + return err + } + + return nil +} + +// FIXME - very specific for webhooks. Make it easier? +func stopWebhook(image string, identifier string) error { + ctx := context.Background() + + containername := fmt.Sprintf("%s-%s", image, identifier) + + cli, err := client.NewEnvClient() + if err != nil { + log.Println("Unable to create docker client") + return err + } + + // containers, err := cli.ContainerList(ctx, types.ContainerListOptions{ + // All: true, + // }) + + if err := cli.ContainerStop(ctx, containername, nil); err != nil { + log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err) + } + + removeOptions := types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + } + + if err := cli.ContainerRemove(ctx, containername, removeOptions); err != nil { + log.Printf("Unable to remove container: %s", err) + } + + return nil +} + +// FIXME - remember to set DOCKER_API_VERSION +// FIXME - remove github.com/docker/docker/vendor +// FIXME - Library dependencies for NAT is fucked.. +// https://docs.docker.com/develop/sdk/examples/ +func deployWebhook(image string, identifier string, path string, port string, callbackurl string, apikey string) error { + cli, err := client.NewEnvClient() + if err != nil { + fmt.Println("Unable to create docker client") + return err + } + + newport, err := natting.NewPort("tcp", port) + if err != nil { + fmt.Println("Unable to create docker port") + return err + } + + // FIXME - logging? + + hostConfig := &container.HostConfig{ + PortBindings: natting.PortMap{ + newport: []natting.PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: port, + }, + }, + }, + RestartPolicy: container.RestartPolicy{ + Name: "always", + }, + LogConfig: container.LogConfig{ + Type: "json-file", + Config: map[string]string{}, + }, + } + + //networkConfig := &network.NetworkSettings{} + networkConfig := &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{}, + } + + test := &network.EndpointSettings{ + Gateway: "helo", + } + + networkConfig.EndpointsConfig["bridge"] = test + + exposedPorts := map[natting.Port]struct{}{ + newport: struct{}{}, + } + + config := &container.Config{ + Image: image, + Env: []string{ + fmt.Sprintf("URIPATH=%s", path), + fmt.Sprintf("HOOKPORT=%s", port), + fmt.Sprintf("CALLBACKURL=%s", callbackurl), + fmt.Sprintf("APIKEY=%s", apikey), + fmt.Sprintf("HOOKID=%s", identifier), + }, + ExposedPorts: exposedPorts, + Hostname: fmt.Sprintf("%s-%s", image, identifier), + } + + cont, err := cli.ContainerCreate( + context.Background(), + config, + hostConfig, + networkConfig, + fmt.Sprintf("%s-%s", image, identifier), + ) + + if err != nil { + log.Println(err) + return err + } + + cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{}) + log.Printf("Container %s is created", cont.ID) + return nil +} + +// Starts a new webhook +func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + ctx := context.Background() + hook, err := getHook(ctx, fileId) + if err != nil { + log.Printf("Failed getting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Status: %s", hook.Status) + log.Printf("Running: %t", hook.Running) + if !hook.Running { + message := fmt.Sprintf("Error: %s isn't running", hook.Id) + log.Println(message) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message))) + return + } + + hook.Status = "stopped" + hook.Running = false + hook.Actions = []HookAction{} + err = setHook(ctx, *hook) + if err != nil { + log.Printf("Failed setting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + image := "webhook" + + // This is here to force stop and remove the old webhook + err = stopWebhook(image, fileId) + if err != nil { + log.Printf("Container stop issue for %s-%s: %s", image, fileId, err) + } + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "message": "Stopped webhook"}`)) +} + +// THis is an example +// Can also be used as base data? +var webhook = `{ + "id": "d6ef8912e8bd37776e654cbc14c2629c", + "info": { + "url": "http://localhost:5001", + "name": "TheHive", + "description": "Webhook for TheHive" + }, + "transforms": {}, + "actions": {}, + "type": "webhook", + "running": false, + "status": "stopped" +}` + +// Starts a new webhook +func handleDeleteHookDocker(resp http.ResponseWriter, request *http.Request) { + ctx := context.Background() + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + err := DeleteKey(ctx, "hooks", fileId) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "Can't delete"}`)) + return + } + + image := "webhook" + + // This is here to force stop and remove the old webhook + err = stopWebhook(image, fileId) + if err != nil { + log.Printf("Container stop issue for %s-%s: %s", image, fileId, err) + resp.Write([]byte(`{"success": false, "message": "Couldn't stop webhook"}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "message": "Deleted webhook"}`)) +} + +// Starts a new webhook +func handleStartHookDocker(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + ctx := context.Background() + hook, err := getHook(ctx, fileId) + if err != nil { + log.Printf("Failed getting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(hook.Info.Url) == 0 { + log.Printf("Hook url can't be empty.") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Status: %s", hook.Status) + log.Printf("Running: %t", hook.Running) + if hook.Running || hook.Status == "Running" { + message := fmt.Sprintf("Error: %s is already running", hook.Id) + log.Println(message) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message))) + return + } + + // FIXME - verify? + // FIXME - static port? Generate from available range. + image := "webhook" + filepath := "/webhook" + baseUrl := "http://localhost" + callbackUrl := "http://localhost:8001" + + // This is here to force stop and remove the old webhook + err = stopWebhook(image, fileId) + if err != nil { + log.Printf("Container stop issue for %s-%s: %s", image, fileId, err) + } + + // Dynamic ish ports + var startPort int64 = 5001 + var endPort int64 = 5010 + port := findAvailablePorts(startPort, endPort) + if len(port) == 0 { + message := fmt.Sprintf("Not ports available in the range %d-%d", startPort, endPort) + log.Println(message) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message))) + return + + } + + hook.Status = "running" + hook.Running = true + + // Set this for more than just hooks? + if hook.Type == "webhook" { + hook.Info.Url = fmt.Sprintf("%s:%s%s", baseUrl, port, filepath) + } + err = setHook(ctx, *hook) + if err != nil { + log.Printf("Failed setting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Cloud run? Let's make a generic webhook that can be deployed easily + log.Printf("Should run a webhook with the following: \nUrl: %s\nId: %s\n", hook.Info.Url, hook.Id) + + // FIXME - set port based on what the user specified / what was generated + // FIXME - add nonstatic APIKEY + apiKey := "ASD" + + err = deployWebhook(image, fileId, filepath, port, callbackUrl, apiKey) + if err != nil { + log.Printf("Failed starting container %s-%s: %s", image, fileId, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - get some real data? + log.Printf("Successfully started %s-%s on port %s with filepath %s", image, fileId, port, filepath) + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "message": "Started webhook"}`)) + return +} + +func hookTest() { + var hook Hook + err := json.Unmarshal([]byte(webhook), &hook) + log.Println(webhook) + if err != nil { + log.Printf("Failed hook unmarshaling: %s", err) + return + } + + ctx := context.Background() + err = setHook(ctx, hook) + if err != nil { + log.Printf("Failed setting hook: %s", err) + } + + returnHook, err := getHook(ctx, hook.Id) + if err != nil { + log.Printf("Failed getting hook: %s", err) + } + + if len(returnHook.Id) > 0 { + log.Printf("Success! - %s", returnHook.Id) + } +} diff --git a/backend/go-app/generated/-12c5230274e48df565e79874f997cb7f/Dockerfile b/backend/go-app/generated/-12c5230274e48df565e79874f997cb7f/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/backend/go-app/generated/-12c5230274e48df565e79874f997cb7f/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/backend/go-app/generated/-12c5230274e48df565e79874f997cb7f/requirements.txt b/backend/go-app/generated/-12c5230274e48df565e79874f997cb7f/requirements.txt new file mode 100644 index 00000000..dfad3eb9 --- /dev/null +++ b/backend/go-app/generated/-12c5230274e48df565e79874f997cb7f/requirements.txt @@ -0,0 +1,3 @@ +# No extra requirements needed +requests +urllib3 diff --git a/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/Dockerfile b/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/api.yaml b/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/api.yaml new file mode 100755 index 00000000..514cd20a --- /dev/null +++ b/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/api.yaml @@ -0,0 +1,50 @@ +name: Asd +is_valid: true +id: 71d39218-1617-429f-b017-a6987c5646dd +link: https://google.com +app_version: 1.0.0 +generated: true +sharing: false +verified: false +tested: false +owner: e0024ac4-e5b3-4fb3-9ed6-3a73c7e20817 +private_id: 37fff3ea5fa10cdde521f21134320c26 +description: asd +environment: cloud +smallimage: data:image/png;base64,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 +large_image: data:image/png;base64,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 +contact_info: + name: "" + url: "" +actions: +- description: hello + name: get_hello + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: The apikey to use + name: apikey + example: "" + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string +authentication: + required: true + parameters: + - description: "" + id: "" + name: what + example: "" + value: ApiKeyAuth + multiline: false + required: true + in: header + scheme: "" diff --git a/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/requirements.txt b/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/requirements.txt new file mode 100644 index 00000000..dfad3eb9 --- /dev/null +++ b/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/requirements.txt @@ -0,0 +1,3 @@ +# No extra requirements needed +requests +urllib3 diff --git a/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/src/app.py b/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/src/app.py new file mode 100755 index 00000000..c0b66f8d --- /dev/null +++ b/backend/go-app/generated/Asd-37fff3ea5fa10cdde521f21134320c26/src/app.py @@ -0,0 +1,29 @@ +import requests +import asyncio +import json + +from walkoff_app_sdk.app_base import AppBase + +class Asd37fff3ea5fa10cdde521f21134320c26(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "1.0" + app_name = "Asd37fff3ea5fa10cdde521f21134320c26" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + + async def get_hello(self, apikey): + headers={} + url=f"https://google.com/lol" + headers["what"] = apikey + + return requests.get(url, headers=headers).text + + +if __name__ == "__main__": + asyncio.run(Asd37fff3ea5fa10cdde521f21134320c26.run(), debug=True) diff --git a/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/Dockerfile b/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/api.yaml b/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/api.yaml new file mode 100755 index 00000000..9f855254 --- /dev/null +++ b/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/api.yaml @@ -0,0 +1,193 @@ +name: Uber API +is_valid: true +id: 6a707aa0-892b-42a0-b0a4-b10b6b56e6e5 +link: https://api.uber.com +app_version: 1.0.0 +generated: true +sharing: false +verified: false +tested: false +owner: 2501f368-edf2-4fee-bb7e-457d65d7410c +private_id: 547f1803-edf7-433f-8016-d508ed978f65 +description: Move your app forward with the Uber API +environment: cloud +smallimage: "" +large_image: "" +contact_info: + name: "" + url: "" +actions: +- description: The Price Estimates endpoint returns an estimated price range for each + product offered at a given location. The price estimate is provided as a formatted + string with the full price range and the localized currency symbol.

            The + response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) + currency code for situations requiring currency conversion. When surge is active + for a particular product, its surge_multiplier will be greater than 1, but the + price estimate already factors in this multiplier. + name: get_price_estimates + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: Latitude component of start location. + name: start_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of start location. + name: start_longitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Latitude component of end location. + name: end_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of end location. + name: end_longitude + example: "" + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string +- description: The Time Estimates endpoint returns ETAs for all products offered at + a given location, with the responses expressed as integers in seconds. We recommend + that this endpoint be called every minute to provide the most accurate, up-to-date + ETAs. + name: get_time_estimates + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: Latitude component of start location. + name: start_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of start location. + name: start_longitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Unique customer identifier to be used for experience customization. + name: customer_uuid + example: "" + multiline: false + required: false + schema: + type: string + - description: Unique identifier representing a specific product for a given latitude + & longitude. + name: product_id + example: "" + multiline: false + required: false + schema: + type: string + returns: + schema: + type: string +- description: The User Activity endpoint returns data about a user's lifetime activity + with Uber. The response will include pickup locations and times, dropoff locations + and times, the distance of past requests, and information about which products + were requested.

            The history array in the response will have a maximum length + based on the limit parameter. The response value count may exceed limit, therefore + subsequent API requests may be necessary. + name: get_user_activity + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: Offset the list of returned results by this amount. Default is zero. + name: offset + example: "" + multiline: false + required: false + schema: + type: string + - description: Number of items to retrieve. Default is 5, maximum is 100. + name: limit + example: "" + multiline: false + required: false + schema: + type: string + returns: + schema: + type: string +- description: The User Profile endpoint returns information about the Uber user that + has authorized with the application. + name: get_user_profile + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: [] + returns: + schema: + type: string +- description: The Products endpoint returns information about the Uber products offered + at a given location. The response includes the display name and other details + about each product, and lists the products in the proper display order. + name: get_product_types + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: Latitude component of location. + name: latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of location. + name: longitude + example: "" + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string +authentication: + required: true + parameters: + - description: "" + id: "" + name: "" + example: "" + value: "" + multiline: false + required: true + in: "" + scheme: "" diff --git a/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/requirements.txt b/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/requirements.txt new file mode 100644 index 00000000..dfad3eb9 --- /dev/null +++ b/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/requirements.txt @@ -0,0 +1,3 @@ +# No extra requirements needed +requests +urllib3 diff --git a/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/src/app.py b/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/src/app.py new file mode 100755 index 00000000..a043e3bd --- /dev/null +++ b/backend/go-app/generated/Uber API-547f1803-edf7-433f-8016-d508ed978f65/src/app.py @@ -0,0 +1,66 @@ +import requests +import asyncio +import json +import urllib3 + +from walkoff_app_sdk.app_base import AppBase + +class UberAPI547f1803edf7433f8016d508ed978f65(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "1.0" + app_name = "UberAPI547f1803edf7433f8016d508ed978f65" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + + async def get_price_estimates(self, start_latitude, start_longitude, end_latitude, end_longitude): + headers={} + url=f"https://api.uber.com/estimates/price?start_latitude={start_latitude}&start_longitude={start_longitude}&end_latitude={end_latitude}&end_longitude={end_longitude}" + + + return requests.get(url, headers=headers).text + + async def get_time_estimates(self, start_latitude, start_longitude, customer_uuid="", product_id="", ): + headers={} + url=f"https://api.uber.com/estimates/time?start_latitude={start_latitude}&start_longitude={start_longitude}" + + + if customer_uuid: + url += f"&customer_uuid={customer_uuid}" + if product_id: + url += f"&product_id={product_id}" + return requests.get(url, headers=headers).text + + async def get_user_activity(self, offset="", limit="", ): + headers={} + url=f"https://api.uber.com/history" + + + if offset: + url += f"&offset={offset}" + if limit: + url += f"&limit={limit}" + return requests.get(url, headers=headers).text + + async def get_user_profile(self): + headers={} + url=f"https://api.uber.com/me" + + + return requests.get(url, headers=headers).text + + async def get_product_types(self, latitude, longitude): + headers={} + url=f"https://api.uber.com/products?latitude={latitude}&longitude={longitude}" + + + return requests.get(url, headers=headers).text + + +if __name__ == "__main__": + asyncio.run(UberAPI547f1803edf7433f8016d508ed978f65.run(), debug=True) diff --git a/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/Dockerfile b/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/api.yaml b/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/api.yaml new file mode 100755 index 00000000..82926474 --- /dev/null +++ b/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/api.yaml @@ -0,0 +1,193 @@ +name: Uber API +is_valid: true +id: 547f1803-edf7-433f-8016-d508ed978f65 +link: https://api.uber.com +app_version: 1.0.0 +generated: true +sharing: false +verified: false +tested: false +owner: 2501f368-edf2-4fee-bb7e-457d65d7410c +private_id: 795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea +description: Move your app forward with the Uber API +environment: cloud +smallimage: "" +large_image: "" +contact_info: + name: "" + url: "" +actions: +- description: The Price Estimates endpoint returns an estimated price range for each + product offered at a given location. The price estimate is provided as a formatted + string with the full price range and the localized currency symbol.

            The + response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) + currency code for situations requiring currency conversion. When surge is active + for a particular product, its surge_multiplier will be greater than 1, but the + price estimate already factors in this multiplier. + name: get_price_estimates + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: Latitude component of start location. + name: start_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of start location. + name: start_longitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Latitude component of end location. + name: end_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of end location. + name: end_longitude + example: "" + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string +- description: The Time Estimates endpoint returns ETAs for all products offered at + a given location, with the responses expressed as integers in seconds. We recommend + that this endpoint be called every minute to provide the most accurate, up-to-date + ETAs. + name: get_time_estimates + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: Latitude component of start location. + name: start_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of start location. + name: start_longitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Unique customer identifier to be used for experience customization. + name: customer_uuid + example: "" + multiline: false + required: false + schema: + type: string + - description: Unique identifier representing a specific product for a given latitude + & longitude. + name: product_id + example: "" + multiline: false + required: false + schema: + type: string + returns: + schema: + type: string +- description: The User Activity endpoint returns data about a user's lifetime activity + with Uber. The response will include pickup locations and times, dropoff locations + and times, the distance of past requests, and information about which products + were requested.

            The history array in the response will have a maximum length + based on the limit parameter. The response value count may exceed limit, therefore + subsequent API requests may be necessary. + name: get_user_activity + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: Offset the list of returned results by this amount. Default is zero. + name: offset + example: "" + multiline: false + required: false + schema: + type: string + - description: Number of items to retrieve. Default is 5, maximum is 100. + name: limit + example: "" + multiline: false + required: false + schema: + type: string + returns: + schema: + type: string +- description: The User Profile endpoint returns information about the Uber user that + has authorized with the application. + name: get_user_profile + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: [] + returns: + schema: + type: string +- description: The Products endpoint returns information about the Uber products offered + at a given location. The response includes the display name and other details + about each product, and lists the products in the proper display order. + name: get_product_types + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: Latitude component of location. + name: latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of location. + name: longitude + example: "" + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string +authentication: + required: true + parameters: + - description: "" + id: "" + name: "" + example: "" + value: "" + multiline: false + required: true + in: "" + scheme: "" diff --git a/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/requirements.txt b/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/requirements.txt new file mode 100644 index 00000000..dfad3eb9 --- /dev/null +++ b/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/requirements.txt @@ -0,0 +1,3 @@ +# No extra requirements needed +requests +urllib3 diff --git a/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/src/app.py b/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/src/app.py new file mode 100755 index 00000000..23f3f83e --- /dev/null +++ b/backend/go-app/generated/Uber API-795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea/src/app.py @@ -0,0 +1,66 @@ +import requests +import asyncio +import json +import urllib3 + +from walkoff_app_sdk.app_base import AppBase + +class UberAPI795ed46f2dbd43d69d0c1f6b9cce14ea(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "1.0" + app_name = "UberAPI795ed46f2dbd43d69d0c1f6b9cce14ea" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + + async def get_price_estimates(self, start_latitude, start_longitude, end_latitude, end_longitude): + headers={} + url=f"https://api.uber.com/estimates/price?start_latitude={start_latitude}&start_longitude={start_longitude}&end_latitude={end_latitude}&end_longitude={end_longitude}" + + + return requests.get(url, headers=headers).text + + async def get_time_estimates(self, start_latitude, start_longitude, customer_uuid="", product_id="", ): + headers={} + url=f"https://api.uber.com/estimates/time?start_latitude={start_latitude}&start_longitude={start_longitude}" + + + if customer_uuid: + url += f"&customer_uuid={customer_uuid}" + if product_id: + url += f"&product_id={product_id}" + return requests.get(url, headers=headers).text + + async def get_user_activity(self, offset="", limit="", ): + headers={} + url=f"https://api.uber.com/history" + + + if offset: + url += f"&offset={offset}" + if limit: + url += f"&limit={limit}" + return requests.get(url, headers=headers).text + + async def get_user_profile(self): + headers={} + url=f"https://api.uber.com/me" + + + return requests.get(url, headers=headers).text + + async def get_product_types(self, latitude, longitude): + headers={} + url=f"https://api.uber.com/products?latitude={latitude}&longitude={longitude}" + + + return requests.get(url, headers=headers).text + + +if __name__ == "__main__": + asyncio.run(UberAPI795ed46f2dbd43d69d0c1f6b9cce14ea.run(), debug=True) diff --git a/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/Dockerfile b/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/api.yaml b/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/api.yaml new file mode 100755 index 00000000..019d93dd --- /dev/null +++ b/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/api.yaml @@ -0,0 +1,228 @@ +name: Uber API +is_valid: true +id: 795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea +link: "" +app_version: 1.0.0 +generated: true +sharing: false +verified: false +tested: false +owner: 2501f368-edf2-4fee-bb7e-457d65d7410c +private_id: f97a44dd-f1c3-48ae-b46f-ceb19ce3039c +description: Move your app forward with the Uber API +environment: cloud +smallimage: "" +large_image: "" +contact_info: + name: "" + url: "" +actions: +- description: The Time Estimates endpoint returns ETAs for all products offered at + a given location, with the responses expressed as integers in seconds. We recommend + that this endpoint be called every minute to provide the most accurate, up-to-date + ETAs. + name: get_time_estimates + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: The URL of the app + name: url + example: "" + multiline: false + required: true + schema: + type: string + - description: Latitude component of start location. + name: start_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of start location. + name: start_longitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Unique customer identifier to be used for experience customization. + name: customer_uuid + example: "" + multiline: false + required: false + schema: + type: string + - description: Unique identifier representing a specific product for a given latitude + & longitude. + name: product_id + example: "" + multiline: false + required: false + schema: + type: string + returns: + schema: + type: string +- description: The User Activity endpoint returns data about a user's lifetime activity + with Uber. The response will include pickup locations and times, dropoff locations + and times, the distance of past requests, and information about which products + were requested.

            The history array in the response will have a maximum length + based on the limit parameter. The response value count may exceed limit, therefore + subsequent API requests may be necessary. + name: get_user_activity + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: The URL of the app + name: url + example: "" + multiline: false + required: true + schema: + type: string + - description: Offset the list of returned results by this amount. Default is zero. + name: offset + example: "" + multiline: false + required: false + schema: + type: string + - description: Number of items to retrieve. Default is 5, maximum is 100. + name: limit + example: "" + multiline: false + required: false + schema: + type: string + returns: + schema: + type: string +- description: The User Profile endpoint returns information about the Uber user that + has authorized with the application. + name: get_user_profile + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: The URL of the app + name: url + example: "" + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string +- description: The Products endpoint returns information about the Uber products offered + at a given location. The response includes the display name and other details + about each product, and lists the products in the proper display order. + name: get_product_types + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: The URL of the app + name: url + example: "" + multiline: false + required: true + schema: + type: string + - description: Latitude component of location. + name: latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of location. + name: longitude + example: "" + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string +- description: The Price Estimates endpoint returns an estimated price range for each + product offered at a given location. The price estimate is provided as a formatted + string with the full price range and the localized currency symbol.

            The + response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) + currency code for situations requiring currency conversion. When surge is active + for a particular product, its surge_multiplier will be greater than 1, but the + price estimate already factors in this multiplier. + name: get_price_estimates + nodetype: action + environment: cloud + sharing: false + privateid: "" + appid: "" + tested: false + parameters: + - description: The URL of the app + name: url + example: "" + multiline: false + required: true + schema: + type: string + - description: Latitude component of start location. + name: start_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of start location. + name: start_longitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Latitude component of end location. + name: end_latitude + example: "" + multiline: false + required: true + schema: + type: string + - description: Longitude component of end location. + name: end_longitude + example: "" + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string +authentication: + required: true + parameters: + - description: "" + id: "" + name: "" + example: "" + value: "" + multiline: false + required: true + in: "" + scheme: "" diff --git a/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/requirements.txt b/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/requirements.txt new file mode 100644 index 00000000..dfad3eb9 --- /dev/null +++ b/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/requirements.txt @@ -0,0 +1,3 @@ +# No extra requirements needed +requests +urllib3 diff --git a/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/src/app.py b/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/src/app.py new file mode 100755 index 00000000..c14d69f9 --- /dev/null +++ b/backend/go-app/generated/Uber API-f97a44dd-f1c3-48ae-b46f-ceb19ce3039c/src/app.py @@ -0,0 +1,66 @@ +import requests +import asyncio +import json +import urllib3 + +from walkoff_app_sdk.app_base import AppBase + +class UberAPIf97a44ddf1c348aeb46fceb19ce3039c(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "1.0" + app_name = "UberAPIf97a44ddf1c348aeb46fceb19ce3039c" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + + async def get_time_estimates(self, baseurl, start_latitude, start_longitude, customer_uuid="", product_id="", ): + headers={} + url=f"{baseurl}/estimates/time?start_latitude={start_latitude}&start_longitude={start_longitude}" + + + if customer_uuid: + url += f"&customer_uuid={customer_uuid}" + if product_id: + url += f"&product_id={product_id}" + return requests.get(url, headers=headers).text + + async def get_user_activity(self, baseurl, offset="", limit="", ): + headers={} + url=f"{baseurl}/history" + + + if offset: + url += f"&offset={offset}" + if limit: + url += f"&limit={limit}" + return requests.get(url, headers=headers).text + + async def get_user_profile(self, baseurl): + headers={} + url=f"{baseurl}/me" + + + return requests.get(url, headers=headers).text + + async def get_product_types(self, baseurl, latitude, longitude): + headers={} + url=f"{baseurl}/products?latitude={latitude}&longitude={longitude}" + + + return requests.get(url, headers=headers).text + + async def get_price_estimates(self, baseurl, start_latitude, start_longitude, end_latitude, end_longitude): + headers={} + url=f"{baseurl}/estimates/price?start_latitude={start_latitude}&start_longitude={start_longitude}&end_latitude={end_latitude}&end_longitude={end_longitude}" + + + return requests.get(url, headers=headers).text + + +if __name__ == "__main__": + asyncio.run(UberAPIf97a44ddf1c348aeb46fceb19ce3039c.run(), debug=True) diff --git a/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/Dockerfile b/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/api.yaml b/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/api.yaml new file mode 100755 index 00000000..c6ae5f28 --- /dev/null +++ b/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/api.yaml @@ -0,0 +1,31 @@ +name: a +is_valid: true +id: 31e2332c-8baa-41e9-98af-c2457d64d946 +link: "" +app_version: 1.0.0 +generated: true +sharing: false +verified: false +tested: false +owner: c2dbe917-f982-40ed-9045-462872c4ca1e +private_id: 757f74fead843a8d9b5a954d2fadfa69 +description: "" +environment: cloud +smallimage: "" +large_image: "" +contact_info: + name: "" + url: "" +actions: [] +authentication: + required: true + parameters: + - description: "" + id: "" + name: "" + example: "" + value: "" + multiline: false + required: true + in: "" + scheme: "" diff --git a/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/requirements.txt b/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/requirements.txt new file mode 100644 index 00000000..dfad3eb9 --- /dev/null +++ b/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/requirements.txt @@ -0,0 +1,3 @@ +# No extra requirements needed +requests +urllib3 diff --git a/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/src/app.py b/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/src/app.py new file mode 100755 index 00000000..12f8dae3 --- /dev/null +++ b/backend/go-app/generated/a-757f74fead843a8d9b5a954d2fadfa69/src/app.py @@ -0,0 +1,23 @@ +import requests +import asyncio +import json + +from walkoff_app_sdk.app_base import AppBase + +class a757f74fead843a8d9b5a954d2fadfa69(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "1.0" + app_name = "a757f74fead843a8d9b5a954d2fadfa69" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + + + +if __name__ == "__main__": + asyncio.run(a757f74fead843a8d9b5a954d2fadfa69.run(), debug=True) diff --git a/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/Dockerfile b/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/api.yaml b/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/api.yaml new file mode 100755 index 00000000..1630dbad --- /dev/null +++ b/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/api.yaml @@ -0,0 +1,31 @@ +name: test +is_valid: true +id: a29d4143-fa3b-4456-86c4-fee2d402ddf6 +link: https://test.com +app_version: 1.0.0 +generated: true +sharing: false +verified: false +tested: false +owner: c2dbe917-f982-40ed-9045-462872c4ca1e +private_id: f383aa4f34d36802daf465e2af5f7aa1 +description: test +environment: cloud +smallimage: "" +large_image: "" +contact_info: + name: "" + url: "" +actions: [] +authentication: + required: true + parameters: + - description: "" + id: "" + name: "" + example: "" + value: "" + multiline: false + required: true + in: "" + scheme: "" diff --git a/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/requirements.txt b/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/requirements.txt new file mode 100644 index 00000000..dfad3eb9 --- /dev/null +++ b/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/requirements.txt @@ -0,0 +1,3 @@ +# No extra requirements needed +requests +urllib3 diff --git a/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/src/app.py b/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/src/app.py new file mode 100755 index 00000000..d3e575ff --- /dev/null +++ b/backend/go-app/generated/test-f383aa4f34d36802daf465e2af5f7aa1/src/app.py @@ -0,0 +1,23 @@ +import requests +import asyncio +import json + +from walkoff_app_sdk.app_base import AppBase + +class testf383aa4f34d36802daf465e2af5f7aa1(AppBase): + """ + Autogenerated class by Shuffler + """ + + __version__ = "1.0" + app_name = "testf383aa4f34d36802daf465e2af5f7aa1" + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + + + +if __name__ == "__main__": + asyncio.run(testf383aa4f34d36802daf465e2af5f7aa1.run(), debug=True) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod new file mode 100644 index 00000000..8f88d119 --- /dev/null +++ b/backend/go-app/go.mod @@ -0,0 +1,31 @@ +module shuffle + +go 1.13 + +require ( + cloud.google.com/go v0.57.0 + cloud.google.com/go/datastore v1.1.0 + cloud.google.com/go/pubsub v1.3.1 + cloud.google.com/go/storage v1.7.0 + github.com/basgys/goxml2json v1.1.0 + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/docker v1.13.1 + github.com/docker/go-connections v0.4.0 + github.com/docker/go-units v0.4.0 // indirect + github.com/getkin/kin-openapi v0.8.0 + github.com/ghodss/yaml v1.0.0 + github.com/go-git/go-billy/v5 v5.0.0 + github.com/go-git/go-git/v5 v5.0.0 + github.com/google/go-github/v28 v28.1.1 + github.com/gorilla/mux v1.7.4 + github.com/h2non/filetype v1.0.12 + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/satori/go.uuid v1.2.0 + golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + google.golang.org/api v0.23.0 + google.golang.org/appengine v1.6.6 + google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31 + gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 +) diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum new file mode 100644 index 00000000..53d18803 --- /dev/null +++ b/backend/go-app/go.sum @@ -0,0 +1,400 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.6.0/go.mod h1:hyFDG0qSGdHNz8Q6nDN8rYIkld0q/+5uBZaelxiDLfE= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.7.0 h1:DzdLPI8Em+DEk7IzA2a10ivq3mxIEASC9GeNJ6FFt5Q= +cloud.google.com/go/storage v1.7.0/go.mod h1:jGMIBwF+L/tL6WN/W5InNgYYu4HP0DvGB6rQ1mufWfs= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw= +github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= +github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/getkin/kin-openapi v0.8.0 h1:a6TQjTqwkyscC4/hShJX7WhCVE+4bi9lzw61XHQW5hE= +github.com/getkin/kin-openapi v0.8.0/go.mod h1:zZQMFkVgRHCdhgb6ihCTIo9dyDZFvX0k/xAKqw1FhPw= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy v4.2.0+incompatible h1:Z6QtVXd5tjxUtcODLugkJg4WaZnGg13CD8qB9pr+7q0= +github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= +github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg= +github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/h2non/filetype v1.0.12 h1:yHCsIe0y2cvbDARtJhGBTD2ecvqMSTvlIcph9En/Zao= +github.com/h2non/filetype v1.0.12/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e h1:hq86ru83GdWTlfQFZGO4nZJTU4Bs2wfHl8oFHRaXsfc= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200409170454-77362c5149f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.23.0 h1:YlvGEOq2NA2my8cZ/9V8BcEO9okD48FlJcdqN0xJL3s= +google.golang.org/api v0.23.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31 h1:Bz1qTn2YRWV+9OKJtxHJiQKCiXIdf+kwuKXdt9cBxyU= +google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0= +gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/backend/go-app/main.go b/backend/go-app/main.go new file mode 100644 index 00000000..acc31784 --- /dev/null +++ b/backend/go-app/main.go @@ -0,0 +1,5587 @@ +package main + +import ( + "bufio" + + "bytes" + "context" + "crypto/md5" + "encoding/hex" + "encoding/json" + "errors" + + "fmt" + "io" + "io/ioutil" + "log" + "net" + "net/http" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + "time" + + // Google cloud + "cloud.google.com/go/datastore" + "cloud.google.com/go/pubsub" + "cloud.google.com/go/storage" + "google.golang.org/appengine/mail" + + "github.com/getkin/kin-openapi/openapi2" + "github.com/getkin/kin-openapi/openapi2conv" + "github.com/getkin/kin-openapi/openapi3" + + "github.com/google/go-github/v28/github" + "golang.org/x/oauth2" + + // Random + xj "github.com/basgys/goxml2json" + gyaml "github.com/ghodss/yaml" + "github.com/satori/go.uuid" + "golang.org/x/crypto/bcrypt" + "gopkg.in/yaml.v3" + + // Web + // "github.com/gorilla/handlers" + "github.com/gorilla/mux" + // Old items (cloud) + // "google.golang.org/appengine" + // "google.golang.org/appengine/memcache" + // applog "google.golang.org/appengine/log" + //cloudrun "google.golang.org/api/run/v1" +) + +// This is used to handle onprem vs offprem databases etc +var gceProject = "shuffle" +var bucketName = "shuffler.appspot.com" +var baseAppPath = "/home/frikky/git/shaffuru/tmp/apps" +var baseDockerName = "frikky/shuffle" + +var dbclient *datastore.Client + +type Userapi struct { + Username string `datastore:"Username"` + ApiKey string `datastore:"apikey"` +} + +type ExecutionInfo struct { + TotalApiUsage int64 `json:"total_api_usage" datastore:"total_api_usage"` + TotalWorkflowExecutions int64 `json:"total_workflow_executions" datastore:"total_workflow_executions"` + TotalAppExecutions int64 `json:"total_app_executions" datastore:"total_app_executions"` + TotalCloudExecutions int64 `json:"total_cloud_executions" datastore:"total_cloud_executions"` + TotalOnpremExecutions int64 `json:"total_onprem_executions" datastore:"total_onprem_executions"` + DailyApiUsage int64 `json:"daily_api_usage" datastore:"daily_api_usage"` + DailyWorkflowExecutions int64 `json:"daily_workflow_executions" datastore:"daily_workflow_executions"` + DailyAppExecutions int64 `json:"daily_app_executions" datastore:"daily_app_executions"` + DailyCloudExecutions int64 `json:"daily_cloud_executions" datastore:"daily_cloud_executions"` + DailyOnpremExecutions int64 `json:"daily_onprem_executions" datastore:"daily_onprem_executions"` +} + +type ParsedOpenApi struct { + Body string `datastore:"body,noindex" json:"body"` + ID string `datastore:"id" json:"id"` + Success bool `datastore:"success,omitempty" json:"success,omitempty"` +} + +// Limits set for a user so that they can't do a shitload +type UserLimits struct { + DailyApiUsage int64 `json:"daily_api_usage" datastore:"daily_api_usage"` + DailyWorkflowExecutions int64 `json:"daily_workflow_executions" datastore:"daily_workflow_executions"` + DailyCloudExecutions int64 `json:"daily_cloud_executions" datastore:"daily_cloud_executions"` + DailyTriggers int64 `json:"daily_triggers" datastore:"daily_triggers"` + DailyMailUsage int64 `json:"daily_mail_usage" datastore:"daily_mail_usage"` + MaxTriggers int64 `json:"max_triggers" datastore:"max_triggers"` + MaxWorkflows int64 `json:"max_workflows" datastore:"max_workflows"` +} + +// Saves some data, not sure what to have here lol +type UserAuth struct { + Description string `json:"description" datastore:"description" yaml:"description"` + Name string `json:"name" datastore:"name" yaml:"name"` + Workflows []string `json:"workflows" datastore:"workflows"` + Username string `json:"username" datastore:"username"` + Fields []UserAuthField `json:"fields" datastore:"fields"` +} + +type UserAuthField struct { + Key string `json:"key" datastore:"key"` + Value string `json:"value" datastore:"value"` +} + +// Not environment, but execution environment +type Environment struct { + Name string `datastore:"name"` + Type string `datastore:"type"` + Registered bool `datastore:"registered"` +} + +type User struct { + Username string `datastore:"Username"` + Password string `datastore:"password,noindex"` + Session string `datastore:"session,noindex"` + Verified bool `datastore:"verified,noindex"` + PrivateApps []WorkflowApp `datastore:"privateapps"` + Role string `datastore:"role"` + VerificationToken string `datastore:"verification_token"` + ApiKey string `datastore:"apikey"` + ResetReference string `datastore:"reset_reference"` + Executions ExecutionInfo `datastore:"executions" json:"executions"` + Limits UserLimits `datastore:"limits" json:"limits"` + Authentication []UserAuth `datastore:"authentication,noindex" json:"authentication"` + ResetTimeout int64 `datastore:"reset_timeout,noindex"` + Id string `datastore:"id" json:"id"` + Orgs string `datastore:"orgs" json:"orgs"` + CreationTime int64 `datastore:"creation_time" json:"creation_time"` +} + +type session struct { + Username string `datastore:"Username,noindex"` + Session string `datastore:"session,noindex"` +} + +type loginStruct struct { + Username string `json:"Username"` + Password string `json:"password"` +} + +type Contact struct { + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Title string `json:"title"` + Companyname string `json:"companyname"` + Phone string `json:"phone"` + Email string `json:"email"` + Message string `json:"message"` +} + +type Translator struct { + Src struct { + Name string `json:"name" datastore:"name"` + Value string `json:"value" datastore:"value"` + Description string `json:"description" datastore:"description"` + Required string `json:"required" datastore:"required"` + Type string `json:"type" datastore:"type"` + Schema struct { + Type string `json:"type" datastore:"type"` + } `json:"schema" datastore:"schema"` + } `json:"src" datastore:"src"` + Dst struct { + Name string `json:"name" datastore:"name"` + Value string `json:"value" datastore:"value"` + Type string `json:"type" datastore:"type"` + Description string `json:"description" datastore:"description"` + Required string `json:"required" datastore:"required"` + Schema struct { + Type string `json:"type" datastore:"type"` + } `json:"schema" datastore:"schema"` + } `json:"dst" datastore:"dst"` +} + +type Appconfig struct { + Key string `json:"key" datastore:"key"` + Value string `json:"value" datastore:"value"` +} + +type ScheduleApp struct { + Foldername string `json:"foldername" datastore:"foldername,noindex"` + Name string `json:"name" datastore:"name,noindex"` + Id string `json:"id" datastore:"id,noindex"` + Description string `json:"description" datastore:"description,noindex"` + Action string `json:"action" datastore:"action,noindex"` + Config []Appconfig `json:"config,omitempty" datastore:"config,noindex"` +} + +type AppInfo struct { + SourceApp ScheduleApp `json:"sourceapp,omitempty" datastore:"sourceapp,noindex"` + DestinationApp ScheduleApp `json:"destinationapp,omitempty" datastore:"destinationapp,noindex"` +} + +// Used for the api integrator +//Username string `datastore:"Username,noindex"` +type ScheduleOld struct { + Id string `json:"id" datastore:"id"` + AppInfo AppInfo `json:"appinfo" datastore:"appinfo,noindex"` + Finished bool `json:"finished" finished:"id"` + BaseAppLocation string `json:"base_app_location" datastore:"baseapplocation,noindex"` + Translator []Translator `json:"translator,omitempty" datastore:"translator"` + Org string `json:"org" datastore:"org"` + CreatedBy string `json:"createdby" datastore:"createdby"` + Availability string `json:"availability" datastore:"availability"` + CreationTime int64 `json:"creationtime" datastore:"creationtime,noindex"` + LastModificationtime int64 `json:"lastmodificationtime" datastore:"lastmodificationtime,noindex"` + LastRuntime int64 `json:"lastruntime" datastore:"lastruntime,noindex"` +} + +// Returned from /GET /schedules +type Schedules struct { + Schedules []ScheduleOld `json:"schedules"` + Success bool `json:"success"` +} + +type ScheduleApps struct { + Apps []ApiYaml `json:"apps"` + Success bool `json:"success"` +} + +// The yaml that is uploaded +type ApiYaml struct { + Name string `json:"name" yaml:"name" required:"true datastore:"name"` + Foldername string `json:"foldername" yaml:"foldername" required:"true datastore:"foldername"` + Id string `json:"id" yaml:"id",required:"true, datastore:"id"` + Description string `json:"description" datastore:"description" yaml:"description"` + AppVersion string `json:"app_version" yaml:"app_version",datastore:"app_version"` + ContactInfo struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Url string `json:"url" datastore:"url" yaml:"url"` + } `json:"contact_info" datastore:"contact_info" yaml:"contact_info"` + Types []string `json:"types" datastore:"types" yaml:"types"` + Input []struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Description string `json:"description" datastore:"description" yaml:"description"` + InputParameters []struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Description string `json:"description" datastore:"description" yaml:"description"` + Required string `json:"required" datastore:"required" yaml:"required"` + Schema struct { + Type string `json:"type" datastore:"type" yaml:"type"` + } `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"inputparameters" datastore:"inputparameters" yaml:"inputparameters"` + OutputParameters []struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Description string `json:"description" datastore:"description" yaml:"description"` + Required string `json:"required" datastore:"required" yaml:"required"` + Schema struct { + Type string `json:"type" datastore:"type" yaml:"type"` + } `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"outputparameters" datastore:"outputparameters" yaml:"outputparameters"` + Config []struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Description string `json:"description" datastore:"description" yaml:"description"` + Required string `json:"required" datastore:"required" yaml:"required"` + Schema struct { + Type string `json:"type" datastore:"type" yaml:"type"` + } `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"config" datastore:"config" yaml:"config"` + } `json:"input" datastore:"input" yaml:"input"` + Output []struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Description string `json:"description" datastore:"description" yaml:"description"` + Config []struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Description string `json:"description" datastore:"description" yaml:"description"` + Required string `json:"required" datastore:"required" yaml:"required"` + Schema struct { + Type string `json:"type" datastore:"type" yaml:"type"` + } `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"config" datastore:"config" yaml:"config"` + InputParameters []struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Description string `json:"description" datastore:"description" yaml:"description"` + Required string `json:"required" datastore:"required" yaml:"required"` + Schema struct { + Type string `json:"type" datastore:"type" yaml:"type"` + } `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"inputparameters" datastore:"inputparameters" yaml:"inputparameters"` + OutputParameters []struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Description string `json:"description" datastore:"description" yaml:"description"` + Required string `json:"required" datastore:"required" yaml:"required"` + Schema struct { + Type string `json:"type" datastore:"type" yaml:"type"` + } `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"outputparameters" datastore:"outputparameters" yaml:"outputparameters"` + } `json:"output" datastore:"output" yaml:"output"` +} + +type Hooks struct { + Hooks []Hook `json:"hooks"` + Success bool `json:"-"` +} + +type Info struct { + Url string `json:"url" datastore:"url"` + Name string `json:"name" datastore:"name"` + Description string `json:"description" datastore:"description"` +} + +// Actions to be done by webhooks etc +// Field is the actual field to use from json +type HookAction struct { + Type string `json:"type" datastore:"type"` + Name string `json:"name" datastore:"name"` + Id string `json:"id" datastore:"id"` + Field string `json:"field" datastore:"field"` +} + +type Hook struct { + Id string `json:"id" datastore:"id"` + Info Info `json:"info" datastore:"info"` + Actions []HookAction `json:"actions" datastore:"actions"` + Type string `json:"type" datastore:"type"` + Owner string `json:"owner" datastore:"owner"` + Status string `json:"status" datastore:"status"` + Running bool `json:"running" datastore:"running"` +} + +func createFileFromFile(ctx context.Context, bucket *storage.BucketHandle, remotePath, localPath string) error { + // [START upload_file] + f, err := os.Open(localPath) + if err != nil { + return err + } + defer f.Close() + + wc := bucket.Object(remotePath).NewWriter(ctx) + if _, err = io.Copy(wc, f); err != nil { + return err + } + if err := wc.Close(); err != nil { + return err + } + // [END upload_file] + return nil +} + +func createFileFromBytes(ctx context.Context, bucket *storage.BucketHandle, remotePath string, data []byte) error { + wc := bucket.Object(remotePath).NewWriter(ctx) + + byteReader := bytes.NewReader(data) + if _, err := io.Copy(wc, byteReader); err != nil { + return err + } + + if err := wc.Close(); err != nil { + return err + } + + // [END upload_file] + return nil +} + +func deleteFile(ctx context.Context, bucket *storage.BucketHandle, remotePath string) error { + + // [START delete_file] + o := bucket.Object(remotePath) + if err := o.Delete(ctx); err != nil { + return err + } + // [END delete_file] + return nil +} + +func readFile(ctx context.Context, bucket *storage.BucketHandle, object string) ([]byte, error) { + // [START download_file] + rc, err := bucket.Object(object).NewReader(ctx) + if err != nil { + return nil, err + } + defer rc.Close() + + data, err := ioutil.ReadAll(rc) + if err != nil { + return nil, err + } + return data, nil + // [END download_file] +} + +func IndexHandler(entrypoint string) func(w http.ResponseWriter, r *http.Request) { + fn := func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, entrypoint) + } + + return http.HandlerFunc(fn) +} + +func GetUsersHandler(w http.ResponseWriter, r *http.Request) { + data := map[string]interface{}{ + "id": "12345", + "ts": time.Now().Format(time.RFC3339), + } + + b, err := json.Marshal(data) + if err != nil { + http.Error(w, err.Error(), 400) + return + } + + w.Write(b) +} + +func jsonPrettyPrint(in string) string { + var out bytes.Buffer + err := json.Indent(&out, []byte(in), "", "\t") + if err != nil { + return in + } + return out.String() +} + +// Does User exist? +// Does User have permission to view / run this? +// Encoding: /json? +// General authentication +func authenticate(request *http.Request) bool { + authField := "authorization" + authenticationKey := "topkek" + //authFound := false + + // This should work right? + for name, headers := range request.Header { + name = strings.ToLower(name) + for _, h := range headers { + if name == authField && h == authenticationKey { + //log.Printf("%v: %v", name, h) + return true + } + } + } + + return false +} + +func publishPubsub(ctx context.Context, topic string, data []byte, attributes map[string]string) error { + client, err := pubsub.NewClient(ctx, gceProject) + if err != nil { + return err + } + + t := client.Topic(topic) + result := t.Publish(ctx, &pubsub.Message{ + Data: data, + Attributes: attributes, + }) + // Block until the result is returned and a server-generated + // ID is returned for the published message. + id, err := result.Get(ctx) + if err != nil { + return err + } + + log.Printf("Published message for topic %s; msg ID: %v\n", topic, id) + + return nil +} + +func checkError(cmdName string, cmdArgs []string) error { + cmd := exec.Command(cmdName, cmdArgs...) + cmdReader, err := cmd.StdoutPipe() + if err != nil { + fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for Cmd", err) + return err + } + + scanner := bufio.NewScanner(cmdReader) + go func() { + for scanner.Scan() { + fmt.Printf("Out: %s\n", scanner.Text()) + } + }() + + err = cmd.Start() + if err != nil { + fmt.Fprintln(os.Stderr, "Error starting Cmd", err) + return err + } + + err = cmd.Wait() + if err != nil { + fmt.Fprintln(os.Stderr, "Error waiting for Cmd", err) + return err + } + + return nil +} + +func md5sum(data []byte) string { + hasher := md5.New() + hasher.Write(data) + newmd5 := hex.EncodeToString(hasher.Sum(nil)) + return newmd5 +} + +func md5sumfile(filepath string) string { + dat, err := ioutil.ReadFile(filepath) + if err != nil { + log.Printf("Error in dat: %s", err) + } + + hasher := md5.New() + hasher.Write(dat) + newmd5 := hex.EncodeToString(hasher.Sum(nil)) + + log.Printf("%s: %s", filepath, newmd5) + return newmd5 +} + +func checkFileExistsLocal(basepath string, filepath string) bool { + User := "test" + // md5sum + // get tmp/results/md5sum/folder/results.json + // parse /tmp/results/md5sum/results.json + path := fmt.Sprintf("%s/%s", basepath, md5sumfile(filepath)) + if _, err := os.Stat(path); os.IsNotExist(err) { + //log.Printf("File error for %s: %s", filepath, err) + return false + } + + log.Printf("File %s exists. Getting for User %s.", filepath, User) + return true +} + +func handleApiAuthentication(resp http.ResponseWriter, request *http.Request) (User, error) { + apikey := request.Header.Get("Authorization") + if len(apikey) > 0 { + if !strings.HasPrefix(apikey, "Bearer ") { + log.Printf("Apikey doesn't start with bearer") + return User{}, errors.New("No bearer token for authorization header") + } + + apikeyCheck := strings.Split(apikey, " ") + if len(apikeyCheck) != 2 { + log.Printf("Invalid format for apikey.") + return User{}, errors.New("Invalid format for apikey") + } + + // fml + //log.Println(apikeyCheck) + + // This is annoying af and is done because of maxlength lol + newApikey := apikeyCheck[1] + if len(newApikey) > 249 { + newApikey = newApikey[0:248] + } + + ctx := context.Background() + //if item, err := memcache.Get(ctx, newApikey); err == memcache.ErrCacheMiss { + // // Not in cache + //} else if err != nil { + // // Error with cache + // log.Printf("Error getting item: %v", err) + //} else { + // var Userdata User + // err = json.Unmarshal(item.Value, &Userdata) + + // if err == nil { + // if len(Userdata.Username) > 0 { + // return Userdata, nil + // } else { + // return Userdata, errors.New("User is invalid") + // } + // } + //} + + // Make specific check for just service user? + // Get the user based on APIkey here + //log.Println(apikeyCheck[1]) + Userdata, err := getApikey(ctx, apikeyCheck[1]) + if err != nil { + log.Printf("Apikey %s doesn't exist: %s", apikey, err) + return User{}, err + } + + // Caching both bad and good apikeys :) + //b, err := json.Marshal(Userdata) + //if err != nil { + // log.Printf("Failed marshalling: %s", err) + // return User{}, err + //} + + // Add to cache if it doesn't exist + //item := &memcache.Item{ + // Key: newApikey, + // Value: b, + // Expiration: time.Minute * 60, + //} + + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // log.Printf("Set cache for %s", item.Key) + //} + + if len(Userdata.Username) > 0 { + return Userdata, nil + } else { + return Userdata, errors.New("User is invalid") + } + } + + // One time API keys + authorizationArr, ok := request.URL.Query()["authorization"] + ctx := context.Background() + if ok { + authorization := "" + if len(authorizationArr) > 0 { + authorization = authorizationArr[0] + } + _ = authorization + + //if item, err := memcache.Get(ctx, authorization); err == memcache.ErrCacheMiss { + // // Doesn't exist :( + // log.Printf("Couldn't find %s in cache!", authorization) + // return User{}, err + //} else if err != nil { + // log.Printf("Error getting item: %v", err) + // return User{}, err + //} else { + // log.Printf("%#v", item.Value) + // var Userdata User + + // log.Printf("Deleting key %s", authorization) + // memcache.Delete(ctx, authorization) + // err = json.Unmarshal(item.Value, &Userdata) + // if err == nil { + // return Userdata, nil + // } + + // return User{}, err + //} + } + + c, err := request.Cookie("session_token") + if err == nil { + //if item, err := memcache.Get(ctx, c.Value); err == memcache.ErrCacheMiss { + // // Not in cache + //} else if err != nil { + // log.Printf("Error getting item: %v", err) + //} else { + // var Userdata User + // err = json.Unmarshal(item.Value, &Userdata) + // if err == nil { + // return Userdata, nil + // } + //} + + sessionToken := c.Value + session, err := getSession(ctx, sessionToken) + if err != nil { + log.Printf("Session %s doesn't exist (api auth): %s", sessionToken, err) + return User{}, err + } + + // Get session first + // Should basically never happen + Userdata, err := getUser(ctx, session.Username) + if err != nil { + log.Printf("Username %s doesn't exist: %s", session.Username, err) + return User{}, err + } + + if Userdata.Session != sessionToken { + return User{}, errors.New("Wrong session token") + } + + // Means session exists, but + return *Userdata, nil + } + + // Key = apikey + return User{}, errors.New("Missing authentication") +} + +func handleGetallSchedules(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + var err error + var limit = 50 + + // FIXME - add org search and public / private + key, ok := request.URL.Query()["limit"] + if ok { + limit, err = strconv.Atoi(key[0]) + if err != nil { + limit = 50 + } + } + + // Max datastore limit + if limit > 1000 { + limit = 1000 + } + + // Get URLs from a database index (mapped by orborus) + ctx := context.Background() + q := datastore.NewQuery("schedules").Limit(limit) + var allschedules Schedules + + _, err = dbclient.GetAll(ctx, q, &allschedules.Schedules) + if err != nil { + log.Println(err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting schedules"}`))) + return + } + + newjson, err := json.Marshal(allschedules) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newjson) +} + +func redirect(w http.ResponseWriter, req *http.Request) { + // remove/add not default ports from req.Host + target := "https://" + req.Host + req.URL.Path + if len(req.URL.RawQuery) > 0 { + target += "?" + req.URL.RawQuery + } + log.Printf("redirect to: %s", target) + http.Redirect(w, req, target, + // see @andreiavrammsd comment: often 307 > 301 + http.StatusTemporaryRedirect) +} + +func parseLoginParameters(resp http.ResponseWriter, request *http.Request) (loginStruct, error) { + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return loginStruct{}, err + } + + var t loginStruct + + err = json.Unmarshal(body, &t) + if err != nil { + return loginStruct{}, err + } + + return t, nil +} + +// Can check against HIBP etc? +func checkPasswordStrength(password string) error { + // Check password strength here + if len(password) < 10 { + return errors.New("Minimum password length is 10.") + } + + if len(password) > 128 { + return errors.New("Maximum password length is 128.") + } + + re := regexp.MustCompile("[0-9]+") + if len(re.FindAllString(password, -1)) == 0 { + return errors.New("Password must contain a number") + } + + re = regexp.MustCompile("[a-z]+") + if len(re.FindAllString(password, -1)) == 0 { + return errors.New("Password must contain a lower case char") + } + + re = regexp.MustCompile("[A-Z]+") + if len(re.FindAllString(password, -1)) == 0 { + return errors.New("Password must contain an upper case char") + } + + return nil +} + +// Fuck emails +func checkUsername(Username string) error { + // Stupid first check of email loool + if !strings.Contains(Username, "@") || !strings.Contains(Username, ".") { + return errors.New("Invalid Username") + } + + if len(Username) < 4 { + return errors.New("Minimum Username length is 2") + } + + return nil +} + +func handleRegisterVerification(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + defaultMessage := "Successfully registered" + + var reference string + location := strings.Split(request.URL.String(), "/") + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + reference = location[4] + + if len(reference) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Id when registering verification is not valid"}`)) + return + } + + ctx := context.Background() + // With user, do a search for workflows with user or user's org attached + // Only giving 200 to not give any suspicion whether they're onto an actual user or not + q := datastore.NewQuery("Users").Filter("verification_token =", reference) + var users []User + _, err := dbclient.GetAll(ctx, q, &users) + if err != nil { + log.Printf("Failed getting users for verification token: %s", err) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) + return + } + + // FIXME - check reset_timeout + if len(users) != 1 { + log.Printf("Error - no user with verification id %s", reference) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) + return + } + + Userdata := users[0] + + // FIXME: Not for cloud! + Userdata.Verified = true + err = setUser(ctx, &Userdata) + if err != nil { + log.Printf("Failed adding verification for user %s: %s", Userdata.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) + log.Printf("%s SUCCESSFULLY FINISHED REGISTRATION", Userdata.Username) +} + +func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // FIXME: Overhaul the top part. + // Only admin can change environments, but if there are no users, anyone can make (first) + user, err := handleApiAuthentication(resp, request) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`)) + return + } + + if user.Role != "admin" { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`)) + return + } + + ctx := context.Background() + var environments []Environment + q := datastore.NewQuery("Environments") + _, err = dbclient.GetAll(ctx, q, &environments) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't get environments when setting"}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("Failed reading body") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed to read data"}`))) + return + } + + var newEnvironments []Environment + err = json.Unmarshal(body, &newEnvironments) + if err != nil { + log.Printf("Failed unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed to unmarshal data"}`))) + return + } + + if len(newEnvironments) < 1 { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "One environment is required"}`))) + return + } + + // Clear old data + for _, item := range environments { + err = DeleteKey(ctx, "Environments", item.Name) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Error cleaning up environment"}`)) + return + } + } + + for _, item := range newEnvironments { + err = setEnvironment(ctx, &item) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed setting environment variable"}`)) + return + } + } + + // FIXME - check which are in use + log.Printf("FIXME: Set new environments: %#v", newEnvironments) + log.Printf("DONT DELETE ONES THAT ARE IN USE") + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +func handleRegister(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // FIXME: Overhaul the top part. + // Only admin can CREATE users, but if there are no users, anyone can make (first) + count, countErr := getUserCount() + user, err := handleApiAuthentication(resp, request) + if err != nil { + if (countErr == nil && count > 0) || countErr != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin"}`)) + return + } + } + + //log.Printf("User role: %s", user.Role) + if err == nil && user.Role != "admin" && count > 0 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin (2)"}`)) + return + } + + // Gets a struct of Username, password + data, err := parseLoginParameters(resp, request) + if err != nil { + log.Printf("Invalid params: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // Returns false if there is an issue + // Use this for register + err = checkPasswordStrength(data.Password) + if err != nil { + log.Printf("Bad password strength: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + err = checkUsername(data.Username) + if err != nil { + log.Printf("Bad Username strength: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // FIXME - use it somehow + ctx := context.Background() + _, err = getUser(ctx, data.Username) + if err == nil { + log.Printf("Username %s exists and can't register", data.Username) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(data.Password), 8) + if err != nil { + log.Printf("Wrong password for %s: %s", data.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + newUser := new(User) + newUser.Username = data.Username + newUser.Password = string(hashedPassword) + newUser.Verified = false + newUser.Role = "user" + newUser.CreationTime = time.Now().Unix() + + // FIXME - Remove this later + newUser.Role = "admin" + + // set limits + // WorkflowExecutions > CloudExecutions simply because of onprem + newUser.Limits.DailyApiUsage = 100 + newUser.Limits.DailyWorkflowExecutions = 1000 + newUser.Limits.DailyCloudExecutions = 100 + newUser.Limits.DailyTriggers = 20 + newUser.Limits.DailyMailUsage = 100 + newUser.Limits.MaxTriggers = 10 + newUser.Limits.MaxWorkflows = 10 + + // Set base info for the user + newUser.Executions.TotalApiUsage = 0 + newUser.Executions.TotalWorkflowExecutions = 0 + newUser.Executions.TotalAppExecutions = 0 + newUser.Executions.TotalCloudExecutions = 0 + newUser.Executions.TotalOnpremExecutions = 0 + newUser.Executions.DailyApiUsage = 0 + newUser.Executions.DailyWorkflowExecutions = 0 + newUser.Executions.DailyAppExecutions = 0 + newUser.Executions.DailyCloudExecutions = 0 + newUser.Executions.DailyOnpremExecutions = 0 + + addr := newUser.Username + + verifyToken := uuid.NewV4() + ID := uuid.NewV4() + newUser.Id = ID.String() + newUser.VerificationToken = verifyToken.String() + err = setUser(ctx, newUser) + if err != nil { + log.Printf("Error adding User %s: %s", data.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String()) + const verifyMessage = ` +Registration URL :) + +%s + ` + + msg := &mail.Message{ + Sender: "Shuffle ", + To: []string{addr}, + Subject: "Verify your username - Shuffle", + Body: fmt.Sprintf(verifyMessage, url), + } + + log.Println(msg.Body) + if err := mail.Send(ctx, msg); err != nil { + log.Printf("Couldn't send email: %v", err) + } + + //sessionToken := uuid.NewV4() + + //// Finally, we set the client cookie for "session_token" as the session token we just generated + //// we also set an expiry time of 120 seconds, the same as the cache + //http.SetCookie(resp, &http.Cookie{ + // Name: "session_token", + // Value: sessionToken.String(), + // Expires: time.Now().Add(1200 * time.Second), + //}) + + //log.Println(Userdata) + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) + log.Printf("%s Successfully registered.", data.Username) + + //err = SetSession(*newUser, sessionToken.String()) + //if err != nil { + // log.Printf("Error adding session to database: %s", err) + //} + + //err = SetApikey(*newUser) + //if err != nil { + // log.Printf("Error adding apikey to database: %s", err) + //} + + //err = SetSession(*newUser, sessionToken.String()) + //if err != nil { + // log.Printf("Error adding apikey to database: %s", err) + //} +} + +func handleCookie(request *http.Request) bool { + c, err := request.Cookie("session_token") + if err != nil { + return false + } + + if len(c.Value) == 0 { + return false + } + + return true +} + +func handleLogout(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Check cookie + c, err := request.Cookie("session_token") + if err != nil { + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } else { + log.Printf("Session cookie is set!") + } + + var Userdata User + ctx := context.Background() + //item, err := memcache.Get(ctx, c.Value) + sessionToken := "" + //// Memcache handling for logout + //if err == nil { + // err = json.Unmarshal(item.Value, &Userdata) + // if err != nil { + // log.Printf("Failed unmarshaling: %s", err) + // resp.WriteHeader(401) + // resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + // return + // } + + // sessionToken = Userdata.Session + //} else { + // // Validate with User + sessionToken = c.Value + session, err := getSession(ctx, sessionToken) + if err != nil { + log.Printf("Session %s doesn't exist (logout): %s", session.Session, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + // Get session first + // Should basically never happen + _, err = getUser(ctx, session.Username) + if err != nil { + log.Printf("Username %s doesn't exist: %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + // Userdata = *tmpdata + //} + + // FIXME + // Session might delete someone elses here? + // No need to think about before possible scale..? + err = SetSession(ctx, Userdata, "") + if err != nil { + log.Printf("Error removing session for: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + err = DeleteKey(ctx, "sessions", sessionToken) + if err != nil { + log.Printf("Error deleting key %s for %s: %s", c.Value, Userdata.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + Userdata.Session = "" + err = setUser(ctx, &Userdata) + if err != nil { + log.Printf("Failed updating user: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed updating apikey"}`)) + return + } + + //memcache.Delete(request.Context(), sessionToken) + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": false, "reason": "Successfully logged out"}`)) + http.SetCookie(resp, c) +} + +func generateApikey(ctx context.Context, userInfo User) (User, error) { + // Generate UUID + // Set uuid to apikey in backend (update) + apikey := uuid.NewV4() + userInfo.ApiKey = apikey.String() + + err := SetApikey(ctx, userInfo) + if err != nil { + log.Printf("Failed updating apikey: %s", err) + return userInfo, err + } + + // Updating user + err = setUser(ctx, &userInfo) + if err != nil { + log.Printf("Failed updating user: %s", err) + return userInfo, err + } + + return userInfo, nil +} + +func handleApiGeneration(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + c, err := request.Cookie("session_token") + if err != nil { + log.Printf("User doesn't have sessiontoken, on apigen: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + ctx := context.Background() + sessionToken := c.Value + session, err := getSession(ctx, sessionToken) + if err != nil { + log.Printf("Session %#v doesn't exist (api gen): %s", session, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + // Get session first + // Should basically never happen + userInfo, err := getUser(ctx, session.Username) + if err != nil { + log.Printf("Username %s doesn't exist: %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + // Delete old apikey from cache + //memcache.Delete(ctx, userInfo.ApiKey) + + if session.Session != userInfo.Session { + log.Printf("Session %s is not the latest. %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + newUserInfo, err := generateApikey(ctx, *userInfo) + if err != nil { + log.Printf("Failed to generate apikey for user %s: %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + userInfo = &newUserInfo + + //memcache.Delete(request.Context(), sessionToken) + + log.Printf("Updated apikey for user %s", userInfo.Username) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "Username": "%s", "verified": %t, "apikey": "%s"}`, userInfo.Username, userInfo.Verified, userInfo.ApiKey))) +} + +func handleSettings(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + c, err := request.Cookie("session_token") + if err != nil { + log.Printf("User doesn't have sessiontoken, on getsettings: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + ctx := context.Background() + sessionToken := c.Value + session, err := getSession(ctx, sessionToken) + if err != nil { + log.Printf("Session %#v doesn't exist (settings): %s", session, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + // Get session first + // Should basically never happen + UserInfo, err := getUser(ctx, session.Username) + if err != nil { + log.Printf("Username %s doesn't exist: %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + log.Printf("%s %s", session.Session, UserInfo.Session) + if session.Session != UserInfo.Session { + log.Printf("Session %s is not the latest. %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "Username": "%s", "verified": %t, "apikey": "%s"}`, UserInfo.Username, UserInfo.Verified, UserInfo.ApiKey))) +} + +func handleInfo(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Should compare with local storage first + c, err := request.Cookie("session_token") + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // FIXME - check memcache here + // Get the item from the memcache + ctx := context.Background() + //if item, err := memcache.Get(ctx, c.Value); err == memcache.ErrCacheMiss { + // // Not in cache + //} else if err != nil { + // log.Printf("Error getting item: %v", err) + //} else { + // var Userdata User + // err = json.Unmarshal(item.Value, &Userdata) + // if err == nil { + // resp.WriteHeader(200) + // resp.Write([]byte(`{"success": true, "reason": "OK"}`)) + // return + // } + //} + + sessionToken := c.Value + session, err := getSession(ctx, sessionToken) + if err != nil { + //log.Printf("Session %#v doesn't exist: %s", session, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + // Get session first + // Should basically never happen + UserInfo, err := getUser(ctx, session.Username) + if err != nil { + log.Printf("Username %s doesn't exist: %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + log.Printf("%s %s", session.Session, UserInfo.Session) + if session.Session != UserInfo.Session { + log.Printf("Session %s is not the latest. %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + expiration := time.Now().Add(1200 * time.Second) + http.SetCookie(resp, &http.Cookie{ + Name: "session_token", + Value: UserInfo.Session, + Expires: expiration, + }) + + returnData := fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, UserInfo.Session, expiration.Unix()) + + //b, err := json.Marshal(UserInfo) + //if err != nil { + // log.Printf("Failed marshalling: %s", err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false}`)) + // return + //} + + // Adding to cache here + // Only keeping it in for 24 hours + //item := &memcache.Item{ + // Key: c.Value, + // Value: b, + // Expiration: time.Hour * 24, + //} + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // log.Printf("Set cache for %s", item.Key) + //} + + resp.WriteHeader(200) + resp.Write([]byte(returnData)) +} + +type passwordReset struct { + Password1 string `json:"newpassword"` + Password2 string `json:"newpassword2"` + Reference string `json:"reference"` +} + +type passwordChange struct { + Password1 string `json:"newpassword"` + Password2 string `json:"newpassword2"` + Password3 string `json:"currentpassword"` +} + +func handlePasswordResetMail(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + log.Println("Handling password reset mail") + defaultMessage := "We have sent you an email :)" + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("Failed reading body") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, defaultMessage))) + return + } + + type passwordReset struct { + Username string `json:"Username"` + } + + var t passwordReset + err = json.Unmarshal(body, &t) + if err != nil { + log.Printf("Failed unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, defaultMessage))) + return + } + + ctx := context.Background() + Userdata, err := getUser(ctx, t.Username) + if err != nil { + log.Printf("Username %s doesn't exist: %s", t.Username, err) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) + return + } + + resetToken := uuid.NewV4() + // FIXME: + // Weakness with this system is that you can spam someone with password resets, + // and they would never be able to reset, as a new token is always generated + url := fmt.Sprintf("https://shuffler.io/passwordreset/%s", resetToken.String()) + + Userdata.ResetReference = resetToken.String() + Userdata.ResetTimeout = 0 + err = setUser(ctx, Userdata) + if err != nil { + log.Printf("Error patching User for mail %s: %s", Userdata.Username, err) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) + return + } + + log.Printf("%#v", Userdata) + addr := t.Username + const confirmMessage = ` +Reset URL :) + +%s + ` + + msg := &mail.Message{ + Sender: "Shuffle ", + To: []string{addr}, + Subject: "Reset your password - Shuffle", + Body: fmt.Sprintf(confirmMessage, url), + } + + log.Println(msg.Body) + if err := mail.Send(ctx, msg); err != nil { + log.Printf("Couldn't send email: %v", err) + } + + // FIXME + // Generate an email to send + // Generate a reset code with a reset link + // Build frontend to handle reset link with "new password" etc. + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) +} + +func handlePasswordReset(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + log.Println("Handling password reset") + defaultMessage := "Successfully handled password reset" + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("Failed reading body") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + return + } + + var t passwordReset + err = json.Unmarshal(body, &t) + if err != nil { + log.Println("Failed unmarshaling") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + return + } + + if t.Password1 != t.Password2 { + resp.WriteHeader(401) + err := "Passwords don't match" + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + if len(t.Password1) < 10 || len(t.Password2) < 10 { + resp.WriteHeader(401) + err := "Passwords don't match - 2" + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + ctx := context.Background() + + // With user, do a search for workflows with user or user's org attached + // Only giving 200 to not give any suspicion whether they're onto an actual user or not + q := datastore.NewQuery("Users").Filter("reset_reference =", t.Reference) + var users []User + _, err = dbclient.GetAll(ctx, q, &users) + if err != nil { + log.Printf("Failed getting users: %s", err) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) + return + } + + // FIXME - check reset_timeout + if len(users) != 1 { + log.Printf("Error - no user with id %s", t.Reference) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) + return + } + + Userdata := users[0] + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(t.Password1), 8) + if err != nil { + log.Printf("Wrong password for %s: %s", Userdata.Username, err) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) + return + } + + Userdata.Password = string(hashedPassword) + Userdata.ResetTimeout = 0 + Userdata.ResetReference = "" + err = setUser(ctx, &Userdata) + if err != nil { + log.Printf("Error adding User %s: %s", Userdata.Username, err) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) + return + } + + // FIXME - maybe send a mail here to say that the password was changed + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, defaultMessage))) +} + +func handlePasswordChange(resp http.ResponseWriter, request *http.Request) { + log.Println("Handling password change") + + cors := handleCors(resp, request) + if cors { + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("Failed reading body") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + return + } + + var t passwordChange + err = json.Unmarshal(body, &t) + if err != nil { + log.Println("Failed unmarshaling") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + return + } + + if t.Password1 != t.Password2 { + resp.WriteHeader(401) + err := "Passwords don't match" + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + if len(t.Password1) < 10 || len(t.Password2) < 10 { + resp.WriteHeader(401) + err := "Passwords don't match - 2" + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + err = checkPasswordStrength(t.Password3) + if err != nil { + log.Printf("Bad password strength: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // Check cookie + c, err := request.Cookie("session_token") + if err != nil { + log.Printf("User doesn't have sessiontoken on pw change: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "You're not logged in."}`))) + return + } + + ctx := context.Background() + // Validate with User + sessionToken := c.Value + session, err := getSession(ctx, sessionToken) + if err != nil { + log.Printf("Session %s doesn't exist (password change): %s", session.Session, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "You're not logged in"}`)) + return + } + + // Get session first + // Should basically never happen + Userdata, err := getUser(ctx, session.Username) + if err != nil { + log.Printf("Username %s doesn't exist: %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + err = bcrypt.CompareHashAndPassword([]byte(Userdata.Password), []byte(t.Password1)) + if err != nil { + log.Printf("Bad password for %s: %s", session.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(t.Password3), 8) + if err != nil { + log.Printf("Wrong password for %s: %s", Userdata.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + Userdata.Password = string(hashedPassword) + err = setUser(ctx, Userdata) + if err != nil { + log.Printf("Error adding User %s: %s", Userdata.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + //memcache.Delete(ctx, sessionToken) + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +// FIXME - forward this to emails or whatever CRM system in use +func handleContact(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + var t Contact + err = json.Unmarshal(body, &t) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + if len(t.Email) < 3 || len(t.Message) == 0 { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Please fill a valid email and message"}`))) + return + } + + ctx := context.Background() + mailContent := fmt.Sprintf("Firsname: %s\nLastname: %s\nTitle: %s\nCompanyname: %s\nPhone: %s\nEmail: %s\nMessage: %s", t.Firstname, t.Lastname, t.Title, t.Companyname, t.Phone, t.Email, t.Message) + log.Printf("Sending contact from %s", t.Email) + + msg := &mail.Message{ + Sender: "Shuffle ", + To: []string{"frikky@shuffler.io"}, + Subject: "Shuffler.io - New contact form", + Body: mailContent, + } + + if err := mail.Send(ctx, msg); err != nil { + log.Printf("Couldn't send email: %v", err) + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "message": "Thanks for reaching out. We will contact you soon!"}`))) +} + +func getEnvironmentCount() (int, error) { + ctx := context.Background() + q := datastore.NewQuery("Environments").Limit(1) + count, err := dbclient.Count(ctx, q) + if err != nil { + return 0, err + } + + return count, nil +} + +func getUserCount() (int, error) { + ctx := context.Background() + q := datastore.NewQuery("Users").Limit(1) + count, err := dbclient.Count(ctx, q) + if err != nil { + return 0, err + } + + return count, nil +} + +func handleGetEnvironments(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + _, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + ctx := context.Background() + var environments []Environment + q := datastore.NewQuery("Environments") + _, err = dbclient.GetAll(ctx, q, &environments) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't get environments"}`)) + return + } + + newjson, err := json.Marshal(environments) + if err != nil { + log.Printf("Failed unmarshal: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking environments"}`))) + return + } + + log.Printf("Existing environments: %s", string(newjson)) + + resp.WriteHeader(200) + resp.Write(newjson) +} + +func handleGetUsers(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Role != "admin" { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Not admin"}`)) + return + } + + ctx := context.Background() + var users []User + q := datastore.NewQuery("Users") + _, err = dbclient.GetAll(ctx, q, &users) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Can't get users"}`)) + return + } + + newUsers := []User{} + for _, item := range users { + if len(item.Username) == 0 { + continue + } + + item.Password = "" + item.Session = "" + item.VerificationToken = "" + + newUsers = append(newUsers, item) + } + + newjson, err := json.Marshal(newUsers) + if err != nil { + log.Printf("Failed unmarshal: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newjson) +} + +func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + count, err := getUserCount() + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + } + + if count == 0 { + log.Printf("No users - redirecting for management user") + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "stay"}`))) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect"}`))) +} + +func handleLogin(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Gets a struct of Username, password + data, err := parseLoginParameters(resp, request) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + log.Printf("Handling login of %s", data.Username) + + err = checkUsername(data.Username) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + ctx := context.Background() + Userdata, err := getUser(ctx, data.Username) + if err != nil { + log.Printf("Username %s doesn't exist: %s", data.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + err = bcrypt.CompareHashAndPassword([]byte(Userdata.Password), []byte(data.Password)) + if err != nil { + log.Printf("Password for %s is incorrect: %s", data.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + return + } + + log.Printf("%s SUCCESSFULLY LOGGED IN", data.Username) + //if !Userdata.Verified { + // log.Printf("User %s is not verified", data.Username) + // resp.WriteHeader(403) + // resp.Write([]byte(`{"success": false, "reason": "Successful login, but your email address isn't verified. Check your mailbox."}`)) + // return + //} + + loginData := `{"success": true}` + + // FIXME - have timeout here + if len(Userdata.Session) != 0 { + //log.Println("Nonexisting session") + expiration := time.Now().Add(1200 * time.Second) + + http.SetCookie(resp, &http.Cookie{ + Name: "session_token", + Value: Userdata.Session, + Expires: expiration, + }) + + loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, Userdata.Session, expiration.Unix()) + log.Printf("SESSION LENGTH MORE THAN 0 IN LOGIN: %s", Userdata.Session) + + err = SetSession(ctx, *Userdata, Userdata.Session) + if err != nil { + log.Printf("Error adding session to database: %s", err) + } + + resp.WriteHeader(200) + resp.Write([]byte(loginData)) + return + } + + sessionToken := uuid.NewV4() + + http.SetCookie(resp, &http.Cookie{ + Name: "session_token", + Value: sessionToken.String(), + Expires: time.Now().Add(1200 * time.Second), + }) + + // ADD TO DATABASE + err = SetSession(ctx, *Userdata, sessionToken.String()) + if err != nil { + log.Printf("Error adding session to database: %s", err) + } + + resp.WriteHeader(200) + resp.Write([]byte(loginData)) +} + +func getApikey(ctx context.Context, apikey string) (User, error) { + // Query for the specifci workflowId + q := datastore.NewQuery("Users").Filter("apikey =", apikey) + var users []User + _, err := dbclient.GetAll(ctx, q, &users) + if err != nil { + log.Printf("Error getting users apikey: %s", err) + return User{}, err + } + + if len(users) == 0 { + log.Printf("No users found for apikey %s", apikey) + return User{}, err + } + + return users[0], nil +} + +func getSession(ctx context.Context, thissession string) (*session, error) { + key := datastore.NameKey("sessions", thissession, nil) + curUser := &session{} + if err := dbclient.Get(ctx, key, curUser); err != nil { + return &session{}, err + } + + return curUser, nil +} + +// ListBooks returns a list of books, ordered by title. +func getUser(ctx context.Context, Username string) (*User, error) { + key := datastore.NameKey("Users", strings.ToLower(Username), nil) + curUser := &User{} + if err := dbclient.Get(ctx, key, curUser); err != nil { + return &User{}, err + } + + return curUser, nil +} + +// Index = Username +func DeleteKey(ctx context.Context, entity string, value string) error { + // Non indexed User data + key1 := datastore.NameKey(entity, value, nil) + + err := dbclient.Delete(ctx, key1) + if err != nil { + log.Printf("Error deleting %s from %s: %s", value, entity, err) + return err + } + + return nil +} + +// Index = Username +func SetApikey(ctx context.Context, Userdata User) error { + // Non indexed User data + newapiUser := new(Userapi) + newapiUser.ApiKey = Userdata.ApiKey + newapiUser.Username = Userdata.Username + key1 := datastore.NameKey("apikey", newapiUser.ApiKey, nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key1, newapiUser); err != nil { + log.Printf("Error adding apikey: %s", err) + return err + } + + return nil +} + +// Index = Username +func SetSession(ctx context.Context, Userdata User, value string) error { + // Non indexed User data + Userdata.Session = value + key1 := datastore.NameKey("Users", strings.ToLower(Userdata.Username), nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key1, &Userdata); err != nil { + log.Printf("rror adding Usersession: %s", err) + return err + } + + if len(Userdata.Session) > 0 { + // Indexed session data + sessiondata := new(session) + sessiondata.Username = Userdata.Username + sessiondata.Session = Userdata.Session + key2 := datastore.NameKey("sessions", sessiondata.Session, nil) + + if _, err := dbclient.Put(ctx, key2, sessiondata); err != nil { + log.Printf("Error adding session: %s", err) + return err + } + } + + return nil +} + +func setOpenApiDatastore(ctx context.Context, id string, data ParsedOpenApi) error { + k := datastore.NameKey("openapi3", id, nil) + if _, err := dbclient.Put(ctx, k, &data); err != nil { + log.Println(err) + return err + } + + return nil +} + +func getOpenApiDatastore(ctx context.Context, id string) (ParsedOpenApi, error) { + key := datastore.NameKey("openapi3", id, nil) + api := &ParsedOpenApi{} + if err := dbclient.Get(ctx, key, api); err != nil { + return ParsedOpenApi{}, err + } + + return *api, nil +} + +func setEnvironment(ctx context.Context, data *Environment) error { + // clear session_token and API_token for user + k := datastore.NameKey("Environments", strings.ToLower(data.Name), nil) + + // New struct, to not add body, author etc + + if _, err := dbclient.Put(ctx, k, data); err != nil { + log.Println(err) + return err + } + + return nil +} + +// ListBooks returns a list of books, ordered by title. +func setUser(ctx context.Context, data *User) error { + // clear session_token and API_token for user + k := datastore.NameKey("Users", strings.ToLower(data.Username), nil) + + // New struct, to not add body, author etc + + if _, err := dbclient.Put(ctx, k, data); err != nil { + log.Println(err) + return err + } + + return nil +} + +func handleCors(resp http.ResponseWriter, request *http.Request) bool { + + // FIXME - this is to handle multiple frontends in test rofl + origin := request.Header["Origin"] + resp.Header().Set("Vary", "Origin") + if len(origin) > 0 { + resp.Header().Set("Access-Control-Allow-Origin", origin[0]) + } else { + resp.Header().Set("Access-Control-Allow-Origin", "http://localhost:4201") + } + //resp.Header().Set("Access-Control-Allow-Origin", "http://localhost:8000") + resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me") + resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE") + resp.Header().Set("Access-Control-Allow-Credentials", "true") + + if request.Method == "OPTIONS" { + resp.WriteHeader(200) + resp.Write([]byte("OK")) + return true + } + + return false +} + +func parseWorkflowParameters(resp http.ResponseWriter, request *http.Request) (map[string]interface{}, error) { + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return nil, err + } + + log.Printf("Parsing data: %s", string(body)) + var t map[string]interface{} + err = json.Unmarshal(body, &t) + if err == nil { + log.Printf("PARSED!! :)") + return t, nil + } + + // Translate XML to json in case of an XML blob. + // FIXME - use Content-Type and Accept headers + + xml := strings.NewReader(string(body)) + curjson, err := xj.Convert(xml) + if err != nil { + return t, err + } + + //fmt.Println(curjson.String()) + //log.Printf("Parsing json a second time: %s", string(curjson.String())) + + err = json.Unmarshal(curjson.Bytes(), &t) + if err != nil { + return t, nil + } + + envelope := t["Envelope"].(map[string]interface{}) + curbody := envelope["Body"].(map[string]interface{}) + + //log.Println(curbody) + + // ALWAYS handle strings only + // FIXME - remove this and get it from config or something + requiredField := "symptomDescription" + _, found := SearchNested(curbody, requiredField) + + // Maxdepth + maxiter := 5 + + // Need to look for parent of the item, as that is most likely root + if found { + cnt := 0 + var previousDifferentItem map[string]interface{} + var previousItem map[string]interface{} + _ = previousItem + for { + if cnt == maxiter { + break + } + + // Already know it exists + key, realItem, _ := SearchNestedParent(curbody, requiredField) + + // First should ALWAYS work since we already have recursion checked + if len(previousDifferentItem) == 0 { + previousDifferentItem = realItem.(map[string]interface{}) + } + + switch t := realItem.(type) { + case map[string]interface{}: + previousItem = realItem.(map[string]interface{}) + curbody = realItem.(map[string]interface{}) + default: + // Gets here if it's not an object + _ = t + //log.Printf("hi %#v", previousItem) + return previousItem, nil + } + + _ = key + cnt += 1 + } + } + + //key, realItem, found = SearchNestedParent(newbody, requiredField) + + //if !found { + // log.Println("NOT FOUND!") + //} + + ////log.Println(realItem[requiredField].(map[string]interface{})) + //log.Println(realItem[requiredField]) + //log.Printf("FOUND PARENT :): %s", key) + + return t, nil +} + +// SearchNested searches a nested structure consisting of map[string]interface{} +// and []interface{} looking for a map with a specific key name. +// If found SearchNested returns the value associated with that key, true +func SearchNestedParent(obj interface{}, key string) (string, interface{}, bool) { + switch t := obj.(type) { + case map[string]interface{}: + if v, ok := t[key]; ok { + return "", v, ok + } + for k, v := range t { + if _, ok := SearchNested(v, key); ok { + return k, v, ok + } + } + case []interface{}: + for _, v := range t { + if _, ok := SearchNested(v, key); ok { + return "", v, ok + } + } + } + + return "", nil, false +} + +// SearchNested searches a nested structure consisting of map[string]interface{} +// and []interface{} looking for a map with a specific key name. +// If found SearchNested returns the value associated with that key, true +// If the key is not found SearchNested returns nil, false +func SearchNested(obj interface{}, key string) (interface{}, bool) { + switch t := obj.(type) { + case map[string]interface{}: + if v, ok := t[key]; ok { + return v, ok + } + for _, v := range t { + if result, ok := SearchNested(v, key); ok { + return result, ok + } + } + case []interface{}: + for _, v := range t { + if result, ok := SearchNested(v, key); ok { + return result, ok + } + } + } + return nil, false +} + +func handleSetHook(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + // FIXME - check basic authentication + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Error with body read: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Println(jsonPrettyPrint(string(body))) + + var hook Hook + err = json.Unmarshal(body, &hook) + if err != nil { + log.Printf("Failed unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Id != hook.Owner && user.Role != "admin" && user.Role != "scheduler" { + log.Printf("Wrong user (%s) for hook %s", user.Username, hook.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if hook.Id != workflowId { + errorstring := fmt.Sprintf(`Id %s != %s`, hook.Id, workflowId) + log.Printf("Ids not matching: %s", errorstring) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, errorstring))) + return + } + + // Verifies the hook JSON. Bad verification :^) + finished, errorstring := verifyHook(hook) + if !finished { + log.Printf("Error with hook: %s", errorstring) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, errorstring))) + return + } + + // Get the ID to see whether it exists + // FIXME - use return and set READONLY fields (don't allow change from User) + ctx := context.Background() + _, err = getHook(ctx, workflowId) + if err != nil { + log.Printf("Failed getting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "Invalid ID"}`)) + return + } + + // Update the fields + err = setHook(ctx, hook) + if err != nil { + log.Printf("Failed setting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +// FIXME - some fields (e.g. status) shouldn't be writeable.. Meh +func verifyHook(hook Hook) (bool, string) { + // required fields: Id, info.name, type, status, running + if hook.Id == "" { + return false, "Missing required field id" + } + + if hook.Info.Name == "" { + return false, "Missing required field info.name" + } + + // Validate type stuff + validTypes := []string{"webhook"} + found := false + for _, key := range validTypes { + if hook.Type == key { + found = true + break + } + } + + if !found { + return false, fmt.Sprintf("Field type is invalid. Allowed: %s", strings.Join(validTypes, ", ")) + } + + // WEbhook specific + if hook.Type == "webhook" { + if hook.Info.Url == "" { + return false, "Missing required field info.url" + } + } + + if hook.Status == "" { + return false, "Missing required field status" + } + + validStatusFields := []string{"running", "stopped", "uninitialized"} + found = false + for _, key := range validStatusFields { + if hook.Status == key { + found = true + break + } + } + + if !found { + return false, fmt.Sprintf("Field status is invalid. Allowed: %s", strings.Join(validStatusFields, ", ")) + } + + // Verify actions + if len(hook.Actions) > 0 { + existingIds := []string{} + for index, action := range hook.Actions { + if action.Type == "" { + return false, fmt.Sprintf("Missing required field actions.type at index %d", index) + } + + if action.Name == "" { + return false, fmt.Sprintf("Missing required field actions.name at index %d", index) + } + + if action.Id == "" { + return false, fmt.Sprintf("Missing required field actions.id at index %d", index) + } + + // Check for duplicate IDs + for _, actionId := range existingIds { + if action.Id == actionId { + return false, fmt.Sprintf("actions.id %s at index %d already exists", actionId, index) + } + } + existingIds = append(existingIds, action.Id) + } + } + + return true, "All items set" + //log.Printf("%#v", hook) + + //Id string `json:"id" datastore:"id"` + //Info Info `json:"info" datastore:"info"` + //Transforms struct{} `json:"transforms" datastore:"transforms"` + //Actions []HookAction `json:"actions" datastore:"actions"` + //Type string `json:"type" datastore:"type"` + //Status string `json:"status" datastore:"status"` + //Running bool `json:"running" datastore:"running"` +} + +func setSpecificSchedule(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + // FIXME - check basic authentication + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Error with body read: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + jsonPrettyPrint(string(body)) + var schedule ScheduleOld + err = json.Unmarshal(body, &schedule) + if err != nil { + log.Printf("Failed unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - check access etc + ctx := context.Background() + err = setSchedule(ctx, schedule) + if err != nil { + log.Printf("Failed setting schedule: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - get some real data? + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) + return +} + +func getSchedule(ctx context.Context, schedulename string) (*ScheduleOld, error) { + key := datastore.NameKey("schedules", strings.ToLower(schedulename), nil) + curUser := &ScheduleOld{} + if err := dbclient.Get(ctx, key, curUser); err != nil { + return &ScheduleOld{}, err + } + + return curUser, nil +} + +func getSpecificWebhook(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + ctx := context.Background() + schedule, err := getSchedule(ctx, workflowId) + if err != nil { + log.Printf("Failed setting schedule: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + //log.Printf("%#v", schedule.Translator[0]) + + b, err := json.Marshal(schedule) + if err != nil { + log.Printf("Failed marshalling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - get some real data? + resp.WriteHeader(200) + resp.Write([]byte(b)) + return +} + +// Starts a new webhook +func handleDeleteSchedule(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + ctx := context.Background() + err := DeleteKey(ctx, "schedules", workflowId) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "Can't delete"}`)) + return + } + + // FIXME - remove schedule too + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "message": "Deleted webhook"}`)) +} + +// Starts a new webhook +func handleNewSchedule(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + randomValue := uuid.NewV4() + h := md5.New() + io.WriteString(h, randomValue.String()) + newId := strings.ToLower(fmt.Sprintf("%X", h.Sum(nil))) + + // FIXME - timestamp! + // FIXME - applocation - cloud function? + timeNow := int64(time.Now().Unix()) + schedule := ScheduleOld{ + Id: newId, + AppInfo: AppInfo{}, + BaseAppLocation: "/home/frikky/git/shaffuru/tmp/apps", + CreationTime: timeNow, + LastModificationtime: timeNow, + LastRuntime: timeNow, + } + + ctx := context.Background() + err := setSchedule(ctx, schedule) + if err != nil { + log.Printf("Failed setting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Println("Generating new schedule") + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "message": "Created new service"}`)) +} + +func handleWebhookRedirect(resp http.ResponseWriter, request *http.Request) { + path := strings.Split(request.URL.String(), "/") + if len(path) < 4 { + resp.WriteHeader(403) + return + } + + //http.Redirect(resp, request, "http://www.google.com", 301) + //https://europe-west1-shuffler.cloudfunctions.net/webhook_e843bfe2-fc36-4fa5-b682-97cdfa0c0091 + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + http.Error(resp, err.Error(), http.StatusInternalServerError) + return + } + + // you can reassign the body if you need to parse it as multipart + request.Body = ioutil.NopCloser(bytes.NewReader(body)) + + // create a new url from the raw RequestURI sent by the client + proxyScheme := "https" + url := fmt.Sprintf("%s://%s-%s.cloudfunctions.net/%s", proxyScheme, defaultLocation, gceProject, path[3]) + log.Println(url) + + proxyReq, err := http.NewRequest(request.Method, url, bytes.NewReader(body)) + + // We may want to filter some headers, otherwise we could just use a shallow copy + // proxyReq.Header = req.Header + proxyReq.Header = make(http.Header) + for h, val := range request.Header { + proxyReq.Header[h] = val + } + + httpClient := &http.Client{} + newresp, err := httpClient.Do(proxyReq) + if err != nil { + http.Error(resp, err.Error(), http.StatusBadGateway) + return + } + defer newresp.Body.Close() +} + +// Starts a new webhook +func handleNewHook(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + type requestData struct { + Type string `json:"type"` + Description string `json:"description"` + Id string `json:"id"` + Name string `json:"name"` + Workflow string `json:"workflow"` + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Body data error: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + ctx := context.Background() + var requestdata requestData + err = yaml.Unmarshal([]byte(body), &requestdata) + if err != nil { + log.Printf("Failed unmarshaling inputdata: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + log.Printf("%#v", requestdata) + + // CBA making a real thing. Already had some code lol + newId := requestdata.Id + if len(newId) != 36 { + log.Printf("Bad ID") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Invalid ID"}`)) + return + } + + if requestdata.Id == "" || requestdata.Name == "" { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Requires fields id and name can't be empty"}`)) + return + + } + + validTypes := []string{ + "webhook", + } + + isTypeValid := false + for _, thistype := range validTypes { + if requestdata.Type == thistype { + isTypeValid = true + break + } + } + + if !(isTypeValid) { + log.Printf("Type %s is not valid. Try any of these: %s", requestdata.Type, strings.Join(validTypes, ", ")) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + hook := Hook{ + Id: newId, + Info: Info{ + Name: requestdata.Name, + Description: requestdata.Description, + Url: fmt.Sprintf("https://shuffler.io/functions/webhooks/webhook_%s", newId), + }, + Type: "webhook", + Owner: user.Username, + Status: "uninitialized", + Actions: []HookAction{ + HookAction{ + Type: "workflow", + Name: requestdata.Name, + Id: requestdata.Workflow, + Field: "", + }, + }, + Running: false, + } + + //b, err := json.Marshal(hook) + //if err != nil { + // log.Printf("Failed marshalling: %s", err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false}`)) + // return + //} + + environmentVariables := map[string]string{ + "FUNCTION_APIKEY": user.ApiKey, + "CALLBACKURL": "https://shuffler.io", + "HOOKID": hook.Id, + } + + applocation := fmt.Sprintf("gs://%s/triggers/webhook.zip", bucketName) + hookname := fmt.Sprintf("webhook_%s", hook.Id) + err = deployWebhookFunction(ctx, hookname, defaultLocation, applocation, environmentVariables) + if err != nil { + log.Printf("Error deploying hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Issue with starting hook. Please wait a second and try again"}`))) + return + } + + hook.Status = "running" + hook.Running = true + err = setHook(ctx, hook) + if err != nil { + log.Printf("Failed setting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Println("Generating new hook") + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +func sendHookResult(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + _ = user + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + ctx := context.Background() + hook, err := getHook(ctx, workflowId) + if err != nil { + log.Printf("Failed getting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Body data error: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("SET the hook results for %s to %s", workflowId, body) + // FIXME - set the hook result in the DB somehow as interface{} + // FIXME - should the hook do the transform? Hmm + + b, err := json.Marshal(hook) + if err != nil { + log.Printf("Failed marshalling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(b)) + return +} + +func handleGetHook(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + ctx := context.Background() + hook, err := getHook(ctx, workflowId) + if err != nil { + log.Printf("Failed getting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Id != hook.Owner && user.Role != "admin" && user.Role != "scheduler" { + log.Printf("Wrong user (%s) for hook %s", user.Username, hook.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + b, err := json.Marshal(hook) + if err != nil { + log.Printf("Failed marshalling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - get some real data? + resp.WriteHeader(200) + resp.Write([]byte(b)) + return +} + +func getSpecificSchedule(resp http.ResponseWriter, request *http.Request) { + if request.Method != "GET" { + setSpecificSchedule(resp, request) + return + } + + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + ctx := context.Background() + schedule, err := getSchedule(ctx, workflowId) + if err != nil { + log.Printf("Failed getting schedule: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + //log.Printf("%#v", schedule.Translator[0]) + + b, err := json.Marshal(schedule) + if err != nil { + log.Printf("Failed marshalling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(b)) +} + +func loadYaml(fileLocation string) (ApiYaml, error) { + apiYaml := ApiYaml{} + + yamlFile, err := ioutil.ReadFile(fileLocation) + if err != nil { + log.Printf("yamlFile.Get err: %s", err) + return ApiYaml{}, err + } + + err = yaml.Unmarshal([]byte(yamlFile), &apiYaml) + if err != nil { + return ApiYaml{}, err + } + + return apiYaml, nil +} + +// This should ALWAYS come from an OUTPUT +func executeSchedule(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + var workflowId string + + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + ctx := context.Background() + log.Printf("EXECUTING %s!", workflowId) + idConfig, err := getSchedule(ctx, workflowId) + if err != nil { + log.Printf("Error getting schedule: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // Basically the src app + inputStrings := map[string]string{} + for _, item := range idConfig.Translator { + if item.Dst.Required == "false" { + log.Println("Skipping not required") + continue + } + + if item.Src.Name == "" { + errorMsg := fmt.Sprintf("Required field %s has no source", item.Dst.Name) + log.Println(errorMsg) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, errorMsg))) + return + } + + inputStrings[item.Dst.Name] = item.Src.Name + } + + configmap := map[string]string{} + for _, config := range idConfig.AppInfo.SourceApp.Config { + configmap[config.Key] = config.Value + } + + // FIXME - this wont work for everything lmao + functionName := strings.ToLower(idConfig.AppInfo.SourceApp.Action) + functionName = strings.Replace(functionName, " ", "_", 10) + + cmdArgs := []string{ + fmt.Sprintf("%s/%s/app.py", baseAppPath, "thehive"), + fmt.Sprintf("--referenceid=%s", workflowId), + fmt.Sprintf("--function=%s", functionName), + } + + for key, value := range configmap { + cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", key, value)) + } + + // FIXME - processname + baseProcess := "python3" + log.Printf("Executing: %s %s", baseProcess, strings.Join(cmdArgs, " ")) + execSubprocess(baseProcess, cmdArgs) + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +func execSubprocess(cmdName string, cmdArgs []string) error { + cmd := exec.Command(cmdName, cmdArgs...) + cmdReader, err := cmd.StdoutPipe() + if err != nil { + fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for Cmd", err) + return err + } + + scanner := bufio.NewScanner(cmdReader) + go func() { + for scanner.Scan() { + fmt.Printf("Out: %s\n", scanner.Text()) + } + }() + + err = cmd.Start() + if err != nil { + fmt.Fprintln(os.Stderr, "Error starting Cmd", err) + return err + } + + err = cmd.Wait() + if err != nil { + fmt.Fprintln(os.Stderr, "Error waiting for Cmd", err) + return err + } + + return nil +} + +// This should ALWAYS come from an OUTPUT +func uploadWorkflowResult(resp http.ResponseWriter, request *http.Request) { + // Post to a key with random data? + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if len(workflowId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + return + } + + // FIXME - check if permission AND whether it exists + + // FIXME - validate ID as well + ctx := context.Background() + schedule, err := getSchedule(ctx, workflowId) + if err != nil { + log.Printf("Failed setting schedule %s: %s", workflowId, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Should use generic interfaces and parse fields OR + // build temporary struct based on api.yaml of the app + data, err := parseWorkflowParameters(resp, request) + if err != nil { + log.Printf("Invalid params: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // Get the actual fields + foldername := schedule.AppInfo.SourceApp.Foldername + curOutputType := schedule.AppInfo.SourceApp.Name + curOutputAppOutput := schedule.AppInfo.SourceApp.Action + curInputType := schedule.AppInfo.DestinationApp.Name + translatormap := schedule.Translator + + if len(curOutputType) <= 0 { + log.Printf("Id %s is invalid. Missing sourceapp name", workflowId) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + return + } + + if len(foldername) == 0 { + foldername = strings.ToLower(curOutputType) + } + + if len(curOutputAppOutput) <= 0 { + log.Printf("Id %s is invalid. Missing source output ", workflowId) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + return + } + + if len(curInputType) <= 0 { + log.Printf("Id %s is invalid. Missing destination name", workflowId) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + return + } + + // Needs to be used for parsing properly + // Might be dumb to have the yaml as a file too + yamlpath := fmt.Sprintf("%s/%s/api.yaml", baseAppPath, foldername) + curyaml, err := loadYaml(yamlpath) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + //validFields := []string{} + requiredFields := []string{} + optionalFields := []string{} + for _, output := range curyaml.Output { + if output.Name != curOutputAppOutput { + continue + } + + for _, outputparam := range output.OutputParameters { + if outputparam.Required == "true" { + if outputparam.Schema.Type == "string" { + requiredFields = append(requiredFields, outputparam.Name) + } else { + log.Printf("Outputparam schematype %s is not implemented.", outputparam.Schema.Type) + } + } else { + optionalFields = append(optionalFields, outputparam.Name) + } + } + + // Wont reach here unless it's the right one + break + } + + // Checks whether ALL required fields are filled + for _, fieldname := range requiredFields { + if data[fieldname] == nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Field %s is required"}`, fieldname))) + return + } else { + log.Printf("%s: %s", fieldname, data[fieldname]) + } + } + + // FIXME + // Verify whether it can be sent from the source to destination here + // Save to DB or send it straight? Idk + // Use e.g. google pubsub if cloud and maybe kafka locally + + // FIXME - add more types :) + sourcedatamap := map[string]string{} + for key, value := range data { + switch v := value.(type) { + case string: + sourcedatamap[key] = value.(string) + default: + log.Printf("unexpected type %T", v) + } + } + + log.Println(data) + log.Println(requiredFields) + log.Println(translatormap) + log.Println(sourcedatamap) + + outputmap := map[string]string{} + for _, translator := range translatormap { + if translator.Src.Type == "static" { + log.Printf("%s = %s", translator.Dst.Name, translator.Src.Value) + outputmap[translator.Dst.Name] = translator.Src.Value + } else { + log.Printf("%s = %s", translator.Dst.Name, translator.Src.Name) + outputmap[translator.Dst.Name] = sourcedatamap[translator.Src.Name] + } + } + + configmap := map[string]string{} + for _, config := range schedule.AppInfo.DestinationApp.Config { + configmap[config.Key] = config.Value + } + + // FIXME - add function to run + // FIXME - add reference somehow + // FIXME - add apikey somehow + // Just package and run really? + + // FIXME - generate from sourceapp + outputmap["function"] = "create_alert" + cmdArgs := []string{ + fmt.Sprintf("%s/%s/app.py", baseAppPath, foldername), + } + + for key, value := range outputmap { + cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", key, value)) + } + + // COnfig map! + for key, value := range configmap { + cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", key, value)) + } + outputmap["referenceid"] = workflowId + + baseProcess := "python3" + log.Printf("Executing: %s %s", baseProcess, strings.Join(cmdArgs, " ")) + execSubprocess(baseProcess, cmdArgs) + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +// Index = Username +func setSchedule(ctx context.Context, schedule ScheduleOld) error { + key1 := datastore.NameKey("schedules", strings.ToLower(schedule.Id), nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key1, &schedule); err != nil { + log.Printf("Error adding schedule: %s", err) + return err + } + + return nil +} + +//dst: {name: "title", required: "true", type: "string"} +// +//"title": "symptomDescription", +//"description": "detailedDescription", +//"type": "ticketType", +//"sourceRef": "ticketId" +//"name": "secureworks", +//"id": "e07910a06a086c83ba41827aa00b26ed", +//"description": "I AM SECUREWORKS DESC", +//"action": "Get Tickets", +//"config": {} +//"name": "thehive", +// "id": "e07910a06a086c83ba41827aa00b26ef", +// "description": "I AM thehive DESC", +// "action": "Add ticket", +// "config": [{ +// "key": "http://localhost:9000", +// "value": "kZJmmn05j8wndOGDGvKg/D9eKub1itwO" +// }] + +func getAllScheduleApps(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + var err error + var limit = 50 + + // FIXME - add org search and public / private + key, ok := request.URL.Query()["limit"] + if ok { + limit, err = strconv.Atoi(key[0]) + if err != nil { + limit = 50 + } + } + + // Max datastore limit + if limit > 1000 { + limit = 1000 + } + + // Get URLs from a database index (mapped by orborus) + ctx := context.Background() + q := datastore.NewQuery("appschedules").Limit(limit) + var allappschedules ScheduleApps + + ret, err := dbclient.GetAll(ctx, q, &allappschedules.Apps) + _ = ret + if err != nil { + log.Printf("Failed getting all apps: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting apps"}`))) + return + } + + newjson, err := json.Marshal(allappschedules) + if err != nil { + log.Printf("Failed unmarshal: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newjson) +} + +func setScheduleApp(ctx context.Context, app ApiYaml, id string) error { + // id = md5(appname:appversion) + key1 := datastore.NameKey("appschedules", id, nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key1, &app); err != nil { + log.Printf("Error adding schedule app: %s", err) + return err + } + + return nil +} + +func findValidScheduleAppFolders(rootAppFolder string) ([]string, error) { + rootFiles, err := ioutil.ReadDir(rootAppFolder) + if err != nil { + return []string{}, err + } + + invalidRootFiles := []string{} + invalidRootFolders := []string{} + invalidAppFolders := []string{} + validAppFolders := []string{} + + // This is dumb + allowedLanguages := []string{"py", "go"} + + for _, rootfile := range rootFiles { + if !rootfile.IsDir() { + invalidRootFiles = append(invalidRootFiles, rootfile.Name()) + continue + } + + appFolderLocation := fmt.Sprintf("%s/%s", rootAppFolder, rootfile.Name()) + appFiles, err := ioutil.ReadDir(appFolderLocation) + if err != nil { + // Invalid app folder (deleted within a few MS lol) + log.Printf("%s", err) + invalidRootFolders = append(invalidRootFolders, rootfile.Name()) + continue + } + + yamlFileDone := false + appFileExists := false + for _, appfile := range appFiles { + if appfile.Name() == "api.yaml" { + err := validateAppYaml( + fmt.Sprintf("%s/%s", appFolderLocation, appfile.Name()), + ) + + if err != nil { + log.Printf("Error in %s: %s", fmt.Sprintf("%s/%s", rootfile.Name(), appfile.Name()), err) + break + } + + log.Printf("YAML FOR %s: %s IS VALID!!", rootfile.Name(), appfile.Name()) + yamlFileDone = true + } + + for _, language := range allowedLanguages { + if appfile.Name() == fmt.Sprintf("app.%s", language) { + log.Printf("Appfile found for %s", rootfile.Name()) + appFileExists = true + break + } + } + } + + if !yamlFileDone || !appFileExists { + invalidAppFolders = append(invalidAppFolders, rootfile.Name()) + } else { + validAppFolders = append(validAppFolders, rootfile.Name()) + } + } + + log.Printf("Invalid rootfiles: %s", strings.Join(invalidRootFiles, ", ")) + log.Printf("Invalid rootfolders: %s", strings.Join(invalidRootFolders, ", ")) + log.Printf("Invalid appfolders: %s", strings.Join(invalidAppFolders, ", ")) + log.Printf("\n=== VALID appfolders ===\n* %s", strings.Join(validAppFolders, "\n")) + + return validAppFolders, err +} + +func validateInputOutputYaml(appType string, apiYaml ApiYaml) error { + if appType == "input" { + for index, input := range apiYaml.Input { + if input.Name == "" { + return errors.New(fmt.Sprintf("YAML field name doesn't exist in index %d of Input", index)) + } + if input.Description == "" { + return errors.New(fmt.Sprintf("YAML field description doesn't exist in index %d of Input", index)) + } + + for paramindex, param := range input.InputParameters { + if param.Name == "" { + return errors.New(fmt.Sprintf("YAML field name doesn't exist in Input %s with index %d", input.Name, paramindex)) + } + + if param.Description == "" { + return errors.New(fmt.Sprintf("YAML field description doesn't exist in Input %s with index %d", input.Name, index)) + } + + if param.Schema.Type == "" { + return errors.New(fmt.Sprintf("YAML field schema.type doesn't exist in Input %s with index %d", input.Name, index)) + } + } + } + } + + return nil +} + +func validateAppYaml(fileLocation string) error { + /* + Requires: + name, description, app_version, contact_info (name), types + */ + + apiYaml, err := loadYaml(fileLocation) + if err != nil { + return err + } + + // Validate fields + if apiYaml.Name == "" { + return errors.New("YAML field name doesn't exist") + } + if apiYaml.Description == "" { + return errors.New("YAML field description doesn't exist") + } + + if apiYaml.AppVersion == "" { + return errors.New("YAML field app_version doesn't exist") + } + + if apiYaml.ContactInfo.Name == "" { + return errors.New("YAML field contact_info.name doesn't exist") + } + + if len(apiYaml.Types) == 0 { + return errors.New("YAML field types doesn't exist") + } + + // Validate types (input/ouput) + validTypes := []string{"input", "output"} + for _, appType := range apiYaml.Types { + // Validate in here lul + for _, validType := range validTypes { + if appType == validType { + err = validateInputOutputYaml(appType, apiYaml) + if err != nil { + return err + } + break + } + } + } + + return nil +} + +func getHook(ctx context.Context, hookId string) (*Hook, error) { + key := datastore.NameKey("hooks", strings.ToLower(hookId), nil) + hook := &Hook{} + if err := dbclient.Get(ctx, key, hook); err != nil { + return &Hook{}, err + } + + return hook, nil +} + +func setHook(ctx context.Context, hook Hook) error { + key1 := datastore.NameKey("hooks", strings.ToLower(hook.Id), nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key1, &hook); err != nil { + log.Printf("Error adding hook: %s", err) + return err + } + + return nil +} + +func handleGetallHooks(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + ctx := context.Background() + // With user, do a search for workflows with user or user's org attached + q := datastore.NewQuery("hooks").Filter("owner =", user.Username) + var allhooks []Hook + _, err = dbclient.GetAll(ctx, q, &allhooks) + if err != nil { + log.Printf("Failed getting workflows for user %s: %s", user.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(allhooks) == 0 { + resp.WriteHeader(200) + resp.Write([]byte("[]")) + return + } + + newjson, err := json.Marshal(allhooks) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newjson) +} + +//func deployWebhookCloudrun(ctx context.Context) { +// service, err := cloudrun.NewService(ctx) +// _ = err +// +// projectsLocationsService := cloudrun.NewProjectsLocationsService(service) +// log.Printf("%#v", projectsLocationsService) +// projectsLocationsGetCall := projectsLocationsService.Get("webhook") +// log.Printf("%#v", projectsLocationsGetCall) +// +// location, err := projectsLocationsGetCall.Do() +// log.Printf("%#v, err: %s", location, err) +// +// //func NewProjectsLocationsService(s *Service) *ProjectsLocationsService { +// //func (r *ProjectsLocationsService) Get(name string) *ProjectsLocationsGetCall { +// //func (c *ProjectsLocationsGetCall) Do(opts ...googleapi.CallOption) (*Location, error) { +//} + +// Finds available ports +func findAvailablePorts(startRange int64, endRange int64) string { + for i := startRange; i < endRange; i++ { + s := strconv.FormatInt(i, 10) + l, err := net.Listen("tcp", ":"+s) + + if err == nil { + l.Close() + return s + } + } + + return "" +} + +func handleSendalert(resp http.ResponseWriter, request *http.Request) { + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in getworkflows: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Role != "mail" && user.Role != "admin" { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "You don't have access to send mail"}`)) + return + } + + // ReferenceExecution and below are for execution continuations when user inputs arrive + type mailcheck struct { + Targets []string `json:"targets"` + Body string `json:"body"` + Subject string `json:"subject"` + Type string `json:"type"` + SenderCompany string `json:"sender_company"` + ReferenceExecution string `json:"reference_execution"` + WorkflowId string `json:"workflow_id"` + ExecutionType string `json:"execution_type"` + Start string `json:"start"` + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Body data error on mail: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + var mailbody mailcheck + err = json.Unmarshal(body, &mailbody) + if err != nil { + log.Printf("Unmarshal error on mail: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + ctx := context.Background() + confirmMessage := ` +You have a new alert from shuffler.io! + +%s + +Please contact us at shuffler.io or frikky@shuffler.io if there is an issue with this message.` + + parsedBody := fmt.Sprintf(confirmMessage, mailbody.Body) + + // FIXME - Make a continuation email here - might need more info from worker + // making the request, e.g. what the next start-node is and execution_id for + // how to make the links + if mailbody.Type == "User input" { + authkey := uuid.NewV4().String() + + log.Printf("Should handle differentiator for user input in email!") + log.Printf("%#v", mailbody) + + url := "https://shuffler.io" + //url := "http://localhost:5001" + continueUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute?authorization=%s&start=%s&reference_execution=%s&answer=true", url, mailbody.WorkflowId, authkey, mailbody.Start, mailbody.ReferenceExecution) + stopUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute?authorization=%s&start=%s&reference_execution=%s&answer=false", url, mailbody.WorkflowId, authkey, mailbody.Start, mailbody.ReferenceExecution) + + //item := &memcache.Item{ + // Key: authkey, + // Value: []byte(fmt.Sprintf(`{"role": "workflow_%s"}`, mailbody.WorkflowId)), + // Expiration: time.Minute * 1200, + //} + + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting new user item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // log.Printf("Set cache for %s", item.Key) + //} + + parsedBody = fmt.Sprintf(` +Action required! + +%s + +If this is TRUE click this: %s + +IF THIS IS FALSE, click this: %s + +Please contact us at shuffler.io or frikky@shuffler.io if there is an issue with this message. +`, mailbody.Body, continueUrl, stopUrl) + + } + + msg := &mail.Message{ + Sender: "Shuffle ", + To: mailbody.Targets, + Subject: fmt.Sprintf("Shuffle - %s - %s", mailbody.Type, mailbody.Subject), + Body: parsedBody, + } + + log.Println(msg.Body) + if err := mail.Send(ctx, msg); err != nil { + log.Printf("Couldn't send email: %v", err) + } + + resp.WriteHeader(200) + resp.Write([]byte("OK")) +} + +func setBadMemcache(ctx context.Context, path string) { + // Add to cache if it doesn't exist + //item := &memcache.Item{ + // Key: path, + // Value: []byte(`{"success": false}`), + // Expiration: time.Minute * 60, + //} + + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // log.Printf("Set cache for %s", item.Key) + //} +} + +func getDocList(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + ctx := context.Background() + //if item, err := memcache.Get(ctx, "docs_list"); err == memcache.ErrCacheMiss { + // // Not in cache + //} else if err != nil { + // // Error with cache + // log.Printf("Error getting item: %v", err) + //} else { + // resp.WriteHeader(200) + // resp.Write([]byte(item.Value)) + // return + //} + + client := github.NewClient(nil) + _, item1, _, err := client.Repositories.GetContents(ctx, "shaffuru", "shuffle-docs", "docs", nil) + if err != nil { + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error listing directory"`))) + return + } + + if len(item1) == 0 { + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No docs available."`))) + return + } + + names := []string{} + for _, item := range item1 { + if !strings.HasSuffix(*item.Name, "md") { + continue + } + + names = append(names, (*item.Name)[0:len(*item.Name)-3]) + } + + log.Println(names) + + type Result struct { + Success bool `json:"success"` + Reason string `json:"reason"` + List []string `json:"list"` + } + + var result Result + result.Success = true + result.Reason = "Success" + result.List = names + b, err := json.Marshal(result) + if err != nil { + http.Error(resp, err.Error(), 500) + return + } + + //item := &memcache.Item{ + // Key: "docs_list", + // Value: b, + // Expiration: time.Minute * 60, + //} + + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // log.Printf("Set cache for %s", item.Key) + //} + + resp.WriteHeader(200) + resp.Write(b) +} + +// r.HandleFunc("/api/v1/docs/{key}", getDocs).Methods("GET", "OPTIONS") +func getDocs(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + if len(location) != 5 { + resp.WriteHeader(404) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad path. Use e.g. /api/v1/docs/workflows.md"`))) + return + } + + //ctx := context.Background() + docPath := fmt.Sprintf("https://raw.githubusercontent.com/shaffuru/shuffle-docs/master/docs/%s.md", location[4]) + //if item, err := memcache.Get(ctx, docPath); err == memcache.ErrCacheMiss { + // // Not in cache + //} else if err != nil { + // // Error with cache + // log.Printf("Error getting item: %v", err) + //} else { + // resp.WriteHeader(200) + // resp.Write([]byte(item.Value)) + // return + //} + + client := &http.Client{} + req, err := http.NewRequest( + "GET", + docPath, + nil, + ) + + if err != nil { + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad path. Use e.g. /api/v1/docs/workflows.md"`))) + resp.WriteHeader(404) + //setBadMemcache(ctx, docPath) + return + } + + newresp, err := client.Do(req) + if err != nil { + resp.WriteHeader(404) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad path. Use e.g. /api/v1/docs/workflows.md"`))) + //setBadMemcache(ctx, docPath) + return + } + + body, err := ioutil.ReadAll(newresp.Body) + if err != nil { + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't parse data"`))) + //setBadMemcache(ctx, docPath) + return + } + + type Result struct { + Success bool `json:"success"` + Reason string `json:"reason"` + } + + var result Result + result.Success = true + + //applog.Infof(ctx, string(body)) + //applog.Infof(ctx, "Url: %s", docPath) + //applog.Infof(ctx, "Status: %d", newresp.StatusCode) + //applog.Infof(ctx, "GOT BODY OF LENGTH %d", len(string(body))) + + result.Reason = string(body) + b, err := json.Marshal(result) + if err != nil { + http.Error(resp, err.Error(), 500) + //setBadMemcache(ctx, docPath) + return + } + + // Add to cache if it doesn't exist + //item := &memcache.Item{ + // Key: docPath, + // Value: b, + // Expiration: time.Minute * 60, + //} + + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // log.Printf("Set cache for %s", item.Key) + //} + + resp.WriteHeader(200) + resp.Write(b) +} + +type OutlookProfile struct { + OdataContext string `json:"@odata.context"` + BusinessPhones []string `json:"businessPhones"` + DisplayName string `json:"displayName"` + GivenName string `json:"givenName"` + JobTitle interface{} `json:"jobTitle"` + Mail string `json:"mail"` + MobilePhone interface{} `json:"mobilePhone"` + OfficeLocation interface{} `json:"officeLocation"` + PreferredLanguage interface{} `json:"preferredLanguage"` + Surname string `json:"surname"` + UserPrincipalName string `json:"userPrincipalName"` + ID string `json:"id"` +} + +type OutlookFolder struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + ParentFolderID string `json:"parentFolderId"` + ChildFolderCount int `json:"childFolderCount"` + UnreadItemCount int `json:"unreadItemCount"` + TotalItemCount int `json:"totalItemCount"` +} + +type OutlookFolders struct { + OdataContext string `json:"@odata.context"` + OdataNextLink string `json:"@odata.nextLink"` + Value []OutlookFolder `json:"value"` +} + +func getOutlookFolders(client *http.Client) (OutlookFolders, error) { + requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/frikky@shuffletest.onmicrosoft.com/mailfolders") + + ret, err := client.Get(requestUrl) + if err != nil { + log.Printf("FolderErr: %s", err) + return OutlookFolders{}, err + } + + if ret.StatusCode != 200 { + log.Printf("Status folders: %d", ret.StatusCode) + return OutlookFolders{}, err + } + + body, err := ioutil.ReadAll(ret.Body) + if err != nil { + log.Printf("Body: %s", err) + return OutlookFolders{}, err + } + + //log.Printf("Body: %s", string(body)) + + mailfolders := OutlookFolders{} + err = json.Unmarshal(body, &mailfolders) + if err != nil { + log.Printf("Unmarshal: %s", err) + return OutlookFolders{}, err + } + + //fmt.Printf("%#v", mailfolders) + // FIXME - recursion for subfolders + // Recursive struct + // folderEndpoint := fmt.Sprintf("%s/%s/childfolders?$top=40", requestUrl, parentId) + //for _, folder := range mailfolders.Value { + // log.Println(folder.DisplayName) + //} + + return mailfolders, nil +} + +func getOutlookProfile(client *http.Client) (OutlookProfile, error) { + requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me?$select=mail") + + ret, err := client.Get(requestUrl) + if err != nil { + log.Printf("FolderErr: %s", err) + return OutlookProfile{}, err + } + + log.Printf("Status folders: %d", ret.StatusCode) + body, err := ioutil.ReadAll(ret.Body) + if err != nil { + log.Printf("Body: %s", err) + return OutlookProfile{}, err + } + + profile := OutlookProfile{} + err = json.Unmarshal(body, &profile) + if err != nil { + log.Printf("Unmarshal: %s", err) + return OutlookProfile{}, err + } + + return profile, nil +} + +func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) { + code := request.URL.Query().Get("code") + if len(code) == 0 { + log.Println("No code") + resp.WriteHeader(401) + return + } + + url := fmt.Sprintf("http://%s%s", request.Host, request.URL.EscapedPath()) + log.Println(url) + ctx := context.Background() + client, accessToken, err := getOutlookClient(ctx, code, OauthToken{}, url) + if err != nil { + log.Printf("Oauth client failure - outlook register: %s", err) + resp.WriteHeader(401) + return + } + // This should be possible, and will also give the actual username + profile, err := getOutlookProfile(client) + if err != nil { + log.Printf("Outlook profile failure: %s", err) + resp.WriteHeader(401) + return + } + + // This is a state workaround, which should really be for CSRF checks lol + state := request.URL.Query().Get("state") + if len(state) == 0 { + log.Println("No state") + resp.WriteHeader(401) + return + } + + stateitems := strings.Split(state, "%26") + if len(stateitems) == 1 { + stateitems = strings.Split(state, "&") + } + + // FIXME - trigger auth + senderUser := "" + trigger := TriggerAuth{} + for _, item := range stateitems { + itemsplit := strings.Split(item, "%3D") + if len(itemsplit) == 1 { + itemsplit = strings.Split(item, "=") + } + + if len(itemsplit) != 2 { + continue + } + + // Do something here + if itemsplit[0] == "workflow_id" { + trigger.WorkflowId = itemsplit[1] + } else if itemsplit[0] == "trigger_id" { + trigger.Id = itemsplit[1] + } else if itemsplit[0] == "type" { + trigger.Type = itemsplit[1] + } else if itemsplit[0] == "username" { + trigger.Username = itemsplit[1] + trigger.Owner = itemsplit[1] + senderUser = itemsplit[1] + } + } + + // THis is an override based on the user in oauth return + trigger.Username = profile.Mail + trigger.Code = code + trigger.OauthToken = OauthToken{ + AccessToken: accessToken.AccessToken, + TokenType: accessToken.TokenType, + RefreshToken: accessToken.RefreshToken, + Expiry: accessToken.Expiry, + } + + //log.Printf("%#v", trigger) + if trigger.WorkflowId == "" || trigger.Id == "" || trigger.Username == "" || trigger.Type == "" { + log.Printf("All oauth items need to contain data to register a new state") + resp.WriteHeader(401) + return + } + + // Should also update the user + Userdata, err := getUser(ctx, senderUser) + if err != nil { + log.Printf("Username %s doesn't exist (oauth2): %s", trigger.Username, err) + resp.WriteHeader(401) + return + } + + Userdata.Authentication = append(Userdata.Authentication, UserAuth{ + Name: "Outlook", + Description: "oauth2", + Workflows: []string{trigger.WorkflowId}, + Username: trigger.Username, + Fields: []UserAuthField{ + UserAuthField{ + Key: "trigger_id", + Value: trigger.Id, + }, + UserAuthField{ + Key: "username", + Value: trigger.Username, + }, + UserAuthField{ + Key: "code", + Value: code, + }, + UserAuthField{ + Key: "type", + Value: trigger.Type, + }, + }, + }) + + // Set apikey for the user if they don't have one + if len(Userdata.ApiKey) == 0 { + newUser, err := generateApikey(ctx, *Userdata) + Userdata = &newUser + if err != nil { + log.Printf("Failed to generate apikey for user %s when creating outlook sub: %s", Userdata.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + } + + //err = setUser(Userdata) + //if err != nil { + // log.Printf("Failed setting user data for %s: %s", Userdata.Username, err) + // resp.WriteHeader(401) + // return + //} + + err = setTriggerAuth(ctx, trigger) + if err != nil { + log.Printf("Failed to set trigger auth for %s - %s", trigger.Username, err) + resp.WriteHeader(401) + return + } + + // FIXME - not sure if these are good at all :) + environmentVariables := map[string]string{ + "FUNCTION_APIKEY": Userdata.ApiKey, + "CALLBACKURL": "https://shuffler.io", + "WORKFLOW_ID": trigger.WorkflowId, + "TRIGGER_ID": trigger.Id, + } + + applocation := fmt.Sprintf("gs://%s/triggers/outlooktrigger.zip", bucketName) + hookname := fmt.Sprintf("outlooktrigger_%s", trigger.Id) + + err = deployCloudFunctionGo(ctx, hookname, defaultLocation, applocation, environmentVariables) + if err != nil { + log.Printf("Error deploying hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Issue with starting hook. Please wait a second and try again"}`))) + return + } + + resp.WriteHeader(200) + resp.Write([]byte("OK")) +} + +type OauthToken struct { + AccessToken string `json:"AccessToken" datastore:"AccessToken,noindex"` + TokenType string `json:"TokenType" datastore:"TokenType,noindex"` + RefreshToken string `json:"RefreshToken" datastore:"RefreshToken,noindex"` + Expiry time.Time `json:"Expiry" datastore:"Expiry,noindex"` +} +type TriggerAuth struct { + Id string `json:"id" datastore:"id"` + SubscriptionId string `json:"subscriptionId" datastore:"subscriptionId"` + + Username string `json:"username" datastore:"username,noindex"` + WorkflowId string `json:"workflow_id" datastore:"workflow_id,noindex"` + Owner string `json:"owner" datastore:"owner"` + Type string `json:"type" datastore:"type"` + Code string `json:"code,omitempty" datastore:"code,noindex"` + OauthToken OauthToken `json:"oauth_token,omitempty" datastore:"oauth_token"` +} + +func getTriggerAuth(ctx context.Context, id string) (*TriggerAuth, error) { + key := datastore.NameKey("trigger_auth", strings.ToLower(id), nil) + triggerauth := &TriggerAuth{} + if err := dbclient.Get(ctx, key, triggerauth); err != nil { + return &TriggerAuth{}, err + } + + return triggerauth, nil +} + +func setTriggerAuth(ctx context.Context, trigger TriggerAuth) error { + key1 := datastore.NameKey("trigger_auth", strings.ToLower(trigger.Id), nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key1, &trigger); err != nil { + log.Printf("Error adding trigger auth: %s", err) + return err + } + + return nil +} + +// THis all of a sudden became really horrible.. fml +func getOutlookClient(ctx context.Context, code string, accessToken OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) { + + conf := &oauth2.Config{ + ClientID: "70e37005-c954-4290-b573-d4b94e484336", + ClientSecret: ".eNw/A[kQFB5zL.agvRputdEJENeJ392", + Scopes: []string{ + "Mail.Read", + "User.Read", + }, + RedirectURL: redirectUri, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://login.microsoftonline.com/common/oauth2/authorize", + TokenURL: "https://login.microsoftonline.com/common/oauth2/token", + }, + } + + if len(code) > 0 { + access_token, err := conf.Exchange(ctx, code) + if err != nil { + log.Printf("Access_token issue: %s", err) + return &http.Client{}, access_token, err + } + + client := conf.Client(ctx, access_token) + return client, access_token, nil + } else { + // Manually recreate the oauthtoken + access_token := &oauth2.Token{ + AccessToken: accessToken.AccessToken, + TokenType: accessToken.TokenType, + RefreshToken: accessToken.RefreshToken, + Expiry: accessToken.Expiry, + } + + client := conf.Client(ctx, access_token) + return client, access_token, nil + } +} + +func handleGetOutlookFolders(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Exchange every time hmm + // FIXME + // Should really just get the code from the trigger that's being used OR the user + triggerId := request.URL.Query().Get("trigger_id") + if len(triggerId) == 0 { + log.Println("No trigger_id supplied") + resp.WriteHeader(401) + return + } + + ctx := context.Background() + trigger, err := getTriggerAuth(ctx, triggerId) + if err != nil { + log.Printf("Trigger %s doesn't exist - outlook folders.", triggerId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Trigger doesn't exist."}`)) + return + } + + // FIXME - should be shuffler in literally every case except testing lol + redirectDomain := "shuffler.io" + url := fmt.Sprintf("https://%s/functions/outlook/register", redirectDomain) + outlookClient, _, err := getOutlookClient(ctx, "", trigger.OauthToken, url) + if err != nil { + log.Printf("Oauth client failure - outlook folders: %s", err) + resp.WriteHeader(401) + return + } + + folders, err := getOutlookFolders(outlookClient) + if err != nil { + resp.WriteHeader(401) + return + } + + b, err := json.Marshal(folders.Value) + if err != nil { + log.Println("Failed to marshal folderdata") + resp.WriteHeader(401) + return + } + + resp.WriteHeader(200) + resp.Write(b) +} + +func handleGetSpecificTrigger(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in getting specific workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + if strings.Contains(workflowId, "?") { + workflowId = strings.Split(workflowId, "?")[0] + } + + ctx := context.Background() + trigger, err := getTriggerAuth(ctx, workflowId) + if err != nil { + log.Printf("Trigger %s doesn't exist - specific trigger.", workflowId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + if user.Username != trigger.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for trigger %s", user.Username, trigger.Id) + resp.WriteHeader(401) + return + } + + trigger.OauthToken = OauthToken{} + trigger.Code = "" + + b, err := json.Marshal(trigger) + if err != nil { + log.Println("Failed to marshal data") + resp.WriteHeader(401) + return + } + + resp.WriteHeader(200) + resp.Write(b) +} + +func handleDeleteOutlookSub(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + var triggerId string + if location[1] == "api" { + if len(location) <= 6 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + triggerId = location[6] + } + + if len(workflowId) == 0 || len(triggerId) == 0 { + log.Printf("Ids can't be zero when deleting %s", workflowId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + ctx := context.Background() + workflow, err := getWorkflow(ctx, workflowId) + if err != nil { + log.Printf("Failed getting the workflow locally: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in outlook deploy: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - have a check for org etc too.. + if user.Id != workflow.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s when deploying outlook", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Check what kind of sub it is + err = handleOutlookSubRemoval(ctx, workflowId, triggerId) + if err != nil { + log.Printf("Failed sub removal: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +func removeOutlookSubscription(outlookClient *http.Client, subscriptionId string) error { + // DELETE https://graph.microsoft.com/v1.0/subscriptions/{id} + fullUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/subscriptions/%s", subscriptionId) + req, err := http.NewRequest( + "DELETE", + fullUrl, + nil, + ) + req.Header.Add("Content-Type", "application/json") + res, err := outlookClient.Do(req) + if err != nil { + log.Printf("Client: %s", err) + return err + } + + if res.StatusCode != 200 && res.StatusCode != 201 && res.StatusCode != 204 { + return errors.New(fmt.Sprintf("Bad status code when deleting subscription: %d", res.StatusCode)) + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Printf("Body: %s", err) + return err + } + + _ = body + + return nil +} + +// Remove AUTH +// Remove function +// Remove subscription +func handleOutlookSubRemoval(ctx context.Context, workflowId, triggerId string) error { + // 1. Get the auth for trigger + // 2. Stop the subscription + // 3. Remove the function + // 4. Remove the database entry for auth + trigger, err := getTriggerAuth(ctx, triggerId) + if err != nil { + log.Printf("Trigger auth %s doesn't exist - outlook sub removal.", triggerId) + return err + } + + url := fmt.Sprintf("https://shuffler.io") + outlookClient, _, err := getOutlookClient(ctx, "", trigger.OauthToken, url) + if err != nil { + log.Printf("Oauth client failure - triggerauth sub removal: %s", err) + return err + } + + notificationURL := fmt.Sprintf("https://%s-%s.cloudfunctions.net/outlooktrigger_%s", defaultLocation, gceProject, trigger.Id) + curSubscriptions, err := getOutlookSubscriptions(outlookClient) + if err == nil { + for _, sub := range curSubscriptions.Value { + if sub.NotificationURL == notificationURL { + log.Printf("Removing existing subscription %s", sub.Id) + removeOutlookSubscription(outlookClient, sub.Id) + } + } + } else { + log.Printf("Failed to get subscriptions - need to overwrite") + } + + // FIXME - not removing the function, as the trigger still exists + //err = removeOutlookTriggerFunction(triggerId) + //if err != nil { + // return err + //} + + return nil +} + +// This sets up the sub with outlook itself +// Parses data from the workflow to see whether access is right to subscribe it +// Creates the cloud function for outlook return +// Wait for it to be available, then schedule a workflow to it +func createOutlookSub(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + location := strings.Split(request.URL.String(), "/") + + var workflowId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowId = location[4] + } + + ctx := context.Background() + workflow, err := getWorkflow(ctx, workflowId) + if err != nil { + log.Printf("Failed getting the workflow locally: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in outlook deploy: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - have a check for org etc too.. + if user.Id != workflow.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s when deploying outlook", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Println("Handle outlook subscription for trigger") + + // Should already be authorized at this point, as the workflow is shared + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Failed body read for workflow %s", workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Println(string(body)) + + // Based on the input data from frontend + type CurTrigger struct { + Name string `json:"name"` + Folders []string `json:"folders"` + ID string `json:"id"` + } + + var curTrigger CurTrigger + err = json.Unmarshal(body, &curTrigger) + if err != nil { + log.Printf("Failed body read unmarshal for trigger %s", workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(curTrigger.Folders) == 0 { + log.Printf("Error for %s. Choosing folders is required, currently 0", workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Now that it's deployed - wait a few seconds before generating: + // 1. Oauth2 token thingies for outlook.office.com + // 2. Set the url to have the right mailboxes (probably ID?) ("https://outlook.office.com/api/v2.0/me/mailfolders('inbox')/messages") + // 3. Set the callback URL to be the new trigger + // 4. Run subscription test + // 5. Set the subscriptionId to the trigger object + + // First - lets regenerate an oauth token for outlook.office.com from the original items + trigger, err := getTriggerAuth(ctx, curTrigger.ID) + if err != nil { + log.Printf("Trigger %s doesn't exist - outlook sub.", curTrigger.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": ""}`)) + return + } + + // url doesn't really matter here + url := fmt.Sprintf("https://shuffler.io") + outlookClient, _, err := getOutlookClient(ctx, "", trigger.OauthToken, url) + if err != nil { + log.Printf("Oauth client failure - triggerauth: %s", err) + resp.WriteHeader(401) + return + } + + // Location + + notificationURL := fmt.Sprintf("https://%s-%s.cloudfunctions.net/outlooktrigger_%s", defaultLocation, gceProject, curTrigger.ID) + log.Println(notificationURL) + + // This is here simply to let the function start + // Usually takes 10 attempts minimum :O + // 10 * 5 = 50 seconds. That's waaay too much :( + //notificationURL = "https://europe-west1-shuffler.cloudfunctions.net/outlooktrigger_e2ce43b0-997e-4980-9617-6eadbc68cf88" + //notificationURL = "https://de4fc12b.ngrok.io" + + curSubscriptions, err := getOutlookSubscriptions(outlookClient) + if err == nil { + for _, sub := range curSubscriptions.Value { + if sub.NotificationURL == notificationURL { + log.Printf("Removing existing subscription %s", sub.Id) + removeOutlookSubscription(outlookClient, sub.Id) + } + } + } else { + log.Printf("Failed to get subscriptions - need to overwrite") + } + + maxFails := 15 + failCnt := 0 + log.Println(curTrigger.Folders) + for { + subId, err := makeOutlookSubscription(outlookClient, curTrigger.Folders, notificationURL) + if err != nil { + failCnt += 1 + log.Printf("Failed making oauth subscription, retrying in 5 seconds: %s", err) + time.Sleep(5 * time.Second) + if failCnt == maxFails { + log.Printf("Failed to set up subscription %d times.", maxFails) + resp.WriteHeader(401) + return + } + + continue + } + + // Set the ID somewhere here + trigger.SubscriptionId = subId + err = setTriggerAuth(ctx, *trigger) + if err != nil { + log.Printf("Failed setting triggerauth: %s", err) + } + + break + } + + log.Printf("Successfully handled outlook subscription for trigger %s in workflow %s", curTrigger.ID, workflow.ID) + + //log.Printf("%#v", user) + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +// Lists the users current subscriptions +func getOutlookSubscriptions(outlookClient *http.Client) (SubscriptionsWrapper, error) { + fullUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/subscriptions") + req, err := http.NewRequest( + "GET", + fullUrl, + nil, + ) + req.Header.Add("Content-Type", "application/json") + res, err := outlookClient.Do(req) + if err != nil { + log.Printf("suberror Client: %s", err) + return SubscriptionsWrapper{}, err + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Printf("Suberror Body: %s", err) + return SubscriptionsWrapper{}, err + } + + newSubs := SubscriptionsWrapper{} + err = json.Unmarshal(body, &newSubs) + if err != nil { + return SubscriptionsWrapper{}, err + } + + return newSubs, nil +} + +type SubscriptionsWrapper struct { + OdataContext string `json:"@odata.context"` + Value []Subscription `json:"value"` +} + +type Subscription struct { + ChangeType string `json:"changeType"` + NotificationURL string `json:"notificationUrl"` + Resource string `json:"resource"` + ExpirationDateTime string `json:"expirationDateTime"` + ClientState string `json:"clientState"` + Id string `json:"id"` +} + +func makeOutlookSubscription(client *http.Client, folderIds []string, notificationURL string) (string, error) { + fullUrl := "https://graph.microsoft.com/v1.0/subscriptions" + + // FIXME - this expires rofl + t := time.Now().Local().Add(time.Minute * time.Duration(4300)) + timeFormat := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d.0000000Z", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) + log.Println(timeFormat) + + resource := fmt.Sprintf("me/mailfolders('%s')/messages", strings.Join(folderIds, "','")) + log.Println(resource) + sub := Subscription{ + ChangeType: "created", + NotificationURL: notificationURL, + ExpirationDateTime: timeFormat, + ClientState: "This is a test", + Resource: resource, + } + + data, err := json.Marshal(sub) + if err != nil { + log.Printf("Marshal: %s", err) + return "", err + } + + req, err := http.NewRequest( + "POST", + fullUrl, + bytes.NewBuffer(data), + ) + req.Header.Add("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + log.Printf("Client: %s", err) + return "", err + } + + log.Printf("Status: %d", res.StatusCode) + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Printf("Body: %s", err) + return "", err + } + + if res.StatusCode != 200 && res.StatusCode != 201 { + return "", errors.New(fmt.Sprintf("Subscription failed: %s", string(body))) + } + + // Use data from body here to create thingy + newSub := Subscription{} + err = json.Unmarshal(body, &newSub) + if err != nil { + return "", err + } + + return newSub.Id, nil +} + +func getOpenapi(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Just here to verify that the user is logged in + _, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in validate swagger: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + var id string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + id = location[4] + } + + if len(id) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - FIX AUTH WITH APP + ctx := context.Background() + //_, err = getApp(ctx, id) + //if err == nil { + // log.Println("You're supposed to be able to continue now.") + //} + + parsedApi, err := getOpenApiDatastore(ctx, id) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + parsedApi.Success = true + data, err := json.Marshal(parsedApi) + if err != nil { + resp.WriteHeader(422) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed marshalling parsed swagger: %s"}`, err))) + return + } + + resp.WriteHeader(200) + resp.Write(data) +} + +func echoOpenapiData(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Just here to verify that the user is logged in + _, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in validate swagger: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Bodyreader err: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`)) + return + } + + newbody := string(body) + newbody = strings.TrimSpace(newbody) + if strings.HasPrefix(newbody, "\"") { + newbody = newbody[1:len(newbody)] + } + + if strings.HasSuffix(newbody, "\"") { + newbody = newbody[0 : len(newbody)-1] + } + + req, err := http.NewRequest("GET", newbody, nil) + if err != nil { + log.Printf("Requestbuilder err: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed building request"}`)) + return + } + + httpClient := &http.Client{} + newresp, err := httpClient.Do(req) + if err != nil { + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed making request for data"`))) + return + } + defer newresp.Body.Close() + + urlbody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't get data from selected uri"`))) + return + } + + resp.WriteHeader(200) + resp.Write(urlbody) +} + +func validateSwagger(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Just here to verify that the user is logged in + _, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in validate swagger: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`)) + return + } + + type versionCheck struct { + Swagger string `datastore:"swagger" json:"swagger" yaml:"swagger"` + SwaggerVersion string `datastore:"swaggerVersion" json:"swaggerVersion" yaml:"swaggerVersion"` + OpenAPI string `datastore:"openapi" json:"openapi" yaml:"openapi"` + } + + //body = []byte(`swagger: "2.0"`) + //body = []byte(`swagger: '1.0'`) + //newbody := string(body) + //newbody = strings.TrimSpace(newbody) + //body = []byte(newbody) + //log.Println(string(body)) + //tmpbody, err := yaml.YAMLToJSON(body) + //log.Println(err) + //log.Println(string(tmpbody)) + + // This has to be done in a weird way because Datastore doesn't + // support map[string]interface and similar (openapi3.Swagger) + var version versionCheck + + isJson := false + err = json.Unmarshal(body, &version) + if err != nil { + log.Printf("Json err: %s", err) + err = yaml.Unmarshal(body, &version) + if err != nil { + log.Printf("Yaml error: %s", err) + //resp.WriteHeader(422) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed reading openapi to json and yaml: %s"}`, err))) + //return + } else { + log.Printf("Successfully parsed YAML!") + } + } else { + isJson = true + log.Printf("Successfully parsed JSON!") + } + + if len(version.SwaggerVersion) > 0 && len(version.Swagger) == 0 { + version.Swagger = version.SwaggerVersion + } + + if strings.HasPrefix(version.Swagger, "3.") || strings.HasPrefix(version.OpenAPI, "3.") { + log.Println("Handling v3 API") + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(body) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + hasher := md5.New() + hasher.Write(body) + idstring := hex.EncodeToString(hasher.Sum(nil)) + + log.Printf("Swagger v3 validation success with ID %s!", idstring) + log.Printf("Paths: %d", len(swagger.Paths)) + + if !isJson { + log.Printf("FIXME: NEED TO TRANSFORM FROM YAML TO JSON for %s", idstring) + } + + parsed := ParsedOpenApi{ + ID: idstring, + Body: string(body), + } + + ctx := context.Background() + err = setOpenApiDatastore(ctx, idstring, parsed) + if err != nil { + log.Printf("Failed uploading openapi to datastore: %s", err) + resp.WriteHeader(422) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed reading openapi2: %s"}`, err))) + return + } + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "id": "%s"}`, idstring))) + return + } else { //strings.HasPrefix(version.Swagger, "2.") || strings.HasPrefix(version.OpenAPI, "2.") { + // Convert + log.Println("Handling v2 API") + var swagger openapi2.Swagger + //log.Println(string(body)) + err = json.Unmarshal(body, &swagger) + if err != nil { + log.Printf("Json error? %s", err) + err = gyaml.Unmarshal(body, &swagger) + if err != nil { + log.Printf("Yaml error: %s", err) + } + + resp.WriteHeader(422) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed reading openapi2: %s"}`, err))) + return + } + + swaggerv3, err := openapi2conv.ToV3Swagger(&swagger) + if err != nil { + log.Printf("Failed converting from openapi2 to 3: %s", err) + resp.WriteHeader(422) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed converting from openapi2 to openapi3: %s"}`, err))) + return + } + + swaggerdata, err := json.Marshal(swaggerv3) + if err != nil { + log.Printf("Failed unmarshaling v3 data: %s", err) + resp.WriteHeader(422) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed marshalling swaggerv3 data: %s"}`, err))) + return + } + + hasher := md5.New() + hasher.Write(swaggerdata) + idstring := hex.EncodeToString(hasher.Sum(nil)) + if !isJson { + log.Printf("FIXME: NEED TO TRANSFORM FROM YAML TO JSON for %s?", idstring) + } + log.Printf("Swagger v2 -> v3 validation success with ID %s!", idstring) + + parsed := ParsedOpenApi{ + ID: idstring, + Body: string(swaggerdata), + } + + ctx := context.Background() + err = setOpenApiDatastore(ctx, idstring, parsed) + if err != nil { + log.Printf("Failed uploading openapi2 to datastore: %s", err) + resp.WriteHeader(422) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed reading openapi2: %s"}`, err))) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "id": "%s"}`, idstring))) + return + } + /* + else { + log.Printf("Swagger / OpenAPI version %s is not supported or there is an error.", version.Swagger) + resp.WriteHeader(422) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Swagger version %s is not currently supported"}`, version.Swagger))) + return + } + */ + + // save the openapi ID + resp.WriteHeader(422) + resp.Write([]byte(`{"success": false}`)) +} + +func verifySwagger(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in verify swagger: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`)) + return + } + + type Test struct { + Editing bool `datastore:"editing"` + Id string `datastore:"id"` + Image string `datastore:"image"` + } + + var test Test + err = json.Unmarshal(body, &test) + if err != nil { + log.Printf("Failed unmarshalling test: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Get an identifier + hasher := md5.New() + hasher.Write(body) + newmd5 := hex.EncodeToString(hasher.Sum(nil)) + if test.Editing { + // Quick verification test + ctx := context.Background() + app, err := getApp(ctx, test.Id) + if err != nil { + log.Printf("Error getting app when editing: %s", app.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME: Check whether it's in use. + if user.Id != app.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for app %s when verifying swagger", user.Username, app.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("EDITING APP WITH ID %s", app.ID) + newmd5 = app.ID + } + + // Generate new app integration (bump version) + // Test = client side with fetch? + + ctx := context.Background() + client, err := storage.NewClient(ctx) + if err != nil { + log.Printf("Failed to create client (storage): %v", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed creating client"}`)) + return + } + + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(body) + if err != nil { + log.Printf("Swagger validation error: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed verifying openapi"}`)) + return + } + + if strings.Contains(swagger.Info.Title, " ") { + strings.Replace(swagger.Info.Title, " ", "", -1) + } + + basePath, err := buildStructure(swagger, newmd5) + if err != nil { + log.Printf("Failed to build base structure: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed building baseline structure"}`)) + return + } + + log.Printf("Should generate yaml") + api, pythonfunctions, err := generateYaml(swagger, newmd5) + if err != nil { + log.Printf("Failed building and generating yaml: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed building and parsing yaml"}`)) + return + } + + api.Owner = user.Id + if len(test.Image) > 0 { + api.SmallImage = test.Image + api.LargeImage = test.Image + } + + err = dumpApi(basePath, api) + if err != nil { + log.Printf("Failed dumping yaml: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed dumping yaml"}`)) + return + } + + identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, newmd5) + classname := strings.Replace(identifier, " ", "", -1) + classname = strings.Replace(classname, "-", "", -1) + parsedCode, err := dumpPython(basePath, classname, swagger.Info.Version, pythonfunctions) + if err != nil { + log.Printf("Failed dumping python: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed dumping appcode"}`)) + return + } + + identifier = strings.Replace(identifier, " ", "-", -1) + identifier = strings.Replace(identifier, "_", "-", -1) + log.Printf("Successfully uploaded %s to bucket. Proceeding to cloud function", identifier) + + // Now that the baseline is setup, we need to make it into a cloud function + // 1. Upload the API to datastore for use + // 2. Get code from baseline/app_base.py & baseline/static_baseline.py + // 3. Stitch code together from these two + our new app + // 4. Zip the folder to cloud storage + // 5. Upload as cloud function + + // 1. Upload the API to datastore + err = deployAppToDatastore(ctx, api) + if err != nil { + log.Printf("Failed adding app to db: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed adding app to db"}`)) + return + } + + // 2. Get all the required code + appbase, staticBaseline, err := getAppbase(ctx, client) + if err != nil { + log.Printf("Failed getting appbase: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed getting appbase code"}`)) + return + } + + // Have to do some quick checks of the python code (: + _, parsedCode = formatAppfile(parsedCode) + + fixedAppbase := fixAppbase(appbase) + runner := getRunner(classname) + + // 2. Put it together + stitched := string(staticBaseline) + strings.Join(fixedAppbase, "\n") + parsedCode + string(runner) + //log.Println(stitched) + + // 3. Zip and stream it directly in the directory + _, err = streamZipdata(ctx, client, identifier, stitched, "requests\nurllib3") + if err != nil { + log.Printf("Zipfile error: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed to build zipfile"}`)) + return + } + + log.Printf("Successfully uploaded ZIPFILE for %s", identifier) + + // 4. Upload as cloud function - this apikey is specifically for cloud functions rofl + //environmentVariables := map[string]string{ + // "FUNCTION_APIKEY": apikey, + //} + + //fullLocation := fmt.Sprintf("gs://%s/%s", bucketName, applocation) + //err = deployCloudFunctionPython(ctx, identifier, defaultLocation, fullLocation, environmentVariables) + //if err != nil { + // log.Printf("Error uploading cloud function: %s", err) + // resp.WriteHeader(500) + // resp.Write([]byte(`{"success": false, "reason": "Failed to upload function"}`)) + // return + //} + + // 4. Build the image locally. + // FIXME: Should be moved to a local docker registry + dockerLocation := fmt.Sprintf("%s/Dockerfile", basePath) + log.Printf("Dockerfile: %s", dockerLocation) + + versionName := fmt.Sprintf("%s_%s", strings.ReplaceAll(api.Name, " ", "-"), api.AppVersion) + dockerTags := []string{ + fmt.Sprintf("%s:%s", baseDockerName, identifier), + fmt.Sprintf("%s:%s", baseDockerName, versionName), + } + + err = buildImage(dockerTags, dockerLocation) + if err != nil { + log.Printf("Docker build error: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Error in Docker build"}`))) + return + } + + found := false + foundNumber := 0 + log.Printf("Checking for api with ID %s", newmd5) + for appCounter, app := range user.PrivateApps { + if app.ID == api.ID { + found = true + foundNumber = appCounter + break + } else if app.Name == api.Name && app.AppVersion == api.AppVersion { + found = true + foundNumber = appCounter + break + } else if app.PrivateID == test.Id && test.Editing { + found = true + foundNumber = appCounter + break + } + } + + // Updating the user with the new app so that it can easily be retrieved + if !found { + user.PrivateApps = append(user.PrivateApps, api) + } else { + user.PrivateApps[foundNumber] = api + } + + err = setUser(ctx, &user) + if err != nil { + log.Printf("Failed adding verification for user %s: %s", user.Username, err) + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Failed updating user"}`))) + return + } + + log.Println(len(user.PrivateApps)) + c, err := request.Cookie("session_token") + if err == nil { + log.Printf("Should've deleted cache for %s with token %s", user.Username, c.Value) + //err = memcache.Delete(request.Context(), c.Value) + //err = memcache.Delete(request.Context(), user.ApiKey) + } + + parsed := ParsedOpenApi{ + ID: api.ID, + Body: string(body), + } + + setOpenApiDatastore(ctx, api.ID, parsed) + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +func healthCheckHandler(resp http.ResponseWriter, request *http.Request) { + fmt.Fprint(resp, "OK") +} + +func init() { + var err error + ctx := context.Background() + + dbclient, err = datastore.NewClient(ctx, gceProject) + if err != nil { + log.Fatalf("DBclient error during init: %s", err) + } + + count, err := getEnvironmentCount() + if count == 0 && err == nil { + item := Environment{ + Name: "Shuffle", + Type: "onprem", + } + + err = setEnvironment(ctx, &item) + if err != nil { + log.Printf("Failed setting up new environment") + } + } + + r := mux.NewRouter() + r.HandleFunc("_ah/health", healthCheckHandler) + + // Webhook redirect to the correct cloud function + r.HandleFunc("/functions/webhooks/{key}", handleWebhookRedirect).Methods("POST", "OPTIONS") + // Sends an email if the right things are specified + r.HandleFunc("/functions/sendmail", handleSendalert).Methods("POST", "OPTIONS") + r.HandleFunc("/functions/outlook/register", handleNewOutlookRegister).Methods("GET", "OPTIONS") + r.HandleFunc("/functions/outlook/getFolders", handleGetOutlookFolders).Methods("GET", "OPTIONS") + + // General + r.HandleFunc("/api/v1/login", handleLogin).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/logout", handleLogout).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/register", handleRegister).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/checkusers", checkAdminLogin).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/getusers", handleGetUsers).Methods("GET", "OPTIONS") + + r.HandleFunc("/api/v1/getenvironments", handleGetEnvironments).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/setenvironments", handleSetEnvironments).Methods("PUT", "OPTIONS") + //r.HandleFunc("/api/v1/register/{key}", handleRegisterVerification).Methods("GET", "OPTIONS") + + r.HandleFunc("/api/v1/getinfo", handleInfo).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/getsettings", handleSettings).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/generateapikey", handleApiGeneration).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/passwordchange", handlePasswordChange).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/passwordresetmail", handlePasswordResetMail).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/passwordreset", handlePasswordReset).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/contact", handleContact).Methods("POST", "OPTIONS") + + r.HandleFunc("/api/v1/docs", getDocList).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/docs/{key}", getDocs).Methods("GET", "OPTIONS") + + // Queuebuilder and Workflow streams. First is to update a stream, second to get a stream + // Changed from workflows/streams to streams, as appengine was messing up + // This does not increase the API counter + r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET") + r.HandleFunc("/api/v1/workflows/queue/confirm", handleGetWorkflowqueueConfirm).Methods("POST") + r.HandleFunc("/api/v1/streams", handleWorkflowQueue).Methods("POST") + r.HandleFunc("/api/v1/streams/results", handleGetStreamResults).Methods("POST", "OPTIONS") + + // Apps + r.HandleFunc("/api/v1/apps/get_existing", loadExistingApps).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/apps/validate", validateAppInput).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/apps/{appId}", deleteWorkflowApp).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/apps/{appId}/config", getWorkflowAppConfig).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/apps", getWorkflowApps).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/apps", setNewWorkflowApp).Methods("PUT", "OPTIONS") + + // Legacy things + r.HandleFunc("/api/v1/workflows/apps/validate", validateAppInput).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/workflows/apps", getWorkflowApps).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/apps", setNewWorkflowApp).Methods("PUT", "OPTIONS") + + // Workflows + // FIXME - implement the queue counter lol + /* Everything below here increases the counters*/ + r.HandleFunc("/api/v1/workflows", getWorkflows).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows", setNewWorkflow).Methods("POST", "OPTIONS") + //r.HandleFunc("/api/v1/workflows/{key}/execute_fs", executeWorkflowFS) + r.HandleFunc("/api/v1/workflows/{key}/execute", executeWorkflow).Methods("GET", "POST", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/schedule", scheduleWorkflow).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/schedule/{schedule}", stopSchedule).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/outlook", createOutlookSub).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/outlook/{triggerId}", handleDeleteOutlookSub).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/executions", getWorkflowExecutions).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/executions/{key}/abort", abortExecution).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}", getSpecificWorkflow).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}", saveWorkflow).Methods("PUT", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}", deleteWorkflow).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/hooks/new", handleNewHook).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/hooks/{key}/delete", handleDeleteHook).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/triggers/{key}", handleGetSpecificTrigger).Methods("GET", "OPTIONS") + + // Weird API's for random things + r.HandleFunc("/api/v1/verify_swagger", verifySwagger).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/verify_openapi", verifySwagger).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/get_openapi_uri", echoOpenapiData).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/validate_openapi", validateSwagger).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS") + + r.HandleFunc("/api/v1/execution_cleanup", cleanupExecutions).Methods("GET", "OPTIONS") + + http.Handle("/", r) +} + +// Had to move away from mux, which means Method is fucked up right now. +func main() { + //init() + hostname, err := os.Hostname() + if err != nil { + hostname = "MISSING" + } + + innerPort := os.Getenv("BACKEND_PORT") + if innerPort == "" { + log.Printf("Running on %s:5001", hostname) + log.Fatal(http.ListenAndServe(":5001", nil)) + } else { + log.Printf("Running on %s:%s", hostname, innerPort) + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", innerPort), nil)) + } +} diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go new file mode 100644 index 00000000..11b0aa8f --- /dev/null +++ b/backend/go-app/walkoff.go @@ -0,0 +1,3594 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" + "strings" + "time" + + "cloud.google.com/go/datastore" + "cloud.google.com/go/scheduler/apiv1" + gyaml "github.com/ghodss/yaml" + "github.com/h2non/filetype" + "github.com/satori/go.uuid" + "google.golang.org/api/cloudfunctions/v1" + schedulerpb "google.golang.org/genproto/googleapis/cloud/scheduler/v1" + + "github.com/go-git/go-billy/v5" + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5" + + "github.com/go-git/go-git/v5/storage/memory" + //"github.com/gorilla/websocket" + //"google.golang.org/appengine" + //"google.golang.org/appengine/memcache" + //"cloud.google.com/go/firestore" + // "google.golang.org/api/option" +) + +var localBase = "http://localhost:5001" +var baseEnvironment = "onprem" + +var cloudname = "cloud" + +var defaultLocation = "europe-west2" + +// To test out firestore before potential merge +var shuffleTestProject = "shuffle-test-258209" +var shuffleTestPath = "./shuffle-test-258209-5a2e8d7e508a.json" + +//var upgrader = websocket.Upgrader{ +// ReadBufferSize: 1024, +// WriteBufferSize: 1024, +// CheckOrigin: func(r *http.Request) bool { +// return true +// }, +//} + +type Org struct { + Name string `json:"name"` + Org string `json:"org"` + Users []User `json:"users"` + Id string `json:"id"` +} + +type WorkflowApp struct { + Name string `json:"name" yaml:"name" required:true datastore:"name"` + IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` + ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"` + Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"` + AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"` + Generated bool `json:"generated" yaml:"generated" required:false datastore:"generated"` + Downloaded bool `json:"downloaded" yaml:"downloaded" required:false datastore:"downloaded"` + Sharing bool `json:"sharing" yaml:"sharing" required:false datastore:"sharing"` + Verified bool `json:"verified" yaml:"verified" required:false datastore:"verified"` + Tested bool `json:"tested" yaml:"tested" required:false datastore:"tested"` + Owner string `json:"owner" datastore:"owner" yaml:"owner"` + PrivateID string `json:"private_id" yaml:"private_id" required:false datastore:"private_id"` + Description string `json:"description" datastore:"description" required:false yaml:"description"` + Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"` + SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"` + LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false` + ContactInfo struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Url string `json:"url" datastore:"url" yaml:"url"` + } `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false` + Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"` + Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"` +} + +type WorkflowAppActionParameter struct { + Description string `json:"description" datastore:"description" yaml:"description"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Name string `json:"name" datastore:"name" yaml:"name"` + Example string `json:"example" datastore:"example" yaml:"example"` + Value string `json:"value" datastore:"value" yaml:"value,omitempty"` + Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` + ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"` + Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"` + Required bool `json:"required" datastore:"required" yaml:"required"` + Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"` +} + +type SchemaDefinition struct { + Type string `json:"type" datastore:"type"` +} + +type WorkflowAppAction struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Name string `json:"name" datastore:"name"` + NodeType string `json:"node_type" datastore:"node_type"` + Environment string `json:"environment" datastore:"environment"` + Sharing bool `json:"sharing" datastore:"sharing"` + PrivateID string `json:"private_id" datastore:"private_id"` + AppID string `json:"app_id" datastore:"app_id"` + Authentication []AuthenticationStore `json:"authentication" datastore:"authentication" yaml:"authentication,omitempty"` + Tested bool `json:"tested" datastore:"tested" yaml:"tested"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"` + Returns struct { + Description string `json:"description" datastore:"returns" yaml:"description,omitempty"` + ID string `json:"id" datastore:"id" yaml:"id,omitempty"` + Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"` + } `json:"returns" datastore:"returns"` +} + +// FIXME: Generate a callback authentication ID? +type WorkflowExecution struct { + Type string `json:"type" datastore:"type"` + Status string `json:"status" datastore:"status"` + Start string `json:"start" datastore:"start"` + ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"` + ExecutionId string `json:"execution_id" datastore:"execution_id"` + WorkflowId string `json:"workflow_id" datastore:"workflow_id"` + LastNode string `json:"last_node" datastore:"last_node"` + Authorization string `json:"authorization" datastore:"authorization"` + Result string `json:"result" datastore:"result,noindex"` + StartedAt int64 `json:"started_at" datastore:"started_at"` + CompletedAt int64 `json:"completed_at" datastore:"completed_at"` + ProjectId string `json:"project_id" datastore:"project_id"` + Locations []string `json:"locations" datastore:"locations"` + Workflow Workflow `json:"workflow" datastore:"workflow,noindex"` + Results []ActionResult `json:"results" datastore:"results,noindex"` +} + +// Added environment for location to execute +type Action struct { + AppName string `json:"app_name" datastore:"app_name"` + AppVersion string `json:"app_version" datastore:"app_version"` + AppID string `json:"app_id" datastore:"app_id"` + Errors []string `json:"errors" datastore:"errors"` + ID string `json:"id" datastore:"id"` + IsValid bool `json:"is_valid" datastore:"is_valid"` + IsStartNode bool `json:"isStartNode" datastore:"isStartNode"` + Sharing bool `json:"sharing" datastore:"sharing"` + PrivateID string `json:"private_id" datastore:"private_id"` + Label string `json:"label" datastore:"label"` + SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"` + LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false` + Environment string `json:"environment" datastore:"environment"` + Name string `json:"name" datastore:"name"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"` + Position struct { + X float64 `json:"x" datastore:"x"` + Y float64 `json:"y" datastore:"y"` + } `json:"position"` + Priority int `json:"priority" datastore:"priority"` +} + +// Added environment for location to execute +type Trigger struct { + AppName string `json:"app_name" datastore:"app_name"` + Status string `json:"status" datastore:"status"` + AppVersion string `json:"app_version" datastore:"app_version"` + Errors []string `json:"errors" datastore:"errors"` + ID string `json:"id" datastore:"id"` + IsValid bool `json:"is_valid" datastore:"is_valid"` + IsStartNode bool `json:"isStartNode" datastore:"isStartNode"` + Label string `json:"label" datastore:"label"` + SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"` + LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false` + Environment string `json:"environment" datastore:"environment"` + TriggerType string `json:"trigger_type" datastore:"trigger_type"` + Name string `json:"name" datastore:"name"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"` + Position struct { + X float64 `json:"x" datastore:"x"` + Y float64 `json:"y" datastore:"y"` + } `json:"position"` + Priority int `json:"priority" datastore:"priority"` +} + +type Branch struct { + DestinationID string `json:"destination_id" datastore:"destination_id"` + ID string `json:"id" datastore:"id"` + SourceID string `json:"source_id" datastore:"source_id"` + Label string `json:"label" datastore:"label"` + HasError bool `json:"has_errors" datastore: "has_errors"` + Conditions []Condition `json:"conditions" datastore: "conditions"` +} + +// Same format for a lot of stuff +type Condition struct { + Condition WorkflowAppActionParameter `json:"condition" datastore:"condition"` + Source WorkflowAppActionParameter `json:"source" datastore:"source"` + Destination WorkflowAppActionParameter `json:"destination" datastore:"destination"` +} + +type Schedule struct { + Name string `json:"name" datastore:"name"` + Frequency string `json:"frequency" datastore:"frequency"` + ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"` + Id string `json:"id" datastore:"id"` +} + +type Workflow struct { + Actions []Action `json:"actions" datastore:"actions,noindex"` + Branches []Branch `json:"branches" datastore:"branches,noindex"` + Triggers []Trigger `json:"triggers" datastore:"triggers,noindex"` + Schedules []Schedule `json:"schedules" datastore:"schedules,noindex"` + Errors []string `json:"errors,omitempty" datastore:"errors"` + Tags []string `json:"tags,omitempty" datastore:"tags"` + ID string `json:"id" datastore:"id"` + IsValid bool `json:"is_valid" datastore:"is_valid"` + Name string `json:"name" datastore:"name"` + Description string `json:"description" datastore:"description"` + Start string `json:"start" datastore:"start"` + Owner string `json:"owner" datastore:"owner"` + Sharing string `json:"sharing" datastore:"sharing"` + Org []Org `json:"org,omitempty" datastore:"org"` + ExecutingOrg Org `json:"execution_org,omitempty" datastore:"execution_org"` + WorkflowVariables []struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id"` + Name string `json:"name" datastore:"name"` + Value string `json:"value" datastore:"value"` + } `json:"workflow_variables" datastore:"workflow_variables"` +} + +type ActionResult struct { + Action Action `json:"action" datastore:"action"` + ExecutionId string `json:"execution_id" datastore:"execution_id"` + Authorization string `json:"authorization" datastore:"authorization"` + Result string `json:"result" datastore:"result,noindex"` + StartedAt int64 `json:"started_at" datastore:"started_at"` + CompletedAt int64 `json:"completed_at" datastore:"completed_at"` + Status string `json:"status" datastore:"status"` +} + +type Authentication struct { + Required bool `json:"required" datastore:"required" yaml:"required" ` + Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"` +} + +type AuthenticationParams struct { + Description string `json:"description" datastore:"description" yaml:"description"` + ID string `json:"id" datastore:"id" yaml:"id"` + Name string `json:"name" datastore:"name" yaml:"name"` + Example string `json:"example" datastore:"example" yaml:"example"` + Value string `json:"value,omitempty" datastore:"value" yaml:"value"` + Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` + Required bool `json:"required" datastore:"required" yaml:"required"` + In string `json:"in" datastore:"in" yaml:"in"` + Scheme string `json:"scheme" datastore:"scheme" yaml:"scheme"` +} + +type AuthenticationStore struct { + Key string `json:"key" datastore:"key"` + Value string `json:"value" datastore:"value"` +} + +type ExecutionRequestWrapper struct { + Data []ExecutionRequest `json:"data"` +} + +func setWorkflowQueue(ctx context.Context, executionRequests ExecutionRequestWrapper, id string) error { + key := datastore.NameKey("workflowqueue", id, nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key, &executionRequests); err != nil { + log.Printf("Error adding workflow queue: %s", err) + return err + } + + return nil +} + +func getWorkflowQueue(ctx context.Context, id string) (ExecutionRequestWrapper, error) { + key := datastore.NameKey("workflowqueue", id, nil) + workflows := ExecutionRequestWrapper{} + if err := dbclient.Get(ctx, key, &workflows); err != nil { + return ExecutionRequestWrapper{}, err + } + + return workflows, nil +} + +//func setWorkflowqueuetest(id string) { +// data := ExecutionRequestWrapper{ +// Data: []ExecutionRequest{ +// ExecutionRequest{ +// ExecutionId: "2349bf96-51ad-68d2-5ca6-75ef8f7ee814", +// WorkflowId: "8e344a2e-db51-448f-804c-eb959a32c139", +// Authorization: "wut", +// }, +// }, +// } +// +// err := setWorkflowQueue(data, id) +// if err != nil { +// log.Printf("Fail: %s", err) +// } +//} + +// Frequency = cronjob OR minutes between execution +func createSchedule(ctx context.Context, scheduleId, workflowId, name, frequency string, body []byte) error { + c, err := scheduler.NewCloudSchedulerClient(ctx) + if err != nil { + log.Printf("%s", err) + return err + } + + testSplit := strings.Split(frequency, "*") + log.Println(len(testSplit)) + cronJob := "" + if len(testSplit) > 5 { + cronJob = frequency + } else { + newfrequency, err := strconv.Atoi(frequency) + if err != nil { + return err + } + + _ = newfrequency + + //if int(newfrequency) < 60 { + // cronJob = fmt.Sprintf("*/%s * * * *") + //} else if int(newfrequency) < + log.Println("FIXME: SHOULD DO Frequency (minutes) to CRON") + } + + if len(cronJob) == 0 { + return errors.New("cronJob isn't formatted correctly") + } + + req := &schedulerpb.CreateJobRequest{ + Parent: fmt.Sprintf("projects/%s/locations/europe-west2", gceProject), + Job: &schedulerpb.Job{ + Name: fmt.Sprintf("projects/%s/locations/europe-west2/jobs/schedule_%s", gceProject, scheduleId), + Schedule: cronJob, + Description: name, + Target: &schedulerpb.Job_HttpTarget{ + HttpTarget: &schedulerpb.HttpTarget{ + Uri: fmt.Sprintf("https://shuffler.io/api/v1/workflows/%s/execute", workflowId), + HttpMethod: 1, + Headers: map[string]string{ + "Authorization": "", + }, + Body: body, + }, + }, + }, + // TODO: Fill request struct fields. + } + resp, err := c.CreateJob(ctx, req) + if err != nil { + log.Printf("%s", err) + return err + } + _ = resp + + return nil +} + +func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + id := request.Header.Get("Org-Id") + if len(id) == 0 { + log.Printf("No Org-Id header set - confirm") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Specify the org-id header."}`))) + return + } + + //setWorkflowqueuetest(id) + ctx := context.Background() + executionRequests, err := getWorkflowQueue(ctx, id) + if err != nil { + log.Printf("(1) Failed reading body for workflowqueue: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Entity parsing error - confirm"}`))) + return + } + + if len(executionRequests.Data) == 0 { + log.Printf("No requests to fix. Why did this request occur?") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Some error"}`))) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("Failed reading body for stream result queue") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // Getting from the request + //log.Println(string(body)) + var removeExecutionRequests ExecutionRequestWrapper + err = json.Unmarshal(body, &removeExecutionRequests) + if err != nil { + log.Printf("Failed executionrequest in queue unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + if len(removeExecutionRequests.Data) == 0 { + log.Printf("No requests to fix remove") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Some removal error"}`))) + return + } + + // remove items from DB + var newExecutionRequests ExecutionRequestWrapper + for _, execution := range executionRequests.Data { + found := false + for _, removeExecution := range removeExecutionRequests.Data { + if removeExecution.ExecutionId == execution.ExecutionId && removeExecution.WorkflowId == execution.WorkflowId { + found = true + break + } + } + + if !found { + newExecutionRequests.Data = append(newExecutionRequests.Data, execution) + } + } + + // Push only the remaining to the DB (remove) + if len(executionRequests.Data) != len(newExecutionRequests.Data) { + err := setWorkflowQueue(ctx, newExecutionRequests, id) + if err != nil { + log.Printf("Fail: %s", err) + } + } + + //newjson, err := json.Marshal(removeExecutionRequests) + //if err != nil { + // resp.WriteHeader(401) + // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`))) + // return + //} + + resp.WriteHeader(200) + resp.Write([]byte("OK")) +} + +func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + id := request.Header.Get("Org-Id") + if len(id) == 0 { + log.Printf("No org-id header set") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Specify the org-id header."}`))) + return + } + + ctx := context.Background() + executionRequests, err := getWorkflowQueue(ctx, id) + if err != nil { + // Skipping as this comes up over and over + //log.Printf("(2) Failed reading body for workflowqueue: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + if len(executionRequests.Data) == 0 { + executionRequests.Data = []ExecutionRequest{} + } + + newjson, err := json.Marshal(executionRequests) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newjson) +} + +func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("Failed reading body for stream result queue") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + var actionResult ActionResult + err = json.Unmarshal(body, &actionResult) + if err != nil { + log.Printf("Failed ActionResult unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + ctx := context.Background() + workflowExecution, err := getWorkflowExecution(ctx, actionResult.ExecutionId) + if err != nil { + log.Printf("Failed getting execution (streamresult) %s: %s", actionResult.ExecutionId, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`))) + return + } + + // Authorization is done here + if workflowExecution.Authorization != actionResult.Authorization { + log.Printf("Bad authorization key when getting stream results %s.", actionResult.ExecutionId) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`))) + return + } + + //for _, action := range workflowExecution.Workflow.Actions { + // log.Printf("Name: %s, Env: %s", action.Name, action.Environment) + //} + + newjson, err := json.Marshal(workflowExecution) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newjson) + +} + +func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Println("(3) Failed reading body for workflowqueue") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + var actionResult ActionResult + err = json.Unmarshal(body, &actionResult) + if err != nil { + log.Printf("Failed ActionResult unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // 1. Get the WorkflowExecution(ExecutionId) from the database + // 2. if ActionResult.Authentication != WorkflowExecution.Authentication -> exit + // 3. Add to and update actionResult in workflowExecution + // 4. Push to db + // IF FAIL: Set executionstatus: abort or cancel + + ctx := context.Background() + workflowExecution, err := getWorkflowExecution(ctx, actionResult.ExecutionId) + if err != nil { + log.Printf("Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist."}`, actionResult.ExecutionId))) + return + } + + if workflowExecution.Authorization != actionResult.Authorization { + log.Printf("Bad authorization key when updating node (workflowQueue) %s. Want: %s, Have: %s", actionResult.ExecutionId, workflowExecution.Authorization, actionResult.Authorization) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key"}`))) + return + } + + if workflowExecution.Status == "FINISHED" { + log.Printf("Workflowexecution is already FINISHED. No further action can be taken") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because of %s with status %s"}`, workflowExecution.LastNode, workflowExecution.Status))) + return + } + + // Not sure what's up here + // FIXME - remove comment + if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { + log.Printf("Workflowexecution is already aborted. No further action can be taken") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status))) + return + } + + if actionResult.Status == "ABORTED" || actionResult.Status == "FAILURE" { + log.Printf("Actionresult is %s. Should set workflowExecution and exit all running functions", actionResult.Status) + workflowExecution.Status = actionResult.Status + workflowExecution.LastNode = actionResult.Action.ID + + // Cleans up aborted, and always gives a result + lastResult := "" + newResults := []ActionResult{} + // type ActionResult struct { + for _, result := range workflowExecution.Results { + if result.Status == "EXECUTING" { + result.Status = actionResult.Status + result.Result = "Aborted because of an unknown error" + } + + if len(result.Result) > 0 { + lastResult = result.Result + } + + newResults = append(newResults, result) + } + + workflowExecution.Result = lastResult + workflowExecution.Results = newResults + } + + // This means it should continue I think :) + if actionResult.Status == "SKIPPED" { + // How the fuck do I do this tho + // Parse _all_ children of the skipped and add them to "finished" + // + log.Printf("Find out how to handle skipped items, as there might be more branches to continue anyway") + // FIXME - simulate that every subnode is skipped + // Check worker, as it contains this code + // Children of children of children... + // Recurse, woo + //for _, item := range children { + + //} + } + + // FIXME rebuild to be like this or something + // workflowExecution/ExecutionId/Nodes/NodeId + // Find the appropriate action + if len(workflowExecution.Results) > 0 { + // FIXME + found := false + outerindex := 0 + for index, item := range workflowExecution.Results { + if item.Action.ID == actionResult.Action.ID { + found = true + outerindex = index + break + } + } + + if found { + // FIXME - this is broken, but why + //if workflowExecution.Results[outerindex].Status == actionResult.Status { + // log.Printf("Status of %s is already %s", actionResult.Action.ID, actionResult.Status) + // resp.WriteHeader(401) + // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Status of %s is already %s"}`, actionResult.Action.ID, actionResult.Status))) + // return + //} + + log.Printf("Updating %s in %s from %s to %s", actionResult.Action.ID, workflowExecution.ExecutionId, workflowExecution.Results[outerindex].Status, actionResult.Status) + workflowExecution.Results[outerindex] = actionResult + } else { + log.Printf("Setting value of %s in %s to %s", actionResult.Action.ID, workflowExecution.ExecutionId, actionResult.Status) + workflowExecution.Results = append(workflowExecution.Results, actionResult) + } + } else { + log.Printf("Setting value of %s in %s to %s", actionResult.Action.ID, workflowExecution.ExecutionId, actionResult.Status) + workflowExecution.Results = append(workflowExecution.Results, actionResult) + } + + extraInputs := 0 + for _, result := range workflowExecution.Results { + if result.Action.Name == "User Input" && result.Action.AppName == "User Input" { + extraInputs += 1 + } + } + + log.Printf("Checking results %d vs %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extraInputs) + if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions)+extraInputs { + finished := true + for _, result := range workflowExecution.Results { + if result.Status != "SUCCESS" && result.Status != "FINISHED" { + finished = false + break + } + } + + if finished { + log.Printf("Execution of %s finished.", workflowExecution.ExecutionId) + //log.Println("Might be finished based on length of results and everything being SUCCESS or FINISHED - VERIFY THIS. Setting status to finished.") + workflowExecution.Status = "FINISHED" + workflowExecution.CompletedAt = int64(time.Now().Unix()) + if workflowExecution.LastNode == "" { + workflowExecution.LastNode = actionResult.Action.ID + } + } + } + + // FIXME - why isn't this how it works otherwise, wtf? + //workflow, err := getWorkflow(workflowExecution.Workflow.ID) + //newActions := []Action{} + //for _, action := range workflowExecution.Workflow.Actions { + // log.Printf("Name: %s, Env: %s", action.Name, action.Environment) + //} + + err = setWorkflowExecution(ctx, *workflowExecution) + if err != nil { + log.Printf("Error saving workflow execution actionresult setting: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult: %s"}`, err))) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +func getWorkflows(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in getworkflows: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + //memcacheName := fmt.Sprintf("%s_workflows", user.Username) + ctx := context.Background() + //if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss { + // // Not in cache + // //log.Printf("Workflows not in cache.") + //} else if err != nil { + // log.Printf("Error getting item: %v", err) + //} else { + // // FIXME - verify if value is ok? Can unmarshal etc. + // resp.WriteHeader(200) + // resp.Write(item.Value) + // return + //} + + // With user, do a search for workflows with user or user's org attached + q := datastore.NewQuery("workflow").Filter("owner =", user.Id) + var workflows []Workflow + _, err = dbclient.GetAll(ctx, q, &workflows) + if err != nil { + log.Printf("Failed getting workflows for user %s: %s", user.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(workflows) == 0 { + resp.WriteHeader(200) + resp.Write([]byte("[]")) + return + } + + newjson, err := json.Marshal(workflows) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflows"}`))) + return + } + + //item := &memcache.Item{ + // Key: memcacheName, + // Value: newjson, + // Expiration: time.Minute * 10, + //} + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("Error adding item: %v", err) + //} else { + // //log.Printf("Set cache for %s", item.Key) + //} + + resp.WriteHeader(200) + resp.Write(newjson) +} + +// FIXME - add to actual database etc +func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Error with body read: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + var workflow Workflow + err = json.Unmarshal(body, &workflow) + if err != nil { + log.Printf("Failed unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflow.ID = uuid.NewV4().String() + workflow.Owner = user.Id + workflow.Sharing = "private" + + ctx := context.Background() + err = setWorkflow(ctx, workflow, workflow.ID) + if err != nil { + log.Printf("Failed setting workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name) + + if len(workflow.Actions) == 0 { + workflow.Actions = []Action{} + } + if len(workflow.Branches) == 0 { + workflow.Branches = []Branch{} + } + if len(workflow.Triggers) == 0 { + workflow.Triggers = []Trigger{} + } + if len(workflow.Errors) == 0 { + workflow.Errors = []string{} + } + + newActions := []Action{} + for _, action := range workflow.Actions { + if action.Environment == "" { + //action.Environment = baseEnvironment + action.IsValid = true + } + + newActions = append(newActions, action) + } + + workflow.Actions = newActions + workflow.IsValid = true + + workflowjson, err := json.Marshal(workflow) + if err != nil { + log.Printf("Failed workflow json setting marshalling: %s", err) + resp.WriteHeader(http.StatusInternalServerError) + resp.Write([]byte(`{"success": false}`)) + return + } + + //memcacheName := fmt.Sprintf("%s_workflows", user.Username) + //memcache.Delete(ctx, memcacheName) + + resp.WriteHeader(200) + //log.Println(string(workflowjson)) + resp.Write(workflowjson) +} + +func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in deleting workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID to delete is not valid"}`)) + return + } + + ctx := context.Background() + workflow, err := getWorkflow(ctx, fileId) + if err != nil { + log.Printf("Failed getting the workflow locally: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - have a check for org etc too.. + if user.Id != workflow.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Clean up triggers and executions + for _, item := range workflow.Triggers { + if item.TriggerType == "SCHEDULE" { + err = deleteSchedule(ctx, item.ID) + if err != nil { + log.Printf("Failed to delete schedule: %s", err) + } + } else if item.TriggerType == "WEBHOOK" { + err = removeWebhookFunction(ctx, item.ID) + if err != nil { + log.Printf("Failed to delete webhook: %s", err) + } + } else if item.TriggerType == "EMAIL" { + err = handleOutlookSubRemoval(ctx, workflow.ID, item.ID) + if err != nil { + log.Printf("Failed to delete email sub: %s", err) + } + } + } + + // FIXME - maybe delete workflow executions + log.Printf("Should delete workflow %s", fileId) + err = DeleteKey(ctx, "workflow", fileId) + if err != nil { + log.Printf("Failed deleting key %s", fileId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed deleting key"}`)) + return + } + + //memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) + //memcache.Delete(ctx, memcacheName) + //memcacheName = fmt.Sprintf("%s_workflows", user.Username) + //memcache.Delete(ctx, memcacheName) + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +// FIXME - check whether all nodes has a branch, otherwise go back +func saveWorkflow(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + log.Println("Start") + user, userErr := handleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("Api authentication failed in edit workflow: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Println("PostUser") + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 36 { + log.Printf(`ID %s is not valid`, fileId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID to save is not valid"}`)) + return + } + + // Here to check access rights + ctx := context.Background() + log.Println("GetWorkflow start") + + tmpworkflow := Workflow{} + // memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) + // if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss { + // // Not in cache + // log.Printf("User workflow %s not in cache.", memcacheName) + // tmpworkflow, err = getWorkflow(ctx, fileId) + // if err != nil { + // log.Printf("Failed getting the workflow locally: %s", err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false}`)) + // return + // } + // } else if err != nil { + // log.Printf("Error getting item: %v", err) + // } else { + // log.Printf("Got workflow %s from cache", fileId) + // // FIXME - verify if value is ok? Can unmarshal etc. + // err = json.Unmarshal(item.Value, &tmpworkflow) + // if err != nil { + // log.Printf("Failed unmarshaling allworkflowapps from memcache: %s", err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false}`)) + // return + // } + // } + + log.Println("GetWorkflow end") + + // FIXME - have a check for org etc too.. + if user.Id != tmpworkflow.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s (save)", user.Username, tmpworkflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Hello") + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Failed hook unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Hello2") + var workflow Workflow + err = json.Unmarshal([]byte(body), &workflow) + //log.Printf(string(body)) + if err != nil { + log.Printf("Failed workflow unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - auth and check if they should have access + if fileId != workflow.ID { + log.Printf("Path and request ID are not matching: %s:%s.", fileId, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - this shouldn't be necessary with proper API checks + newActions := []Action{} + allNodes := []string{} + log.Println("Pre") + for _, action := range workflow.Actions { + allNodes = append(allNodes, action.ID) + log.Printf("ENV: %s", action.Environment) + if action.Environment == "" { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "An environment for %s is required"}`, action.Label))) + return + action.IsValid = true + } + newActions = append(newActions, action) + } + + workflow.Actions = newActions + + for _, trigger := range workflow.Triggers { + log.Println("TRIGGERS") + allNodes = append(allNodes, trigger.ID) + } + + if len(workflow.Actions) == 0 { + workflow.Actions = []Action{} + } + if len(workflow.Branches) == 0 { + workflow.Branches = []Branch{} + } + if len(workflow.Triggers) == 0 { + workflow.Triggers = []Trigger{} + } + if len(workflow.Errors) == 0 { + workflow.Errors = []string{} + } + + // FIXME - do actual checks ROFL + // FIXME - minor issues with e.g. hello world and self.console_logger + // Nodechecks + foundNodes := []string{} + for _, node := range allNodes { + for _, branch := range workflow.Branches { + log.Println("branch") + //log.Println(node) + //log.Println(branch.DestinationID) + if node == branch.DestinationID || node == branch.SourceID { + foundNodes = append(foundNodes, node) + break + } + } + } + + // FIXME - append all nodes (actions, triggers etc) to one single array here + if len(foundNodes) != len(allNodes) || len(workflow.Actions) <= 0 { + // This shit takes a few seconds lol + if !workflow.IsValid { + oldworkflow, err := getWorkflow(ctx, fileId) + if err != nil { + log.Printf("Workflow %s doesn't exist - oldworkflow.", fileId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Item already exists."}`)) + return + } + + oldworkflow.IsValid = false + err = setWorkflow(ctx, *oldworkflow, fileId) + if err != nil { + log.Printf("Failed saving workflow to database: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + } + + // FIXME - more checks here - force reload of data or something + //if len(allNodes) == 0 { + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "reason": "Please insert a node"}`)) + // return + //} + + // Allowed with only a start node + //if len(allNodes) != 1 { + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "reason": "There are nodes with no branches"}`)) + // return + //} + } + + // FIXME - might be a sploit to run someone elses app if getAllWorkflowApps + // doesn't check sharing=true + // Have to do it like this to add the user's apps + log.Println("Apps set starting") + workflowApps := []WorkflowApp{} + //memcacheName = "all_apps" + //if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss { + // // Not in cache + // log.Printf("Apps not in cache.") + workflowApps, err = getAllWorkflowApps(ctx) + if err != nil { + log.Printf("Failed getting all workflow apps from database: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + //} else if err != nil { + // log.Printf("Error getting item: %v", err) + //} else { + // // FIXME - verify if value is ok? Can unmarshal etc. + // err = json.Unmarshal(item.Value, &workflowApps) + // if err != nil { + // log.Printf("Failed unmarshaling allworkflowapps from memcache: %s", err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false}`)) + // return + // } + + // if userErr == nil && len(user.PrivateApps) > 0 { + // workflowApps = append(workflowApps, user.PrivateApps...) + // } + //} + + // Started getting the single apps, but if it's weird, this is faster + log.Println("Apps set done") + + // Check every app action and param to see whether they exist + newActions = []Action{} + for _, action := range workflow.Actions { + curapp := WorkflowApp{} + // FIXME - can this work with ONLY AppID? + for _, app := range workflowApps { + if app.ID == action.AppID { + curapp = app + break + } + + if app.Name == action.AppName && app.AppVersion == action.AppVersion { + curapp = app + break + } + } + + // Check to see if the whole app is valid + if curapp.Name != action.AppName { + log.Printf("App %s doesn't exist.", action.AppName) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName))) + return + } + + // Check tosee if the appaction is valid + curappaction := WorkflowAppAction{} + for _, curAction := range curapp.Actions { + if action.Name == curAction.Name { + curappaction = curAction + break + } + log.Println(action.Name, curAction.Name) + } + + // Check to see if the action is valid + if curappaction.Name != action.Name { + log.Printf("Appaction %s doesn't exist.", action.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - check all parameters to see if they're valid + // Includes checking required fields + + newParams := []WorkflowAppActionParameter{} + for _, param := range curappaction.Parameters { + found := false + + // Handles check for parameter exists + value not empty in used fields + for _, actionParam := range action.Parameters { + if actionParam.Name == param.Name { + found = true + + if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true { + log.Printf("Appaction %s with required param '%s' is empty.", action.Name, param.Name) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) + return + + } + + if actionParam.Variant == "" { + actionParam.Variant = "STATIC_VALUE" + } + + newParams = append(newParams, actionParam) + } + } + + // Handles check for required params + if !found && param.Required { + log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + } + + action.Parameters = newParams + newActions = append(newActions, action) + } + + workflow.Actions = newActions + workflow.IsValid = true + + err = setWorkflow(ctx, workflow, fileId) + if err != nil { + log.Printf("Failed saving workflow to database: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + //newbody, err := json.Marshal(workflow) + ////newbody, err := json.Marshal(workflowapps) + //if err != nil { + // log.Printf("Failed unmarshalling all apps: %s", err) + // resp.WriteHeader(401) + // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow apps"}`))) + // return + //} + + //memcacheName = fmt.Sprintf("%s_%s", user.Username, fileId) + //item := &memcache.Item{ + // Key: memcacheName, + // Value: newbody, + // Expiration: time.Minute * 10, + //} + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // //log.Printf("Set cache for %s", item.Key) + //} + + log.Printf("Saved new version of workflow %s", fileId) + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +func getWorkflowLocal(fileId string, request *http.Request) ([]byte, error) { + fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s", localBase, fileId) + client := &http.Client{} + req, err := http.NewRequest( + "GET", + fullUrl, + nil, + ) + + if err != nil { + return []byte{}, err + } + + for key, value := range request.Header { + req.Header.Add(key, strings.Join(value, ";")) + } + + newresp, err := client.Do(req) + if err != nil { + return []byte{}, err + } + + body, err := ioutil.ReadAll(newresp.Body) + if err != nil { + return []byte{}, err + } + + // Temporary solution + if strings.Contains(string(body), "reason") && strings.Contains(string(body), "false") { + return []byte{}, errors.New(fmt.Sprintf("Failed getting workflow %s with message %s", fileId, string(body))) + } + + return body, nil +} + +type ExecutionRequest struct { + ExecutionId string `json:"execution_id"` + ExecutionArgument string `json:"execution_argument"` + WorkflowId string `json:"workflow_id"` + Authorization string `json:"authorization"` + Environments []string `json:"environments"` + Start string `json:"start"` +} + +func abortExecution(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in abort workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID to abort is not valid"}`)) + return + } + + executionId := location[6] + if len(executionId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "ExecutionID not valid"}`)) + return + } + + ctx := context.Background() + workflowExecution, err := getWorkflowExecution(ctx, executionId) + if err != nil { + log.Printf("Failed getting execution (abort) %s: %s", executionId, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist (abort)."}`, executionId))) + return + } + + // FIXME - have a check for org etc too.. + if user.Id != workflowExecution.Workflow.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflowexecution workflow %s", user.Username, workflowExecution.Workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" || workflowExecution.Status == "FINISHED" { + log.Printf("Stopped execution of %s with status %s", executionId, workflowExecution.Status) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Status for %s is %s, which can't be aborted."}`, executionId, workflowExecution.Status))) + return + } + + topic := "workflowexecution" + + workflowExecution.CompletedAt = int64(time.Now().Unix()) + workflowExecution.Status = "ABORTED" + + lastResult := "" + newResults := []ActionResult{} + // type ActionResult struct { + for _, result := range workflowExecution.Results { + if result.Status == "EXECUTING" { + result.Status = "ABORTED" + result.Result = "Aborted because of an unknown error" + } + + if len(result.Result) > 0 { + lastResult = result.Result + } + + newResults = append(newResults, result) + } + + workflowExecution.Results = newResults + if len(workflowExecution.Result) == 0 { + workflowExecution.Result = lastResult + } + + err = setWorkflowExecution(ctx, *workflowExecution) + if err != nil { + log.Printf("Error saving workflow execution for updates when aborting %s: %s", topic, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution status to abort"}`))) + return + } + + // FIXME - allowed to edit it? idk + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) + + // Not sure what's up here + //if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { + // log.Printf("Workflowexecution is already aborted. No further action can be taken") + // resp.WriteHeader(401) + // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status))) + // return + //} +} + +//// New execution with firestore + +func cleanupExecutions(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in execute workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "message": "Not authenticated"}`)) + return + } + + //if user.Role != "admin" { + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "message": "Insufficient permissions"}`)) + // return + //} + + log.Printf("CLEANUP!") + log.Printf("%#v", user) + + ctx := context.Background() + // Removes three months from today + timestamp := int64(time.Now().AddDate(0, -2, 0).Unix()) + log.Println(timestamp) + q := datastore.NewQuery("workflowexecution").Filter("started_at <", timestamp) + var workflowExecutions []WorkflowExecution + _, err = dbclient.GetAll(ctx, q, &workflowExecutions) + if err != nil { + log.Printf("Error getting workflowexec: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting all workflowexecutions"}`))) + return + } + + log.Println(len(workflowExecutions)) + + resp.WriteHeader(200) + resp.Write([]byte("OK")) +} + +func executeWorkflow(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in execute workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID to execute is not valid"}`)) + return + } + + //memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) + var workflow Workflow + ctx := context.Background() + //if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss { + // // Not in cache + // log.Printf("Workflow %s not in cache.", memcacheName) + tmpworkflow, err := getWorkflow(ctx, fileId) + if err != nil { + log.Printf("Failed getting the workflow locally: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflow = *tmpworkflow + //} else if err != nil { + // log.Printf("Error getting item: %v", err) + //} else { + // // FIXME - verify if value is ok? Can unmarshal etc. + // log.Printf("Got workflow %s from cache", fileId) + // err = json.Unmarshal(item.Value, &workflow) + // if err != nil { + // log.Printf("Failed cache unmarshal in executeworkflow for %s", fileId) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false}`)) + // } + //} + + // FIXME - have a check for org etc too.. + // FIXME - admin check like this? idk + if user.Id != workflow.Owner && user.Role != "admin" && user.Role != "scheduler" && user.Role != fmt.Sprintf("workflow_%s", fileId) { + log.Printf("Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(workflow.Actions) == 0 { + workflow.Actions = []Action{} + } + if len(workflow.Branches) == 0 { + workflow.Branches = []Branch{} + } + if len(workflow.Triggers) == 0 { + workflow.Triggers = []Trigger{} + } + if len(workflow.Errors) == 0 { + workflow.Errors = []string{} + } + + if !workflow.IsValid { + log.Printf("Stopped execution as workflow %s is not valid.", workflow.ID) + resp.WriteHeader(403) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "workflow %s is invalid"}`, workflow.ID))) + return + } + + workflowBytes, err := json.Marshal(workflow) + if err != nil { + log.Printf("Failed workflow unmarshal in execution: %s", err) + resp.WriteHeader(http.StatusInternalServerError) + resp.Write([]byte(`{"success": false}`)) + return + } + + //log.Println(workflow) + var workflowExecution WorkflowExecution + err = json.Unmarshal(workflowBytes, &workflowExecution.Workflow) + if err != nil { + log.Printf("Failed execution unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + makeNew := true + if request.Method == "POST" { + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Failed hook unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + var execution ExecutionRequest + err = json.Unmarshal(body, &execution) + if err != nil { + log.Printf("Failed execution POST unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - this should have "execution_argument" from executeWorkflow frontend + if len(execution.ExecutionArgument) > 0 { + workflowExecution.ExecutionArgument = execution.ExecutionArgument + } + + log.Printf("Execution data: %#v", execution) + if len(execution.Start) == 36 { + log.Printf("SHOULD START ON NODE %s", execution.Start) + workflow.Start = execution.Start + } + + if len(execution.ExecutionId) == 36 { + workflowExecution.ExecutionId = execution.ExecutionId + } else { + sessionToken := uuid.NewV4() + workflowExecution.ExecutionId = sessionToken.String() + } + } else { + // Check for parameters of start and ExecutionId + start, startok := request.URL.Query()["start"] + answer, answerok := request.URL.Query()["answer"] + referenceId, referenceok := request.URL.Query()["reference_execution"] + if answerok && referenceok { + // If answer is false, reference execution with result + log.Printf("Answer is OK AND reference is OK!") + if answer[0] == "false" { + log.Printf("Should update reference and return, no need for further execution!") + + // Get the reference execution + oldExecution, err := getWorkflowExecution(ctx, referenceId[0]) + if err != nil { + log.Printf("Failed getting execution (execution) %s: %s", referenceId[0], err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist."}`, referenceId[0]))) + return + } + + if oldExecution.Workflow.ID != fileId { + log.Println("Wrong workflowid!") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad ID %s"}`, referenceId))) + return + } + + newResults := []ActionResult{} + //log.Printf("%#v", oldExecution.Results) + for _, result := range oldExecution.Results { + log.Printf("%s - %s", result.Action.ID, start[0]) + if result.Action.ID == start[0] { + note, noteok := request.URL.Query()["note"] + if noteok { + result.Result = fmt.Sprintf("User note: %s", note[0]) + } else { + result.Result = fmt.Sprintf("User clicked %s", answer[0]) + } + + // Stopping the whole thing + result.CompletedAt = int64(time.Now().Unix()) + result.Status = "ABORTED" + oldExecution.Status = result.Status + oldExecution.Result = result.Result + oldExecution.LastNode = result.Action.ID + } + + newResults = append(newResults, result) + } + + oldExecution.Results = newResults + err = setWorkflowExecution(ctx, *oldExecution) + if err != nil { + log.Printf("Error saving workflow execution actionresult setting: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult in execution: %s"}`, err))) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Updating %s with your information."}`, referenceId[0]))) + return + } + } + + if referenceok { + log.Printf("Handling an old execution continuation!") + // Will use the old name, but still continue with NEW ID + oldExecution, err := getWorkflowExecution(ctx, referenceId[0]) + if err != nil { + log.Printf("Failed getting execution (execution) %s: %s", referenceId[0], err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist."}`, referenceId[0]))) + return + } + + workflowExecution = *oldExecution + } + + if len(workflowExecution.ExecutionId) == 0 { + log.Println("Making new executionId!") + sessionToken := uuid.NewV4() + workflowExecution.ExecutionId = sessionToken.String() + } else { + log.Printf("Using the same executionId as before: %s", workflowExecution.ExecutionId) + makeNew = false + } + + if startok { + log.Printf("Setting start to %s based on query!", start[0]) + workflowExecution.Workflow.Start = start[0] + workflowExecution.Start = start[0] + } + + } + + // FIXME - regex uuid, and check if already exists? + if len(workflowExecution.ExecutionId) != 36 { + log.Printf("Invalid uuid: %s", workflowExecution.ExecutionId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Invalid uuid."}`)) + return + } + + // FIXME - find owner of workflow + // FIXME - get the actual workflow itself and build the request + // MAYBE: Don't send the workflow within the pubsub, as this requires more data to be sent + // Check if a worker already exists for company, else run one with: + // locations, project IDs and subscription names + + // When app is executed: + // Should update with status execution (somewhere), which will trigger the next node + // IF action.type == internal, we need the internal watcher to be running and executing + // This essentially means the WORKER has to be the responsible party for new actions in the INTERNAL landscape + // Results are ALWAYS posted back to cloud@execution_id? + if makeNew { + workflowExecution.Type = "workflow" + //workflowExecution.Stream = "tmp" + //workflowExecution.WorkflowQueue = "tmp" + //workflowExecution.SubscriptionNameNodestream = "testcompany-nodestream" + workflowExecution.ProjectId = gceProject + workflowExecution.Locations = []string{"europe-west2"} + workflowExecution.WorkflowId = workflow.ID + workflowExecution.StartedAt = int64(time.Now().Unix()) + workflowExecution.CompletedAt = 0 + workflowExecution.Authorization = uuid.NewV4().String() + + // Status for the entire workflow. + workflowExecution.Status = "EXECUTING" + } + // Local authorization for this single workflow used in workers. + + // FIXME: Used for cloud + //mappedData, err := json.Marshal(workflowExecution) + //if err != nil { + // log.Printf("Failed workflowexecution marshalling: %s", err) + // resp.WriteHeader(http.StatusInternalServerError) + // resp.Write([]byte(`{"success": false}`)) + // return + //} + + //log.Println(string(mappedData)) + topic := "workflows" + // FIXME - remove this? + newActions := []Action{} + for _, action := range workflowExecution.Workflow.Actions { + action.LargeImage = "" + //log.Println(action.Environment) + + if action.Environment == "" { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Environment is not defined for %s"}`, action.Name))) + return + } + newActions = append(newActions, action) + } + workflowExecution.Workflow.Actions = newActions + + //log.Printf("%#v", workflowExecution.Workflow.Actions) + + // Verification for execution environments + onpremExecution := false + environments := []string{} + for _, action := range workflowExecution.Workflow.Actions { + if action.Environment != cloudname { + found := false + for _, env := range environments { + if env == action.Environment { + found = true + break + } + } + + if !found { + environments = append(environments, action.Environment) + } + + onpremExecution = true + } + } + + err = setWorkflowExecution(ctx, workflowExecution) + if err != nil { + log.Printf("Error saving workflow execution for updates %s: %s", topic, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution"}`))) + return + } + + log.Printf("Environments: %#v", environments) + + // Adds queue for onprem execution + // FIXME - add specifics to executionRequest, e.g. specific environment (can run multi onprem) + if onpremExecution { + // FIXME - tmp name based on future companyname-companyId + for _, environment := range environments { + log.Printf("EXECUTION: %s should execute onprem with execution environment \"%s\"", workflowExecution.ExecutionId, environment) + + executionRequest := ExecutionRequest{ + ExecutionId: workflowExecution.ExecutionId, + WorkflowId: workflowExecution.Workflow.ID, + Authorization: workflowExecution.Authorization, + Environments: environments, + } + + executionRequestWrapper, err := getWorkflowQueue(ctx, environment) + if err != nil { + executionRequestWrapper = ExecutionRequestWrapper{ + Data: []ExecutionRequest{executionRequest}, + } + } else { + executionRequestWrapper.Data = append(executionRequestWrapper.Data, executionRequest) + } + + log.Printf("Execution request: %#v", executionRequest) + + err = setWorkflowQueue(ctx, executionRequestWrapper, environment) + if err != nil { + log.Printf("Failed adding to db: %s", err) + } + } + } + + //body, err := json.Marshal(workflow) + //if err != nil { + // log.Printf("Failed workflow SET marshalling: %s", err) + // resp.WriteHeader(http.StatusInternalServerError) + // resp.Write([]byte(`{"success": false}`)) + // return + //} + + //item := &memcache.Item{ + // Key: memcacheName, + // Value: body, + // Expiration: time.Minute * 10, + //} + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // log.Printf("Set cache for %s", item.Key) + //} + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization))) + return +} + +func stopSchedule(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in schedule workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + var scheduleId string + if location[1] == "api" { + if len(location) <= 6 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + scheduleId = location[6] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID to stop schedule is not valid"}`)) + return + } + + if len(scheduleId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Schedule ID not valid"}`)) + return + } + + ctx := context.Background() + workflow, err := getWorkflow(ctx, fileId) + if err != nil { + log.Printf("Failed getting the workflow locally: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - have a check for org etc too.. + // FIXME - admin check like this? idk + if user.Id != workflow.Owner && user.Role != "admin" && user.Role != "scheduler" { + log.Printf("Wrong user (%s) for workflow %s (stop schedule)", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(workflow.Actions) == 0 { + workflow.Actions = []Action{} + } + if len(workflow.Branches) == 0 { + workflow.Branches = []Branch{} + } + if len(workflow.Triggers) == 0 { + workflow.Triggers = []Trigger{} + } + if len(workflow.Errors) == 0 { + workflow.Errors = []string{} + } + + err = deleteSchedule(ctx, scheduleId) + if err != nil { + if strings.Contains(err.Error(), "Job not found") { + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) + } else { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed stopping schedule"}`))) + } + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) + return +} + +func deleteSchedule(ctx context.Context, id string) error { + c, err := scheduler.NewCloudSchedulerClient(ctx) + if err != nil { + log.Printf("%s", err) + return err + } + + req := &schedulerpb.DeleteJobRequest{ + Name: fmt.Sprintf("projects/%s/locations/europe-west2/jobs/schedule_%s", gceProject, id), + } + + err = c.DeleteJob(ctx, req) + if err != nil { + log.Printf("%s", err) + return err + } + + return nil +} + +func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in schedule workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID to start schedule is not valid"}`)) + return + } + + ctx := context.Background() + workflow, err := getWorkflow(ctx, fileId) + if err != nil { + log.Printf("Failed getting the workflow locally: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - have a check for org etc too.. + // FIXME - admin check like this? idk + if user.Id != workflow.Owner && user.Role != "admin" && user.Role != "scheduler" { + log.Printf("Wrong user (%s) for workflow %s", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(workflow.Actions) == 0 { + workflow.Actions = []Action{} + } + if len(workflow.Branches) == 0 { + workflow.Branches = []Branch{} + } + if len(workflow.Triggers) == 0 { + workflow.Triggers = []Trigger{} + } + if len(workflow.Errors) == 0 { + workflow.Errors = []string{} + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Failed hook unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + var schedule Schedule + err = json.Unmarshal(body, &schedule) + if err != nil { + log.Printf("Failed schedule POST unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(schedule.Id) != 36 { + log.Printf("ID length is not 36 for schedule: %s", err) + resp.WriteHeader(http.StatusInternalServerError) + resp.Write([]byte(`{"success": false, "reason": "Invalid data"}`)) + return + } + + if len(schedule.Name) == 0 { + log.Printf("Empty name.") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Schedule name can't be empty"}`)) + return + } + + if len(schedule.Frequency) == 0 { + log.Printf("Empty frequency.") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Frequency can't be empty"}`)) + return + } + + type tmp struct { + ExecutionArgument string `json:"execution_argument"` + } + + var tmpArg tmp + tmpArg.ExecutionArgument = schedule.ExecutionArgument + scheduleArg, err := json.Marshal(tmpArg) + if err != nil { + log.Printf("Failed scheduleArg marshal: %s", err) + resp.WriteHeader(http.StatusInternalServerError) + resp.Write([]byte(`{"success": false}`)) + return + } + + err = createSchedule( + ctx, + schedule.Id, + workflow.ID, + schedule.Name, + schedule.Frequency, + scheduleArg, + ) + + // FIXME - real error message lol + if err != nil { + log.Printf("Failed creating schedule: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid argument. Try cron */15 * * * *"}`))) + return + } + + workflow.Schedules = append(workflow.Schedules, schedule) + err = setWorkflow(ctx, *workflow, workflow.ID) + if err != nil { + log.Printf("Failed setting workflow for schedule: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) + return +} + +// FIXME - add to actual database etc +func getSpecificWorkflow(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in getting specific workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if strings.Contains(fileId, "?") { + fileId = strings.Split(fileId, "?")[0] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID when getting workflow is not valid"}`)) + return + } + + ctx := context.Background() + //memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) + //if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss { + // // Not in cache + // log.Printf("User %s not in cache.", memcacheName) + //} else if err != nil { + // log.Printf("Error getting item: %v", err) + //} else { + // log.Printf("Got workflow %s from cache", fileId) + // // FIXME - verify if value is ok? Can unmarshal etc. + // resp.WriteHeader(200) + // resp.Write(item.Value) + // return + //} + + workflow, err := getWorkflow(ctx, fileId) + if err != nil { + log.Printf("Workflow %s doesn't exist.", fileId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Item already exists."}`)) + return + } + + // CHECK orgs of user, or if user is owner + // FIXME - add org check too, and not just owner + // Check workflow.Sharing == private / public / org too + if user.Id != workflow.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s (get workflow)", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(workflow.Actions) == 0 { + workflow.Actions = []Action{} + } + if len(workflow.Branches) == 0 { + workflow.Branches = []Branch{} + } + if len(workflow.Triggers) == 0 { + workflow.Triggers = []Trigger{} + } + if len(workflow.Errors) == 0 { + workflow.Errors = []string{} + } + + // Only required for individuals I think + //newactions := []Action{} + //for _, item := range workflow.Actions { + // item.LargeImage = "" + // item.SmallImage = "" + // newactions = append(newactions, item) + //} + //workflow.Actions = newactions + + //newtriggers := []Trigger{} + //for _, item := range workflow.Triggers { + // item.LargeImage = "" + // newtriggers = append(newtriggers, item) + //} + //workflow.Triggers = newtriggers + + body, err := json.Marshal(workflow) + if err != nil { + log.Printf("Failed workflow GET marshalling: %s", err) + resp.WriteHeader(http.StatusInternalServerError) + resp.Write([]byte(`{"success": false}`)) + return + } + + //item := &memcache.Item{ + // Key: memcacheName, + // Value: body, + // Expiration: time.Minute * 60, + //} + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // //log.Printf("Set cache for %s", item.Key) + //} + + resp.WriteHeader(200) + resp.Write(body) +} + +//func setWorkflowExecutionFS(ctx context.Context, reference string, workflowExecution WorkflowExecution) error { +// if len(workflowExecution.ExecutionId) == 0 { +// log.Printf("Workflowexeciton executionId can't be empty.") +// return errors.New("ExecutionId can't be empty.") +// } +// +// firestoreClient, err := firestore.NewClient(ctx, shuffleTestProject, option.WithCredentialsFile(shuffleTestPath)) +// if err != nil { +// return err +// } +// +// executionRef := firestoreClient.Doc(reference) +// _, err = executionRef.Set(ctx, workflowExecution) +// if err != nil { +// return err +// } +// +// return nil +//} + +func setWorkflowExecution(ctx context.Context, workflowExecution WorkflowExecution) error { + if len(workflowExecution.ExecutionId) == 0 { + log.Printf("Workflowexeciton executionId can't be empty.") + return errors.New("ExecutionId can't be empty.") + } + + key := datastore.NameKey("workflowexecution", workflowExecution.ExecutionId, nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key, &workflowExecution); err != nil { + log.Printf("Error adding workflow_execution: %s", err) + return err + } + + return nil +} + +func getWorkflowExecution(ctx context.Context, id string) (*WorkflowExecution, error) { + key := datastore.NameKey("workflowexecution", strings.ToLower(id), nil) + workflowExecution := &WorkflowExecution{} + if err := dbclient.Get(ctx, key, workflowExecution); err != nil { + return &WorkflowExecution{}, err + } + + return workflowExecution, nil +} + +func getApp(ctx context.Context, id string) (*WorkflowApp, error) { + key := datastore.NameKey("workflowapp", strings.ToLower(id), nil) + workflowApp := &WorkflowApp{} + if err := dbclient.Get(ctx, key, workflowApp); err != nil { + return &WorkflowApp{}, err + } + + return workflowApp, nil +} + +func getWorkflow(ctx context.Context, id string) (*Workflow, error) { + key := datastore.NameKey("workflow", strings.ToLower(id), nil) + workflow := &Workflow{} + if err := dbclient.Get(ctx, key, workflow); err != nil { + return &Workflow{}, err + } + + return workflow, nil +} + +func getAllWorkflows(ctx context.Context) ([]Workflow, error) { + var allworkflows []Workflow + q := datastore.NewQuery("workflow") + + _, err := dbclient.GetAll(ctx, q, &allworkflows) + if err != nil { + return []Workflow{}, err + } + + return allworkflows, nil +} + +// Hmm, so I guess this should use uuid :( +// Consistency PLX +func setWorkflow(ctx context.Context, workflow Workflow, id string) error { + key := datastore.NameKey("workflow", id, nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key, &workflow); err != nil { + log.Printf("Error adding workflow: %s", err) + return err + } + + return nil +} + +func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, userErr := handleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("Api authentication failed in edit workflow: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + log.Printf("%#v", location) + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + ctx := context.Background() + log.Printf("ID: %s", fileId) + app, err := getApp(ctx, fileId) + if err != nil { + log.Printf("Error getting app %s: %s", app.Name, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - check whether it's in use and maybe restrict again for later? + // FIXME - actually delete other than private apps too.. + if app.Downloaded { + log.Printf("Deleting downloaded app (anyone can do this)") + } else if user.Id != app.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for app %s (delete)", user.Username, app.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Not really deleting it, just removing from user cache + var privateApps []WorkflowApp + for _, item := range user.PrivateApps { + log.Println(item.ID, fileId) + if item.ID == fileId { + continue + } + + privateApps = append(privateApps, item) + } + + user.PrivateApps = privateApps + err = setUser(ctx, &user) + if err != nil { + log.Printf("Failed removing %s app for user %s: %s", app.Name, user.Username, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": true"}`))) + return + } + + // Delete memcache for user + // Check cookie + c, err := request.Cookie("session_token") + if err != nil { + log.Printf("User doesn't have sessiontoken on pw change: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "You're not logged in."}`))) + return + } + sessionToken := c.Value + session, err := getSession(ctx, sessionToken) + if err != nil { + log.Printf("Session %s doesn't exist: %s", session.Session, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "You're not logged in"}`)) + return + } + + //err = memcache.Delete(request.Context(), sessionToken) + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +func getWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, userErr := handleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("Api authentication failed in edit workflow: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + log.Printf("%#v", location) + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + ctx := context.Background() + app, err := getApp(ctx, fileId) + if err != nil { + log.Printf("Error getting app: %s", app.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Id != app.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for app %s", user.Username, app.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + parsedApi, err := getOpenApiDatastore(ctx, fileId) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + parsedApi.Success = true + data, err := json.Marshal(parsedApi) + if err != nil { + resp.WriteHeader(422) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed marshalling new parsed swagger: %s"}`, err))) + return + } + + resp.WriteHeader(200) + resp.Write(data) +} + +func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // FIXME - set this to be per user IF logged in, as there might exist private and public + //memcacheName := "all_apps" + + ctx := context.Background() + // Just need to be logged in + // FIXME - need to be logged in? + user, userErr := handleApiAuthentication(resp, request) + _ = userErr + + //if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss { + // // Not in cache + // log.Printf("Apps not in cache.") + //} else if err != nil { + // log.Printf("Error getting item: %v", err) + //} else { + // // FIXME - verify if value is ok? Can unmarshal etc. + // allApps := item.Value + + // if userErr == nil && len(user.PrivateApps) > 0 { + // var parsedApps []WorkflowApp + // err = json.Unmarshal(allApps, &parsedApps) + // if err == nil { + // log.Printf("Shouldve added %d apps", len(user.PrivateApps)) + // user.PrivateApps = append(user.PrivateApps, parsedApps...) + + // tmpApps, err := json.Marshal(user.PrivateApps) + // if err == nil { + // allApps = tmpApps + // } + // } + // } + + // resp.WriteHeader(200) + // resp.Write(allApps) + // return + //} + + workflowapps, err := getAllWorkflowApps(ctx) + if err != nil { + log.Printf("Failed getting apps: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + //log.Printf("Length: %d", len(workflowapps)) + + // FIXME - this is really garbage, but is here to protect again null values etc. + newapps := []WorkflowApp{} + baseApps := []WorkflowApp{} + + if len(user.PrivateApps) > 0 { + newapps = append(newapps, user.PrivateApps...) + } + + for _, workflowapp := range workflowapps { + if !workflowapp.Sharing { + continue + } + + //workflowapp.Environment = "cloud" + newactions := []WorkflowAppAction{} + for _, action := range workflowapp.Actions { + //action.Environment = workflowapp.Environment + if len(action.Parameters) == 0 { + action.Parameters = []WorkflowAppActionParameter{} + } + + newactions = append(newactions, action) + } + + workflowapp.Actions = newactions + newapps = append(newapps, workflowapp) + baseApps = append(baseApps, workflowapp) + } + + // Double unmarshal because of user apps + newbody, err := json.Marshal(newapps) + //newbody, err := json.Marshal(workflowapps) + if err != nil { + log.Printf("Failed unmarshalling all newapps: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow apps"}`))) + return + } + + //basebody, err := json.Marshal(baseApps) + ////newbody, err := json.Marshal(workflowapps) + //if err != nil { + // log.Printf("Failed unmarshalling all baseapps: %s", err) + // resp.WriteHeader(401) + // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow apps"}`))) + // return + //} + + // Refreshed every hour + //item := &memcache.Item{ + // Key: memcacheName, + // Value: basebody, + // Expiration: time.Minute * 60, + //} + //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { + // if err := memcache.Set(ctx, item); err != nil { + // log.Printf("Error setting item: %v", err) + // } + //} else if err != nil { + // log.Printf("error adding item: %v", err) + //} else { + // log.Printf("Set cache for %s", item.Key) + //} + + //log.Println(string(body)) + //log.Println(string(newbody)) + resp.WriteHeader(200) + resp.Write(newbody) +} + +// Bad check for workflowapps :) +// FIXME - use tags and struct reflection +func checkWorkflowApp(workflowApp WorkflowApp) error { + // Validate fields + if workflowApp.Name == "" { + return errors.New("App field name doesn't exist") + } + + if workflowApp.Description == "" { + return errors.New("App field description doesn't exist") + } + + if workflowApp.AppVersion == "" { + return errors.New("App field app_version doesn't exist") + } + + if workflowApp.ContactInfo.Name == "" { + return errors.New("App field contact_info.name doesn't exist") + } + + return nil +} + +func handleGetfile(resp http.ResponseWriter, request *http.Request) ([]byte, error) { + // Upload file here first + request.ParseMultipartForm(32 << 20) + file, _, err := request.FormFile("file") + if err != nil { + log.Printf("Error parsing: %s", err) + return []byte{}, err + } + defer file.Close() + + buf := bytes.NewBuffer(nil) + if _, err := io.Copy(buf, file); err != nil { + return []byte{}, err + } + + return buf.Bytes(), nil +} + +func validateAppInput(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Just need to be logged in + // FIXME - should have some permissions? + _, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new app: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + filebytes, err := handleGetfile(resp, request) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + kind, err := filetype.Match(filebytes) + if err != nil { + log.Printf("Failed parsing filetype") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + //fmt.Printf("File type: %s. MIME: %s\n", kind.Extension, kind.MIME.Value) + if kind == filetype.Unknown { + fmt.Println("Unknown file type") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if kind.MIME.Value != "application/zip" { + fmt.Println("Not zip, can't unzip") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - validate folderstructure, Dockerfile, python scripts, api.yaml, requirements.txt, src/ + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +// Deploy to google cloud function :) +func deployCloudFunctionPython(ctx context.Context, name, localization, applocation string, environmentVariables map[string]string) error { + service, err := cloudfunctions.NewService(ctx) + if err != nil { + return err + } + + // ProjectsLocationsListCall + projectsLocationsFunctionsService := cloudfunctions.NewProjectsLocationsFunctionsService(service) + location := fmt.Sprintf("projects/%s/locations/%s", gceProject, localization) + functionName := fmt.Sprintf("%s/functions/%s", location, name) + + cloudFunction := &cloudfunctions.CloudFunction{ + AvailableMemoryMb: 128, + EntryPoint: "authorization", + EnvironmentVariables: environmentVariables, + HttpsTrigger: &cloudfunctions.HttpsTrigger{}, + MaxInstances: 0, + Name: functionName, + Runtime: "python37", + SourceArchiveUrl: applocation, + } + + //getCall := projectsLocationsFunctionsService.Get(fmt.Sprintf("%s/functions/function-5", location)) + //resp, err := getCall.Do() + + createCall := projectsLocationsFunctionsService.Create(location, cloudFunction) + _, err = createCall.Do() + if err != nil { + log.Printf("Failed creating new function. SKIPPING patch, as it probably already exists: %s", err) + + // FIXME - have patching code or nah? + createCall := projectsLocationsFunctionsService.Patch(fmt.Sprintf("%s/functions/%s", location, name), cloudFunction) + _, err = createCall.Do() + if err != nil { + log.Println("Failed patching function") + return err + } + + log.Printf("Successfully patched %s to %s", name, localization) + } else { + log.Printf("Successfully deployed %s to %s", name, localization) + } + + // FIXME - use response to define the HTTPS entrypoint. It's default to an easy one tho + + return nil +} + +// Deploy to google cloud function :) +func deployCloudFunctionGo(ctx context.Context, name, localization, applocation string, environmentVariables map[string]string) error { + service, err := cloudfunctions.NewService(ctx) + if err != nil { + return err + } + + // ProjectsLocationsListCall + projectsLocationsFunctionsService := cloudfunctions.NewProjectsLocationsFunctionsService(service) + location := fmt.Sprintf("projects/%s/locations/%s", gceProject, localization) + functionName := fmt.Sprintf("%s/functions/%s", location, name) + + cloudFunction := &cloudfunctions.CloudFunction{ + AvailableMemoryMb: 128, + EntryPoint: "Authorization", + EnvironmentVariables: environmentVariables, + HttpsTrigger: &cloudfunctions.HttpsTrigger{}, + MaxInstances: 1, + Name: functionName, + Runtime: "go111", + SourceArchiveUrl: applocation, + } + + //getCall := projectsLocationsFunctionsService.Get(fmt.Sprintf("%s/functions/function-5", location)) + //resp, err := getCall.Do() + + createCall := projectsLocationsFunctionsService.Create(location, cloudFunction) + _, err = createCall.Do() + if err != nil { + log.Println("Failed creating new function. Attempting patch, as it might exist already") + + createCall := projectsLocationsFunctionsService.Patch(fmt.Sprintf("%s/functions/%s", location, name), cloudFunction) + _, err = createCall.Do() + if err != nil { + log.Println("Failed patching function") + return err + } + + log.Printf("Successfully patched %s to %s", name, localization) + } else { + log.Printf("Successfully deployed %s to %s", name, localization) + } + + // FIXME - use response to define the HTTPS entrypoint. It's default to an easy one tho + + return nil +} + +// Deploy to google cloud function :) +func deployWebhookFunction(ctx context.Context, name, localization, applocation string, environmentVariables map[string]string) error { + service, err := cloudfunctions.NewService(ctx) + if err != nil { + return err + } + + // ProjectsLocationsListCall + projectsLocationsFunctionsService := cloudfunctions.NewProjectsLocationsFunctionsService(service) + location := fmt.Sprintf("projects/%s/locations/%s", gceProject, localization) + functionName := fmt.Sprintf("%s/functions/%s", location, name) + + cloudFunction := &cloudfunctions.CloudFunction{ + AvailableMemoryMb: 128, + EntryPoint: "Authorization", + EnvironmentVariables: environmentVariables, + HttpsTrigger: &cloudfunctions.HttpsTrigger{}, + MaxInstances: 1, + Name: functionName, + Runtime: "go111", + SourceArchiveUrl: applocation, + } + + //getCall := projectsLocationsFunctionsService.Get(fmt.Sprintf("%s/functions/function-5", location)) + //resp, err := getCall.Do() + + createCall := projectsLocationsFunctionsService.Create(location, cloudFunction) + _, err = createCall.Do() + if err != nil { + log.Println("Failed creating new function. Attempting patch, as it might exist already") + + createCall := projectsLocationsFunctionsService.Patch(fmt.Sprintf("%s/functions/%s", location, name), cloudFunction) + _, err = createCall.Do() + if err != nil { + log.Println("Failed patching function") + return err + } + + log.Printf("Successfully patched %s to %s", name, localization) + } else { + log.Printf("Successfully deployed %s to %s", name, localization) + } + + // FIXME - use response to define the HTTPS entrypoint. It's default to an easy one tho + + return nil +} + +func loadExistingApps(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Just need to be logged in + // FIXME - should have some permissions? + _, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in load apps: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fs := memfs.New() + storer := memory.NewStorage() + r, err := git.Clone(storer, fs, &git.CloneOptions{ + URL: "https://github.com/frikky/shuffle-apps", + }) + + if err != nil { + log.Printf("Failed loading repo into memory: %s", err) + } + + dir, err := fs.ReadDir("/") + if err != nil { + log.Printf("FAiled reading folder: %s", err) + } + _ = r + iterateAppGithubFolders(fs, dir, "") + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra string) error { + var err error + runUpload := false + for _, file := range dir { + // Folder? + switch mode := file.Mode(); { + case mode.IsDir(): + tmpExtra := fmt.Sprintf("%s%s/", extra, file.Name()) + dir, err := fs.ReadDir(tmpExtra) + if err != nil { + break + } + + // Go routine? Hmm, this can be super quick I guess + err = iterateAppGithubFolders(fs, dir, tmpExtra) + if err != nil { + break + } + case mode.IsRegular(): + // Check the file + filename := file.Name() + if filename == "Dockerfile" { + log.Printf("Handle Dockerfile in location %s", extra) + + extraSplit := strings.Split(extra, "/") + tags := []string{} + if len(extraSplit) > 1 { + tags = []string{ + fmt.Sprintf("%s:%s_%s", baseDockerName, strings.ReplaceAll(extraSplit[0], " ", "-"), extraSplit[1]), + // Version = folder of last part of extra + // Name = first folder of extra + } + } else { + // Skip + runUpload = false + log.Printf("Skipping folder %s because the extra variable is empty~", extra) + break + //return nil + } + + /// Only upload if successful and no errors + err := buildImageMemory(fs, tags, extra) + if err != nil { + log.Printf("Failed image build memory: %s", err) + runUpload = false + } else { + runUpload = true + } + } + } + } + + // Done sequentailly to prevent bad uploads + if runUpload && err == nil { + for _, file := range dir { + if file.Name() == "api.yaml" || file.Name() == "api.yaml" { + log.Printf("Run update of %sapi.yaml in backend if it doesn't exist!!", extra) + fullPath := fmt.Sprintf("%s%s", extra, file.Name()) + + fileReader, err := fs.Open(fullPath) + if err != nil { + return err + } + + readFile, err := ioutil.ReadAll(fileReader) + if err != nil { + log.Printf("Filereader error: %s", err) + return err + } + + var workflowapp WorkflowApp + err = gyaml.Unmarshal(readFile, &workflowapp) + if err != nil { + log.Printf("Failed api.yaml unmarshal: %s", err) + return err + } + + log.Printf("APIName: %s", workflowapp.Name) + extraSplit := strings.Split(extra, "/") + appName := fmt.Sprintf("%s_%s", strings.ReplaceAll(extraSplit[0], " ", "-"), extraSplit[1]) + + ctx := context.Background() + allapps, err := getAllWorkflowApps(ctx) + if err != nil { + log.Printf("Failed getting apps to verify: %s", err) + return err + } + + log.Printf("APPS: %d", len(allapps)) + + for _, app := range allapps { + if app.Name == workflowapp.Name && app.AppVersion == workflowapp.AppVersion { + log.Printf("App upload for %s:%s already exists.", app.Name, app.AppVersion) + return errors.New(fmt.Sprintf("App %s already exists. ", appName)) + } + } + + err = checkWorkflowApp(workflowapp) + if err != nil { + log.Printf("%s for app %s:%s", err, workflowapp.Name, workflowapp.AppVersion) + return err + } + + //if workflowapp.Environment == "" { + // workflowapp.Environment = baseEnvironment + //} + + workflowapp.ID = uuid.NewV4().String() + workflowapp.IsValid = true + workflowapp.Verified = true + workflowapp.Sharing = true + workflowapp.Downloaded = true + + err = setWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID) + if err != nil { + log.Printf("Failed setting workflowapp: %s", err) + return err + } + + log.Printf("Added %s:%s to the database", workflowapp.Name, workflowapp.AppVersion) + //memcache.Delete(ctx, "all_apps") + //os.Exit(3) + } + } + } + + return err +} + +func setNewWorkflowApp(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // Just need to be logged in + // FIXME - should have some permissions? + _, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new app: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Error with body read: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + var workflowapp WorkflowApp + err = json.Unmarshal(body, &workflowapp) + if err != nil { + log.Printf("Failed unmarshaling: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + ctx := context.Background() + allapps, err := getAllWorkflowApps(ctx) + if err != nil { + log.Printf("Failed getting apps to verify: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + appfound := false + for _, app := range allapps { + if app.Name == workflowapp.Name && app.AppVersion == workflowapp.AppVersion { + log.Printf("App upload for %s:%s already exists.", app.Name, app.AppVersion) + appfound = true + break + } + } + + if appfound { + log.Printf("App %s:%s already exists. Bump the version.", workflowapp.Name, workflowapp.AppVersion) + resp.WriteHeader(409) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s:%s already exists."}`, workflowapp.Name, workflowapp.AppVersion))) + return + } + + err = checkWorkflowApp(workflowapp) + if err != nil { + log.Printf("%s for app %s:%s", err, workflowapp.Name, workflowapp.AppVersion) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s for app %s:%s"}`, err, workflowapp.Name, workflowapp.AppVersion))) + return + } + + //if workflowapp.Environment == "" { + // workflowapp.Environment = baseEnvironment + //} + + workflowapp.ID = uuid.NewV4().String() + workflowapp.IsValid = true + workflowapp.Generated = false + + err = setWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID) + if err != nil { + log.Printf("Failed setting workflowapp: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } else { + log.Printf("Added %s:%s to the database", workflowapp.Name, workflowapp.AppVersion) + } + + //memcache.Delete(ctx, "all_apps") + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +func getWorkflowExecutions(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in getting specific workflow: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID when getting workflow executions is not valid"}`)) + return + } + + ctx := context.Background() + workflow, err := getWorkflow(ctx, fileId) + if err != nil { + log.Printf("Failed getting the workflow locally: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME - have a check for org etc too.. + if user.Id != workflow.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s (get execution)", user.Username, workflow.ID) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Query for the specifci workflowId + q := datastore.NewQuery("workflowexecution").Filter("workflow_id =", fileId) + var workflowExecutions []WorkflowExecution + _, err = dbclient.GetAll(ctx, q, &workflowExecutions) + if err != nil { + log.Printf("Error getting workflowexec: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting all workflowexecutions for %s"}`, fileId))) + return + } + + if len(workflowExecutions) == 0 { + resp.Write([]byte("[]")) + resp.WriteHeader(200) + return + } + + newjson, err := json.Marshal(workflowExecutions) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow executions"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newjson) +} + +func getAllWorkflowApps(ctx context.Context) ([]WorkflowApp, error) { + var allworkflowapps []WorkflowApp + q := datastore.NewQuery("workflowapp") + + _, err := dbclient.GetAll(ctx, q, &allworkflowapps) + if err != nil { + return []WorkflowApp{}, err + } + + return allworkflowapps, nil +} + +// Hmm, so I guess this should use uuid :( +// Consistency PLX +func setWorkflowAppDatastore(ctx context.Context, workflowapp WorkflowApp, id string) error { + key := datastore.NameKey("workflowapp", id, nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key, &workflowapp); err != nil { + log.Printf("Error adding workflow app: %s", err) + return err + } + + return nil +} + +// Starts a new webhook +func handleStopHook(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 32 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID when stopping hook is not valid"}`)) + return + } + + ctx := context.Background() + hook, err := getHook(ctx, fileId) + if err != nil { + log.Printf("Failed getting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Id != hook.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s", user.Username, hook.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Status: %s", hook.Status) + log.Printf("Running: %t", hook.Running) + if !hook.Running { + message := fmt.Sprintf("Error: %s isn't running", hook.Id) + log.Println(message) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, message))) + return + } + + hook.Status = "stopped" + hook.Running = false + hook.Actions = []HookAction{} + err = setHook(ctx, *hook) + if err != nil { + log.Printf("Failed setting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + image := "webhook" + + // This is here to force stop and remove the old webhook + // FIXME + err = removeWebhookFunction(ctx, fileId) + if err != nil { + log.Printf("Container stop issue for %s-%s: %s", image, fileId, err) + } + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "reason": "Stopped webhook"}`)) +} + +func handleDeleteHook(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID when deleting hook is not valid"}`)) + return + } + + ctx := context.Background() + hook, err := getHook(ctx, fileId) + if err != nil { + log.Printf("Failed getting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Id != hook.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s", user.Username, hook.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + hook.Status = "stopped" + err = setHook(ctx, *hook) + if err != nil { + log.Printf("Failed setting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // This is here to force stop and remove the old webhook + image := "webhook" + err = removeWebhookFunction(ctx, fileId) + if err != nil { + log.Printf("Function removal issue for %s-%s: %s", image, fileId, err) + if strings.Contains(err.Error(), "does not exist") { + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "reason": "Stopped webhook"}`)) + + } else { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Couldn't stop webhook, please try again later"}`)) + } + + return + } + + log.Printf("Successfully deleted webhook %s", fileId) + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "reason": "Stopped webhook"}`)) +} + +func removeWebhookFunction(ctx context.Context, hookid string) error { + service, err := cloudfunctions.NewService(ctx) + if err != nil { + return err + } + + // ProjectsLocationsListCall + projectsLocationsFunctionsService := cloudfunctions.NewProjectsLocationsFunctionsService(service) + location := fmt.Sprintf("projects/%s/locations/%s", gceProject, defaultLocation) + functionName := fmt.Sprintf("%s/functions/webhook_%s", location, hookid) + + deleteCall := projectsLocationsFunctionsService.Delete(functionName) + resp, err := deleteCall.Do() + if err != nil { + log.Printf("Failed to delete %s from %s: %s", hookid, defaultLocation, err) + return err + } else { + log.Printf("Successfully deleted %s from %s", hookid, defaultLocation) + } + + _ = resp + return nil +} + +func handleStartHook(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in set new workflowhandler: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if len(fileId) != 36 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Workflow ID when starting hook is not valid"}`)) + return + } + + ctx := context.Background() + hook, err := getHook(ctx, fileId) + if err != nil { + log.Printf("Failed getting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Id != hook.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for workflow %s", user.Username, hook.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Status: %s", hook.Status) + log.Printf("Running: %t", hook.Running) + if hook.Running || hook.Status == "Running" { + message := fmt.Sprintf("Error: %s is already running", hook.Id) + log.Println(message) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, message))) + return + } + + environmentVariables := map[string]string{ + "FUNCTION_APIKEY": user.ApiKey, + "CALLBACKURL": "https://shuffler.io", + "HOOKID": fileId, + } + + applocation := fmt.Sprintf("gs://%s/triggers/webhook.zip", bucketName) + hookname := fmt.Sprintf("webhook_%s", fileId) + err = deployWebhookFunction(ctx, hookname, "europe-west2", applocation, environmentVariables) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + hook.Status = "running" + hook.Running = true + err = setHook(ctx, *hook) + if err != nil { + log.Printf("Failed setting hook: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Starting function %s?", fileId) + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "reason": "Started webhook"}`)) + return +} + +func removeOutlookTriggerFunction(ctx context.Context, triggerId string) error { + service, err := cloudfunctions.NewService(ctx) + if err != nil { + return err + } + + // ProjectsLocationsListCall + projectsLocationsFunctionsService := cloudfunctions.NewProjectsLocationsFunctionsService(service) + location := fmt.Sprintf("projects/%s/locations/%s", gceProject, defaultLocation) + functionName := fmt.Sprintf("%s/functions/outlooktrigger_%s", location, triggerId) + + deleteCall := projectsLocationsFunctionsService.Delete(functionName) + resp, err := deleteCall.Do() + if err != nil { + log.Printf("Failed to delete %s from %s: %s", triggerId, defaultLocation, err) + return err + } else { + log.Printf("Successfully deleted %s from %s", triggerId, defaultLocation) + } + + _ = resp + return nil +} diff --git a/backend/go-app/webapp b/backend/go-app/webapp new file mode 100755 index 00000000..400c09ac Binary files /dev/null and b/backend/go-app/webapp differ diff --git a/backend/run.sh b/backend/run.sh new file mode 100644 index 00000000..edfe195c --- /dev/null +++ b/backend/run.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# docker stop nginx +# docker rm nginx +# docker rmi nginx +# +# echo "Running build for website" +# sudo npm run build +# docker build . -t nginx + +echo "Starting server" +docker run -it \ + -p 5001:5001 \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --env DATASTORE_EMULATOR_HOST=192.168.3.6:8000 \ + frikky/shuffle:backend diff --git a/backend/tests/cleanup.sh b/backend/tests/cleanup.sh new file mode 100644 index 00000000..53c3779b --- /dev/null +++ b/backend/tests/cleanup.sh @@ -0,0 +1,2 @@ + +curl http://localhost:5001/api/v1/execution_cleanup -H "Authorization: Bearer e08c6f22-9a55-4557-b008-04388cc51fb0" diff --git a/backend/tests/execute.sh b/backend/tests/execute.sh new file mode 100644 index 00000000..a4584bbe --- /dev/null +++ b/backend/tests/execute.sh @@ -0,0 +1,5 @@ +#!/bin/sh +curl -XPOST http://localhost:5001/api/v1/workflows/1d9d8ce2-566e-4c3f-8a37-5d6c7d2000b5/execute -d '{"execution_argument":""}' -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" + + +curl -XPOST http://localhost:5001/api/v1/workflows/1d9d8ce2-566e-4c3f-8a37-5d6c7d2000b5/execute -d '{"execution_argument":""}' -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ" diff --git a/backend/tests/hooks.sh b/backend/tests/hooks.sh new file mode 100644 index 00000000..7905b184 --- /dev/null +++ b/backend/tests/hooks.sh @@ -0,0 +1,24 @@ +#curl localhost:5000/api/v1/hooks + +# Starts a webhook +# curl localhost:5000/api/v1/hooks/d6ef8912e8bd37776e654cbc14c2629c/start + +# Runs a request towards the webhook created +#curl -XPOST localhost:5002/webhook -d '{"helo": "hi"}' + +# Gets the ID of a new hook + +#curl -X PUT localhost:5001/api/v1/hooks/e6f77059e6d469a6c4c314cc06d5a4c0 -d '{"name": "asd", "description": "hola", "type": "webhook", "id": "e6f77059e6d469a6c4c314cc06d5a4c0", "status": "stopped", "info": {"name": "lul", "url": "http://test"}}' + +#curl -X POST localhost:5000/api/v1/hooks/new -d '{"name": "asd", "description": "hola", "type": "webhook"}' + +#curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_982995716e67c3a549092d3a3a7921cd" -H "Content-Type:application/json" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data '{"name":"Keyboard Cat"}' -v + + +#jcurl http://localhost:5001/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" +#curl https://shuffler.io/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" +#curl -X POST "http://localhost:8080" -H "Content-Type:application/json" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data '{"test": {"hello": "HEYOOOO"}}' -v + +#curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_3ceff795-ce9a-43a2-a2f5-d4401a6e772d" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut' +curl POST "https://europe-west1-shuffler.cloudfunctions.net/outlooktrigger_be4dbb0a-d396-4544-bc36-e57d1bdb2e40" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut' -vvv + diff --git a/backend/tests/run_function.py b/backend/tests/run_function.py new file mode 100644 index 00000000..7749a572 --- /dev/null +++ b/backend/tests/run_function.py @@ -0,0 +1,69 @@ +# This is a script to test a function by itself + +import requests +import json + +def invoke(url, headers, message): + # Used for testing + try: + ret = requests.post(url, headers=headers, json=message, timeout=5) + print(ret.text) + print(ret.status_code) + except requests.exceptions.ConnectionError as e: + print(f"Requesterror: {e}") + +def invoke_multi(url, headers, message): + cnt = 0 + maxcnt = 100 + print("Running %d requests towards %s." % (maxcnt, url)) + while(1): + try: + ret = requests.post(url, headers=headers, json=message, timeout=1) + print(ret.status_code) + except requests.exceptions.ConnectionError as e: + print(f"Connectionerror: {e}") + except requests.exceptions.ReadTimeout as e: + print(f"Readtimeout: {e}") + + cnt += 1 + if cnt == maxcnt: + break + + print("Done :)") + +if __name__ == "__main__": + # Specific thingies for hello_world + message = { + "parameters": [{ + "id_": "asd", + "name": "call", + "value": "REPEAT THIS DATA PLEASE THANKS", + "variant": "STATIC_VALUE", + }], + "name": "repeat_back_to_me", + "execution_id": "asd", + "label": "", + "position": "", + "app_name": "hello_world", + "app_version": "1.0.0", + "label": "lul", + "priority": "1", + "id_": "test", + "id": "test", + "authorization": "hey", + } + + apikey = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {apikey}" + } + + location = "europe-west2" + functionname = "hello-world-1-0-6" + project = "shuffler" + + url = f"https://{location}-{project}.cloudfunctions.net/{functionname}" + print(url) + invoke(url, headers, message) + #invoke_multi(url, headers, message) diff --git a/backend/tests/scheduleapps.sh b/backend/tests/scheduleapps.sh new file mode 100644 index 00000000..695a87af --- /dev/null +++ b/backend/tests/scheduleapps.sh @@ -0,0 +1,2 @@ + +curl localhost:5000/api/v1/schedules/apps diff --git a/backend/tests/schedules.sh b/backend/tests/schedules.sh new file mode 100644 index 00000000..339f982d --- /dev/null +++ b/backend/tests/schedules.sh @@ -0,0 +1,2 @@ +# Fails cus of unmarshal +curl -XPOST http://localhost:5001/api/v1/workflows/1d9d8ce2-566e-4c3f-8a37-5d6c7d2000b5/schedule -d '{"name": "hey", "frequency": "*/1 * * * *", "execution_argument": "{\"test\": \"hey\"}"}' -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ" diff --git a/backend/tests/sendmail.sh b/backend/tests/sendmail.sh new file mode 100644 index 00000000..18cc572c --- /dev/null +++ b/backend/tests/sendmail.sh @@ -0,0 +1,3 @@ +#curl -X POST -H "Content-Type: application/json" shuffler.io/functions/sendmail -H "Authorization: Bearer " -d '{"target": "frikky@shuffler.io", "body": "Hey, this is a body for something to look at", "subject": "SOS check me", "type": "alert", "sender_company": "shuffler"}' + +curl -X POST -H "Content-Type: application/json" localhost:5001/functions/sendmail -H "Authorization: Bearer " -d '{"targets": ["frikky@shuffler.io", "rheyix.yt@gmail.com"], "body": "Hey, this is a body for something to look at", "subject": "SOS check me", "type": "alert", "sender_company": "shuffler"}' diff --git a/backend/tests/testWorkflows.sh b/backend/tests/testWorkflows.sh new file mode 100644 index 00000000..c8921975 --- /dev/null +++ b/backend/tests/testWorkflows.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Should give 401 +curl localhost:5000/api/v1/uploadResult/asdasd -d {} +echo + +# Should give 200 if it exists +curl localhost:5000/api/v1/uploadResult/e07910a06a086c83ba41827aa00b26ed -d '{"title": "helo","description": "wut", "type": "hi", "source": "wutface", "sourceRef": "halvor hei"}' diff --git a/backend/tests/triggers.sh b/backend/tests/triggers.sh new file mode 100644 index 00000000..75d3d436 --- /dev/null +++ b/backend/tests/triggers.sh @@ -0,0 +1 @@ +curl -H "Content-Type: application/json" localhost:5001/api/v1/triggers/9e845679-5843-4959-a76c-a6d664e9df35 -H "Authorization: Bearer 377469e8-dd5d-4521-8d9e-416d8d2f6fd4" diff --git a/backend/tests/websocket.sh b/backend/tests/websocket.sh new file mode 100644 index 00000000..ec6b9793 --- /dev/null +++ b/backend/tests/websocket.sh @@ -0,0 +1,4 @@ +#!/bin/bash +curl http://localhost:5001/ws -H "Connections: Upgrade" + +#curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_982995716e67c3a549092d3a3a7921cd" -H "Content-Type:application/json" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data '{"name":"Keyboard Cat"}' -v diff --git a/backend/tests/workflowdata.json b/backend/tests/workflowdata.json new file mode 100644 index 00000000..9062e524 --- /dev/null +++ b/backend/tests/workflowdata.json @@ -0,0 +1,202 @@ +[ + { + "actions": [ + { + "app_name": "hello_world", + "app_version": "1.0.0", + "errors": [], + "id_": "2686a5d4-531d-158f-6b1a-0c1d23481304", + "is_valid": true, + "label": "check_bool", + "name": "check_bool", + "environment": "cloud", + "parameters": [], + "position": { + "x": 329.98133726556375, + "y": 160.01013778166904 + }, + "priority": 3 + } + ], + "branches": [ + { + "destination_id": "6478ecae-b10e-88e9-e34d-9bbe6aff393d", + "id_": "5fd6a357-ae33-b1af-5dc2-0306efa28887", + "source_id": "2686a5d4-531d-158f-6b1a-0c1d23481304" + }, + { + "destination_id": "2686a5d4-531d-158f-6b1a-0c1d23481304", + "id_": "d46d0b05-5757-5084-c339-70ac63985781", + "source_id": "6478ecae-b10e-88e9-e34d-9bbe6aff393d" + } + ], + "conditions": [ + { + "app_name": "Builtin", + "app_version": "1.0.0", + "conditional": "", + "errors": [], + "id_": "6478ecae-b10e-88e9-e34d-9bbe6aff393d", + "is_valid": true, + "label": "Condition", + "name": "Condition", + "position": { + "x": 320.97142988802364, + "y": 394.9753467582139 + } + } + ], + "description": "", + "errors": [], + "id_": "a5f82cfd-0f38-3474-20e2-f757f3718707", + "is_valid": true, + "name": "asd2", + "start": "2686a5d4-531d-158f-6b1a-0c1d23481304", + "tags": [], + "transforms": [], + "triggers": [], + "workflow_variables": [] + }, + { + "actions": [ + { + "app_name": "hello_world", + "app_version": "1.0.0", + "errors": [], + "id_": "51df7c4f-b856-1aca-402b-9fec660b6505", + "is_valid": true, + "label": "wut", + "name": "hello_world", + "parameters": [], + "position": { + "x": 250, + "y": 150 + }, + "priority": 3 + }, + { + "app_name": "hello_world", + "app_version": "1.0.0", + "errors": [], + "id_": "36975212-3b9a-2e4c-4ad7-0ee5a6325842", + "is_valid": true, + "label": "check_bool", + "name": "check_bool", + "parameters": [], + "position": { + "x": 241.00207363715955, + "y": 294.9896318142021 + }, + "priority": 3 + } + ], + "branches": [ + { + "destination_id": "36975212-3b9a-2e4c-4ad7-0ee5a6325842", + "id_": "44231b8f-4331-764e-0c1d-ccbc901d1309", + "source_id": "51df7c4f-b856-1aca-402b-9fec660b6505" + }, + { + "destination_id": "51df7c4f-b856-1aca-402b-9fec660b6505", + "id_": "9b3dc561-e879-4877-c32e-ab7149d83b39", + "source_id": "36975212-3b9a-2e4c-4ad7-0ee5a6325842" + } + ], + "conditions": [], + "description": "", + "errors": [], + "id_": "4e437698-fc18-29d3-e875-969b57354685", + "is_valid": true, + "name": "hi", + "start": "51df7c4f-b856-1aca-402b-9fec660b6505", + "tags": [], + "transforms": [], + "triggers": [], + "workflow_variables": [] + }, + { + "actions": [ + { + "app_name": "hello_world", + "app_version": "1.0.0", + "errors": [], + "id_": "d0dbd1c4-dd61-6d4a-70e1-ac05dad0fe1f", + "is_valid": true, + "label": "i am bool", + "name": "check_bool", + "parameters": [], + "position": { + "x": 377.9998262128892, + "y": 330.01190441708906 + }, + "priority": 3 + }, + { + "app_name": "hello_world", + "app_version": "1.0.0", + "errors": [], + "id_": "39353c1c-b179-152c-f977-615a15a5de37", + "is_valid": true, + "label": "hello_world", + "name": "hello_world", + "parameters": [], + "position": { + "x": 375.2590614780782, + "y": 160.6226941577116 + }, + "priority": 3 + } + ], + "branches": [ + { + "destination_id": "d0dbd1c4-dd61-6d4a-70e1-ac05dad0fe1f", + "id_": "7c81993f-5fd6-fc83-2f41-8074d6c8dc25", + "source_id": "39353c1c-b179-152c-f977-615a15a5de37" + } + ], + "conditions": [], + "description": "", + "errors": [], + "id_": "af8467be-43ca-d38b-3f7e-9aeb922fb21a", + "is_valid": true, + "name": "new!", + "start": "39353c1c-b179-152c-f977-615a15a5de37", + "tags": [], + "transforms": [], + "triggers": [], + "workflow_variables": [] + }, + { + "actions": [ + { + "app_name": "Builtin", + "app_version": "1.0.0", + "errors": [], + "id_": "7dddec9a-b493-8b58-9234-1b74dd9b420a", + "is_valid": true, + "label": "Boolean", + "name": "Boolean", + "parameters": [], + "position": { + "x": 350, + "y": 250 + }, + "priority": 3 + } + ], + "branches": [], + "conditions": [], + "description": "", + "errors": [ + "Action Builtin.Boolean does not exist" + ], + "id_": "07bae551-21e6-c0f5-4db3-81e8a1e8a805", + "is_valid": false, + "name": "wutface", + "start": "7dddec9a-b493-8b58-9234-1b74dd9b420a", + "tags": [], + "transforms": [], + "triggers": [], + "workflow_variables": [] + } +] diff --git a/backend/tests/workflowresults.sh b/backend/tests/workflowresults.sh new file mode 100644 index 00000000..f9bceee9 --- /dev/null +++ b/backend/tests/workflowresults.sh @@ -0,0 +1,23 @@ +#curl -X POST -H "Content-Type: application/json" localhost:5001/api/v1/workflows/3d14ca4a-67bd-8dfb-2673-2864f1ccf59c/execute -d '{"workflow_id": "3d14ca4a-67bd-8dfb-2673-2864f1ccf59c", "execution_id": "eaaa8d19-a761-12b8-cac2-f34eb50c3711"}' + +curl -X POST -H "Content-Type: application/json" https://shuffle-241517.appspot.com/api/v1/workflows/3d14ca4a-67bd-8dfb-2673-2864f1ccf59c/execute -d '{"workflow_id": "3d14ca4a-67bd-8dfb-2673-2864f1ccf59c", "execution_id": "eaaa8d19-a761-12b8-cac2-f34eb50c3711"}' + +#curl -X POST http://localhost:5001/api/v1/workflows/streams -H "Content-Type: application/json" \ +# -d '{"execution_id": "eaaa8d19-a761-12b8-cac2-f34eb50c3711", +# "result": "hello_result", +# "started_at": 1562309342, +# "authorization": "afcc298d-c6c2-4b0d-8221-1603b44d072d", +# "status": "ABORTED", +# "action": { +# "app_name": "hi", +# "app_version": "ho", +# "id_": "this_is_an_id", +# "label": "wut", +# "name": "stream_testing", +# "parameters": [], +# "position": { +# "x": 100, +# "y": 100 +# }, +# "priority": 1 +# }}' diff --git a/backend/tests/workflows.sh b/backend/tests/workflows.sh new file mode 100644 index 00000000..ed83e6dd --- /dev/null +++ b/backend/tests/workflows.sh @@ -0,0 +1,15 @@ +# Get all workflows +curl localhost:5001/api/v1/workflows + +# Get A workflow +#curl localhost:5001/api/v1/workflows/a5f82cfd-0f38-3474-20e2-f757f3718707 + +# NEW workflow +# curl -XPOST localhost:5001/api/v1/workflows -d '{"tags":[],"actions":[],"branches":[],"conditions":[{"label":"Condition","app_name":"Builtin","name":"Condition","conditional":"","id_":"18165f42-aab9-6c9a-0ef4-4e5a37a3b2ad","app_version":"1.0.0","position":{"x":224,"y":168}}],"workflow_variables":[],"name":"asd","start":"18165f42-aab9-6c9a-0ef4-4e5a37a3b2ad","id_":"3d14ca4a-67bd-8dfb-2673-2864f1ccf59c"}' + +# Add a workflow +#curl localhost:5001/api/v1/workflows/a5f82cfd-0f38-3474-20e2-f757f3718707 -d '{"actions":[{"app_name":"hello_world","app_version":"1.0.0","errors":[],"id_":"2686a5d4-531d-158f-6b1a-0c1d23481304","is_valid":true,"label":"check_bool","name":"check_bool","parameters":[],"position":{"x":329.98133726556375,"y":160.01013778166904},"priority":3}],"branches":[{"destination_id":"6478ecae-b10e-88e9-e34d-9bbe6aff393d","id_":"5fd6a357-ae33-b1af-5dc2-0306efa28887","source_id":"2686a5d4-531d-158f-6b1a-0c1d23481304"},{"destination_id":"2686a5d4-531d-158f-6b1a-0c1d23481304","id_":"d46d0b05-5757-5084-c339-70ac63985781","source_id":"6478ecae-b10e-88e9-e34d-9bbe6aff393d"}],"conditions":[{"app_name":"Builtin","app_version":"1.0.0","conditional":"","errors":[],"id_":"6478ecae-b10e-88e9-e34d-9bbe6aff393d","is_valid":true,"label":"Condition","name":"Condition","position":{"x":320.97142988802364,"y":394.9753467582139}}],"description":"","errors":[],"id_":"a5f82cfd-0f38-3474-20e2-f757f3718707","is_valid":true,"name":"asd2","start":"2686a5d4-531d-158f-6b1a-0c1d23481304","tags":[],"transforms":[],"triggers":[],"workflow_variables":[]}' + +# Execute a workflow +# curl -H "Content-Type: application/json" localhost:5001/api/v1/workflows/3d14ca4a-67bd-8dfb-2673-2864f1ccf59c/execute +#curl -X POST -H "Content-Type: application/json" localhost:5001/api/v1/workflows/3d14ca4a-67bd-8dfb-2673-2864f1ccf59c/execute -d '{"workflow_id": "3d14ca4a-67bd-8dfb-2673-2864f1ccf59c", "execution_id": "eaaa8d19-a761-12b8-cac2-f34eb50c3711"}' diff --git a/backend/webhook/Dockerfile b/backend/webhook/Dockerfile new file mode 100644 index 00000000..4b709eb5 --- /dev/null +++ b/backend/webhook/Dockerfile @@ -0,0 +1,15 @@ +# Build environment +# production environment +from golang as builder + +RUN go get github.com/gorilla/handlers +RUN go get github.com/gorilla/mux + +WORKDIR /app +COPY webhook.go /app/webhook.go +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook . + +from scratch +COPY --from=builder /app/ / + +CMD ["./webhook"] diff --git a/backend/webhook/README.md b/backend/webhook/README.md new file mode 100644 index 00000000..8b87bffb --- /dev/null +++ b/backend/webhook/README.md @@ -0,0 +1,7 @@ +# Steps to deploy to Google cloud function +1. +```bash +zip webhook.zip * +``` +2. Go to google cloud bucket and upload the zip +3. Go to worker for webhook and upload diff --git a/backend/webhook/functionhook.go b/backend/webhook/functionhook.go new file mode 100644 index 00000000..2df691b0 --- /dev/null +++ b/backend/webhook/functionhook.go @@ -0,0 +1,44 @@ +package function + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "time" +) + +// GetUserDetails - Get one user's details from randomuser.me API +func GetUserDetails(w http.ResponseWriter, r *http.Request) { + randomUserClient := http.Client{ + Timeout: time.Second * 3, + } + + req, err := http.NewRequest(http.MethodGet, "https://randomuser.me/api/", nil) + if err != nil { + log.Fatal(err) + return + } + + res, err2 := randomUserClient.Do(req) + if err2 != nil { + log.Fatal(err2) + return + } + + body, err3 := ioutil.ReadAll(res.Body) + if err3 != nil { + log.Fatal(err3) + } + + var o map[string]interface{} + json.Unmarshal([]byte(body), &o) + + results := o["results"].([]interface{}) + result := results[0].(map[string]interface{}) + + result["generator"] = "google-cloud-function" + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} diff --git a/backend/webhook/gcp_run.sh b/backend/webhook/gcp_run.sh new file mode 100644 index 00000000..605777b5 --- /dev/null +++ b/backend/webhook/gcp_run.sh @@ -0,0 +1,4 @@ +docker build . -t gcr.io/shuffle-241517/webhook +docker push gcr.io/shuffle-241517/webhook + +gcloud beta run deploy webhook --image gcr.io/shuffle-241517/webhook diff --git a/backend/webhook/run.sh b/backend/webhook/run.sh new file mode 100644 index 00000000..e45a9c6c --- /dev/null +++ b/backend/webhook/run.sh @@ -0,0 +1,16 @@ +docker stop webhook +docker rm webhook +docker rmi webhook + +docker build . -t webhook +docker run -d \ + -e "HOOKPORT=5001" \ + -e "URIPATH=/webhook" \ + -e "CALLBACKURL=http://192.168.159.151:5000/api/v1/hooks/d6ef8912e8bd37776e654cbc14c2629c/result" \ + -p 5001:5001 \ + --name webhook \ + -h webhook \ + --restart always \ + webhook + +docker logs -f webhook diff --git a/backend/webhook/webhook.go b/backend/webhook/webhook.go new file mode 100644 index 00000000..bd256d78 --- /dev/null +++ b/backend/webhook/webhook.go @@ -0,0 +1,260 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" +) + +type Info struct { + Url string `json:"url" datastore:"url"` + Name string `json:"name" datastore:"name"` + Description string `json:"description" datastore:"description"` +} + +// Actions to be done by webhooks etc +// Field is the actual field to use from json +type HookAction struct { + Type string `json:"type" datastore:"type"` + Name string `json:"name" datastore:"name"` + Id string `json:"id" datastore:"id"` + Field string `json:"field" datastore:"field"` +} + +type Hook struct { + Id string `json:"id" datastore:"id"` + Info Info `json:"info" datastore:"info"` + Transforms struct{} `json:"transforms" datastore:"transforms"` + Actions []HookAction `json:"actions" datastore:"actions"` + Type string `json:"type" datastore:"type"` + Status string `json:"status" datastore:"status"` + Running bool `json:"running" datastore:"running"` +} + +var hook Hook + +func handleWorkflowAction(request *http.Request, action HookAction) error { + //log.Printf("WORKFLOW!: %#v", action) + log.Printf("Should execute workflow %s", action.Id) + + callbackUrl := os.Getenv("CALLBACKURL") + apikey := os.Getenv("APIKEY") + fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", callbackUrl, action.Id) + + // ret = requests.post(fullurl, headers=headers, json=data) + //if ret.status_code != 202: + // print(ret.text) + // print(ret.status_code) + // print("Exiting workflows - run queue") + // exit() + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return err + } + + // Execute a workflow + client := &http.Client{} + req, err := http.NewRequest( + "POST", + fullUrl, + bytes.NewBuffer(body), + ) + + if err != nil { + log.Printf("Error making http request: %s", req) + return err + } + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey)) + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + log.Printf("Error in http request: %s", req) + } + + log.Printf("%#v", resp) + return nil +} + +// FIXME - refresh hook information once in a while. Compare timestamps or something +func callback(resp http.ResponseWriter, request *http.Request) { + //apikey = os.Getenv("APIKEY") + //hookId = os.Getenv("HOOKID") + + handledWorkflowIds := []string{} + for _, item := range hook.Actions { + if item.Type == "" { + log.Printf("CONTINUE AAS EMPTY ITEM: %#v", item) + continue + } + + if item.Type == "workflow" { + found := false + for _, workflowId := range handledWorkflowIds { + if item.Id == workflowId { + found = true + break + } + } + + if found { + continue + } + + handledWorkflowIds = append(handledWorkflowIds, item.Id) + err := handleWorkflowAction(request, item) + if err != nil { + log.Printf("Error in workflow exec: %s", err) + } + } + } + + // FIXME - send the webhookdata to a logging service? Idk + //body, err := ioutil.ReadAll(request.Body) + //if err != nil { + // log.Println("Failed reading body") + // resp.WriteHeader(401) + // resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) + // return + //} + + //callback, err := http.Post(callbackUrl, "application/json", bytes.NewBuffer(body)) + //if err != nil { + // log.Printf("Failed sending callback to %s", callbackUrl) + //} + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) + return +} + +func loadConfiguration(fullUrl string, apikey string) error { + client := &http.Client{} + + req, err := http.NewRequest( + "GET", + fullUrl, + nil, + ) + + if err != nil { + log.Printf("Error making http request: %s", req) + return err + } + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey)) + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + log.Printf("Error in http request: %s", req) + return err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("Error reading response: %s", req) + return err + } + + err = json.Unmarshal(body, &hook) + if err != nil { + log.Printf("Failed unmarshaling hook API", req) + return err + } + + log.Printf("%#v", hook) + log.Println(hook.Actions) + return nil +} + +func webhook() { + // FIXME - remove static + ip := "0.0.0.0" + + // Basic webserver stuff + baseFilePath := os.Getenv("URIPATH") + basePort := os.Getenv("HOOKPORT") + callbackUrl := os.Getenv("CALLBACKURL") + apikey := os.Getenv("APIKEY") + hookId := os.Getenv("HOOKID") + + if len(baseFilePath) == 0 { + log.Println("Env URIPATH not set") + os.Exit(3) + } + + if len(basePort) == 0 { + log.Println("Env HOOKPORT not set") + os.Exit(3) + } + + if len(callbackUrl) == 0 { + log.Println("Env CALLBACKURL not set") + os.Exit(3) + } + + if len(apikey) == 0 { + log.Println("Env APIKEY not set") + os.Exit(3) + } + + if len(hookId) == 0 { + log.Println("Env HOOKID not set") + os.Exit(3) + } + + log.Println("Loading hook configuration") + err := loadConfiguration( + fmt.Sprintf("%s/api/v1/hooks/%s", callbackUrl, hookId), + apikey, + ) + + if err != nil { + log.Fatalf("Error loading config: %s", err) + } + + // Optional + // if len(callbackOpts) == 0 { + // log.Println("Env CALLBACKOPTS not set") + // os.Exit(3) + // } + + port := fmt.Sprintf(":%s", basePort) + log.Printf("Starting webhook on %s%s with path %s", ip, port, baseFilePath) + + // Routing + mux := mux.NewRouter() + mux.SkipClean(true) + + // FIXME - Add path for updating the hook? Can be a specific POST requeuest from backend + mux.HandleFunc(baseFilePath, callback).Methods("POST") + + handlers.LoggingHandler(os.Stdout, mux) + loggedRouter := handlers.LoggingHandler(os.Stdout, mux) + + err = http.ListenAndServe( + port, + loggedRouter, + ) + + if err != nil { + log.Fatal("ListenAndServer: ", err) + } +} + +func F(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.Write([]byte(r.Header.Get("X-Forwarded-For"))) +} + +func main() { + webhook() +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..c15f481d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,68 @@ +version: '3' +# Remove database port +# Remove backend port +services: + frontend: + build: ./frontend + image: frikky/shuffle:frontend + container_name: shuffle-frontend + hostname: shuffle-frontend + ports: + - "3001:80" + networks: + - shuffle + restart: unless-stopped + database: + build: ./backend/database + image: frikky/shuffle:database + container_name: shuffle-database + hostname: shuffle-database + ports: + - "8000:8000" + networks: + - shuffle + restart: unless-stopped + volumes: + - /etc/shuffle:/etc/shuffle + backend: + build: ./backend + image: frikky/shuffle:backend + container_name: shuffle-backend + hostname: ${BACKEND_HOSTNAME} + ports: + - "${BACKEND_PORT}:${BACKEND_PORT}" + networks: + - shuffle + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - ORG_ID=${ORG_ID} + - DATASTORE_EMULATOR_HOST=shuffle-database:8000 + - BACKEND_PORT=${BACKEND_PORT} + restart: unless-stopped + depends_on: + - database + - frontend + orborus: + build: ./functions/onprem/orborus + image: frikky/shuffle:orborus + container_name: shuffle-orborus + hostname: shuffle-orborus + networks: + - shuffle + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - ORG_ID=${ORG_ID} + - ENVIRONMENT_NAME=${ENVIRONMENT_NAME} + - BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT} + restart: unless-stopped + app_sdk: + build: ./functions/onprem/app_sdk + image: frikky/shuffle:app_sdk + worker: + build: ./functions/onprem/worker + image: frikky/shuffle:worker +networks: + shuffle: + driver: bridge diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..bfba0553 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,33 @@ +# Build environment +FROM node as builder + +RUN mkdir /usr/src/app +WORKDIR /usr/src/app +ENV PATH /usr/src/app/node_modules/.bin:$PATH +COPY package.json /usr/src/app/package.json + +RUN npm install --verbose + +COPY . /usr/src/app + +RUN npm run-script build + +# Production environment +from nginx:latest + +RUN mkdir -p /usr/share/nginx/html/build +RUN mkdir -p /usr/share/nginx/html/css +RUN mkdir -p /usr/share/nginx/html/js +RUN mkdir -p /usr/share/nginx/html/img + +COPY --from=builder /usr/src/app/build /usr/share/nginx/html +COPY --from=builder /usr/src/app/certs/fullchain.pem /etc/nginx/fullchain.cert.pem +COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem + +# Test +COPY --from=builder /usr/src/app/nginxtest.conf /etc/nginx/nginx.conf +# Prod +#COPY --from=builder /usr/src/app/nginx.conf /etc/nginx/nginx.conf + +EXPOSE 80 +EXPOSE 443 diff --git a/frontend/build.sh b/frontend/build.sh new file mode 100644 index 00000000..9dc0626f --- /dev/null +++ b/frontend/build.sh @@ -0,0 +1,3 @@ +npm run build +rm -rf ../backend/go-app/build +cp -r build/ ../backend/go-app/build diff --git a/frontend/certs/README b/frontend/certs/README new file mode 100644 index 00000000..15194ae3 --- /dev/null +++ b/frontend/certs/README @@ -0,0 +1,10 @@ +This directory contains your keys and certificates. + +`privkey.pem` : the private key for your certificate. +`fullchain.pem`: the certificate file used in most server software. +`chain.pem` : used for OCSP stapling in Nginx >=1.3.7. +`cert.pem` : will break many server configurations, and should not be used + without reading further documentation (see link below). + +We recommend not moving these files. For more information, see the Certbot +User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates. diff --git a/frontend/certs/cert.pem b/frontend/certs/cert.pem new file mode 100644 index 00000000..30df4192 --- /dev/null +++ b/frontend/certs/cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFRzCCBC+gAwIBAgISA2OOAaaFtVzoqzLXCBV19fpjMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTAyMDkxMjQxMzlaFw0x +OTA1MTAxMjQxMzlaMBMxETAPBgNVBAMTCGVuZGFvLmlvMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAr4nj/G0MxSEWHnD0oC+jS03ofE6hXC87z0h6iNh5 +lRdVoh9o2H22iQNTmWOWB56oqdBQEOeCcNFexYOzL9S91IVy3Q+hDev5rkJ44uFP +0VtgKbTc/aKIpEcSYPda2b1lb1mAYpJ/HqvzMDrJ+izCpTLEs2h+mD+2LAx7Ie3Z +OE+mFhU7aYdJ7rbUob7BT9cxzbp4CLGjaZHFKrC1uafSSh3bFFlXJpYmleunGVS9 +4BUuveZdWJGLFMN1cAJiMGLewsKp5YXKB9eiH6gAg90udRaanPz3NgFa/8qju9iA +64YJcDNr9Aj04EIntRbPJm286+TrJbaf57ESCS32cqaDEwIDAQABo4ICXDCCAlgw +DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM +BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTaQOYhr73PSHSd+P+b7FJhYcVPwDAfBgNV +HSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYI +KwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYI +KwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBMG +A1UdEQQMMAqCCGVuZGFvLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQB +gt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3Jn +MIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHYA4mlLribo6UAJ6IYbtjuD1D7n/nSI ++6SPKJMBnd3x2/4AAAFo0n3+fgAABAMARzBFAiBebCfZn5HQoIloL8iS7Q2U9DE/ +IlMLvhK0UFlhr1u5agIhAN84nfBT1slMt86E95tvvYbEHcAfapogmJmOxSoI5ati +AHUAY/Lbzeg7zCzPC3KEJ1drM6SNYXePvXWmOLHHaFRL2I0AAAFo0n4AegAABAMA +RjBEAiARET8ZHf3qcahgGa9O440U7/Zbfi5+WO0XRlN/cVQZBgIgKQpHRXv9urpk +MS+tz9jvsxERUYEEjRgsSnM1Bs3AF44wDQYJKoZIhvcNAQELBQADggEBADPUIj7w +z+He68As1CFu9tAwGMeLZDgXtPsybAOY9cWm9aL5sC5RldK+ob/5Xi2aa7YEeeij +VP44lOjXtr3EmGm34vN9pL7JyAhYAmEZToyJEt4dbyCbLhO0IT9Po4ropiXk7ze0 +CRNEg2CCdXkG7KLHIxP6Am1vr1/TA8IC4lf0ocYJs5sU4/7Bj8eViuZ7F8ZcJo1t +O24jBw5m48fbmmDWkcc3YHVD55sIys3RDEQMLABgIxgN+kqRqfCslREpPYWa/o2T +jOt99S/zjdeRgGJfO8XKtI2TH8ZMHNyKaJYjmdX0CcZFmm2E8SZRYuB4j/KUdd8a +xje2fid5pAJf2Tk= +-----END CERTIFICATE----- diff --git a/frontend/certs/chain.pem b/frontend/certs/chain.pem new file mode 100644 index 00000000..0002462c --- /dev/null +++ b/frontend/certs/chain.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/frontend/certs/fullchain.pem b/frontend/certs/fullchain.pem new file mode 100644 index 00000000..6d400cf2 --- /dev/null +++ b/frontend/certs/fullchain.pem @@ -0,0 +1,58 @@ +-----BEGIN CERTIFICATE----- +MIIFRzCCBC+gAwIBAgISA2OOAaaFtVzoqzLXCBV19fpjMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTAyMDkxMjQxMzlaFw0x +OTA1MTAxMjQxMzlaMBMxETAPBgNVBAMTCGVuZGFvLmlvMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAr4nj/G0MxSEWHnD0oC+jS03ofE6hXC87z0h6iNh5 +lRdVoh9o2H22iQNTmWOWB56oqdBQEOeCcNFexYOzL9S91IVy3Q+hDev5rkJ44uFP +0VtgKbTc/aKIpEcSYPda2b1lb1mAYpJ/HqvzMDrJ+izCpTLEs2h+mD+2LAx7Ie3Z +OE+mFhU7aYdJ7rbUob7BT9cxzbp4CLGjaZHFKrC1uafSSh3bFFlXJpYmleunGVS9 +4BUuveZdWJGLFMN1cAJiMGLewsKp5YXKB9eiH6gAg90udRaanPz3NgFa/8qju9iA +64YJcDNr9Aj04EIntRbPJm286+TrJbaf57ESCS32cqaDEwIDAQABo4ICXDCCAlgw +DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM +BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTaQOYhr73PSHSd+P+b7FJhYcVPwDAfBgNV +HSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYI +KwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYI +KwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBMG +A1UdEQQMMAqCCGVuZGFvLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQB +gt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3Jn +MIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHYA4mlLribo6UAJ6IYbtjuD1D7n/nSI ++6SPKJMBnd3x2/4AAAFo0n3+fgAABAMARzBFAiBebCfZn5HQoIloL8iS7Q2U9DE/ +IlMLvhK0UFlhr1u5agIhAN84nfBT1slMt86E95tvvYbEHcAfapogmJmOxSoI5ati +AHUAY/Lbzeg7zCzPC3KEJ1drM6SNYXePvXWmOLHHaFRL2I0AAAFo0n4AegAABAMA +RjBEAiARET8ZHf3qcahgGa9O440U7/Zbfi5+WO0XRlN/cVQZBgIgKQpHRXv9urpk +MS+tz9jvsxERUYEEjRgsSnM1Bs3AF44wDQYJKoZIhvcNAQELBQADggEBADPUIj7w +z+He68As1CFu9tAwGMeLZDgXtPsybAOY9cWm9aL5sC5RldK+ob/5Xi2aa7YEeeij +VP44lOjXtr3EmGm34vN9pL7JyAhYAmEZToyJEt4dbyCbLhO0IT9Po4ropiXk7ze0 +CRNEg2CCdXkG7KLHIxP6Am1vr1/TA8IC4lf0ocYJs5sU4/7Bj8eViuZ7F8ZcJo1t +O24jBw5m48fbmmDWkcc3YHVD55sIys3RDEQMLABgIxgN+kqRqfCslREpPYWa/o2T +jOt99S/zjdeRgGJfO8XKtI2TH8ZMHNyKaJYjmdX0CcZFmm2E8SZRYuB4j/KUdd8a +xje2fid5pAJf2Tk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/frontend/certs/old/cert1.pem b/frontend/certs/old/cert1.pem new file mode 100644 index 00000000..c5aab798 --- /dev/null +++ b/frontend/certs/old/cert1.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF+TCCBOGgAwIBAgISA8rwfiC3p89WYm/X9RR3XhzTMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODExMDkyMTEzNTNaFw0x +OTAyMDcyMTEzNTNaMBMxETAPBgNVBAMTCGVuZGFvLmlvMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA2NIGtVlzW3fktmv74YG23LKLM6xIxn/mIPt4PgZ+ +K3407GryQueToAnG5iTXTox5UnyArvqxAe5cTonrlWTai9Do5CuhrKI22EMpwrSl +Uo38dtNu2AHLENrY+aFzEwsVvkWGpIQ8/Y63T8M9ohiInF/S5ZPojIJxy7OVQ3io +MdLbb1atYfBcnt0DFKyMC1yxDyYj8w1jvwQWlKGBYQeRd2MTWEjHO7CJPgiRwiLi +rFBlDWnFz8GKntXLHI2pYL6+X4WgG9XfwzSN/w+ju48JAgCc6yTxvaQxQDpHst3D +kv4CXNTP3gt2RSIOkQldBQzi/FgBLDv/qSuXAnLGsIgk5QIDAQABo4IDDjCCAwow +DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM +BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBThSBGBk0ZSYT1ptylvWQGZqdkBDjAfBgNV +HSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYI +KwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYI +KwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBMG +A1UdEQQMMAqCCGVuZGFvLmlvMIH+BgNVHSAEgfYwgfMwCAYGZ4EMAQIBMIHmBgsr +BgEEAYLfEwEBATCB1jAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlw +dC5vcmcwgasGCCsGAQUFBwICMIGeDIGbVGhpcyBDZXJ0aWZpY2F0ZSBtYXkgb25s +eSBiZSByZWxpZWQgdXBvbiBieSBSZWx5aW5nIFBhcnRpZXMgYW5kIG9ubHkgaW4g +YWNjb3JkYW5jZSB3aXRoIHRoZSBDZXJ0aWZpY2F0ZSBQb2xpY3kgZm91bmQgYXQg +aHR0cHM6Ly9sZXRzZW5jcnlwdC5vcmcvcmVwb3NpdG9yeS8wggECBgorBgEEAdZ5 +AgQCBIHzBIHwAO4AdQB0ftqDMa0zEJEhnM4lT0Jwwr/9XkIgCMY3NXnmEHvMVgAA +AWb6ieWoAAAEAwBGMEQCIE8+auAtQOLY4U1yoEDvCTQULv4PS9Xdo1uJzQF5nhBA +AiBsfKR2UMxxIatzPOQPtuRrVEAv4+tX4o/ch5e6TAnOCAB1ACk8UZZUyDlluqpQ +/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABZvqJ6BEAAAQDAEYwRAIgKj2gwlC/9AIF +hrx8nl2oEwxUoi/b4ZCCdQpz8JsJhOACIHRkuo56NErZR87lVtI0oZItZ6nzFPQz +BFaxx89eBJKPMA0GCSqGSIb3DQEBCwUAA4IBAQCFURbxUFKAF3CJRiaUsRrjFDuc +R25Dm86QgDqDLGkYf7/y5by2/amNzBbBLvcqUhScpjtalqO38Efw+2+c/UBGTQZ7 +C8vU5KrjG6bnOmoHx/t+ml4E88omMlEmlRIEiL/6yfpOnCCv+3IHJjUw8iPuH7+j +57Vhwtq6mOvId2QfDfL86ELgXKvBKfbqac7YfzUHVyB4WeXagfpYiObpspTud0tZ +Yf5Qs7g8GLkXfpkOJHG1+91Hi7+Pb1cCAqkkgIwFG4oeBdxAkxkaIyBRk3ZfnXYw +9G1qofLrs2UbLUhIEyMgavI7kKwLg1pwsYs4Yn+RVR5hZPkj0ZUnc4gW7wCP +-----END CERTIFICATE----- diff --git a/frontend/certs/old/certbot.log b/frontend/certs/old/certbot.log new file mode 100644 index 00000000..c930643a --- /dev/null +++ b/frontend/certs/old/certbot.log @@ -0,0 +1,12 @@ +Traceback (most recent call last): + File "/usr/bin/certbot", line 11, in + load_entry_point('certbot==0.10.2', 'console_scripts', 'certbot')() + File "/usr/lib/python2.7/dist-packages/certbot/main.py", line 836, in main + setup_logging(config) + File "/usr/lib/python2.7/dist-packages/certbot/main.py", line 700, in setup_logging + config, logfile=logfile, fmt=file_fmt) + File "/usr/lib/python2.7/dist-packages/certbot/main.py", line 667, in setup_log_file_handler + raise errors.Error(_PERM_ERR_FMT.format(error)) +Error: The following error was encountered: +[Errno 13] Permission denied: '/var/log/letsencrypt/letsencrypt.log' +If running as non-root, set --config-dir, --logs-dir, and --work-dir to writeable paths. diff --git a/frontend/certs/old/chain1.pem b/frontend/certs/old/chain1.pem new file mode 100644 index 00000000..0002462c --- /dev/null +++ b/frontend/certs/old/chain1.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/frontend/certs/old/fullchain1.pem b/frontend/certs/old/fullchain1.pem new file mode 100644 index 00000000..2a92418e --- /dev/null +++ b/frontend/certs/old/fullchain1.pem @@ -0,0 +1,61 @@ +-----BEGIN CERTIFICATE----- +MIIF+TCCBOGgAwIBAgISA8rwfiC3p89WYm/X9RR3XhzTMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODExMDkyMTEzNTNaFw0x +OTAyMDcyMTEzNTNaMBMxETAPBgNVBAMTCGVuZGFvLmlvMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA2NIGtVlzW3fktmv74YG23LKLM6xIxn/mIPt4PgZ+ +K3407GryQueToAnG5iTXTox5UnyArvqxAe5cTonrlWTai9Do5CuhrKI22EMpwrSl +Uo38dtNu2AHLENrY+aFzEwsVvkWGpIQ8/Y63T8M9ohiInF/S5ZPojIJxy7OVQ3io +MdLbb1atYfBcnt0DFKyMC1yxDyYj8w1jvwQWlKGBYQeRd2MTWEjHO7CJPgiRwiLi +rFBlDWnFz8GKntXLHI2pYL6+X4WgG9XfwzSN/w+ju48JAgCc6yTxvaQxQDpHst3D +kv4CXNTP3gt2RSIOkQldBQzi/FgBLDv/qSuXAnLGsIgk5QIDAQABo4IDDjCCAwow +DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM +BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBThSBGBk0ZSYT1ptylvWQGZqdkBDjAfBgNV +HSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYI +KwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYI +KwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBMG +A1UdEQQMMAqCCGVuZGFvLmlvMIH+BgNVHSAEgfYwgfMwCAYGZ4EMAQIBMIHmBgsr +BgEEAYLfEwEBATCB1jAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlw +dC5vcmcwgasGCCsGAQUFBwICMIGeDIGbVGhpcyBDZXJ0aWZpY2F0ZSBtYXkgb25s +eSBiZSByZWxpZWQgdXBvbiBieSBSZWx5aW5nIFBhcnRpZXMgYW5kIG9ubHkgaW4g +YWNjb3JkYW5jZSB3aXRoIHRoZSBDZXJ0aWZpY2F0ZSBQb2xpY3kgZm91bmQgYXQg +aHR0cHM6Ly9sZXRzZW5jcnlwdC5vcmcvcmVwb3NpdG9yeS8wggECBgorBgEEAdZ5 +AgQCBIHzBIHwAO4AdQB0ftqDMa0zEJEhnM4lT0Jwwr/9XkIgCMY3NXnmEHvMVgAA +AWb6ieWoAAAEAwBGMEQCIE8+auAtQOLY4U1yoEDvCTQULv4PS9Xdo1uJzQF5nhBA +AiBsfKR2UMxxIatzPOQPtuRrVEAv4+tX4o/ch5e6TAnOCAB1ACk8UZZUyDlluqpQ +/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABZvqJ6BEAAAQDAEYwRAIgKj2gwlC/9AIF +hrx8nl2oEwxUoi/b4ZCCdQpz8JsJhOACIHRkuo56NErZR87lVtI0oZItZ6nzFPQz +BFaxx89eBJKPMA0GCSqGSIb3DQEBCwUAA4IBAQCFURbxUFKAF3CJRiaUsRrjFDuc +R25Dm86QgDqDLGkYf7/y5by2/amNzBbBLvcqUhScpjtalqO38Efw+2+c/UBGTQZ7 +C8vU5KrjG6bnOmoHx/t+ml4E88omMlEmlRIEiL/6yfpOnCCv+3IHJjUw8iPuH7+j +57Vhwtq6mOvId2QfDfL86ELgXKvBKfbqac7YfzUHVyB4WeXagfpYiObpspTud0tZ +Yf5Qs7g8GLkXfpkOJHG1+91Hi7+Pb1cCAqkkgIwFG4oeBdxAkxkaIyBRk3ZfnXYw +9G1qofLrs2UbLUhIEyMgavI7kKwLg1pwsYs4Yn+RVR5hZPkj0ZUnc4gW7wCP +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/frontend/certs/old/privkey1.pem b/frontend/certs/old/privkey1.pem new file mode 100644 index 00000000..34e1d5e6 --- /dev/null +++ b/frontend/certs/old/privkey1.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDY0ga1WXNbd+S2 +a/vhgbbcsoszrEjGf+Yg+3g+Bn4rfjTsavJC55OgCcbmJNdOjHlSfICu+rEB7lxO +ieuVZNqL0OjkK6GsojbYQynCtKVSjfx2027YAcsQ2tj5oXMTCxW+RYakhDz9jrdP +wz2iGIicX9Llk+iMgnHLs5VDeKgx0ttvVq1h8Fye3QMUrIwLXLEPJiPzDWO/BBaU +oYFhB5F3YxNYSMc7sIk+CJHCIuKsUGUNacXPwYqe1cscjalgvr5fhaAb1d/DNI3/ +D6O7jwkCAJzrJPG9pDFAOkey3cOS/gJc1M/eC3ZFIg6RCV0FDOL8WAEsO/+pK5cC +csawiCTlAgMBAAECggEALGoxp5qQT+9qcZgDO/mnbORCMa6cJdVzXdRFmGcaF2y+ +CKQLxnrLud/m16Q6WsPZ4nTQo4eFFQEv9YK5OJw1TKjZ3Eu3zbZZB8oSVulMaHHf +grPjI+qSH3zFL7XL7d26cYSqhS71k3dVw8gZ6wedjHLwr8ixvX7HMtQxwmWIBtRr +Sj9zYyqkoBLDWrhoGh7GlAHHiPw4cFY7XRaAbq36JYhkO+VUK7BRJMMPwSSMbmx1 +S3O8PhXy5he893HVRAHhQq8xIup/Q+PktB39xgQX0M+mjMoqD9mJ0fgaKnff8K3U +C3ovU9YmAR9VTGE/1S4yeT5BreXGbCwXKGSHO4KBLQKBgQDtZWjbtUCHyuiNk8pp +17mgpWEZOHl7+hehO6uN2GwE4bdrBRxK3b+ZQRJqVRQnhQRkYA4x1vVOG05A0b2C +QJBERd66XWMZHs3820kexo+Lw0XgAaM5bIoW8VZxAhHL9J49piiboawp/sNlFPVl +HL0x5ztkP/7QHRwvInY9giVNAwKBgQDpz9S7N/wNwQf6NrqbboYpUkA8WcAjGdzi +gsgK47rm2Nvs5CTWgVykTVokN/HjUCnkzNcET2e0lwF3M2Z6f3wyWiwngrGCWVyL +YXgcMT080CPWvfpVZ/kjkOORsD4zhceVs/ECJDPwNH7rKderILxQld7BQ+PcY32n +5iu4L3Cd9wKBgQDIUWsjAhX6v+BuHwiNOYicoxCFHJ9+WvF3jwdbAQVdNS31s3FF +R0q2wi8M5M/F7Ttgi6FOswl5qBbnIVTdRSe3RJIGBmlpuBGvTUatHnXgRJ2AwUD/ +YrQ/WdRMNafYx4iDkuLvOIQzR5OPhxjvAkovguQd2tZkvDudFmJZ0qkxCwKBgAKK +R31hNlPP7Q5C2fQmVz7Lrfb54qR++29ajGHR543qfTktoVjTPvZqALi0AuS9Ujcy +IoPhePb1TQbGgC6EhAzn/eLQAK2x2teIz40+27N1b2490iJrZsGEKXTDvWlB4tE1 +i0DCs+3AJEDZy4YIbQNTHYBsBGW4jvzr+z2dBlQxAoGAZaQ8pL6wnaXqCQA/ULO+ +lcF5c98pOLHpNTE19zodDEpG1OmavHgDhThOXJYfogkeAJaoovaQ5hHS/YLkXVOU +Xd2JN5FLbAwLN1jorB5tH1+c2SOOXaGoi7BTM9/mcUkJqYIthK4SoYw0oO2nphGZ +Mm8KDWDzu7D3r3XB8Xpl8ng= +-----END PRIVATE KEY----- diff --git a/frontend/certs/privkey.pem b/frontend/certs/privkey.pem new file mode 100644 index 00000000..5700cd10 --- /dev/null +++ b/frontend/certs/privkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvieP8bQzFIRYe +cPSgL6NLTeh8TqFcLzvPSHqI2HmVF1WiH2jYfbaJA1OZY5YHnqip0FAQ54Jw0V7F +g7Mv1L3UhXLdD6EN6/muQnji4U/RW2AptNz9ooikRxJg91rZvWVvWYBikn8eq/Mw +Osn6LMKlMsSzaH6YP7YsDHsh7dk4T6YWFTtph0nuttShvsFP1zHNungIsaNpkcUq +sLW5p9JKHdsUWVcmliaV66cZVL3gFS695l1YkYsUw3VwAmIwYt7CwqnlhcoH16If +qACD3S51Fpqc/Pc2AVr/yqO72IDrhglwM2v0CPTgQie1Fs8mbbzr5Osltp/nsRIJ +LfZypoMTAgMBAAECggEAVrRic0WeACTWmxSqEBFXtBN4WSYxl6oQ/dLMC5n5fAX5 +m0mubPizV7vX6DUDXyIkJzSox6DCkl0oVaE2udJ4LWf5E23r3EeJnWRh95bY6Q0c +U51tZq6IlbQxRMoJCvH9D2IDAy1fMtQ2G8V+DF9diWGpPQDJPwSBRAKqM1kbaS9E +40zoKzzkYWQYHnNrgiLnUpT6LxVJrKQo4B/HxhoovlQ+Uu8oRbK+JdRvKTWQ7ELV +evRtXxHSeq+22TdTysDfalOVWudaUSe2CqzX90mGzxerA7Baorb0Fas9nhTPFZus +pqOYJPDUSU0SPj939lArlsAHCpUY2/NOK6tOeotQ0QKBgQDX7I/GmqiN4BXnmlBC +fvix1BSQNs0d1fVMWiVXhgjUnTWc7oyEnE5tDkS6OyUKFHiK+xYxhy9rjis8ch+h +CfyafADku6y3lG2QwktDA48cRavzmRZhwbbDqsRLIKRUqJyon8Xn4jsX0QTTHM75 +jJPSFLny0nsvIbQgixz7D0FrmQKBgQDQHnOIRvcYYGWV9kxGw+l9I+JNnX8RZWmD +u9fZnqUQ01NtR6DHhmes73GtvRmhEeEQgICwpmBY65cUYHYkQ6WmhxdPGrSxLLCn +539nvAhVs5WgFbEGsV7alFob1A2kbMWxnmvtk/mmaXyFf1D7jgDn9mabtsXlUVzk +2OW+/mAviwKBgH0LGSQ5sn0fFXBflDU+FMXe/N2bbuPlYT4LQm7SR1DhjgfIugWe +A2jyh2iWEdgpjLlnuS2LjTGrTmyd5qU7BFdukpHkAZz6zwyzCCTR7dHAo1jaAfUw +2qerwCuFxsTkNH0Oseycvf4H5NRhbZdlCsHxkNIHhKEB0q+6AFmANzGZAoGBAK5r +Zu4mc34Fg/3Lfp+sH2oWgdoEjfL5aBXUfxFOaQ6R3ZBvgGrX1NnVjxNJsUoRXuti +lvf1aDf9sg+MzFaWLyVCgKF2Q0tdpewCEv/QUbfpmxOgOk3epx1PSbma3ZgY5RJ4 +MAHbi1YRgB+t1SQOHepJ0jLcWjxXFSBiOyH3tIsDAoGANQ6zOvNV/Mjkj82AJZGa +bW21TcMWBZhCJZc7jI93S9oCJl+qp9eHjUVbsg6pf+8MWOmMtXFLgC/idJF8tB1E +OzsW2xoKKSstj8JdAPsx8vR2kkLfkXPMT6ymOA7Lbc4ZwKSDbwo5xdAR8qBAnkic +xIKG1OZD55i9MuHng2uBJBc= +-----END PRIVATE KEY----- diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 00000000..150d0612 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,55 @@ +user nobody nogroup; +worker_processes auto; # auto-detect number of logical CPU cores + +events { + worker_connections 512; # set the max number of simultaneous connections (per worker process) +} + +http { + client_max_body_size 250M; + + include mime.types; + + # thanks stackoverflow http://stackoverflow.com/a/5132440/2406040 + gzip on; + gzip_http_version 1.1; + gzip_vary on; + gzip_comp_level 6; + gzip_proxied any; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml; + + # make sure gzip does not lose large gzipped js or css files + # see http://blog.leetsoft.com/2007/07/25/nginx-gzip-ssl.html + gzip_buffers 16 8k; + + # Disable gzip for certain browsers. + gzip_disable “MSIE [1-6].(?!.*SV1)”; + + server { + listen 80; + server_name "shuffler.io"; + return 301 https://$server_name$request_uri; + } + + server { + listen 443 ssl; + server_name "shuffler.io"; + ssl_certificate fullchain.cert.pem; + ssl_certificate_key privkey.pem; + ssl_protocols TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + root /usr/share/nginx/html; + gzip_static on; + expires 1y; + add_header Cache-Control public; + add_header ETag ""; + try_files $uri /index.html; + } + + location /api/v1 { + proxy_pass http://192.168.239.142:5001; + } + } +} diff --git a/frontend/nginxtest.conf b/frontend/nginxtest.conf new file mode 100644 index 00000000..59405dca --- /dev/null +++ b/frontend/nginxtest.conf @@ -0,0 +1,89 @@ +user nobody nogroup; +worker_processes auto; # auto-detect number of logical CPU cores + +events { + worker_connections 512; # set the max number of simultaneous connections (per worker process) +} + +http { + client_max_body_size 250M; + + include mime.types; + + # thanks stackoverflow http://stackoverflow.com/a/5132440/2406040 + gzip on; + gzip_http_version 1.1; + gzip_vary on; + gzip_comp_level 6; + gzip_proxied any; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml; + + # make sure gzip does not lose large gzipped js or css files + # see http://blog.leetsoft.com/2007/07/25/nginx-gzip-ssl.html + gzip_buffers 16 8k; + + # Disable gzip for certain browsers. + gzip_disable “MSIE [1-6].(?!.*SV1)”; + + server { + listen 80; + server_name "localhost"; + location / { + # avoid clickjacking + add_header X-Frame-Options DENY; + # block MIME sniffing + add_header X-Content-Type-Options nosniff; + + # security headers + add_header X-XSS-Protection "1; mode=block"; + # add_header Content-Security-Policy "default-src 'self'"; + add_header Referrer-Policy "no-referrer"; + server_tokens off; + + root /usr/share/nginx/html; + gzip_static on; + expires 1y; + add_header Cache-Control public; + add_header ETag ""; + try_files $uri /index.html; + } + + location /api/v1 { + proxy_pass http://shuffle-backend:5001; + } + } + + server { + listen 443 ssl; + server_name "localhost"; + ssl_certificate fullchain.cert.pem; + ssl_certificate_key privkey.pem; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + # avoid clickjacking + add_header X-Frame-Options DENY; + # block MIME sniffing + add_header X-Content-Type-Options nosniff; + + # security headers + add_header X-XSS-Protection "1; mode=block"; + # add_header Content-Security-Policy "default-src 'self'"; + add_header Referrer-Policy "no-referrer"; + server_tokens off; + + root /usr/share/nginx/html; + gzip_static on; + expires 1y; + add_header Cache-Control public; + add_header ETag ""; + try_files $uri /index.html; + } + + # Get the hostname from environment here? + location /api/v1 { + proxy_pass http://shuffle-backend:5001; + } + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 00000000..94f34ac5 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,23203 @@ +{ + "name": "shuffler", + "version": "0.3.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", + "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helpers": "^7.2.0", + "@babel/parser": "^7.2.2", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.2.2", + "@babel/types": "^7.2.2", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.10", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "requires": { + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", + "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", + "requires": { + "@babel/types": "^7.3.0", + "esutils": "^2.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", + "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz", + "integrity": "sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-split-export-declaration": "^7.4.4" + } + }, + "@babel/helper-define-map": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", + "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.4.4", + "lodash": "^4.17.11" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", + "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz", + "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/types": "^7.4.4", + "lodash": "^4.17.11" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + }, + "@babel/helper-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz", + "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==", + "requires": { + "lodash": "^4.17.11" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz", + "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", + "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "requires": { + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", + "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz", + "integrity": "sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.3.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.3.0.tgz", + "integrity": "sha512-3W/oCUmsO43FmZIqermmq6TKaRSYhmh/vybPfVFwQWdSb8xwki38uAIvknCRzuyHRuYfCYmJzL9or1v0AffPjg==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.3.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-decorators": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz", + "integrity": "sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", + "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz", + "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-flow": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz", + "integrity": "sha512-r6YMuZDWLtLlu0kqIim5o/3TNRAlWb073HwT3e2nKf9I8IIvOggPrnILYPsrrKilmn/mYEMCf/Z07w3yQJF6dg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", + "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", + "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz", + "integrity": "sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz", + "integrity": "sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.11" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz", + "integrity": "sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-split-export-declaration": "^7.4.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz", + "integrity": "sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", + "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-flow-strip-types": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.2.3.tgz", + "integrity": "sha512-xnt7UIk9GYZRitqCnsVMjQK1O2eKZwFB3CvvHjf5SGx6K6vr/MScCKQDnf1DxRaj501e3pXjti+inbSXX2ZUoQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.2.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", + "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", + "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz", + "integrity": "sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==", + "requires": { + "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz", + "integrity": "sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", + "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", + "requires": { + "regexp-tree": "^0.1.6" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", + "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", + "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "requires": { + "@babel/helper-call-delegate": "^7.4.4", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.2.0.tgz", + "integrity": "sha512-YYQFg6giRFMsZPKUM9v+VcHOdfSQdz9jHCx3akAi3UYgyjndmdYGSXylQ/V+HswQt4fL8IklchD9HTsaOCrWQQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", + "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", + "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", + "requires": { + "@babel/helper-builder-react-jsx": "^7.3.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", + "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz", + "integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", + "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "requires": { + "regenerator-transform": "^0.14.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", + "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz", + "integrity": "sha512-jIgkljDdq4RYDnJyQsiWbdvGeei/0MOTtSHKO/rfbd/mXBxNpdlulMx49L0HQ4pug1fXannxoqCI+fYSle9eSw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "resolve": "^1.8.1", + "semver": "^5.5.1" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", + "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.4.5.tgz", + "integrity": "sha512-RPB/YeGr4ZrFKNwfuQRlMf2lxoCUaU01MTw39/OFE/RiL8HDjtn68BwEPft1P7JN4akyEmjGWAMNldOV7o9V2g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.2.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", + "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/preset-env": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.5.tgz", + "integrity": "sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.4.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.4.4", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.4.4", + "@babel/plugin-transform-classes": "^7.4.4", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.4", + "@babel/plugin-transform-function-name": "^7.4.4", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-member-expression-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.4.4", + "@babel/plugin-transform-modules-systemjs": "^7.4.4", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", + "@babel/plugin-transform-new-target": "^7.4.4", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.4.4", + "@babel/plugin-transform-property-literals": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.4.5", + "@babel/plugin-transform-reserved-words": "^7.2.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.4.4", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "browserslist": "^4.6.0", + "core-js-compat": "^3.1.1", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.5.0" + } + }, + "@babel/preset-react": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", + "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0" + } + }, + "@babel/preset-typescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.1.0.tgz", + "integrity": "sha512-LYveByuF9AOM8WrsNne5+N79k1YxjNB6gmpCQsnuSBAcV8QUeB+ZUxQzL7Rz7HksPbahymKkq2qBR+o36ggFZA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.1.0" + } + }, + "@babel/runtime": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", + "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", + "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@babel/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", + "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + } + } + }, + "@csstools/convert-colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" + }, + "@date-io/moment": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-1.3.5.tgz", + "integrity": "sha512-b0JQb10Lie07iW2/9uKCQSrXif262d6zfYBstCLLJUk0JVA+7o/yLDg5p2+GkjgJbmodjHozIXs4Bi34RRhL8Q==" + }, + "@emotion/hash": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", + "integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw==" + }, + "@emotion/is-prop-valid": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.3.tgz", + "integrity": "sha512-We7VBiltAJ70KQA0dWkdPMXnYoizlxOXpvtjmu5/MBnExd+u0PGgV27WCYanmLAbCwAU30Le/xA0CQs/F/Otig==", + "requires": { + "@emotion/memoize": "0.7.3" + } + }, + "@emotion/memoize": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.3.tgz", + "integrity": "sha512-2Md9mH6mvo+ygq1trTeVp2uzAKwE2P7In0cRpD/M9Q70aH8L+rxMLbb3JCN2JoSWsV2O+DdFjfbbXoMoLBczow==" + }, + "@emotion/unitless": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.4.tgz", + "integrity": "sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ==" + }, + "@fortawesome/fontawesome-free": { + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.11.2.tgz", + "integrity": "sha512-XiUPoS79r1G7PcpnNtq85TJ7inJWe0v+b5oZJZKb0pGHNIV6+UiNeQWiFGmuQ0aj7GEhnD/v9iqxIsjuRKtEnQ==" + }, + "@material-ui/core": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.3.tgz", + "integrity": "sha512-REIj62+zEvTgI/C//YL4fZxrCVIySygmpZglsu/Nl5jPqy3CDjZv1F9ubBYorHqmRgeVPh64EghMMWqk4egmfg==", + "requires": { + "@babel/runtime": "^7.2.0", + "@material-ui/system": "^3.0.0-alpha.0", + "@material-ui/utils": "^3.0.0-alpha.2", + "@types/jss": "^9.5.6", + "@types/react-transition-group": "^2.0.8", + "brcast": "^3.0.1", + "classnames": "^2.2.5", + "csstype": "^2.5.2", + "debounce": "^1.1.0", + "deepmerge": "^3.0.0", + "dom-helpers": "^3.2.1", + "hoist-non-react-statics": "^3.2.1", + "is-plain-object": "^2.0.4", + "jss": "^9.8.7", + "jss-camel-case": "^6.0.0", + "jss-default-unit": "^8.0.2", + "jss-global": "^3.0.0", + "jss-nested": "^6.0.1", + "jss-props-sort": "^6.0.0", + "jss-vendor-prefixer": "^7.0.0", + "normalize-scroll-left": "^0.1.2", + "popper.js": "^1.14.1", + "prop-types": "^15.6.0", + "react-event-listener": "^0.6.2", + "react-transition-group": "^2.2.1", + "recompose": "0.28.0 - 0.30.0", + "warning": "^4.0.1" + } + }, + "@material-ui/icons": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.5.1.tgz", + "integrity": "sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, + "@material-ui/styles": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", + "integrity": "sha512-O0NSAECHK9f3DZK6wy56PZzp8b/7KSdfpJs8DSC7vnXUAoMPCTtchBKLzMtUsNlijiJFeJjSxNdQfjWXgyur5A==", + "requires": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.7.1", + "@material-ui/types": "^4.1.1", + "@material-ui/utils": "^4.1.0", + "clsx": "^1.0.2", + "csstype": "^2.5.2", + "deepmerge": "^4.0.0", + "hoist-non-react-statics": "^3.2.1", + "jss": "^10.0.0", + "jss-plugin-camel-case": "^10.0.0", + "jss-plugin-default-unit": "^10.0.0", + "jss-plugin-global": "^10.0.0", + "jss-plugin-nested": "^10.0.0", + "jss-plugin-props-sort": "^10.0.0", + "jss-plugin-rule-value-function": "^10.0.0", + "jss-plugin-vendor-prefixer": "^10.0.0", + "prop-types": "^15.7.2" + }, + "dependencies": { + "@material-ui/utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.4.0.tgz", + "integrity": "sha512-UXoQVwArQEQWXxf2FPs0iJGT+MePQpKr0Qh0CPoLc1OdF0GSMTmQczcqCzwZkeHxHAOq/NkIKM1Pb/ih1Avicg==", + "requires": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.6" + } + }, + "deepmerge": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.0.tgz", + "integrity": "sha512-/pED+kD8V9n15L1lon8DXEiWLQMW4tTiegn1kIWIQ+DBudOkFitz1cfjWQiSeKMPBQOknT3LpueyAmMVJ1Ho2g==" + }, + "jss": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", + "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + } + } + }, + "@material-ui/system": { + "version": "3.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz", + "integrity": "sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA==", + "requires": { + "@babel/runtime": "^7.2.0", + "deepmerge": "^3.0.0", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + } + }, + "@material-ui/types": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-4.1.1.tgz", + "integrity": "sha512-AN+GZNXytX9yxGi0JOfxHrRTbhFybjUJ05rnsBVjcB+16e466Z0Xe5IxawuOayVZgTBNDxmPKo5j4V6OnMtaSQ==", + "requires": { + "@types/react": "*" + } + }, + "@material-ui/utils": { + "version": "3.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz", + "integrity": "sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng==", + "requires": { + "@babel/runtime": "^7.2.0", + "prop-types": "^15.6.0", + "react-is": "^16.6.3" + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" + }, + "@reach/auto-id": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.2.0.tgz", + "integrity": "sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg==" + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", + "integrity": "sha512-j7KnilGyZzYr/jhcrSYS3FGWMZVaqyCG0vzMCwzvei0coIkczuYMcniK07nI0aHJINciujjH11T72ICW5eL5Ig==" + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-4.2.0.tgz", + "integrity": "sha512-3XHLtJ+HbRCH4n28S7y/yZoEQnRpl0tvTZQsHqvaeNXPra+6vE5tbRliH3ox1yZYPCxrlqaJT/Mg+75GpDKlvQ==" + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-4.2.0.tgz", + "integrity": "sha512-yTr2iLdf6oEuUE9MsRdvt0NmdpMBAkgK8Bjhl6epb+eQWk6abBaX3d65UZ3E3FWaOwePyUgNyNCMVG61gGCQ7w==" + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-4.2.0.tgz", + "integrity": "sha512-U9m870Kqm0ko8beHawRXLGLvSi/ZMrl89gJ5BNcT452fAjtF2p4uRzXkdzvGJJJYBgx7BmqlDjBN/eCp5AAX2w==" + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.0.tgz", + "integrity": "sha512-3eI17Pb3jlg3oqV4Tie069n1SelYKBUpI90txDcnBWk4EGFW+YQGyQjy6iuJAReH0RnpUJ9jUExrt/xniGvhqw==" + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-4.2.0.tgz", + "integrity": "sha512-C0Uy+BHolCHGOZ8Dnr1zXy/KgpBOkEUYY9kI/HseHVPeMbluaX3CijJr7D4C5uR8zrc1T64nnq/k63ydQuGt4w==" + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-4.2.0.tgz", + "integrity": "sha512-7YvynOpZDpCOUoIVlaaOUU87J4Z6RdD6spYN4eUb5tfPoKGSF9OG2NuhgYnq4jSkAxcpMaXWPf1cePkzmqTPNw==" + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-4.2.0.tgz", + "integrity": "sha512-hYfYuZhQPCBVotABsXKSCfel2slf/yvJY8heTVX1PCTaq/IgASq1IyxPPKJ0chWREEKewIU/JMSsIGBtK1KKxw==" + }, + "@svgr/babel-preset": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-4.3.0.tgz", + "integrity": "sha512-Lgy1RJiZumGtv6yJroOxzFuL64kG/eIcivJQ7y9ljVWL+0QXvFz4ix1xMrmjMD+rpJWwj50ayCIcFelevG/XXg==", + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "^4.2.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^4.2.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^4.2.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^4.2.0", + "@svgr/babel-plugin-svg-dynamic-title": "^4.3.0", + "@svgr/babel-plugin-svg-em-dimensions": "^4.2.0", + "@svgr/babel-plugin-transform-react-native-svg": "^4.2.0", + "@svgr/babel-plugin-transform-svg-component": "^4.2.0" + } + }, + "@svgr/core": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-4.3.0.tgz", + "integrity": "sha512-Ycu1qrF5opBgKXI0eQg3ROzupalCZnSDETKCK/3MKN4/9IEmt3jPX/bbBjftklnRW+qqsCEpO0y/X9BTRw2WBg==", + "requires": { + "@svgr/plugin-jsx": "^4.3.0", + "camelcase": "^5.3.1", + "cosmiconfig": "^5.2.0" + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-4.2.0.tgz", + "integrity": "sha512-IvAeb7gqrGB5TH9EGyBsPrMRH/QCzIuAkLySKvH2TLfLb2uqk98qtJamordRQTpHH3e6TORfBXoTo7L7Opo/Ow==", + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@svgr/plugin-jsx": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-4.3.0.tgz", + "integrity": "sha512-0ab8zJdSOTqPfjZtl89cjq2IOmXXUYV3Fs7grLT9ur1Al3+x3DSp2+/obrYKUGbQUnLq96RMjSZ7Icd+13vwlQ==", + "requires": { + "@babel/core": "^7.4.3", + "@svgr/babel-preset": "^4.3.0", + "@svgr/hast-util-to-babel-ast": "^4.2.0", + "rehype-parse": "^6.0.0", + "unified": "^7.1.0", + "vfile": "^4.0.0" + }, + "dependencies": { + "@babel/core": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", + "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helpers": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.5", + "@babel/types": "^7.4.4", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@svgr/plugin-svgo": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-4.2.0.tgz", + "integrity": "sha512-zUEKgkT172YzHh3mb2B2q92xCnOAMVjRx+o0waZ1U50XqKLrVQ/8dDqTAtnmapdLsGurv8PSwenjLCUpj6hcvw==", + "requires": { + "cosmiconfig": "^5.2.0", + "merge-deep": "^3.0.2", + "svgo": "^1.2.1" + } + }, + "@svgr/webpack": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-4.1.0.tgz", + "integrity": "sha512-d09ehQWqLMywP/PT/5JvXwPskPK9QCXUjiSkAHehreB381qExXf5JFCBWhfEyNonRbkIneCeYM99w+Ud48YIQQ==", + "requires": { + "@babel/core": "^7.1.6", + "@babel/plugin-transform-react-constant-elements": "^7.0.0", + "@babel/preset-env": "^7.1.6", + "@babel/preset-react": "^7.0.0", + "@svgr/core": "^4.1.0", + "@svgr/plugin-jsx": "^4.1.0", + "@svgr/plugin-svgo": "^4.0.3", + "loader-utils": "^1.1.0" + } + }, + "@types/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" + }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "@types/jss": { + "version": "9.5.8", + "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.8.tgz", + "integrity": "sha512-bBbHvjhm42UKki+wZpR89j73ykSXg99/bhuKuYYePtpma3ZAnmeGnl0WxXiZhPGsIfzKwCUkpPC0jlrVMBfRxA==", + "requires": { + "csstype": "^2.0.0", + "indefinite-observable": "^1.0.1" + } + }, + "@types/node": { + "version": "12.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.8.tgz", + "integrity": "sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==" + }, + "@types/object-assign": { + "version": "4.0.30", + "resolved": "https://registry.npmjs.org/@types/object-assign/-/object-assign-4.0.30.tgz", + "integrity": "sha1-iUk3HVqZ9Dge4PHfCpt6GH4H5lI=" + }, + "@types/prop-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", + "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==" + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==" + }, + "@types/react": { + "version": "16.8.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.20.tgz", + "integrity": "sha512-ZLmI+ubSJpfUIlQuULDDrdyuFQORBuGOvNnMue8HeA0GVrAJbWtZQhcBvnBPNRBI/GrfSfrKPFhthzC2SLEtLQ==", + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "@types/react-text-mask": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/@types/react-text-mask/-/react-text-mask-5.4.6.tgz", + "integrity": "sha512-0KkER9oXZY/v1x8aoMTHwANlWnKT5tnmV7Zz+g81gBvcHRtcIHotcpY4KgWRwx0T5JMcsYmEh7wGOz0lwdONew==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-transition-group": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.2.tgz", + "integrity": "sha512-5Fv2DQNO+GpdPZcxp2x/OQG/H19A01WlmpjVD9cKvVFmoVLOZ9LvBgSWG6pSXIU4og5fgbvGPaCV5+VGkWAEHA==", + "requires": { + "@types/react": "*" + } + }, + "@types/tapable": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.2.tgz", + "integrity": "sha512-42zEJkBpNfMEAvWR5WlwtTH22oDzcMjFsL9gDGExwF8X8WvAiw7Vwop7hPw03QT8TKfec83LwbHj6SvpqM4ELQ==" + }, + "@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" + }, + "@types/vfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", + "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", + "requires": { + "@types/node": "*", + "@types/unist": "*", + "@types/vfile-message": "*" + } + }, + "@types/vfile-message": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-1.0.1.tgz", + "integrity": "sha512-mlGER3Aqmq7bqR1tTTIVHq8KSAFFRyGbrxuM8C/H82g6k7r2fS+IMEkIu3D7JHzG10NvPdR8DNx0jr0pwpp4dA==", + "requires": { + "@types/node": "*", + "@types/unist": "*" + } + }, + "@use-it/interval": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@use-it/interval/-/interval-0.1.3.tgz", + "integrity": "sha512-chshdtDZTFoWA9aszBz1Cc04Ca9NBD2JTi/GMjdJ+HGm4q7Vy1v71+2mm22r7Kfb2nYW+lTRsPcEHdB/VFVHsQ==" + }, + "@webassemblyjs/ast": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz", + "integrity": "sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==", + "requires": { + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz", + "integrity": "sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz", + "integrity": "sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz", + "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==" + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz", + "integrity": "sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==", + "requires": { + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz", + "integrity": "sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==" + }, + "@webassemblyjs/helper-module-context": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz", + "integrity": "sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==" + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz", + "integrity": "sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz", + "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz", + "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==", + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz", + "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==", + "requires": { + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/utf8": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz", + "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz", + "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/helper-wasm-section": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-opt": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz", + "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz", + "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz", + "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz", + "integrity": "sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/floating-point-hex-parser": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-code-frame": "1.7.11", + "@webassemblyjs/helper-fsm": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz", + "integrity": "sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "@xtuc/long": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.1.tgz", + "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==" + }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==" + }, + "acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "requires": { + "acorn": "^5.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + } + } + }, + "acorn-globals": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", + "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + } + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==" + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==" + }, + "address": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", + "integrity": "sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg==" + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + }, + "ajv-keywords": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==" + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "append-transform": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", + "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "requires": { + "default-require-extensions": "^1.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + }, + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=" + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=" + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=" + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "attr-accept": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", + "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", + "requires": { + "core-js": "^2.5.0" + } + }, + "autoprefixer": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.0.tgz", + "integrity": "sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==", + "requires": { + "browserslist": "^4.6.1", + "caniuse-lite": "^1.0.30000971", + "chalk": "^2.4.2", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.16", + "postcss-value-parser": "^3.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "axobject-query": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "requires": { + "ast-types-flow": "0.0.7" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + } + } + }, + "babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==" + }, + "babel-eslint": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-9.0.0.tgz", + "integrity": "sha512-itv1MwE3TMbY0QtNfeL7wzak1mV47Uy+n6HtSOO4Xd7rvmO+tsGQSgyOEEgo6Y2vHZKZphaoelNeSVj4vkLA1g==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "^1.0.0" + } + }, + "babel-extract-comments": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz", + "integrity": "sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ==", + "requires": { + "babylon": "^6.18.0" + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + } + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-jest": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", + "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", + "requires": { + "babel-plugin-istanbul": "^4.1.6", + "babel-preset-jest": "^23.2.0" + } + }, + "babel-loader": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz", + "integrity": "sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==", + "requires": { + "find-cache-dir": "^2.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1", + "util.promisify": "^1.0.0" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz", + "integrity": "sha512-fP899ELUnTaBcIzmrW7nniyqqdYWrWuJUyPWHxFa/c7r7hS6KC8FscNfLlBNIoPSc55kYMGEEKjPjJGCLbE1qA==", + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.13.0", + "find-up": "^2.1.0", + "istanbul-lib-instrument": "^1.10.1", + "test-exclude": "^4.2.1" + } + }, + "babel-plugin-jest-hoist": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", + "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=" + }, + "babel-plugin-macros": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.5.0.tgz", + "integrity": "sha512-BWw0lD0kVZAXRD3Od1kMrdmfudqzDzYv2qrN3l2ISR1HVp1EgLKfbOrYV9xmY5k3qx3RIu5uPAUZZZHpo0o5Iw==", + "requires": { + "cosmiconfig": "^5.0.5", + "resolve": "^1.8.1" + } + }, + "babel-plugin-named-asset-import": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.2.tgz", + "integrity": "sha512-CxwvxrZ9OirpXQ201Ec57OmGhmI8/ui/GwTDy0hSp6CmRvgRC0pSair6Z04Ck+JStA0sMPZzSJ3uE4n17EXpPQ==" + }, + "babel-plugin-styled-components": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.6.tgz", + "integrity": "sha512-gyQj/Zf1kQti66100PhrCRjI5ldjaze9O0M3emXRPAN80Zsf8+e1thpTpaXJXVHXtaM4/+dJEgZHyS9Its+8SA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" + } + }, + "babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" + }, + "babel-preset-jest": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", + "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", + "requires": { + "babel-plugin-jest-hoist": "^23.2.0", + "babel-plugin-syntax-object-rest-spread": "^6.13.0" + } + }, + "babel-preset-react-app": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-7.0.2.tgz", + "integrity": "sha512-mwCk/u2wuiO8qQqblN5PlDa44taY0acq7hw6W+a70W522P7a9mIcdggL1fe5/LgAT7tqCq46q9wwhqaMoYKslQ==", + "requires": { + "@babel/core": "7.2.2", + "@babel/plugin-proposal-class-properties": "7.3.0", + "@babel/plugin-proposal-decorators": "7.3.0", + "@babel/plugin-proposal-object-rest-spread": "7.3.2", + "@babel/plugin-syntax-dynamic-import": "7.2.0", + "@babel/plugin-transform-classes": "7.2.2", + "@babel/plugin-transform-destructuring": "7.3.2", + "@babel/plugin-transform-flow-strip-types": "7.2.3", + "@babel/plugin-transform-react-constant-elements": "7.2.0", + "@babel/plugin-transform-react-display-name": "7.2.0", + "@babel/plugin-transform-runtime": "7.2.0", + "@babel/preset-env": "7.3.1", + "@babel/preset-react": "7.0.0", + "@babel/preset-typescript": "7.1.0", + "@babel/runtime": "7.3.1", + "babel-loader": "8.0.5", + "babel-plugin-dynamic-import-node": "2.2.0", + "babel-plugin-macros": "2.5.0", + "babel-plugin-transform-react-remove-prop-types": "0.4.24" + }, + "dependencies": { + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz", + "integrity": "sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz", + "integrity": "sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.3.2.tgz", + "integrity": "sha512-Lrj/u53Ufqxl/sGxyjsJ2XNtNuEjDyjpqdhMNh5aZ+XFOdThL46KBj27Uem4ggoezSYBxKWAil6Hu8HtwqesYw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/preset-env": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.3.1.tgz", + "integrity": "sha512-FHKrD6Dxf30e8xgHQO0zJZpUPfVZg+Xwgz5/RdSWCbza9QLNk4Qbp40ctRoqDxml3O8RMzB1DU55SXeDG6PqHQ==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.2.0", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.2.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.2.0", + "@babel/plugin-transform-classes": "^7.2.0", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.2.0", + "@babel/plugin-transform-dotall-regex": "^7.2.0", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.2.0", + "@babel/plugin-transform-function-name": "^7.2.0", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.2.0", + "@babel/plugin-transform-modules-systemjs": "^7.2.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.3.0", + "@babel/plugin-transform-new-target": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.2.0", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.2.0", + "browserslist": "^4.3.4", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.3.0" + } + }, + "@babel/runtime": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz", + "integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + }, + "bail": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", + "integrity": "sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bfj": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.1.tgz", + "integrity": "sha512-+GUNvzHR4nRyGybQc2WpNJL4MJazMuvf92ueIyA0bIkPRwhhQu3IfZQ2PSoVPpCBJfmoSdOxu5rnotfFLlvYRQ==", + "requires": { + "bluebird": "^3.5.1", + "check-types": "^7.3.0", + "hoopy": "^0.1.2", + "tryer": "^1.0.0" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "bootstrap-css-only": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/bootstrap-css-only/-/bootstrap-css-only-4.3.1.tgz", + "integrity": "sha512-xPQNmTR6skX7boM3Q/K2vWDL8RFhfHm5PbTcn/vd7nZtkzg9tc6ScNreIIsMaP9QLUxeqvUx+OGnDaiK4KBRiQ==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "brcast": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.1.tgz", + "integrity": "sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg==" + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.2.tgz", + "integrity": "sha512-2neU/V0giQy9h3XMPwLhEY3+Ao0uHSwHvU8Q1Ea6AgLVL1sXbX3dzPrJ8NWe5Hi4PoTkCYXOtVR9rfRLI0J/8Q==", + "requires": { + "caniuse-lite": "^1.0.30000974", + "electron-to-chromium": "^1.3.150", + "node-releases": "^1.1.23" + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + } + } + }, + "buffer-from": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", + "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "cacache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, + "can-use-dom": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz", + "integrity": "sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo=" + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30000974", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000974.tgz", + "integrity": "sha512-xc3rkNS/Zc3CmpMKuczWEdY2sZgx09BkAxfvkxlAEBTqcMHeL8QnPqhKse+5sRTi3nrw2pJwToD2WvKn1Uhvww==" + }, + "capture-exit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", + "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", + "requires": { + "rsvp": "^3.3.3" + } + }, + "case-sensitive-paths-webpack-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.2.0.tgz", + "integrity": "sha512-u5ElzokS8A1pm9vM3/iDgTcI3xqHxuCao94Oz8etI3cf0Tio0p8izkDYbTIn09uP3yUUr6+veaE6IkjnTYS46g==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "ccount": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.4.tgz", + "integrity": "sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "change-emitter": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", + "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" + }, + "character-entities": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", + "integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==" + }, + "character-entities-legacy": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz", + "integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==" + }, + "character-reference-invalid": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz", + "integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==" + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "chart.js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.8.0.tgz", + "integrity": "sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.3.0.tgz", + "integrity": "sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^0.5.3" + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, + "check-types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz", + "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==" + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "optional": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + } + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" + }, + "class-transformer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.2.3.tgz", + "integrity": "sha512-qsP+0xoavpOlJHuYsQJsN58HXSl8Jvveo+T37rEvCEeRfMWoytAyR0Ua/YsFgpM6AZYZ/og2PJwArwzJl1aXtQ==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", + "requires": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + } + }, + "clsx": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz", + "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg==" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collapse-white-space": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.5.tgz", + "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } + }, + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "comma-separated-tokens": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz", + "integrity": "sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ==" + }, + "commander": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", + "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==" + }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "requires": { + "mime-db": ">= 1.40.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "compute-scroll-into-view": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.11.tgz", + "integrity": "sha512-uUnglJowSe0IPmWOdDtrlHXof5CTIJitfJEyITHBW6zDVOGu9Pjk5puaLM73SLcwak0L4hEjO7Td88/a6P5i7A==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "confusing-browser-globals": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.7.tgz", + "integrity": "sha512-cgHI1azax5ATrZ8rJ+ODDML9Fvu67PimB6aNxBrc/QwSaDaM9eTfIEUHx3bBLJJ82ioSb+/5zfsMCCEJax3ByQ==" + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "requires": { + "date-now": "^0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + }, + "core-js-compat": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", + "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", + "requires": { + "browserslist": "^4.6.2", + "core-js-pure": "3.1.4", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==" + } + } + }, + "core-js-pure": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz", + "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "create-react-app": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/create-react-app/-/create-react-app-2.1.8.tgz", + "integrity": "sha512-osHOZ3fw4BT4+L3CnhGsr/92CXEczQ/0fl/vd1Tbud4gT1ykt3a+zQlNJpOchDCnZjSqpHOxoWaH0XljFOhJLg==", + "requires": { + "chalk": "1.1.3", + "commander": "2.18.0", + "cross-spawn": "4.0.2", + "envinfo": "5.11.1", + "fs-extra": "5.0.0", + "hyperquest": "2.1.3", + "semver": "5.5.1", + "tar-pack": "3.4.1", + "tmp": "0.0.33", + "validate-npm-package-name": "3.0.0" + } + }, + "create-react-class": { + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "create-react-context": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", + "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", + "requires": { + "gud": "^1.0.0", + "warning": "^4.0.3" + } + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "requires": { + "postcss": "^7.0.5" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.0.tgz", + "integrity": "sha512-tMXlTYf3mIMt3b0dDCOQFJiVvxbocJ5Ho577WiGPYPZcqVEO218L2iU22pDXzkTZCLDE+9AmGSUkWxeh/nZReA==", + "requires": { + "babel-code-frame": "^6.26.0", + "css-selector-tokenizer": "^0.7.0", + "icss-utils": "^2.1.0", + "loader-utils": "^1.0.2", + "lodash.camelcase": "^4.3.0", + "postcss": "^6.0.23", + "postcss-modules-extract-imports": "^1.2.0", + "postcss-modules-local-by-default": "^1.2.0", + "postcss-modules-scope": "^1.1.0", + "postcss-modules-values": "^1.3.0", + "postcss-value-parser": "^3.3.0", + "source-list-map": "^2.0.0" + } + }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "requires": { + "postcss": "^7.0.5" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-select": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", + "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^2.1.2", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=" + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "css-to-react-native": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.2.tgz", + "integrity": "sha512-VOFaeZA053BqvvvqIA8c9n0+9vFppVBAHCp6JgFTtTMU3Mzi+XnelJ9XC9ul3BqFzZyQ5N+H0SnwsWT2Ebchxw==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^3.3.0" + } + }, + "css-tree": { + "version": "1.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", + "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + }, + "css-unit-converter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", + "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=" + }, + "css-url-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz", + "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=" + }, + "css-vendor": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz", + "integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=", + "requires": { + "is-in-browser": "^1.0.2" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "cssdb": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=" + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" + }, + "csso": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", + "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", + "requires": { + "css-tree": "1.0.0-alpha.29" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", + "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + } + } + }, + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==" + }, + "cssstyle": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", + "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", + "requires": { + "cssom": "0.3.x" + } + }, + "csstype": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.5.tgz", + "integrity": "sha512-JsTaiksRsel5n7XwqPAfB0l3TFKdpjW/kgAELf9vrb5adGA7UCPLajKK5s3nFrcFm3Rkyp/Qkgl73ENc1UY3cA==" + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" + }, + "cytoscape": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.11.0.tgz", + "integrity": "sha512-xA9S5UiTjwFwZjxZCTIJjDU5ef2XSqw+/gk+bLcqdOZ496EoPSfnbozGRnKZhxKIVT++NNyWtT7fW1eLU7denQ==", + "requires": { + "heap": "^0.2.6", + "lodash.debounce": "^4.0.8" + } + }, + "cytoscape-clipboard": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cytoscape-clipboard/-/cytoscape-clipboard-2.2.1.tgz", + "integrity": "sha512-9TUSA138FZFBqEKmwDwpx4BRIScJkZRZUfxaiQIzek01rOYObOhH2NOIFbvDiitGtBUoW2bQjAizQW+vDDm9Qw==" + }, + "cytoscape-cxtmenu": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cytoscape-cxtmenu/-/cytoscape-cxtmenu-3.1.1.tgz", + "integrity": "sha512-Rttr61Z3YP71GpCaN34IekO3rQJrf8fLv8ImCfQJ0J0ROGFMgk431WxXXStG40izZxZuspqi374x/3kSlT2idg==" + }, + "cytoscape-edgehandles": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/cytoscape-edgehandles/-/cytoscape-edgehandles-3.6.0.tgz", + "integrity": "sha512-XUzz+zmK42cN6l8uV7b66uUIZruAGgtgrfj/iuUOoCDWHMExARCS23bUju3Pqxywafe75Te3prXH0jrnZ+fYxw==", + "requires": { + "lodash.memoize": "^4.1.2", + "lodash.throttle": "^4.1.1" + } + }, + "cytoscape-grid-guide": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cytoscape-grid-guide/-/cytoscape-grid-guide-2.1.2.tgz", + "integrity": "sha512-PgyvTsIFL9jgoG3EBYfuMeg6ugTimg2UJPLYj3uJ6FMu+LUrUO93DaL07f9I4/bBGVlkSK1uH79oYjf18AyC1Q==", + "requires": { + "functional-red-black-tree": "^1.0.1" + } + }, + "cytoscape-node-html-label": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/cytoscape-node-html-label/-/cytoscape-node-html-label-1.1.5.tgz", + "integrity": "sha512-ztclrtuinJdJ0KwKTmd7Dgmqw+0WWXdZpRmxLUYU56DHIVek7Ma5Eh4FTGht0iyQUOakAPM0aZv3A4ifwMTW2Q==" + }, + "cytoscape-panzoom": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/cytoscape-panzoom/-/cytoscape-panzoom-2.5.3.tgz", + "integrity": "sha512-//qLOqbbFUCGddarNKHDZArItOJHgnkQ1TvxI9nV2/8aOOl/5wuEOHmra3fL/aWSjB4AYpYTG4LX7w96uWfRTQ==", + "requires": { + "jquery": "^1.4 || ^2.0 || ^3.0" + } + }, + "cytoscape-undo-redo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/cytoscape-undo-redo/-/cytoscape-undo-redo-1.3.2.tgz", + "integrity": "sha512-RnJI0uWc19gyWx/qWY2Q/vg159MR32dpXHk+QmQih2buLoC7fxYRBPSPgNGUkF8QIxSmkavQEic1ARQO7GBTtg==" + }, + "d3": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/d3/-/d3-4.10.2.tgz", + "integrity": "sha512-0PxXZbD+Remq9x4wdes1gs6rYcGJKA3+e0xwbma0r4ricKOKBHUHfDWcxKQICS3ZZxhzwNYWl196pxDqcAgRpw==", + "requires": { + "d3-array": "1.2.0", + "d3-axis": "1.0.8", + "d3-brush": "1.0.4", + "d3-chord": "1.0.4", + "d3-collection": "1.0.4", + "d3-color": "1.0.3", + "d3-dispatch": "1.0.3", + "d3-drag": "1.1.1", + "d3-dsv": "1.0.7", + "d3-ease": "1.0.3", + "d3-force": "1.0.6", + "d3-format": "1.2.0", + "d3-geo": "1.6.4", + "d3-hierarchy": "1.1.5", + "d3-interpolate": "1.1.5", + "d3-path": "1.0.5", + "d3-polygon": "1.0.3", + "d3-quadtree": "1.0.3", + "d3-queue": "3.0.7", + "d3-random": "1.1.0", + "d3-request": "1.0.6", + "d3-scale": "1.0.6", + "d3-selection": "1.1.0", + "d3-shape": "1.2.0", + "d3-time": "1.0.7", + "d3-time-format": "2.0.5", + "d3-timer": "1.0.7", + "d3-transition": "1.1.0", + "d3-voronoi": "1.1.2", + "d3-zoom": "1.5.0" + } + }, + "d3-array": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.0.tgz", + "integrity": "sha1-FH0mlyDhdMQFen9CvosPPyulMQg=" + }, + "d3-axis": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz", + "integrity": "sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo=" + }, + "d3-brush": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.4.tgz", + "integrity": "sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.4.tgz", + "integrity": "sha1-fexPC6iG9xP+ERxF92NBT290yiw=", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz", + "integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI=" + }, + "d3-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz", + "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=" + }, + "d3-dispatch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz", + "integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg=" + }, + "d3-drag": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.1.1.tgz", + "integrity": "sha512-51aazbUuZZhPZzXv9xxwPOJTeDSVv8cXNd8oFxqJyR8ZBD9yLd09CFGSDSm3ArViHg2D5Wo1qCaKl7Efj/qchg==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.7.tgz", + "integrity": "sha512-12szKhDhM/tM5U/Ch3hyJ7sMdcwPqMRmrUWitLLdPBMKO9Wuox95ezKZvemy/fxFbefLF/HIPKUmJMBLLuFDaQ==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz", + "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4=" + }, + "d3-force": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.0.6.tgz", + "integrity": "sha1-6n4bdzDiZkzTFPWU1nGMV8wTK3k=", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.0.tgz", + "integrity": "sha1-a0gLqohohdRlHcJIqPSsnaFtsHo=" + }, + "d3-geo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.6.4.tgz", + "integrity": "sha1-8g4eRhyxhF9ai+Vatvh2VCp+MZk=", + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz", + "integrity": "sha1-ochFxC+Eoga88cAcAQmOpN2qeiY=" + }, + "d3-interpolate": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.5.tgz", + "integrity": "sha1-aeCZ/zkhRxblY8muw+qdHqS4p58=", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.5.tgz", + "integrity": "sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q=" + }, + "d3-polygon": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.3.tgz", + "integrity": "sha1-FoiOkCZGCTPysXllKtN4Ik04LGI=" + }, + "d3-quadtree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz", + "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg=" + }, + "d3-queue": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", + "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=" + }, + "d3-random": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz", + "integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM=" + }, + "d3-request": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz", + "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-dsv": "1", + "xmlhttprequest": "1" + } + }, + "d3-scale": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.6.tgz", + "integrity": "sha1-vOGdqA06DPQiyVQ64zIghiILNO0=", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-color": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-selection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.1.0.tgz", + "integrity": "sha1-GZhoSJZIj4OcoDchI9o08dMYgJw=" + }, + "d3-shape": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.0.tgz", + "integrity": "sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.7.tgz", + "integrity": "sha1-lMr27bt4ebuAnQ0fdXK8SEgvcnA=" + }, + "d3-time-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.0.5.tgz", + "integrity": "sha1-nXeAIE98kRnJFwsaVttN6aivly4=", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz", + "integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA==" + }, + "d3-transition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.0.tgz", + "integrity": "sha1-z8hcdOUjkyQpBUZiNXKZBWDDlm8=", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha1-Fodmfo8TotFYyAwUgMWinLDYlzw=" + }, + "d3-zoom": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.5.0.tgz", + "integrity": "sha512-tc/ONeSUVuwHczjjK4jQPd0T1iZ+lfsz8TbguAAceY5qs057hp4WLglkPWValkuVjCyeGpqiA2iTm8S++NJ84w==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "damerau-levenshtein": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", + "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" + }, + "debounce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", + "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "deepmerge": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.1.tgz", + "integrity": "sha512-+hbDSzTqEW0fWgnlKksg7XAOtT+ddZS5lHZJ6f6MdixRs9wQy+50fm1uUCVb1IkvjLUYX/SfFO021ZNwriURTw==" + }, + "default-gateway": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz", + "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", + "requires": { + "execa": "^0.10.0", + "ip-regex": "^2.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, + "default-require-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", + "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "requires": { + "strip-bom": "^2.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + }, + "detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "requires": { + "utila": "~0.4" + } + }, + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", + "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" + }, + "dotenv-expand": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-4.2.0.tgz", + "integrity": "sha1-3vHxyl1gWdJKdm5YeULCEQbOEnU=" + }, + "downshift": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-3.3.5.tgz", + "integrity": "sha512-OLBKLfP8cUaB7/wF0Al74v+znYOV/BG6hMGIj/JMuPcnNHQ/1WrF3btCZx+KnJcYtfMdQHEwWv0yC1cdPinyXw==", + "requires": { + "@babel/runtime": "^7.4.5", + "@reach/auto-id": "^0.2.0", + "compute-scroll-into-view": "^1.0.9", + "prop-types": "^15.7.2", + "react-is": "^16.9.0" + }, + "dependencies": { + "react-is": { + "version": "16.10.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz", + "integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==" + } + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "requires": { + "readable-stream": "~1.1.9" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "electron-to-chromium": { + "version": "1.3.162", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.162.tgz", + "integrity": "sha512-/cCwFlLV0lImvAfNsJpEVIZFhJBoutb7L0AHd56K4h8McUqpdkbBvAbMnY/mfNKnCqkX6GZVvQc+BVod4t2EMw==" + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "envinfo": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-5.11.1.tgz", + "integrity": "sha512-rmEr5fZLYYSRCj3kDhriz6ju/oMgEzC92MwF3mggFba2EMjK+CUE13MQo17Ua2CDT+KFFPAGFosodUoL/wxjug==" + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, + "eslint": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.12.0.tgz", + "integrity": "sha512-LntwyPxtOHrsJdcSwyQKVtHofPHdv+4+mFwEe91r2V13vqpM8yLr7b1sW+Oo/yheOPkWYsYlYJCkzlFAt8KV7g==", + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "eslint-config-react-app": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-3.0.8.tgz", + "integrity": "sha512-Ovi6Bva67OjXrom9Y/SLJRkrGqKhMAL0XCH8BizPhjEVEhYczl2ZKiNZI2CuqO5/CJwAfMwRXAVGY0KToWr1aA==", + "requires": { + "confusing-browser-globals": "^1.0.6" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "eslint-loader": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.1.1.tgz", + "integrity": "sha512-1GrJFfSevQdYpoDzx8mEE2TDWsb/zmFuY09l6hURg1AeFIKQOvZ+vH0UPjzmd1CZIbfTV5HUkMeBmFiDBkgIsQ==", + "requires": { + "loader-fs-cache": "^1.0.0", + "loader-utils": "^1.0.2", + "object-assign": "^4.0.1", + "object-hash": "^1.1.4", + "rimraf": "^2.6.1" + } + }, + "eslint-module-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", + "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "eslint-plugin-flowtype": { + "version": "2.50.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.1.tgz", + "integrity": "sha512-9kRxF9hfM/O6WGZcZPszOVPd2W0TLHBtceulLTsGfwMPtiCCLnCW0ssRiOOiXyqrCA20pm1iXdXm7gQeN306zQ==", + "requires": { + "lodash": "^4.17.10" + } + }, + "eslint-plugin-import": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", + "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", + "requires": { + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.2.0", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.2.tgz", + "integrity": "sha512-7gSSmwb3A+fQwtw0arguwMdOdzmKUgnUcbSNlo+GjKLAQFuC2EZxWqG9XHRI8VscBJD5a8raz3RuxQNFW+XJbw==", + "requires": { + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.1", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^6.5.1", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", + "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==" + } + } + }, + "eslint-plugin-react": { + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", + "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", + "requires": { + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1", + "object.fromentries": "^2.0.0", + "prop-types": "^15.6.2", + "resolve": "^1.9.0" + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "requires": { + "eslint-visitor-keys": "^1.0.0" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==" + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exec-sh": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", + "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", + "requires": { + "merge": "^1.2.0" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "^2.1.0" + } + }, + "expect": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", + "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", + "requires": { + "ansi-styles": "^3.2.0", + "jest-diff": "^23.6.0", + "jest-get-type": "^22.1.0", + "jest-matcher-utils": "^23.6.0", + "jest-message-util": "^23.4.0", + "jest-regex-util": "^23.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "requires": { + "bser": "^2.0.0" + } + }, + "fbemitter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-2.1.1.tgz", + "integrity": "sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=", + "requires": { + "fbjs": "^0.8.4" + } + }, + "fbjs": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", + "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", + "requires": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "file-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-2.0.0.tgz", + "integrity": "sha512-YCsBfd1ZGCyonOKLxPiKPdu+8ld9HAaMEvJewzz+b2eTF7uL5Zm/HdBF6FjCrpCMRq25Mi0U1gl4pwn2TlH7hQ==", + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^1.0.0" + } + }, + "file-selector": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.12.tgz", + "integrity": "sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==" + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "flux": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/flux/-/flux-3.1.3.tgz", + "integrity": "sha1-0jvtUVp5oi2TOrU6tK2hnQWy8Io=", + "requires": { + "fbemitter": "^2.0.0", + "fbjs": "^0.8.0" + } + }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "fork-ts-checker-webpack-plugin": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.0.0-alpha.6.tgz", + "integrity": "sha512-s/V+58nLrUjuXyzYk8AL11XG8bxIirTbafDLMn26sL59HQx8QvvsRTqOkhq4MV0coIkog1jZuH/E9Abm8zFZ2g==", + "requires": { + "babel-code-frame": "^6.22.0", + "chalk": "^2.4.1", + "chokidar": "^2.0.4", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "optional": true + } + } + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "requires": { + "fstream": "^1.0.0", + "inherits": "2", + "minimatch": "^3.0.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "get-own-enumerable-property-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", + "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "github-markdown-css": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-3.0.1.tgz", + "integrity": "sha512-9G5CIPsHoyk5ObDsb/H4KTi23J8KE1oDd4KYU51qwqeM+lKWAiO7abpSgCkyWswgmSKBiuE7/4f8xUz7f2qAiQ==" + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "^2.0.0" + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "requires": { + "array-union": "^1.0.1", + "dir-glob": "2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, + "gzip-size": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz", + "integrity": "sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==", + "requires": { + "duplexer": "^0.1.1", + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==" + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "harmony-reflect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz", + "integrity": "sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hast-util-from-parse5": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.1.tgz", + "integrity": "sha512-UfPzdl6fbxGAxqGYNThRUhRlDYY7sXu6XU9nQeX4fFZtV+IHbyEJtd+DUuwOqNV4z3K05E/1rIkoVr/JHmeWWA==", + "requires": { + "ccount": "^1.0.3", + "hastscript": "^5.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.1.2", + "xtend": "^4.0.1" + } + }, + "hast-util-parse-selector": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.2.tgz", + "integrity": "sha512-jIMtnzrLTjzqgVEQqPEmwEZV+ea4zHRFTP8Z2Utw0I5HuBOXHzUPPQWr6ouJdJqDKLbFU/OEiYwZ79LalZkmmw==" + }, + "hastscript": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.0.tgz", + "integrity": "sha512-7mOQX5VfVs/gmrOGlN8/EDfp1GqV6P3gTNVt+KnX4gbYhpASTM8bklFdFQCbFRAadURXAmw0R1QQdBdqp7jswQ==", + "requires": { + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.2.0", + "property-information": "^5.0.1", + "space-separated-tokens": "^1.0.0" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "heap": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", + "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" + }, + "history": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", + "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^2.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^0.4.0" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "hoist-non-react-statics": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", + "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "requires": { + "react-is": "^16.7.0" + } + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==" + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" + }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + } + } + }, + "html-to-react": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.1.tgz", + "integrity": "sha512-Ys2gGxF8LBF9bD8tbnsU0xgEDOTC3Sy81mtpIH/61hSqGE1l4QetnN1yv0oAK/PuvwABmiNS+ggqvuzo+GfoiA==", + "requires": { + "domhandler": "^3.0", + "htmlparser2": "^4.0", + "lodash.camelcase": "^4.3.0", + "ramda": "^0.26" + }, + "dependencies": { + "dom-serializer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", + "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + }, + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", + "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", + "requires": { + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" + } + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + }, + "htmlparser2": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.0.0.tgz", + "integrity": "sha512-cChwXn5Vam57fyXajDtPXL1wTYc8JtLbr2TN76FYu05itVVVealxLowe2B3IEznJG4p9HAYn/0tJaRlGuEglFQ==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + } + } + }, + "html-webpack-plugin": { + "version": "4.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-alpha.2.tgz", + "integrity": "sha512-tyvhjVpuGqD7QYHi1l1drMQTg5i+qRxpQEGbdnYFREgOKy7aFDf/ocQ/V1fuEDlQx7jV2zMap3Hj2nE9i5eGXw==", + "requires": { + "@types/tapable": "1.0.2", + "html-minifier": "^3.2.3", + "loader-utils": "^1.1.0", + "lodash": "^4.17.10", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "util.promisify": "1.0.0" + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=" + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", + "requires": { + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" + }, + "hyperquest": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/hyperquest/-/hyperquest-2.1.3.tgz", + "integrity": "sha512-fUuDOrB47PqNK/BAMOS13v41UoaqIxqSLHX6CAbOD7OfT+/GCWO1/vPLfTNutOeXrv1ikuaZ3yux+33Z9vh+rw==", + "requires": { + "buffer-from": "^0.1.1", + "duplexer2": "~0.0.2", + "through2": "~0.6.3" + } + }, + "hyphenate-style-name": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", + "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" + }, + "icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "requires": { + "postcss": "^6.0.1" + } + }, + "identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=", + "requires": { + "harmony-reflect": "^1.4.6" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + }, + "immer": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", + "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==" + }, + "import": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/import/-/import-0.0.6.tgz", + "integrity": "sha1-0Ot534aqJnfG22FXilISswMeYEI=", + "requires": { + "optimist": "0.3.x" + }, + "dependencies": { + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "requires": { + "wordwrap": "~0.0.2" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "requires": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "indefinite-observable": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.2.tgz", + "integrity": "sha512-Mps0898zEduHyPhb7UCgNmfzlqNZknVmaFz5qzr0mm04YQ5FGLhAyK/dJ+NaRxGyR6juQXIxh5Ev0xx+qq0nYA==", + "requires": { + "symbol-observable": "1.2.0" + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "inquirer": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", + "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "internal-ip": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz", + "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", + "requires": { + "default-gateway": "^2.6.0", + "ipaddr.js": "^1.5.2" + } + }, + "interweave": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/interweave/-/interweave-11.2.0.tgz", + "integrity": "sha512-33h9LOXbT52tMin3IyLBPcd5RbiwroP/Sxr0OamnJJU7A/jh0XtZKGvdcSNKYRC7sLZuDk+ZJ2XVrmkcMU5i6w==", + "requires": { + "@types/react": "*", + "escape-html": "^1.0.3", + "prop-types": "^15.7.2" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-alphabetical": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", + "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==" + }, + "is-alphanumerical": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz", + "integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==", + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-decimal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", + "integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-generator-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", + "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-hexadecimal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", + "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==" + }, + "is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, + "is-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.0.0.tgz", + "integrity": "sha512-F/pJIk8QD6OX5DNhRB7hWamLsUilmkDGho48KbgZ6xg/lmAZXHxzXQ91jzB3yRSw5kdQGGGc4yz8HYhTYIMWPg==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "is-what": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.3.1.tgz", + "integrity": "sha512-seFn10yAXy+yJlTRO+8VfiafC+0QJanGLMPTBWLrJm/QPauuchy0UXh8B6H5o9VA8BAzk0iYievt6mNp6gfaqA==" + }, + "is-whitespace-character": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz", + "integrity": "sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-word-character": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.3.tgz", + "integrity": "sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-api": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", + "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", + "requires": { + "async": "^2.1.4", + "fileset": "^2.0.2", + "istanbul-lib-coverage": "^1.2.1", + "istanbul-lib-hook": "^1.2.2", + "istanbul-lib-instrument": "^1.10.2", + "istanbul-lib-report": "^1.1.5", + "istanbul-lib-source-maps": "^1.2.6", + "istanbul-reports": "^1.5.1", + "js-yaml": "^3.7.0", + "mkdirp": "^0.5.1", + "once": "^1.4.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==" + }, + "istanbul-lib-hook": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", + "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", + "requires": { + "append-transform": "^0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", + "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", + "requires": { + "istanbul-lib-coverage": "^1.2.1", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", + "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.2.1", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "istanbul-reports": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", + "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", + "requires": { + "handlebars": "^4.0.3" + } + }, + "jest": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", + "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", + "requires": { + "import-local": "^1.0.0", + "jest-cli": "^23.6.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "jest-cli": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", + "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "import-local": "^1.0.0", + "is-ci": "^1.0.10", + "istanbul-api": "^1.3.1", + "istanbul-lib-coverage": "^1.2.0", + "istanbul-lib-instrument": "^1.10.1", + "istanbul-lib-source-maps": "^1.2.4", + "jest-changed-files": "^23.4.2", + "jest-config": "^23.6.0", + "jest-environment-jsdom": "^23.4.0", + "jest-get-type": "^22.1.0", + "jest-haste-map": "^23.6.0", + "jest-message-util": "^23.4.0", + "jest-regex-util": "^23.3.0", + "jest-resolve-dependencies": "^23.6.0", + "jest-runner": "^23.6.0", + "jest-runtime": "^23.6.0", + "jest-snapshot": "^23.6.0", + "jest-util": "^23.4.0", + "jest-validate": "^23.6.0", + "jest-watcher": "^23.4.0", + "jest-worker": "^23.2.0", + "micromatch": "^2.3.11", + "node-notifier": "^5.2.1", + "prompts": "^0.1.9", + "realpath-native": "^1.0.0", + "rimraf": "^2.5.4", + "slash": "^1.0.0", + "string-length": "^2.0.0", + "strip-ansi": "^4.0.0", + "which": "^1.2.12", + "yargs": "^11.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-changed-files": { + "version": "23.4.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", + "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", + "requires": { + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", + "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", + "requires": { + "babel-core": "^6.0.0", + "babel-jest": "^23.6.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^23.4.0", + "jest-environment-node": "^23.4.0", + "jest-get-type": "^22.1.0", + "jest-jasmine2": "^23.6.0", + "jest-regex-util": "^23.3.0", + "jest-resolve": "^23.6.0", + "jest-util": "^23.4.0", + "jest-validate": "^23.6.0", + "micromatch": "^2.3.11", + "pretty-format": "^23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-diff": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", + "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", + "requires": { + "chalk": "^2.0.1", + "diff": "^3.2.0", + "jest-get-type": "^22.1.0", + "pretty-format": "^23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-docblock": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", + "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", + "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", + "requires": { + "chalk": "^2.0.1", + "pretty-format": "^23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-environment-jsdom": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", + "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", + "requires": { + "jest-mock": "^23.2.0", + "jest-util": "^23.4.0", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", + "integrity": "sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=", + "requires": { + "jest-mock": "^23.2.0", + "jest-util": "^23.4.0" + } + }, + "jest-get-type": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==" + }, + "jest-haste-map": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", + "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", + "requires": { + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.1.11", + "invariant": "^2.2.4", + "jest-docblock": "^23.2.0", + "jest-serializer": "^23.0.1", + "jest-worker": "^23.2.0", + "micromatch": "^2.3.11", + "sane": "^2.0.0" + } + }, + "jest-jasmine2": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", + "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", + "requires": { + "babel-traverse": "^6.0.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^23.6.0", + "is-generator-fn": "^1.0.0", + "jest-diff": "^23.6.0", + "jest-each": "^23.6.0", + "jest-matcher-utils": "^23.6.0", + "jest-message-util": "^23.4.0", + "jest-snapshot": "^23.6.0", + "jest-util": "^23.4.0", + "pretty-format": "^23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-leak-detector": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", + "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", + "requires": { + "pretty-format": "^23.6.0" + } + }, + "jest-matcher-utils": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", + "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", + "requires": { + "chalk": "^2.0.1", + "jest-get-type": "^22.1.0", + "pretty-format": "^23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-message-util": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", + "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", + "requires": { + "@babel/code-frame": "^7.0.0-beta.35", + "chalk": "^2.0.1", + "micromatch": "^2.3.11", + "slash": "^1.0.0", + "stack-utils": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-mock": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", + "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=" + }, + "jest-pnp-resolver": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.0.2.tgz", + "integrity": "sha512-H2DvUlwdMedNGv4FOliPDnxani6ATWy70xe2eckGJgkLoMaWzRPqpSlc5ShqX0Ltk5OhRQvPQY2LLZPOpgcc7g==" + }, + "jest-regex-util": { + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", + "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=" + }, + "jest-resolve": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", + "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", + "requires": { + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "realpath-native": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", + "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", + "requires": { + "jest-regex-util": "^23.3.0", + "jest-snapshot": "^23.6.0" + } + }, + "jest-runner": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", + "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", + "requires": { + "exit": "^0.1.2", + "graceful-fs": "^4.1.11", + "jest-config": "^23.6.0", + "jest-docblock": "^23.2.0", + "jest-haste-map": "^23.6.0", + "jest-jasmine2": "^23.6.0", + "jest-leak-detector": "^23.6.0", + "jest-message-util": "^23.4.0", + "jest-runtime": "^23.6.0", + "jest-util": "^23.4.0", + "jest-worker": "^23.2.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + }, + "dependencies": { + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "jest-runtime": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", + "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", + "requires": { + "babel-core": "^6.0.0", + "babel-plugin-istanbul": "^4.1.6", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "exit": "^0.1.2", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.11", + "jest-config": "^23.6.0", + "jest-haste-map": "^23.6.0", + "jest-message-util": "^23.4.0", + "jest-regex-util": "^23.3.0", + "jest-resolve": "^23.6.0", + "jest-snapshot": "^23.6.0", + "jest-util": "^23.4.0", + "jest-validate": "^23.6.0", + "micromatch": "^2.3.11", + "realpath-native": "^1.0.0", + "slash": "^1.0.0", + "strip-bom": "3.0.0", + "write-file-atomic": "^2.1.0", + "yargs": "^11.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-serializer": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", + "integrity": "sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=" + }, + "jest-snapshot": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", + "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", + "requires": { + "babel-types": "^6.0.0", + "chalk": "^2.0.1", + "jest-diff": "^23.6.0", + "jest-matcher-utils": "^23.6.0", + "jest-message-util": "^23.4.0", + "jest-resolve": "^23.6.0", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^23.6.0", + "semver": "^5.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-util": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", + "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", + "requires": { + "callsites": "^2.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.11", + "is-ci": "^1.0.10", + "jest-message-util": "^23.4.0", + "mkdirp": "^0.5.1", + "slash": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-validate": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", + "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", + "requires": { + "chalk": "^2.0.1", + "jest-get-type": "^22.1.0", + "leven": "^2.1.0", + "pretty-format": "^23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-watch-typeahead": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.2.1.tgz", + "integrity": "sha512-xdhEtKSj0gmnkDQbPTIHvcMmXNUDzYpHLEJ5TFqlaI+schi2NI96xhWiZk9QoesAS7oBmKwWWsHazTrYl2ORgg==", + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.4.1", + "jest-watcher": "^23.1.0", + "slash": "^2.0.0", + "string-length": "^2.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-watcher": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", + "integrity": "sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=", + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "string-length": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jest-worker": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", + "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", + "requires": { + "merge-stream": "^1.0.1" + } + }, + "joi": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-11.4.0.tgz", + "integrity": "sha512-O7Uw+w/zEWgbL6OcHbyACKSj0PkQeUgmehdoXVSxt92QFCq4+1390Rwh5moI2K/OgC7D8RHRZqHZxT2husMJHA==", + "requires": { + "hoek": "4.x.x", + "isemail": "3.x.x", + "topo": "2.x.x" + } + }, + "jquery": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==" + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jss": { + "version": "9.8.7", + "resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz", + "integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==", + "requires": { + "is-in-browser": "^1.1.3", + "symbol-observable": "^1.1.0", + "warning": "^3.0.0" + }, + "dependencies": { + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, + "jss-camel-case": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz", + "integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==", + "requires": { + "hyphenate-style-name": "^1.0.2" + } + }, + "jss-default-unit": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz", + "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg==" + }, + "jss-global": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz", + "integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q==" + }, + "jss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz", + "integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==", + "requires": { + "warning": "^3.0.0" + }, + "dependencies": { + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, + "jss-plugin-camel-case": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0.tgz", + "integrity": "sha512-yALDL00+pPR4FJh+k07A8FeDvfoPPuXU48HLy63enAubcVd3DnS+2rgqPXglHDGixIDVkCSXecl/l5GAMjzIbA==", + "requires": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.0.0" + }, + "dependencies": { + "jss": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", + "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + } + } + }, + "jss-plugin-default-unit": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0.tgz", + "integrity": "sha512-sURozIOdCtGg9ap18erQ+ijndAfEGtTaetxfU3H4qwC18Bi+fdvjlY/ahKbuu0ASs7R/+WKCP7UaRZOjUDMcdQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0" + }, + "dependencies": { + "jss": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", + "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + } + } + }, + "jss-plugin-global": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0.tgz", + "integrity": "sha512-80ofWKSQUo62bxLtRoTNe0kFPtHgUbAJeOeR36WEGgWIBEsXLyXOnD5KNnjPqG4heuEkz9eSLccjYST50JnI7Q==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0" + }, + "dependencies": { + "jss": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", + "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + } + } + }, + "jss-plugin-nested": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0.tgz", + "integrity": "sha512-waxxwl/po1hN3azTyixKnr8ReEqUv5WK7WsO+5AWB0bFndML5Yqnt8ARZ90HEg8/P6WlqE/AB2413TkCRZE8bA==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0", + "tiny-warning": "^1.0.2" + }, + "dependencies": { + "jss": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", + "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + } + } + }, + "jss-plugin-props-sort": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0.tgz", + "integrity": "sha512-41mf22CImjwNdtOG3r+cdC8+RhwNm616sjHx5YlqTwtSJLyLFinbQC/a4PIFk8xqf1qpFH1kEAIw+yx9HaqZ3g==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0" + }, + "dependencies": { + "jss": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", + "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + } + } + }, + "jss-plugin-rule-value-function": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0.tgz", + "integrity": "sha512-Jw+BZ8JIw1f12V0SERqGlBT1JEPWax3vuZpMym54NAXpPb7R1LYHiCTIlaJUyqvIfEy3kiHMtgI+r2whGgRIxQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.0" + }, + "dependencies": { + "jss": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", + "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + } + } + }, + "jss-plugin-vendor-prefixer": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0.tgz", + "integrity": "sha512-qslqvL0MUbWuzXJWdUxpj6mdNUX8jr4FFTo3aZnAT65nmzWL7g8oTr9ZxmTXXgdp7ANhS1QWE7036/Q2isFBpw==", + "requires": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.6", + "jss": "10.0.0" + }, + "dependencies": { + "css-vendor": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz", + "integrity": "sha512-VS9Rjt79+p7M0WkPqcAza4Yq1ZHrsHrwf7hPL/bjQB+c1lwmAI+1FXxYTYt818D/50fFVflw0XKleiBN5RITkg==", + "requires": { + "@babel/runtime": "^7.6.2", + "is-in-browser": "^1.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz", + "integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + } + } + }, + "jss": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", + "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + } + } + }, + "jss-props-sort": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz", + "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g==" + }, + "jss-vendor-prefixer": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz", + "integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==", + "requires": { + "css-vendor": "^0.3.8" + } + }, + "jsx-ast-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz", + "integrity": "sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA==", + "requires": { + "array-includes": "^3.0.3" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + } + } + }, + "kleur": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", + "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==" + }, + "last-call-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", + "requires": { + "lodash": "^4.17.5", + "webpack-sources": "^1.1.0" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + } + } + }, + "loader-fs-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz", + "integrity": "sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw==", + "requires": { + "find-cache-dir": "^0.1.1", + "mkdirp": "0.5.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "requires": { + "commondir": "^1.0.1", + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "requires": { + "find-up": "^1.0.0" + } + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" + }, + "lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=" + }, + "lodash.template": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", + "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "requires": { + "lodash._reinterpolate": "~3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", + "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "requires": { + "lodash._reinterpolate": "~3.0.0" + } + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "lodash.tonumber": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.tonumber/-/lodash.tonumber-4.0.3.tgz", + "integrity": "sha1-C5azGzVnJ5Prf1pj7nkfG56QJdk=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "loglevel": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz", + "integrity": "sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "requires": { + "tmpl": "1.0.x" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "markdown-escapes": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", + "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==" + }, + "material-icons": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-0.3.1.tgz", + "integrity": "sha512-5Hbj76A6xDPcDZEbM4oxTknhWuMwGWnAHVLLPCEq9eVlcHb0fn4koU9ZeyMy1wjARtDEPAHfd5ZdL2Re5hf0zQ==" + }, + "material-icons-react": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/material-icons-react/-/material-icons-react-1.0.4.tgz", + "integrity": "sha512-ZTCD0Nl+/hTyvONWz8N6gCrpLWfbkOF77NWbD9ThYdkBK8gtvFMc/ORS8nfU0qlmS1wNdVXf1oTaBRWLCiNkxw==", + "requires": { + "prop-types": "^15.6.1", + "react": "^15.0.0", + "react-dom": "^15.0.0", + "webfontloader": "^1.6.28" + }, + "dependencies": { + "react": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", + "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", + "requires": { + "create-react-class": "^15.6.0", + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + } + }, + "react-dom": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", + "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + } + } + } + }, + "material-ui-pickers": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/material-ui-pickers/-/material-ui-pickers-2.2.4.tgz", + "integrity": "sha512-QCQh08Ylmnt+o4laW+rPs92QRAcESv3sPXl50YadLm++rAZAXAOh3K8lreGdynCMYFgZfdyu81Oz9xzTlAZNfw==", + "requires": { + "@types/react-text-mask": "^5.4.3", + "clsx": "^1.0.2", + "react-event-listener": "^0.6.6", + "react-text-mask": "^5.4.3", + "react-transition-group": "^2.5.3", + "tslib": "^1.9.3" + } + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" + }, + "md5-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-4.0.0.tgz", + "integrity": "sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg==" + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdast-add-list-metadata": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz", + "integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==", + "requires": { + "unist-util-visit-parents": "1.1.2" + } + }, + "mdbreact": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/mdbreact/-/mdbreact-4.21.1.tgz", + "integrity": "sha512-UmgLQh2WRXMdHrfhwavtMg2Gw/sq1gfBpU7+ukDEzU43wnBMZxll5dqGUS9ReSWKIJwHXhxM+8xxwFa2dsYEHg==", + "requires": { + "@date-io/moment": "1.3.5", + "@fortawesome/fontawesome-free": "^5.10.2", + "@material-ui/core": "3.9.3", + "bootstrap-css-only": "4.3.1", + "chart.js": "2.8.0", + "classnames": "2.2.6", + "material-ui-pickers": "2.2.4", + "moment": "2.24.0", + "perfect-scrollbar": "1.4.0", + "raf": "3.4.1", + "react-chartjs-2": "2.7.6", + "react-image-lightbox": "5.1.0", + "react-numeric-input": "2.2.3", + "react-popper": "^1.3.4", + "react-router-dom": "^5.0.1", + "react-scroll": "1.7.11", + "react-toastify": "5.1.0", + "react-transition-group": "4.0.1" + }, + "dependencies": { + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "react-chartjs-2": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.7.6.tgz", + "integrity": "sha512-xDr0jhgt/o26atftXxTVsepz+QYZI2GNKBYpxtLvYgwffLUm18a9n562reUJAHvuwKsy2v+qMlK5HyjFtSW0mg==", + "requires": { + "lodash": "^4.17.4", + "prop-types": "^15.5.8" + } + }, + "react-router": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", + "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.3.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-router-dom": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", + "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.1.2", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-transition-group": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.0.1.tgz", + "integrity": "sha512-SsLcBYhO4afXJC9esL8XMxi/y0ZvEc7To0TvtrBELqzpjXQHPZOTxvuPh2/4EhYc0uSMfp2SExIxsyJ0pBdNzg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + } + } + }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "memoize-one": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "merge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==" + }, + "merge-anything": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-2.4.1.tgz", + "integrity": "sha512-dYOIAl9GFCJNctSIHWOj9OJtarCjsD16P8ObCl6oxrujAG+kOvlwJuOD9/O9iYZ9aTi1RGpGTG9q9etIvuUikQ==", + "requires": { + "is-what": "^3.3.1" + } + }, + "merge-deep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", + "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", + "requires": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "requires": { + "readable-stream": "^2.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "merge2": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", + "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "mini-create-react-context": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", + "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "requires": { + "@babel/runtime": "^7.4.0", + "gud": "^1.0.0", + "tiny-warning": "^1.0.2" + } + }, + "mini-css-extract-plugin": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", + "integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==", + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "moment": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "node-notifier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "node-releases": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.23.tgz", + "integrity": "sha512-uq1iL79YjfYC0WXoHbC/z28q/9pOl8kSHaXdWmAAc8No+bDwqkZbzIJz55g/MUsPgSGm9LZ7QSUbzTcH5tz47w==", + "requires": { + "semver": "^5.3.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + }, + "normalize-scroll-left": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz", + "integrity": "sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg==" + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "nwsapi": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", + "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.11.0", + "function-bind": "^1.1.1", + "has": "^1.0.1" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opn": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } + }, + "optimize-css-assets-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-Rqm6sSjWtx9FchdP0uzTQDc7GXDKnwVEGoSxjezPkzMewx7gEWE9IMUYKmigTRC4U3RaNSwYVnUDLuIdtTpm0A==", + "requires": { + "cssnano": "^4.1.0", + "last-call-webpack-plugin": "^3.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "requires": { + "no-case": "^2.2.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + } + } + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", + "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "requires": { + "isarray": "0.0.1" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "perfect-scrollbar": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.4.0.tgz", + "integrity": "sha512-/2Sk/khljhdrsamjJYS5NjrH+GKEHEwh7zFSiYyxROyYKagkE4kSn2zDQDRTOMo8mpT2jikxx6yI1dG7lNP/hw==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + } + } + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "^2.1.0" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==" + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + }, + "pnp-webpack-plugin": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.2.1.tgz", + "integrity": "sha512-W6GctK7K2qQiVR+gYSv/Gyt6jwwIH4vwdviFqx+Y2jAtVf5eZyYIDf5Ac2NCDMBiX5yWscBLZElPTsyA1UtVVA==", + "requires": { + "ts-pnp": "^1.0.0" + } + }, + "popper.js": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", + "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==" + }, + "portfinder": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-attribute-case-insensitive": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.1.tgz", + "integrity": "sha512-L2YKB3vF4PetdTIthQVeT+7YiSzMoNMLLYxPXXppOOP7NoazEAy45sh2LvJ8leCQjfBcfkYQs8TtCcQjeZTp8A==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-calc": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", + "integrity": "sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==", + "requires": { + "css-unit-converter": "^1.1.1", + "postcss": "^7.0.5", + "postcss-selector-parser": "^5.0.0-rc.4", + "postcss-value-parser": "^3.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-functional-notation": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-hex-alpha": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", + "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-mod-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-custom-media": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", + "requires": { + "postcss": "^7.0.14" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-custom-properties": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.10.tgz", + "integrity": "sha512-GDL0dyd7++goDR4SSasYdRNNvp4Gqy1XMzcCnTijiph7VB27XXpJ8bW/AI0i2VSBZ55TpdGhMr37kMSpRfYD0Q==", + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-custom-selectors": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-double-position-gradients": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-env-function": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-flexbugs-fixes": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz", + "integrity": "sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA==", + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-focus-visible": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-focus-within": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-font-variant": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", + "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-gap-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-image-set-function": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-initial": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.0.tgz", + "integrity": "sha512-WzrqZ5nG9R9fUtrA+we92R4jhVvEB32IIRTzfIG/PLL8UV4CvbF1ugTEHEFX6vWxl41Xt5RTCJPEZkuWzrOM+Q==", + "requires": { + "lodash.template": "^4.2.4", + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-lab-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-logical": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-media-minmax": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", + "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", + "requires": { + "postcss": "^6.0.1" + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + } + }, + "postcss-nesting": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.0.tgz", + "integrity": "sha512-WSsbVd5Ampi3Y0nk/SKr5+K34n52PqMqEfswu6RtU4r7wA8vSD+gM8/D9qq4aJkHImwn1+9iEFTbjoWsQeqtaQ==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-page-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-place": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-preset-env": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.5.0.tgz", + "integrity": "sha512-RdsIrYJd9p9AouQoJ8dFP5ksBJEIegA4q4WzJDih8nevz3cZyIP/q1Eaw3pTVpUAu3n7Y32YmvAW3X07mSRGkw==", + "requires": { + "autoprefixer": "^9.4.2", + "browserslist": "^4.3.5", + "caniuse-lite": "^1.0.30000918", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.3.0", + "postcss": "^7.0.6", + "postcss-attribute-case-insensitive": "^4.0.0", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.2", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.7", + "postcss-custom-properties": "^8.0.9", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-safe-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz", + "integrity": "sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==", + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-matches": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-not": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", + "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + } + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + }, + "postcss-values-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", + "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "pretty-bytes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=" + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "pretty-format": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "requires": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" + }, + "promise-window": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/promise-window/-/promise-window-1.2.1.tgz", + "integrity": "sha512-fDU8dCyw/nAY7Ccy6gOkEXUDijJC+rT3ZBEIAmMjB73WaWXZpbOtBwaUPeYmsN6SISuf/rvl/fSy1Dq7VZu29Q==", + "dev": true + }, + "prompts": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", + "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", + "requires": { + "kleur": "^2.0.1", + "sisteransi": "^0.1.1" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "property-information": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.1.0.tgz", + "integrity": "sha512-tODH6R3+SwTkAQckSp2S9xyYX8dEKYkeXw+4TmJzTxnNzd6mQPu1OD4f9zPrvw/Rm4wpPgI+Zp63mNSGNzUgHg==", + "requires": { + "xtend": "^4.0.1" + } + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.1.32", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "pure-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", + "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + } + } + }, + "react": { + "version": "16.10.2", + "resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz", + "integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-alert": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-alert/-/react-alert-5.5.0.tgz", + "integrity": "sha512-PzB6ktBmtNZkmgZVdPp/Wbe/zjIBzqBu9QHLTrfquUVVPCVVfn3lPWOebejEg6VOxGepIPOSAsb1bd3ESMyC0Q==" + }, + "react-alert-template-basic": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-alert-template-basic/-/react-alert-template-basic-1.0.0.tgz", + "integrity": "sha512-6x5Us0oc+jj8BDNkvSWfQMESk5SdyGKitXdLb7CwIlIlecyATjCTKSWpLABg8tpKAPOSJu4Dv/fYUqxXEio/XA==" + }, + "react-app-polyfill": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-0.2.2.tgz", + "integrity": "sha512-mAYn96B/nB6kWG87Ry70F4D4rsycU43VYTj3ZCbKP+SLJXwC0x6YCbwcICh3uW8/C9s1VgP197yx+w7SCWeDdQ==", + "requires": { + "core-js": "2.6.4", + "object-assign": "4.1.1", + "promise": "8.0.2", + "raf": "3.4.1", + "whatwg-fetch": "3.0.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.4.tgz", + "integrity": "sha512-05qQ5hXShcqGkPZpXEFLIpxayZscVD2kuMBZewxiIPPEagukO4mqgPA9CWhUvFBJfy3ODdK2p9xyHh7FTU9/7A==" + }, + "promise": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.2.tgz", + "integrity": "sha512-EIyzM39FpVOMbqgzEHhxdrEhtOSDOtjMZQ0M6iVfCE+kWNgCkAyOdnuCWqfmflylftfadU6FkiMgHZA2kUzwRw==", + "requires": { + "asap": "~2.0.6" + } + } + } + }, + "react-base16-styling": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", + "integrity": "sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=", + "requires": { + "base16": "^1.0.0", + "lodash.curry": "^4.0.1", + "lodash.flow": "^3.3.0", + "pure-color": "^1.2.0" + } + }, + "react-chartjs-2": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.8.0.tgz", + "integrity": "sha512-BPpC+qfnh37DkcXvxRwA1rdD9rX/0AQrwru4VZTLofCCuZBwRsc7PbfxjilvoB6YlHhorwZu40YDWEQkoz7xfQ==", + "requires": { + "lodash": "^4.17.4", + "prop-types": "^15.5.8" + } + }, + "react-cookie": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.1.tgz", + "integrity": "sha512-h61qAtSXvfjNa81h3XCFdFoyFaF+nb7gjK0cxQuTiCPMPAe50D950FjLCFhaIfSpAesQFAmkxf5XFpWoEVBDAA==", + "requires": { + "@types/hoist-non-react-statics": "^3.0.1", + "hoist-non-react-statics": "^3.0.0", + "universal-cookie": "^4.0.0" + } + }, + "react-cytoscapejs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-cytoscapejs/-/react-cytoscapejs-1.2.0.tgz", + "integrity": "sha512-VhOhICJbI+X377RvTX7X+lpMSLWiI2Y7O6xJOc82u2dIF2qrtksP9H4IMGoo2TYL6otLuYV81RfFyywk4KCxBg==", + "requires": { + "cytoscape": "^3.2.19", + "prop-types": "^15.6.2" + } + }, + "react-dev-utils": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-8.0.0.tgz", + "integrity": "sha512-TK8cj7eghvxfe7bfBluLGpI/upo4EXC+G74hYmPucAG8C2XcbT+vKnlWPwLnABb75Zk+mR6D556Da+yvDjljrw==", + "requires": { + "@babel/code-frame": "7.0.0", + "address": "1.0.3", + "browserslist": "4.4.1", + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "detect-port-alt": "1.1.6", + "escape-string-regexp": "1.0.5", + "filesize": "3.6.1", + "find-up": "3.0.0", + "fork-ts-checker-webpack-plugin": "1.0.0-alpha.6", + "global-modules": "2.0.0", + "globby": "8.0.2", + "gzip-size": "5.0.0", + "immer": "1.10.0", + "inquirer": "6.2.1", + "is-root": "2.0.0", + "loader-utils": "1.2.3", + "opn": "5.4.0", + "pkg-up": "2.0.0", + "react-error-overlay": "^5.1.4", + "recursive-readdir": "2.2.2", + "shell-quote": "1.6.1", + "sockjs-client": "1.3.0", + "strip-ansi": "5.0.0", + "text-table": "0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "browserslist": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz", + "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==", + "requires": { + "caniuse-lite": "^1.0.30000929", + "electron-to-chromium": "^1.3.103", + "node-releases": "^1.1.3" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "inquirer": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "requires": { + "ansi-regex": "^4.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "react-device-detect": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/react-device-detect/-/react-device-detect-1.9.10.tgz", + "integrity": "sha512-Rb618NuNwJojJeIgI5UoONwztbd4wMT4TBofzvYOHnPNu61LYYz8b78cXQb7kXRrPn6iI5T1gFtNQh0GxHEPxw==", + "requires": { + "ua-parser-js": "^0.7.20" + }, + "dependencies": { + "ua-parser-js": { + "version": "0.7.20", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz", + "integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw==" + } + } + }, + "react-dom": { + "version": "16.10.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz", + "integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.16.2" + } + }, + "react-draggable": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-3.3.2.tgz", + "integrity": "sha512-oaz8a6enjbPtx5qb0oDWxtDNuybOylvto1QLydsXgKmwT7e3GXC2eMVDwEMIUYJIFqVG72XpOv673UuuAq6LhA==", + "requires": { + "classnames": "^2.2.5", + "prop-types": "^15.6.0" + } + }, + "react-dropzone": { + "version": "10.1.10", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.1.10.tgz", + "integrity": "sha512-vcLBdkYo7wgZpw1o4cz7uk8/Mmm+sYHeiTfFSshA/EGthz/TjjrTOrKwvFHm3o1p1LPk+x+KbDDlw5OeIo6eYA==", + "requires": { + "attr-accept": "^1.1.3", + "file-selector": "^0.1.11", + "prop-types": "^15.7.2" + } + }, + "react-error-overlay": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.6.tgz", + "integrity": "sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q==" + }, + "react-event-listener": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", + "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", + "requires": { + "@babel/runtime": "^7.2.0", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + } + }, + "react-ga": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-2.7.0.tgz", + "integrity": "sha512-AjC7UOZMvygrWTc2hKxTDvlMXEtbmA0IgJjmkhgmQQ3RkXrWR11xEagLGFGaNyaPnmg24oaIiaNPnEoftUhfXA==" + }, + "react-iframe": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/react-iframe/-/react-iframe-1.8.0.tgz", + "integrity": "sha512-NYi89+rEqREwQxW9sDf+akh6/dtwWd3bOjByoVEIQ7SicOxVawRMer3pLdWjFaHXpuxTB9NqobPf/Ohj2iAKkg==", + "requires": { + "object-assign": "^4.1.1" + } + }, + "react-image-lightbox": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-image-lightbox/-/react-image-lightbox-5.1.0.tgz", + "integrity": "sha512-R46QvffoDBscLQgTl4s3kFxVbnP7a+nIh7AXJNS0EXVeDaa6zKDKtIT+jFeEvs+F9oUHtZfenG1NHhTkO4hEOA==", + "requires": { + "prop-types": "^15.6.2", + "react-modal": "^3.6.1" + } + }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + }, + "react-json-pretty": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-json-pretty/-/react-json-pretty-2.2.0.tgz", + "integrity": "sha512-3UMzlAXkJ4R8S4vmkRKtvJHTewG4/rn1Q18n0zqdu/ipZbUPLVZD+QwC7uVcD/IAY3s8iNVHlgR2dMzIUS0n1A==", + "requires": { + "prop-types": "^15.6.2" + } + }, + "react-json-view": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.19.1.tgz", + "integrity": "sha512-u5e0XDLIs9Rj43vWkKvwL8G3JzvXSl6etuS5G42a8klMohZuYFQzSN6ri+/GiBptDqlrXPTdExJVU7x9rrlXhg==", + "requires": { + "flux": "^3.1.3", + "react-base16-styling": "^0.6.0", + "react-lifecycles-compat": "^3.0.4", + "react-textarea-autosize": "^6.1.0" + } + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-markdown": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.2.2.tgz", + "integrity": "sha512-/STJiRFmJuAIUdeBPp/VyO5bcenTIqP3LXuC3gYvregmYGKjnszGiFc2Ph0LsWC17Un3y/CT8TfxnwJT7v9EJw==", + "requires": { + "html-to-react": "^1.3.4", + "mdast-add-list-metadata": "1.0.1", + "prop-types": "^15.7.2", + "react-is": "^16.8.6", + "remark-parse": "^5.0.0", + "unified": "^6.1.5", + "unist-util-visit": "^1.3.0", + "xtend": "^4.0.1" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "unified": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", + "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^2.0.0", + "x-is-string": "^0.1.0" + } + }, + "vfile": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", + "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "requires": { + "is-buffer": "^1.1.4", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + } + } + } + }, + "react-markdown-github": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/react-markdown-github/-/react-markdown-github-3.3.1.tgz", + "integrity": "sha512-7svOrx7lZ1EdqqmcCXEWFY/Bdj9MgtAxFvpXRcvKNDz8Dt9lkZzii17qhE/qnc446NI5LamifayHILhj/70iAA==", + "requires": { + "react-markdown": "^4.0.8", + "url-parse": "^1.4.0" + } + }, + "react-modal": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.10.1.tgz", + "integrity": "sha512-2DKIfdOc8+WY+SYJ/xf/WBwOYMmNAYAyGkYlc4e1TCs9rk1xY4QBz04hB3UHGcrLChh7ce77rHAe6VPNmuLYsQ==", + "requires": { + "exenv": "^1.2.0", + "prop-types": "^15.5.10", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + } + }, + "react-numeric-input": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-numeric-input/-/react-numeric-input-2.2.3.tgz", + "integrity": "sha1-S/WRjD6v7YUagN8euZLZQQArtVI=" + }, + "react-popper": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.4.tgz", + "integrity": "sha512-9AcQB29V+WrBKk6X7p0eojd1f25/oJajVdMZkywIoAV6Ag7hzE1Mhyeup2Q1QnvFRtGQFQvtqfhlEoDAPfKAVA==", + "requires": { + "@babel/runtime": "^7.1.2", + "create-react-context": "^0.3.0", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.7", + "warning": "^4.0.2" + } + }, + "react-powerhooks": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/react-powerhooks/-/react-powerhooks-0.0.7.tgz", + "integrity": "sha512-Z5vke2LBcmGkTIA986DNvudONetmgDo11WYlzolew/+1HYAsb4d5NTBipKq+/oAl1AoajFwn1wHkzA8J3yCd2g==" + }, + "react-router": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", + "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", + "requires": { + "history": "^4.7.2", + "hoist-non-react-statics": "^2.5.0", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.1", + "warning": "^4.0.1" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + } + } + }, + "react-router-dom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", + "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", + "requires": { + "history": "^4.7.2", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.1", + "react-router": "^4.3.1", + "warning": "^4.0.1" + } + }, + "react-scripts": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-2.1.8.tgz", + "integrity": "sha512-mDC8fYWCyuB9VROti8OCPdHE79UEchVVZmuS/yaIs47VkvZpgZqUvzghYBswZRchqnW0aARNY8xXrzoFRhhK7A==", + "requires": { + "@babel/core": "7.2.2", + "@svgr/webpack": "4.1.0", + "babel-core": "7.0.0-bridge.0", + "babel-eslint": "9.0.0", + "babel-jest": "23.6.0", + "babel-loader": "8.0.5", + "babel-plugin-named-asset-import": "^0.3.1", + "babel-preset-react-app": "^7.0.2", + "bfj": "6.1.1", + "case-sensitive-paths-webpack-plugin": "2.2.0", + "css-loader": "1.0.0", + "dotenv": "6.0.0", + "dotenv-expand": "4.2.0", + "eslint": "5.12.0", + "eslint-config-react-app": "^3.0.8", + "eslint-loader": "2.1.1", + "eslint-plugin-flowtype": "2.50.1", + "eslint-plugin-import": "2.14.0", + "eslint-plugin-jsx-a11y": "6.1.2", + "eslint-plugin-react": "7.12.4", + "file-loader": "2.0.0", + "fs-extra": "7.0.1", + "fsevents": "1.2.4", + "html-webpack-plugin": "4.0.0-alpha.2", + "identity-obj-proxy": "3.0.0", + "jest": "23.6.0", + "jest-pnp-resolver": "1.0.2", + "jest-resolve": "23.6.0", + "jest-watch-typeahead": "^0.2.1", + "mini-css-extract-plugin": "0.5.0", + "optimize-css-assets-webpack-plugin": "5.0.1", + "pnp-webpack-plugin": "1.2.1", + "postcss-flexbugs-fixes": "4.1.0", + "postcss-loader": "3.0.0", + "postcss-preset-env": "6.5.0", + "postcss-safe-parser": "4.0.1", + "react-app-polyfill": "^0.2.2", + "react-dev-utils": "^8.0.0", + "resolve": "1.10.0", + "sass-loader": "7.1.0", + "style-loader": "0.23.1", + "terser-webpack-plugin": "1.2.2", + "url-loader": "1.1.2", + "webpack": "4.28.3", + "webpack-dev-server": "3.1.14", + "webpack-manifest-plugin": "2.0.4", + "workbox-webpack-plugin": "3.6.3" + }, + "dependencies": { + "dotenv": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", + "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==" + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "optional": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "optional": true + } + } + } + } + }, + "react-scroll": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/react-scroll/-/react-scroll-1.7.11.tgz", + "integrity": "sha512-MCWtt8KWTBzBlo9oFE7xgAhGcgbslsfQAuGZAfYlBTt3Pxi2CX+kh8OoTUVAuOwNlt9XkoWcvDTWQwtHzm2uOg==", + "requires": { + "lodash.throttle": "^4.1.1", + "prop-types": "^15.5.8" + } + }, + "react-text-mask": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.4.3.tgz", + "integrity": "sha1-mR77QpnjDC5sLEbRP2FxaUY+DS0=", + "requires": { + "prop-types": "^15.5.6" + } + }, + "react-textarea-autosize": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz", + "integrity": "sha512-F6bI1dgib6fSvG8so1HuArPUv+iVEfPliuLWusLF+gAKz0FbB4jLrWUrTAeq1afnPT2c9toEZYUdz/y1uKMy4A==", + "requires": { + "prop-types": "^15.6.0" + } + }, + "react-toastify": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.1.0.tgz", + "integrity": "sha512-0kVAAE7VO609EeXLVaFHDTc6Bnd/OUAb7rrRAwMsHeaThKEhH+WEQEPftTjuA4rP59K0QhCnWu4Ds2hXAcFxaw==", + "requires": { + "@babel/runtime": "^7.4.2", + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-transition-group": "^2.6.1" + } + }, + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + }, + "reactstrap": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-7.1.0.tgz", + "integrity": "sha512-wtc4RkgnGn1TsZ0AxOZ2OqT+b8YmCWZj/tErPujWLepxzlEEhveZGC+uDerdaHVSAzJUP2DTk605iper7hutQQ==", + "requires": { + "@babel/runtime": "^7.2.0", + "classnames": "^2.2.3", + "lodash.isfunction": "^3.0.9", + "lodash.isobject": "^3.0.2", + "lodash.tonumber": "^4.0.3", + "prop-types": "^15.5.8", + "react-lifecycles-compat": "^3.0.4", + "react-popper": "^0.10.4", + "react-transition-group": "^2.3.1" + }, + "dependencies": { + "react-popper": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.10.4.tgz", + "integrity": "sha1-rypBXqIike3VBGeNev2opu4ylao=", + "requires": { + "popper.js": "^1.14.1", + "prop-types": "^15.6.1" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "requires": { + "util.promisify": "^1.0.0" + } + }, + "recompose": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", + "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", + "requires": { + "@babel/runtime": "^7.0.0", + "change-emitter": "^0.1.2", + "fbjs": "^0.8.1", + "hoist-non-react-statics": "^2.3.1", + "react-lifecycles-compat": "^3.0.2", + "symbol-observable": "^1.0.4" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + } + } + }, + "recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "requires": { + "minimatch": "3.0.4" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + }, + "regenerator-transform": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz", + "integrity": "sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==", + "requires": { + "private": "^0.1.6" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp-tree": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.10.tgz", + "integrity": "sha512-K1qVSbcedffwuIslMwpe6vGlj+ZXRnGkvjAtFHfDZZZuEdA/h0dxljAPu9vhUo6Rrx2U2AwJ+nSQ6hK+lrP5MQ==" + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==" + }, + "regexpu-core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", + "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.0.2", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==" + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "rehype-parse": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.0.tgz", + "integrity": "sha512-V2OjMD0xcSt39G4uRdMTqDXXm6HwkUbLMDayYKA/d037j8/OtVSQ+tqKwYWOuyBeoCs/3clXRe30VUjeMDTBSA==", + "requires": { + "hast-util-from-parse5": "^5.0.0", + "parse5": "^5.0.0", + "xtend": "^4.0.1" + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" + }, + "remark-parse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", + "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "requires": { + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^1.1.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^1.0.0", + "vfile-location": "^2.0.0", + "xtend": "^4.0.1" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "renderkid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", + "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "requires": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + }, + "dependencies": { + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + } + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "requires": { + "is-finite": "^1.0.0" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + }, + "resolve-pathname": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rsvp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", + "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==" + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "requires": { + "aproba": "^1.1.1" + } + }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, + "rxjs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sane": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", + "integrity": "sha1-tNwYYcIbQn6SlQej51HiosuKs/o=", + "requires": { + "anymatch": "^2.0.0", + "capture-exit": "^1.2.0", + "exec-sh": "^0.2.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.3", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5", + "watch": "~0.18.0" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + } + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "requires": { + "for-in": "^1.0.1" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serialize-javascript": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", + "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==" + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", + "requires": { + "is-buffer": "^1.0.2" + } + }, + "lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=" + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "requires": { + "array-filter": "~0.0.0", + "array-map": "~0.0.0", + "array-reduce": "~0.0.0", + "jsonify": "~0.0.0" + } + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "simplebar": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/simplebar/-/simplebar-4.2.3.tgz", + "integrity": "sha512-9no0pK7/1y+8/oTF3sy/+kx0PjQ3uk4cYwld5F1CJGk2gx+prRyUq8GRfvcVLq5niYWSozZdX73a2wIr1o9l/g==", + "requires": { + "can-use-dom": "^0.1.0", + "core-js": "^3.0.1", + "lodash.debounce": "^4.0.8", + "lodash.memoize": "^4.1.2", + "lodash.throttle": "^4.1.1", + "resize-observer-polyfill": "^1.5.1" + }, + "dependencies": { + "core-js": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.3.tgz", + "integrity": "sha512-0xmD4vUJRY8nfLyV9zcpC17FtSie5STXzw+HyYw2t8IIvmDnbq7RJUULECCo+NstpJtwK9kx8S+898iyqgeUow==" + } + } + }, + "sisteransi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", + "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + }, + "dependencies": { + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "requires": { + "source-map": "^0.5.6" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "space-separated-tokens": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz", + "integrity": "sha512-UyhMSmeIqZrQn2UdjYpxEkwY9JUrn8pP+7L4f91zRzOQuI8MF1FGLfYU9DKCYeLdo7LXMxwrX5zKFy7eeeVHuA==" + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==" + }, + "spdy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", + "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + }, + "state-toggle": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", + "integrity": "sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-comments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz", + "integrity": "sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw==", + "requires": { + "babel-extract-comments": "^1.0.0", + "babel-plugin-transform-object-rest-spread": "^6.26.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "style-loader": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + } + }, + "styled-components": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.4.0.tgz", + "integrity": "sha512-xQ6vTI/0zNjZ1BBDRxyjvBddrxhQ3DxjeCdaLM1lSn5FDnkTOQgRkmWvcUiTajqc5nJqKVl+7sUioMqktD0+Zw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@emotion/is-prop-valid": "^0.8.1", + "@emotion/unitless": "^0.7.0", + "babel-plugin-styled-components": ">= 1", + "css-to-react-native": "^2.2.2", + "memoize-one": "^5.0.0", + "merge-anything": "^2.2.4", + "prop-types": "^15.5.4", + "react-is": "^16.6.0", + "stylis": "^3.5.0", + "stylis-rule-sheet": "^0.0.10", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "stylis": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" + }, + "stylis-rule-sheet": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", + "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "svgo": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.2.2.tgz", + "integrity": "sha512-rAfulcwp2D9jjdGu+0CuqlrAUin6bBWrpoqXWwKDZZZJfXcUXQSxLJOFJCQCSA0x0pP2U0TxSlJu2ROq5Bq6qA==", + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.28", + "css-url-regex": "^1.1.0", + "csso": "^3.5.1", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "table": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.0.tgz", + "integrity": "sha512-nHFDrxmbrkU7JAFKqKbDJXfzrX2UBsWmrieXFTGxiI5e4ncg3VqsZeI4EzNmX0ncp4XNGVeoxIWJXfCIXwrsvw==", + "requires": { + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, + "tar-pack": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", + "requires": { + "debug": "^2.2.0", + "fstream": "^1.0.10", + "fstream-ignore": "^1.0.5", + "once": "^1.3.3", + "readable-stream": "^2.1.4", + "rimraf": "^2.5.1", + "tar": "^2.2.1", + "uid-number": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "terser": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", + "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "requires": { + "commander": "^2.19.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.10" + }, + "dependencies": { + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "terser-webpack-plugin": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.2.tgz", + "integrity": "sha512-1DMkTk286BzmfylAvLXwpJrI7dWa5BnFmscV/2dCr8+c56egFcbaeFAl7+sujAjdmpLam21XRdhA4oifLyiWWg==", + "requires": { + "cacache": "^11.0.2", + "find-cache-dir": "^2.0.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "terser": "^3.16.1", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "test-exclude": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", + "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", + "requires": { + "arrify": "^1.0.1", + "micromatch": "^2.3.11", + "object-assign": "^4.1.0", + "read-pkg-up": "^1.0.1", + "require-main-filename": "^1.0.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==" + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "requires": { + "setimmediate": "^1.0.4" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + }, + "tiny-invariant": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.4.tgz", + "integrity": "sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==" + }, + "tiny-warning": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz", + "integrity": "sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "topo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", + "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", + "requires": { + "hoek": "4.x.x" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "requires": { + "punycode": "^2.1.0" + } + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, + "trim-trailing-lines": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz", + "integrity": "sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==" + }, + "trough": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.4.tgz", + "integrity": "sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==" + }, + "tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, + "ts-pnp": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.2.tgz", + "integrity": "sha512-f5Knjh7XCyRIzoC/z1Su1yLLRrPrFCgtUAh/9fCSP6NKbATwpOL1+idQVXQokK9GRFURn/jYPGPfegIctwunoA==" + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typed-styles": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "ua-parser-js": { + "version": "0.7.19", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", + "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" + }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + }, + "unherit": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.2.tgz", + "integrity": "sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==", + "requires": { + "inherits": "^2.0.1", + "xtend": "^4.0.1" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==" + }, + "unified": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", + "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", + "requires": { + "@types/unist": "^2.0.0", + "@types/vfile": "^3.0.0", + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^3.0.0", + "x-is-string": "^0.1.0" + }, + "dependencies": { + "vfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", + "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", + "requires": { + "is-buffer": "^2.0.0", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + } + } + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==" + }, + "unist-util-remove-position": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.3.tgz", + "integrity": "sha512-CtszTlOjP2sBGYc2zcKA/CvNdTdEs3ozbiJ63IPBxh8iZg42SCCb8m04f8z2+V1aSk5a7BxbZKEdoDjadmBkWA==", + "requires": { + "unist-util-visit": "^1.1.0" + } + }, + "unist-util-stringify-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==" + }, + "unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "requires": { + "unist-util-visit-parents": "^2.0.0" + }, + "dependencies": { + "unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "requires": { + "unist-util-is": "^3.0.0" + } + } + } + }, + "unist-util-visit-parents": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz", + "integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q==" + }, + "universal-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.2.tgz", + "integrity": "sha512-n14lhA//lQeYRweP9j9uXsshN9Cs4LunVSnvAGmnA69SofwsjpUU03geaCaPC9LlsH2rkBy99o3zxQyVOldGvA==", + "requires": { + "@types/cookie": "^0.3.3", + "@types/object-assign": "^4.0.30", + "cookie": "^0.4.0", + "object-assign": "^4.1.1" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==" + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "url-loader": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", + "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", + "requires": { + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "requires": { + "builtins": "^1.0.3" + } + }, + "value-equal": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "vendors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.3.tgz", + "integrity": "sha512-fOi47nsJP5Wqefa43kyWSg80qF+Q3XA6MUkgi7Hp1HQaKDQW4cQrK2D0P7mmbFtsV1N89am55Yru/nyEwRubcw==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vfile": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.0.1.tgz", + "integrity": "sha512-lRHFCuC4SQBFr7Uq91oJDJxlnftoTLQ7eKIpMdubhYcVMho4781a8MWXLy3qZrZ0/STD1kRiKc0cQOHm4OkPeA==", + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "dependencies": { + "unist-util-stringify-position": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.1.tgz", + "integrity": "sha512-Zqlf6+FRI39Bah8Q6ZnNGrEHUhwJOkHde2MHVk96lLyftfJJckaPslKgzhVcviXj8KcE9UJM9F+a4JEiBUTYgA==", + "requires": { + "@types/unist": "^2.0.2" + } + }, + "vfile-message": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.1.tgz", + "integrity": "sha512-KtasSV+uVU7RWhUn4Lw+wW1Zl/nW8JWx7JCPps10Y9JRRIDeDXf8wfBLoOSsJLyo27DqMyAi54C6Jf/d6Kr2Bw==", + "requires": { + "@types/unist": "^2.0.2", + "unist-util-stringify-position": "^2.0.0" + } + } + } + }, + "vfile-location": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.5.tgz", + "integrity": "sha512-Pa1ey0OzYBkLPxPZI3d9E+S4BmvfVwNAAXrrqGbwTVXWaX2p9kM1zZ+n35UtVM06shmWKH4RPRN8KI80qE3wNQ==" + }, + "vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "requires": { + "unist-util-stringify-position": "^1.1.1" + } + }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==" + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "requires": { + "makeerror": "1.0.x" + } + }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "watch": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", + "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", + "requires": { + "exec-sh": "^0.2.0", + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "web-namespaces": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.3.tgz", + "integrity": "sha512-r8sAtNmgR0WKOKOxzuSgk09JsHlpKlB+uHi937qypOu3PZ17UxPrierFKDye/uNHjNTTEshu5PId8rojIPj/tA==" + }, + "webfontloader": { + "version": "1.6.28", + "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", + "integrity": "sha1-23hhKSU8tujq5UwvsF+HCvZnW64=" + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "webpack": { + "version": "4.28.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.28.3.tgz", + "integrity": "sha512-vLZN9k5I7Nr/XB1IDG9GbZB4yQd1sPuvufMFgJkx0b31fi2LD97KQIjwjxE7xytdruAYfu5S0FLBLjdxmwGJCg==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/wasm-edit": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.1.0", + "terser-webpack-plugin": "^1.1.0", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", + "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", + "requires": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + } + }, + "webpack-dev-server": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", + "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^2.0.0", + "internal-ip": "^3.0.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "schema-utils": "^1.0.0", + "selfsigned": "^1.9.1", + "semver": "^5.6.0", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "3.4.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.2" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "requires": { + "xregexp": "4.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-manifest-plugin": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.0.4.tgz", + "integrity": "sha512-nejhOHexXDBKQOj/5v5IZSfCeTO3x1Dt1RZEcGfBSul891X/eLIcIVH31gwxPDdsi2Z8LKKFGpM4w9+oTBOSCg==", + "requires": { + "fs-extra": "^7.0.0", + "lodash": ">=3.5 <5", + "tapable": "^1.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "websocket": { + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.30.tgz", + "integrity": "sha512-aO6klgaTdSMkhfl5VVJzD5fm+Srhh5jLYbS15+OiI1sN6h/RU/XW6WN9J1uVIpUKNmsTvT3Hs35XAFjn9NMfOw==", + "requires": { + "debug": "^2.2.0", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + } + }, + "websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", + "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "requires": { + "http-parser-js": ">=0.4.0 <0.4.11", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, + "workbox-background-sync": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-3.6.3.tgz", + "integrity": "sha512-ypLo0B6dces4gSpaslmDg5wuoUWrHHVJfFWwl1udvSylLdXvnrfhFfriCS42SNEe5lsZtcNZF27W/SMzBlva7Q==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-broadcast-cache-update": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-broadcast-cache-update/-/workbox-broadcast-cache-update-3.6.3.tgz", + "integrity": "sha512-pJl4lbClQcvp0SyTiEw0zLSsVYE1RDlCPtpKnpMjxFtu8lCFTAEuVyzxp9w7GF4/b3P4h5nyQ+q7V9mIR7YzGg==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-build": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-3.6.3.tgz", + "integrity": "sha512-w0clZ/pVjL8VXy6GfthefxpEXs0T8uiRuopZSFVQ8ovfbH6c6kUpEh6DcYwm/Y6dyWPiCucdyAZotgjz+nRz8g==", + "requires": { + "babel-runtime": "^6.26.0", + "common-tags": "^1.4.0", + "fs-extra": "^4.0.2", + "glob": "^7.1.2", + "joi": "^11.1.1", + "lodash.template": "^4.4.0", + "pretty-bytes": "^4.0.2", + "stringify-object": "^3.2.2", + "strip-comments": "^1.0.2", + "workbox-background-sync": "^3.6.3", + "workbox-broadcast-cache-update": "^3.6.3", + "workbox-cache-expiration": "^3.6.3", + "workbox-cacheable-response": "^3.6.3", + "workbox-core": "^3.6.3", + "workbox-google-analytics": "^3.6.3", + "workbox-navigation-preload": "^3.6.3", + "workbox-precaching": "^3.6.3", + "workbox-range-requests": "^3.6.3", + "workbox-routing": "^3.6.3", + "workbox-strategies": "^3.6.3", + "workbox-streams": "^3.6.3", + "workbox-sw": "^3.6.3" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "workbox-cache-expiration": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-cache-expiration/-/workbox-cache-expiration-3.6.3.tgz", + "integrity": "sha512-+ECNph/6doYx89oopO/UolYdDmQtGUgo8KCgluwBF/RieyA1ZOFKfrSiNjztxOrGJoyBB7raTIOlEEwZ1LaHoA==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-cacheable-response": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-3.6.3.tgz", + "integrity": "sha512-QpmbGA9SLcA7fklBLm06C4zFg577Dt8u3QgLM0eMnnbaVv3rhm4vbmDpBkyTqvgK/Ly8MBDQzlXDtUCswQwqqg==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-core": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-3.6.3.tgz", + "integrity": "sha512-cx9cx0nscPkIWs8Pt98HGrS9/aORuUcSkWjG25GqNWdvD/pSe7/5Oh3BKs0fC+rUshCiyLbxW54q0hA+GqZeSQ==" + }, + "workbox-google-analytics": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-3.6.3.tgz", + "integrity": "sha512-RQBUo/6SXtIaQTRFj4RQZ9e1gAl7D8oS5S+Hi173Kk70/BgJjzPwXpC5A249Jv5YfkCOLMQCeF9A27BiD0b0ig==", + "requires": { + "workbox-background-sync": "^3.6.3", + "workbox-core": "^3.6.3", + "workbox-routing": "^3.6.3", + "workbox-strategies": "^3.6.3" + } + }, + "workbox-navigation-preload": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-3.6.3.tgz", + "integrity": "sha512-dd26xTX16DUu0i+MhqZK/jQXgfIitu0yATM4jhRXEmpMqQ4MxEeNvl2CgjDMOHBnCVMax+CFZQWwxMx/X/PqCw==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-precaching": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-3.6.3.tgz", + "integrity": "sha512-aBqT66BuMFviPTW6IpccZZHzpA8xzvZU2OM1AdhmSlYDXOJyb1+Z6blVD7z2Q8VNtV1UVwQIdImIX+hH3C3PIw==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-range-requests": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-3.6.3.tgz", + "integrity": "sha512-R+yLWQy7D9aRF9yJ3QzwYnGFnGDhMUij4jVBUVtkl67oaVoP1ymZ81AfCmfZro2kpPRI+vmNMfxxW531cqdx8A==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-routing": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-3.6.3.tgz", + "integrity": "sha512-bX20i95OKXXQovXhFOViOK63HYmXvsIwZXKWbSpVeKToxMrp0G/6LZXnhg82ijj/S5yhKNRf9LeGDzaqxzAwMQ==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-strategies": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-3.6.3.tgz", + "integrity": "sha512-Pg5eulqeKet2y8j73Yw6xTgLdElktcWExGkzDVCGqfV9JCvnGuEpz5eVsCIK70+k4oJcBCin9qEg3g3CwEIH3g==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-streams": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-3.6.3.tgz", + "integrity": "sha512-rqDuS4duj+3aZUYI1LsrD2t9hHOjwPqnUIfrXSOxSVjVn83W2MisDF2Bj+dFUZv4GalL9xqErcFW++9gH+Z27w==", + "requires": { + "workbox-core": "^3.6.3" + } + }, + "workbox-sw": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-3.6.3.tgz", + "integrity": "sha512-IQOUi+RLhvYCiv80RP23KBW/NTtIvzvjex28B8NW1jOm+iV4VIu3VXKXTA6er5/wjjuhmtB28qEAUqADLAyOSg==" + }, + "workbox-webpack-plugin": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-3.6.3.tgz", + "integrity": "sha512-RwmKjc7HFHUFHoOlKoZUq9349u0QN3F8W5tZZU0vc1qsBZDINWXRiIBCAKvo/Njgay5sWz7z4I2adnyTo97qIQ==", + "requires": { + "babel-runtime": "^6.26.0", + "json-stable-stringify": "^1.0.1", + "workbox-build": "^3.6.3" + } + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=" + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yaml": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz", + "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", + "requires": { + "@babel/runtime": "^7.6.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + } + } + }, + "yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "requires": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + } + }, + "yargs": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + } + } + }, + "zone.js": { + "version": "0.8.29", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.29.tgz", + "integrity": "sha512-mla2acNCMkWXBD+c+yeUrBUrzOxYMNFdQ6FGfigGGtEVBPJx07BQeJekjt9DmH1FtZek4E9rE1eRR9qQpxACOQ==" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..d9e800a9 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,78 @@ +{ + "name": "shuffler", + "homepage": "https://shuffler.io", + "version": "0.3.0", + "private": true, + "dependencies": { + "@material-ui/core": "^3.9.3", + "@material-ui/icons": "^4.5.1", + "@material-ui/styles": "^4.5.0", + "@use-it/interval": "^0.1.3", + "class-transformer": "^0.2.0", + "create-react-app": "^2.0.3", + "cytoscape": "^3.11.0", + "cytoscape-clipboard": "^2.2.1", + "cytoscape-cxtmenu": "^3.1.1", + "cytoscape-edgehandles": "^3.6.0", + "cytoscape-grid-guide": "~2.1.2", + "cytoscape-node-html-label": "^1.1.5", + "cytoscape-panzoom": "^2.5.2", + "cytoscape-undo-redo": "^1.3.2", + "d3": "~4.10.0", + "dotenv": "^6.1.0", + "downshift": "^3.3.5", + "github-markdown-css": "^3.0.1", + "import": "0.0.6", + "interweave": "^11.2.0", + "material-icons": "^0.3.1", + "material-icons-react": "^1.0.4", + "md5-file": "^4.0.0", + "mdbreact": "^4.21.1", + "moment": "~2.20.1", + "react": "^16.10.2", + "react-alert": "^5.5.0", + "react-alert-template-basic": "^1.0.0", + "react-chartjs-2": "^2.8.0", + "react-cookie": "^4.0.1", + "react-cytoscapejs": "^1.2.0", + "react-device-detect": "^1.9.10", + "react-dom": "^16.10.2", + "react-draggable": "^3.3.2", + "react-dropzone": "^10.1.10", + "react-ga": "^2.7.0", + "react-iframe": "^1.8.0", + "react-json-pretty": "^2.2.0", + "react-json-view": "^1.19.1", + "react-markdown": "^4.2.2", + "react-markdown-github": "^3.3.1", + "react-powerhooks": "0.0.7", + "react-router": "^4.3.1", + "react-router-dom": "^4.3.1", + "react-scripts": "^2.1.8", + "reactstrap": "^7.1.0", + "simplebar": "^4.2.3", + "styled-components": "^4.4.0", + "websocket": "^1.0.30", + "yaml": "^1.7.2", + "yamljs": "^0.3.0", + "zone.js": "~0.8.26" + }, + "scripts": { + "start": "set HTTPS=true&&react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ], + "devDependencies": { + "promise-window": "^1.2.1" + } +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 00000000..09c34ad4 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 00000000..2ece9bc4 --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,15 @@ + + + + + + + + + Shuffle + + + +
            + + diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json new file mode 100644 index 00000000..41df9303 --- /dev/null +++ b/frontend/public/manifest.json @@ -0,0 +1,14 @@ +{ + "short_name": "Shuffle", + "name": "Shuffle webapp", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/frontend/run.sh b/frontend/run.sh new file mode 100755 index 00000000..2e2737b5 --- /dev/null +++ b/frontend/run.sh @@ -0,0 +1,17 @@ +#!/bin/sh +docker stop frikky/shuffle:frontend +docker rm frikky/shuffle:frontend +docker rmi frikky/shuffle:frontend + +echo "Running build for website" +sudo npm run build +docker build . -t frikky/shuffle:frontend + +echo "Starting server" +# Rerun build locally for it to update :) +docker run -it \ + -p 3001:80 \ + -p 3443:443 \ + -v $(pwd)/build:/usr/share/nginx/html:ro \ + --rm \ + nginx diff --git a/frontend/src/About.js b/frontend/src/About.js new file mode 100644 index 00000000..913eff3d --- /dev/null +++ b/frontend/src/About.js @@ -0,0 +1,49 @@ +import React from 'react'; + +const hrefStyle = { + color: "#f85a3e", + textDecoration: "none" +} + +const About = () => { + + return ( +
            +

            About

            + +

            + Endao was started as a project in late 2018 as a free service to analyze APK (and soon IPA) files for vulnerabilities. The project was started after I, + @frikkylikeme + , found multiple vulnerabilities in IoT devices based purely on their apps. As I wanted to learn more about these kind of vulnerabilities, I looked for solutions that work for my purpose, but didn't find any good, free and easy to use service - hence this site was born. +

            + +

            + My personal goal has and will always be to make the internet safer. As the IoT sphere grows, I want to be able to add ways of finding possible vulnerabilities fast to this website. This will hopefully include blogposts when I get around to it, as well as actual implementations. The vulnerability discovery field is in no way new, but I'll try my best to add whatever I can to it. As a disclaimer, I'm an "Ops" person, and I had never done frontend before creating this site. This is as much of a learning project within web development as it is in vulnerability discovery. +

            + +

            + This site currently uses the following projects +

            + + +

            Hopefully it is of use to some people :)

            + +

            Thanks

            +

            + Thanks to Andy for the initial frontend help :) +

            + +

            Regards

            +

            + @frikkylikeme +

            +
            + ) +} + +export default About; diff --git a/frontend/src/Admin.js b/frontend/src/Admin.js new file mode 100644 index 00000000..5d2e8158 --- /dev/null +++ b/frontend/src/Admin.js @@ -0,0 +1,395 @@ +import React, { useEffect} from 'react'; + +import Paper from '@material-ui/core/Paper'; +import List from '@material-ui/core/List'; +import Divider from '@material-ui/core/Divider'; +import TextField from '@material-ui/core/TextField'; +import ListItem from '@material-ui/core/ListItem'; +import Button from '@material-ui/core/Button'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; + + +import { useAlert } from "react-alert"; + +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; + + +const surfaceColor = "#27292D" +const inputColor = "#383B40" +const Admin = (props) => { + const { globalUrl, } = props; + const [firstRequest, setFirstRequest] = React.useState(true); + const [modalUser, setModalUser] = React.useState({}); + const [modalOpen, setModalOpen] = React.useState(false); + const [loginInfo, setLoginInfo] = React.useState(""); + const [curTab, setCurTab] = React.useState(0); + const [users, setUsers] = React.useState([]); + const [environments, setEnvironments] = React.useState([]); + + const alert = useAlert() + + const submitUser = (data) => { + // FIXME - add some check here ROFL + console.log("INPUT: ", data) + + // Just use this one? + var data = {"username": data.Username, "password": data.Password} + var baseurl = globalUrl + const url = baseurl+'/api/v1/register'; + fetch(url, { + method: 'POST', + credentials: "include", + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo("Error in input: "+responseJson.reason) + } else { + setLoginInfo("") + setModalOpen(false) + getUsers() + } + }), + ) + .catch(error => { + console.log("Error in userdata: ", error) + }); + } + + const deleteEnvironment = (name) => { + // FIXME - add some check here ROFL + var newEnv = [] + for (var key in environments) { + if (environments[key].Name == name) { + continue + } + + newEnv.push(environments[key]) + } + + // Just use this one? + const url = globalUrl+'/api/v1/setenvironments'; + fetch(url, { + method: 'PUT', + credentials: "include", + body: JSON.stringify(newEnv), + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + alert.error(responseJson.reason) + } else { + setLoginInfo("") + setModalOpen(false) + getEnvironments() + } + }), + ) + //.catch(error => { + // console.log("Error in userdata: ", error) + //}); + } + + const submitEnvironment = (data) => { + // FIXME - add some check here ROFL + environments.push({"name": data.environment, "type": "onprem"}) + + // Just use this one? + var baseurl = globalUrl + const url = baseurl+'/api/v1/setenvironments'; + fetch(url, { + method: 'PUT', + credentials: "include", + body: JSON.stringify(environments), + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo("Error in input: "+responseJson.reason) + } else { + setLoginInfo("") + setModalOpen(false) + getEnvironments() + } + }), + ) + .catch(error => { + console.log("Error in userdata: ", error) + }); + } + + const getEnvironments = () => { + fetch(globalUrl+"/api/v1/getenvironments", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + return + } + + return response.json() + }) + .then((responseJson) => { + setEnvironments(responseJson) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const getUsers = () => { + fetch(globalUrl+"/api/v1/getusers", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + return + } + + return response.json() + }) + .then((responseJson) => { + setUsers(responseJson) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + if (firstRequest) { + setFirstRequest(false) + getUsers() + } + + const paperStyle = { + minWidth: "100%", + maxWidth: "100%", + color: "white", + backgroundColor: surfaceColor, + marginBottom: 10, + padding: 20, + } + + const changeModalData = (field, value) => { + modalUser[field] = value + } + + const modalView = + {setModalOpen(false)}} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + Add user + + {curTab === 0 ? +
            + Username + changeModalData("Username", event.target.value)} + /> + Password + changeModalData("Password", event.target.value)} + /> +
            + : curTab === 1 ? +
            + Environment Name + changeModalData("environment", event.target.value)} + /> +
            + : null } + {loginInfo} +
            + + + + +
            + + const usersView = curTab === 0 ? +
            +

            + User management +

            + + + + {users === undefined ? null : users.map(data => { + console.log(data) + return ( + + {data.Username} + + ) + })} + +
            + : null + + const environmentView = curTab === 1 ? +
            +

            + Environments +

            + + + + {environments === undefined ? null : environments.map(environment => { + return ( + + + - {environment.Name} + + ) + })} + +
            + : null + + const setConfig = (event, newValue) => { + if (newValue === 1) { + getEnvironments() + } + + setModalUser({}) + setCurTab(newValue) + } + + const data = +
            + + + + + +
            + {usersView} + {environmentView} + +
            + + return ( +
            + {modalView} + {data} +
            + ) +} + +export default Admin diff --git a/frontend/src/AdminSetup.js b/frontend/src/AdminSetup.js new file mode 100644 index 00000000..ce17db46 --- /dev/null +++ b/frontend/src/AdminSetup.js @@ -0,0 +1,224 @@ +/* eslint-disable react/no-multi-comp */ +import React, {useState} from 'react'; +import { makeStyles } from '@material-ui/styles'; + +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; + +const hrefStyle = { + color: "white", + textDecoration: "none" +} + +const bodyDivStyle = { + margin: "auto", + marginTop: "100px", + width: "500px", +} + +const surfaceColor = "#27292D" +const inputColor = "#383B40" + +const boxStyle = { + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, +} + +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important" + }, +}); + +const AdminAccount = props => { + const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, } = props; + + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [firstRequest, setFirstRequest] = useState(true); + + // Used to swap from login to register. True = login, false = register + const register = true + + const classes = useStyles(); + // Error messages etc + const [loginInfo, setLoginInfo] = useState(""); + + const handleValidateForm = () => { + return (username.length > 1 && password.length > 8); + } + + if (isLoggedIn === true) { + window.location.pathname = "/workflows" + } + + const checkAdmin = () => { + const url = globalUrl+'/api/v1/checkusers'; + fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + if (responseJson.reason === "redirect") { + window.location.pathname = "/login" + } + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata: ", error) + }) + } + + if (firstRequest) { + setFirstRequest(false) + checkAdmin() + } + + const onSubmit = (e) => { + e.preventDefault() + // FIXME - add some check here ROFL + + // Just use this one? + var data = {"username": username, "password": password} + var baseurl = globalUrl + const url = baseurl+'/api/v1/register'; + fetch(url, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + setLoginInfo("Successful register :)") + window.location.pathname = "/login" + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata: ", error) + }); + } + + const onChangeUser = (e) => { + setUsername(e.target.value) + } + + const onChangePass = (e) => { + setPassword(e.target.value) + } + + //const onClickRegister = () => { + // if (props.location.pathname === "/login") { + // window.location.pathname = "/register" + // } else { + // window.location.pathname = "/login" + // } + + // setLoginCheck(!register) + //} + + //var loginChange = register ? (

            Want to register? Click here.

            ) : (

            Go back to login? Click here.

            ); + var formtitle = register ?
            Login
            :
            Register
            + + formtitle = "Create administrator account" + + const basedata = +
            + +
            +

            {formtitle}

            + Username +
            + +
            + Password +
            + +
            +
            + + +
            +
            + {loginInfo} +
            +
            +
            +
            + + const loadedCheck = isLoaded ? +
            + {basedata} +
            + : +
            +
            + + return ( +
            + {loadedCheck} +
            + ) +} + +export default AdminAccount; diff --git a/frontend/src/AlertPopup.js b/frontend/src/AlertPopup.js new file mode 100644 index 00000000..67b54596 --- /dev/null +++ b/frontend/src/AlertPopup.js @@ -0,0 +1,26 @@ +import React, { useEffect} from 'react'; + +const Popup = (props) => { + const { data } = props; + + const popupStyle = { + position: "fixed", + width: "300px", + height: "50px", + backgroundColor: "black", + color: "white", + } + + const popupData = +
            + HEY +
            + + return ( +
            + {popupData} +
            + ) +} + +export default Popup diff --git a/frontend/src/AlertTemplate.js b/frontend/src/AlertTemplate.js new file mode 100644 index 00000000..bf5066dd --- /dev/null +++ b/frontend/src/AlertTemplate.js @@ -0,0 +1,44 @@ +import React from 'react' +import InfoIcon from './icons/InfoIcon' +import SuccessIcon from './icons/SuccessIcon' +import ErrorIcon from './icons/ErrorIcon' +import CloseIcon from './icons/CloseIcon' + +const alertStyle = { + backgroundColor: '#151515', + color: 'white', + padding: '10px', + textTransform: 'uppercase', + borderRadius: '3px', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + boxShadow: '0px 2px 2px 2px rgba(0, 0, 0, 0.03)', + fontFamily: 'Arial', + width: '300px', + boxSizing: 'border-box' +} + +const buttonStyle = { + marginLeft: '20px', + border: 'none', + backgroundColor: 'transparent', + cursor: 'pointer', + color: '#FFFFFF' +} + +const AlertTemplate = ({ message, options, style, close }) => { + return ( +
            + {options.type === 'info' && } + {options.type === 'success' && } + {options.type === 'error' && } + {message} + +
            + ) +} + +export default AlertTemplate diff --git a/frontend/src/AngularWorkflow.js b/frontend/src/AngularWorkflow.js new file mode 100644 index 00000000..e10e468e --- /dev/null +++ b/frontend/src/AngularWorkflow.js @@ -0,0 +1,4325 @@ +import React, {useState, useEffect, useLayoutEffect} from 'react'; +import { useInterval } from 'react-powerhooks'; + +import uuid from "uuid"; + +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; +import Grid from '@material-ui/core/Grid'; +import ButtonBase from '@material-ui/core/ButtonBase'; +import Tooltip from '@material-ui/core/Tooltip'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; +import Divider from '@material-ui/core/Divider'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogContent from '@material-ui/core/DialogContent'; +import FormControl from '@material-ui/core/FormControl'; +import IconButton from '@material-ui/core/IconButton'; +import Menu from '@material-ui/core/Menu'; +import Input from '@material-ui/core/Input'; +import FormGroup from '@material-ui/core/FormGroup'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Checkbox from '@material-ui/core/Checkbox'; + + +import PlayArrowIcon from '@material-ui/icons/PlayArrow'; +import AspectRatioIcon from '@material-ui/icons/AspectRatio'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import AppsIcon from '@material-ui/icons/Apps'; +import ScheduleIcon from '@material-ui/icons/Schedule'; +import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'; +import PauseIcon from '@material-ui/icons/Pause'; +import DeleteIcon from '@material-ui/icons/Delete'; +import SaveIcon from '@material-ui/icons/Save'; +import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft'; +import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight'; + +import * as cytoscape from 'cytoscape'; +import * as edgehandles from 'cytoscape-edgehandles'; +import * as clipboard from 'cytoscape-clipboard'; +import CytoscapeComponent from 'react-cytoscapejs'; +import undoRedo from 'cytoscape-undo-redo'; +import Draggable from 'react-draggable'; + +import environmentdata from './environmentdata'; +import cytoscapestyle from './defaultCytoscapeStyle'; +import cxtmenu from 'cytoscape-cxtmenu'; + +import { w3cwebsocket as W3CWebSocket } from "websocket"; +import { useAlert } from "react-alert"; + +const hoverColor = "#f85a3e" +const hoverOutColor = "#e8eaf6" +const surfaceColor = "#27292D" +const inputColor = "#383B40" + +// http://apps.cytoscape.org/apps/yfileslayoutalgorithms +cytoscape.use(edgehandles); +cytoscape.use(clipboard); +cytoscape.use(undoRedo); +cytoscape.use( cxtmenu ); + +// https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react +function useWindowSize() { + const [size, setSize] = useState([0, 0]); + useLayoutEffect(() => { + function updateSize() { + setSize([window.innerWidth, window.innerHeight]); + } + window.addEventListener('resize', updateSize); + updateSize(); + return () => window.removeEventListener('resize', updateSize); + }, []); + return size; +} + +const splitter = "|~|" +//const referenceUrl = "https://shuffler.io/functions/webhooks/" +const referenceUrl = window.location.origin+"/functions/webhooks/" +console.log(window.location) +const AngularWorkflow = (props) => { + const { globalUrl, isLoggedIn, isLoaded } = props; + const alert = useAlert() + + const [bodyWidth, bodyHeight] = useWindowSize(); + const appBarSize = 74 + const [cystyle, ] = useState(cytoscapestyle) + const [cy, setCy] = React.useState() + + const [currentView, setCurrentView] = React.useState("apps") + const [, setAppSearchValue] = React.useState("") + const [triggerAuthentication, setTriggerAuthentication] = React.useState({}) + const [triggerFolders, setTriggerFolders] = React.useState([]) + + const [workflow, setWorkflow] = React.useState({}); + const [leftViewOpen, setLeftViewOpen] = React.useState(true); + const [leftBarSize, setLeftBarSize] = React.useState(350) + const [executionText, setExecutionText] = React.useState(""); + const [executionRequestStarted, setExecutionRequestStarted] = React.useState(false); + + const [appAuthentication, setAppAuthentication] = React.useState({}); + const [variablesModalOpen, setVariablesModalOpen] = React.useState(false); + const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false); + const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false); + const [newVariableName, setNewVariableName] = React.useState(""); + const [newVariableDescription, setNewVariableDescription] = React.useState(""); + const [newVariableValue, setNewVariableValue] = React.useState(""); + const [workflowDone, setWorkflowDone] = React.useState(false) + const [localFirstrequest, setLocalFirstrequest] = React.useState(true) + + const [variableAnchorEl, setVariableAnchorEl] = React.useState(null) + + const [sourceValue, setSourceValue] = React.useState({}) + const [destinationValue, setDestinationValue] = React.useState({}) + const [conditionValue, setConditionValue] = React.useState({}) + + // Trigger stuff + const [selectedTrigger, setSelectedTrigger] = React.useState({}); + const [selectedTriggerIndex, setSelectedTriggerIndex] = React.useState({}); + const [selectedEdge, setSelectedEdge] = React.useState({}); + const [selectedEdgeIndex, setSelectedEdgeIndex] = React.useState({}); + + + const [visited, setVisited] = React.useState([]); + //const [workflow, setWorkflow] = React.useState(workflowdata); + + const [apps, setApps] = React.useState([]); + const [filteredApps, setFilteredApps] = React.useState([]); + const [firstrequest, setFirstrequest] = React.useState(true) + //const [apps, setApps] = React.useState(appdata); + //const [filteredApps, setFilteredApps] = React.useState(); + + const [environments, setEnvironments] = React.useState([]); + const [established, setEstablished] = React.useState(false); + + const [graphSetup, setGraphSetup] = React.useState(false); + + const [selectedApp, setSelectedApp] = React.useState({}); + const [selectedAction, setSelectedAction] = React.useState({}); + const [selectedActionName, setSelectedActionName] = React.useState({}); + const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); + + const [executionRequest, setExecutionRequest] = React.useState({}) + + const [, setExecutingNodes] = React.useState([]) + const [executionRunning, setExecutionRunning] = React.useState(false) + + const [lastSaved, setLastSaved] = React.useState(true) + + const [AppsHoverColor, setAppsHoverColor] = useState(hoverOutColor); + const [VariablesHoverColor, setVariablesHoverColor] = useState(hoverOutColor); + const [HookHoverColor, setHookHoverColor] = useState(hoverOutColor); + const [update, setUpdate] = useState(""); + + const [elements, setElements] = useState([]) + const { start, stop } = useInterval({ + duration: 5000, + startImmediate: false, + callback: () => { + fetchUpdates() + } + }); + + const fetchUpdates = () => { + fetch(globalUrl+"/api/v1/streams/results", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(executionRequest), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!") + } + + return response.json() + }) + .then((responseJson) => { + handleUpdateResults(responseJson) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const abortExecution = () => { + setExecutionRunning(false) + + alert.success("Aborting execution") + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/executions/"+executionRequest.execution_id+"/abort", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } + + return response.json() + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const handleUpdateResults = (responseJson) => { + //console.log(responseJson) + // Loop nodes and find results + // Update on every interval? idk + if (responseJson.execution_id !== executionRequest.execution_id) { + cy.elements().removeClass('success-highlight failure-highlight executing-highlight') + return + } + + if (responseJson.results !== null && responseJson.results !== []) { + for (var key in responseJson.results) { + var item = responseJson.results[key] + var currentnode = cy.getElementById(item.action.id) + if (currentnode.length === 0) { + continue + } + + currentnode = currentnode[0] + const outgoingEdges = currentnode.outgoers('edge') + const incomingEdges = currentnode.incomers('edge') + + //currentnode.removeClass('success-highlight failure-highlight executing-highlight') + switch (item.status) { + case "EXECUTING": + currentnode.removeClass('not-executing-highlight') + currentnode.removeClass('success-highlight') + currentnode.removeClass('failure-highlight') + currentnode.removeClass('awaiting-data-highlight') + currentnode.addClass('executing-highlight') + incomingEdges.addClass('success-highlight') + break + case "WAITING": + currentnode.removeClass('not-executing-highlight') + currentnode.removeClass('success-highlight') + currentnode.removeClass('failure-highlight') + currentnode.removeClass('awaiting-data-highlight') + currentnode.addClass('executing-highlight') + + if (!visited.includes(item.action.label)) { + if (executionRunning) { + alert.show("WAITING FOR "+item.action.label+" with result "+item.result) + visited.push(item.action.label) + setVisited(visited) + } + } + + // FIXME - add outgoing nodes to executing + //const outgoingNodes = outgoingEdges.find().data().target + if (outgoingEdges.length > 0) { + outgoingEdges.addClass('success-highlight') + } + break + case "SUCCESS": + currentnode.removeClass('not-executing-highlight') + currentnode.removeClass('executing-highlight') + currentnode.removeClass('failure-highlight') + currentnode.removeClass('awaiting-data-highlight') + currentnode.addClass('success-highlight') + + if (!visited.includes(item.action.label)) { + if (executionRunning) { + alert.show("Success for "+item.action.label+" with result "+item.result) + visited.push(item.action.label) + setVisited(visited) + } + } + + // FIXME - add outgoing nodes to executing + //const outgoingNodes = outgoingEdges.find().data().target + if (outgoingEdges.length > 0) { + outgoingEdges.addClass('success-highlight') + } + break + case "FAILURE": + currentnode.removeClass('not-executing-highlight') + currentnode.removeClass('executing-highlight') + currentnode.removeClass('success-highlight') + currentnode.removeClass('awaiting-data-highlight') + currentnode.addClass('failure-highlight') + + if (!visited.includes(item.action.label)) { + alert.error("Success for "+item.action.label+" with result "+item.result) + visited.push(item.action.label) + setVisited(visited) + } + break + case "AWAITING_DATA": + currentnode.removeClass('not-executing-highlight') + currentnode.removeClass('executing-highlight') + currentnode.removeClass('success-highlight') + currentnode.removeClass('failure-highlight') + currentnode.addClass('awaiting-data-highlight') + break + default: + break + } + } + } + + if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE") { + stop() + + + setExecutionRunning(false) + var curelements = cy.elements() + for (var i = 0; i < curelements.length; i++) { + if (curelements[i].classes().includes("executing-highlight")) { + curelements[i].removeClass("executing-highlight") + curelements[i].addClass("failure-highlight") + } else { + curelements[i].removeClass('not-executing-highlight') + curelements[i].removeClass('executing-highlight') + curelements[i].removeClass('success-highlight') + curelements[i].removeClass('awaiting-data-highlight') + curelements[i].removeClass('failure-highlight') + } + } + } else if (responseJson.status === "FINISHED") { + setExecutionRunning(false) + stop() + } + } + + const saveWorkflow = (curworkflow) => { + var success = false + + // This might not be the right course of action, but seems logical, as items could be running already + // Makes it possible to update with a version in current render + stop() + var useworkflow = workflow + if (curworkflow !== undefined) { + useworkflow = curworkflow + } else { + alert.info("Saving workflow") + } + + var cyelements = cy.elements() + var newActions = [] + var newTriggers = [] + var newBranches = [] + for (var key in cyelements) { + if (cyelements[key].data === undefined) { + continue + } + + var type = cyelements[key].data()["type"] + if (type === undefined) { + if (cyelements[key].data().source === undefined || + cyelements[key].data().target === undefined) { + continue + } + + var parsedElement = { + id: cyelements[key].data().id, + source_id: cyelements[key].data().source, + destination_id: cyelements[key].data().target, + conditions: cyelements[key].data().conditions, + } + + newBranches.push(parsedElement) + } else { + if (type === "ACTION") { + // FIXME - check whether position is new to not fuck up params etc. + var curworkflowAction = useworkflow.actions.find(a => a.id === cyelements[key].data()["id"]) + if (curworkflowAction === undefined) { + curworkflowAction = cyelements[key].data() + } + + curworkflowAction.position = cyelements[key].position() + + // workaround to fix some edgecases + if (curworkflowAction.parameters === "" || curworkflowAction.parameters === null) { + curworkflowAction.parameters = [] + } + + newActions.push(curworkflowAction) + } else if (type === "TRIGGER") { + //console.log("TRIGGER") + var curworkflowTrigger = useworkflow.triggers.find(a => a.id === cyelements[key].data()["id"]) + if (curworkflowTrigger === undefined) { + curworkflowTrigger = cyelements[key].data() + } + + curworkflowTrigger.position = cyelements[key].position() + //console.log(curworkflowTrigger) + + newTriggers.push(curworkflowTrigger) + } + } + } + + useworkflow.actions = newActions + useworkflow.triggers = newTriggers + useworkflow.branches = newBranches + + setLastSaved(true) + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(useworkflow), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for setting workflows :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + console.log(responseJson) + alert.error("Failed to save: "+responseJson.reason) + } else { + success = true + alert.success("Successfully saved workflow") + } + }) + .catch(error => { + alert.error(error.toString()) + }); + + return success + } + + const monitorUpdates = () => { + const firstnode = cy.getElementById(workflow.start) + if (firstnode.length === 0) { + return false + } + + cy.elements().removeClass('success-highlight failure-highlight executing-highlight') + firstnode[0].addClass('executing-highlight') + return true + } + + const executeWorkflowWebsocket = () => { + if (!lastSaved) { + //alert.error("You might have forgotten to save before executing.") + console.log("FIXME: Might have forgotten to save before executing.") + } + + var returncheck = monitorUpdates() + if (!returncheck) { + alert.error("No startnode set.") + return + } + + setVisited([]) + setExecutionRunning(true) + setExecutionRequest({}) + + var curelements = cy.elements() + for (var i = 0; i < curelements.length; i++) { + curelements[i].addClass("not-executing-highlight") + } + + if (executionText.length > 0) { + alert.success("Starting execution with argument "+executionText) + } else { + alert.success("Starting execution") + } + + const data = {"execution_argument": executionText} + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/execute_fs", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + body: JSON.stringify(data), + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to start: "+responseJson.reason) + stop() + return + } + + setExecutionRequest({ + "execution_id": responseJson.execution_id, + "authorization": responseJson.authorization, + }) + setExecutingNodes([workflow.start]) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const executeWorkflow = () => { + if (!lastSaved) { + //alert.error("You might have forgotten to save before executing.") + console.log("FIXME: Might have forgotten to save before executing.") + } + + var returncheck = monitorUpdates() + if (!returncheck) { + alert.error("No startnode set.") + return + } + + setVisited([]) + setExecutionRequest({}) + setExecutionRequestStarted(true) + stop() + + var curelements = cy.elements() + for (var i = 0; i < curelements.length; i++) { + curelements[i].addClass("not-executing-highlight") + } + + if (executionText.length > 0) { + alert.success("Starting execution with argument "+executionText) + } else { + alert.success("Starting execution") + } + + const data = {"execution_argument": executionText, "start": workflow.start} + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/execute", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + body: JSON.stringify(data), + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to start: "+responseJson.reason) + stop() + return + } else { + setExecutionRunning(true) + setExecutionRequestStarted(false) + } + + setExecutionRequest({ + "execution_id": responseJson.execution_id, + "authorization": responseJson.authorization, + }) + setExecutingNodes([workflow.start]) + start() + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + //const handleAppVersioning = (apps) => { + // var newapps = [] + // for (var key in apps) { + // var item = apps[key] + // const previtem = newapps.findIndex(data => data.name === item.name) + // if (previtem === -1) { + // item["versions"] = [item.app_version] + // newapps.push(item) + // continue + // } + + // // THere might be duplicates for some reason.. + // if (!newapps[previtem]["versions"].includes(item.app_version)) { + // newapps[previtem]["versions"].push(item.app_version) + // } + // } + // + // // FIXME - handle this, as we can't have more than one of each :) + // //setVersionedApps(newapps) + //} + + const getApps = () => { + fetch(globalUrl+"/api/v1/workflows/apps", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + } + + return response.json() + }) + .then((responseJson) => { + // FIXME - handle versions on left bar + //handleAppVersioning(responseJson) + setApps(responseJson) + setFilteredApps(responseJson) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const getWorkflow = () => { + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!") + window.location.pathname = "/workflows" + } + + return response.json() + }) + .then((responseJson) => { + setWorkflow(responseJson) + setWorkflowDone(true) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const onUnselect = (event) => { + //console.log("Unselect?") + console.time("UNSELECT") + + // FIXME - check if they have value before overriding like this for no reason. + // Would save a lot of time (400~ ms -> 30ms) + //setSelectedActionName({}) + setSelectedAction({}) + setSelectedApp({}) + setSelectedTrigger({}) + setSelectedEdge({}) + // setSelectedTriggerIndex(-1) + //setSelectedActionEnvironment({}) + //setSelectedEdge({}) + //setTriggerAuthentication({}) + //setSelectedTriggerIndex(-1) + //setTriggerFolders([]) + + //setLocalFirstrequest(true) + console.timeEnd("UNSELECT") + } + + const onEdgeSelect = (event) => { + setSelectedEdgeIndex(workflow.branches.findIndex(data => data.id === event.target.data()["id"])) + setSelectedEdge(event.target.data()) + } + + const onNodeSelect = (event) => { + const data = event.target.data() + //console.log(data) + + if (data.type === "ACTION") { + // FIXME - unselect + //console.log(cy.elements('[_id!="${data._id}"]`)) + console.time('ACTIONSTART'); + const curaction = workflow.actions.find(a => a.id === data.id) + if (!curaction || curaction === undefined) { + //console.log("Action not found error") + return + } + + const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version) + if (!curapp || curapp === undefined) { + //console.log("App "+curaction.app_name+":"+curaction.app_version+" not found error") + return + } + + var env = environments.find(a => a.name === curaction.environment) + if (!env || env === undefined) { + env = environments[0] + } + + console.log(curapp) + + setSelectedApp(curapp) + setSelectedActionEnvironment(env) + setSelectedActionName(curaction.name) + setSelectedAction(curaction) + console.timeEnd("ACTIONSTART") + } else if (data.type === "TRIGGER") { + //console.log("Should handle trigger "+data.triggertype) + //console.log(data) + + setSelectedTriggerIndex(workflow.triggers.findIndex(a => a.id === data.id)) + setSelectedTrigger(data) + setSelectedActionEnvironment(data.env) + setSelectedActionName(data.name) + } else { + console.log("Should handle type "+data.type) + } + } + + const onEdgeAdded = (event) => { + setLastSaved(false) + const edge = event.target.data() + + // Check if: + // dest == source && source == dest + // dest == dest && source == source + // backend: check all children? to stop recursion + var found = false + for (var key in workflow.branches) { + if (workflow.branches[key].destination_id === edge.source && workflow.branches[key].source_id === edge.target) { + alert.error("A pointer in the opposite direction already exists") + event.target.remove() + found = true + break + } else if (workflow.branches[key].destination_id === edge.target && workflow.branches[key].source_id === edge.source) { + // Checks if NOT trigger + if (workflow.triggers !== undefined && workflow.triggers.find(data => data.id === workflow.branches[key].source_id).length === 0) { + alert.error("That pointer already exists") + event.target.remove() + } + found = true + break + } else if (edge.target === workflow.start) { + var targetnode = workflow.triggers.findIndex(data => data.id === edge.source) + if (targetnode === -1) { + alert.error("Can't make arrow to starting node") + event.target.remove() + found = true + break + } + } else { + // Find the targetnode and check if its a trigger + // FIXME - do this for both actions and other types? + //targetnode = workflow.triggers.findIndex(data => data.id === edge.target) + //if (targetnode !== -1) { + // alert.error("Can't have triggers as target") + // event.target.remove() + // found = true + // break + //} + } + } + + var newbranch = { + "source_id": edge.source, + "destination_id": edge.target, + "id_": edge.id, + "id": edge.id, + "hasErrors": false, + } + + if (!found) { + newbranch["hasErrors"] = false + } + + workflow.branches.push(newbranch) + setWorkflow(workflow) + } + + const onNodeAdded = (event) => { + //setLastSaved(false) + const node = event.target + + if (node.isNode() && cy.nodes().size() === 1) { + //setStartNode(node.data('id')) + workflow.start = node.data('id') + setWorkflow(workflow) + } + + } + + const onEdgeRemoved = (event) => { + const edge = event.target + + workflow.branches = workflow.branches.filter(a => a.id !== edge.data().id) + setWorkflow(workflow) + + // trigger as source check + const indexcheck = workflow.triggers.findIndex(data => edge.data()["source"] === data.id) + if (indexcheck !== -1) { + //alert.error("Can't remove edge from a trigger") + console.log("Shouldnt remove edge from trigger") + //const edgeToBeAdded = { + // group: "edges", + // data: newcybranch, + //} + } + } + + const onNodeRemoved = (event) => { + const node = event.target + const data = node.data() + + //var currentnode = cy.getElementById(data.id) + //if (currentnode.length === 0) { + //} + if (workflow.start === data.id && workflow.actions.length > 1) { + // FIXME - should check branches connected to startnode, as picking random + // might just be confusing + cy.nodes().forEach(function( ele ) { + if (ele.id() !== workflow.start && ele.data()["label"] !== undefined) { + alert.success("Changed startnode to "+ele.data()["label"]) + ele.data("isStartNode", true) + workflow.start = ele.id() + return + } + }); + } + + + workflow.actions = workflow.actions.filter(a => a.id !== data.id) + workflow.triggers = workflow.triggers.filter(a => a.id !== data.id) + + setWorkflow(workflow) + if (data.type === "TRIGGER") { + saveWorkflow(workflow) + } + } + + var previouskey = 0 + const handleKeyDown = (event) => { + // SHIFT = 16 + // CTRL = 17 + //console.log(event.keyCode) + switch( event.keyCode ) { + case 27: + console.log("ESCAPE") + break; + case 46: + removeNode() + console.log("DELETE") + break; + case 38: + console.log("UP") + break; + case 37: + console.log("LEFT") + break; + case 40: + console.log("DOWN") + break; + case 39: + console.log("RIGHT") + break; + case 90: + if (previouskey === 17) { + console.log("CTRL+Z") + } + break; + case 67: + if (previouskey === 17) { + console.log("CTRL+C") + } + break; + case 86: + if (previouskey === 17) { + console.log("CTRL+V") + } + break; + case 88: + if (previouskey === 17) { + console.log("CTRL+V") + } + break; + case 83: + if (previouskey === 17) { + event.preventDefault() + saveWorkflow() + } + break; + case 70: + if (previouskey === 17) { + event.preventDefault() + cy.fit(null, 50) + } + break; + case 65: + // As a poweruser myself, I found myself hitting this a few + // too many times to just edit text. Need a better bind + // + //if (previouskey === 17) { + // event.preventDefault() + // if (executionRunning || executionRequestStarted) { + // abortExecution() + // } else { + // executeWorkflow() + // } + // cy.fit(null, 50) + //} + break; + default: + //console.log(event.keyCode) + break; + } + + previouskey = event.keyCode + } + + const registerKeys = () => { + document.addEventListener("keydown", handleKeyDown); + } + + const getEnvironments = () => { + fetch(globalUrl+"/api/v1/getenvironments", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + return + } + + return response.json() + }) + .then((responseJson) => { + setEnvironments(responseJson) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + useEffect(() => { + if (firstrequest) { + setFirstrequest(false) + getWorkflow() + getApps() + getEnvironments() + return + } + + // App length necessary cus of cy initialization + if (elements.length === 0 && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) { + setGraphSetup(true) + setupGraph() + } else if (!established && cy !== undefined && apps.length > 0 && Object.getOwnPropertyNames(workflow).length > 0){ + setEstablished(true) + cy.edgehandles({ + handleNodes: (el) => el.isNode(), + preview: false, + toggleOffOnLeave: false, + loopAllowed: function( node ){ + return false; + }, + }) + + cy.fit(null, 200) + + cy.on('select', 'node', (e) => onNodeSelect(e)) + cy.on('select', 'edge', (e) => onEdgeSelect(e)) + cy.on('unselect', (e) => onUnselect(e)) + + cy.on('add', 'node', (e) => onNodeAdded(e)) + cy.on('add', 'edge', (e) => onEdgeAdded(e)) + cy.on('remove', 'node', (e) => onNodeRemoved(e)) + cy.on('remove', 'edge', (e) => onEdgeRemoved(e)) + + cy.on('mouseover', 'edge', (e) => onEdgeHover(e)) + cy.on('mouseout', 'edge', (e) => onEdgeHoverOut(e)) + cy.on('mouseover', 'node', (e) => onNodeHover(e)) + cy.on('mouseout', 'node', (e) => onNodeHoverOut(e)) + + //cy.on('mouseover', 'node', () => $(targetElement).addClass('mouseover')); + + //cy.on('cxttapstart', 'node', (e) => edgeHandler.start(e.target)) + //cy.on('cxttapend', 'node', (e) => edgeHandler.stop()) + //cy.on('cxtdragover', 'node', (e) => edgeHandler.preview(e.target)) + //cy.on('cxtdragout', 'node', (e) => edgeHandler.unpreview(e.target)) + + // RIGHT HERE..? + // This is wrong sometimes.. I'm mad + document.title = "Workflow - "+workflow.name + registerKeys() + //setStartNode(workflow.start) + } else if (established) { + //console.log("established - should fix colors of things") + //console.log(cy.elements()) + } + }) + + var previousnodecolor = "" + //var previousedgecolor = "" + const animationDuration = 150 + const onNodeHoverOut = (event) => { + event.target.animate({ + style: { + "border-width": "1px", + } + }, { + duration: animationDuration, + }) + + console.log(previousnodecolor) + } + + const onNodeHover = (event) => { + //event.target.style("border-width", "5px") + event.target.animate({ + style: { + "border-width": "5px", + "border-opacity": ".7", + } + }, { + duration: animationDuration, + }) + + previousnodecolor = event.target.style("border-color") + } + + const onEdgeHoverOut = (event) => { + //event.target.removeStyle() + } + + // This is here to have a proper transition for lines + const onEdgeHover = (event) => { + + //console.log(event.target.data()) + //const sourcecolor = cy.getElementById(event.target.data("source")).style("border-color") + //const targetcolor = cy.getElementById(event.target.data("target")).style("border-color") + //event.target.animate({ + // style: { + // "line-fill": "linear-gradient", + // 'target-arrow-color': targetcolor, + // "line-gradient-stop-colors": [sourcecolor, targetcolor], + // "line-gradient-stop-positions": [0, 1], + // }, + // duration: 0, + //}) + } + + + const setupGraph = () => { + const actions = workflow.actions.map(action => { + const node = {} + node.position = action.position + node.data = action + + node.data._id = action["id"] + node.data.type = "ACTION" + node.isStartNode = action["id"] === workflow.start + + return node; + }) + + const triggers = workflow.triggers.map(trigger => { + const node = {} + node.position = trigger.position + node.data = trigger + + node.data._id = trigger["id"] + node.data.type = "TRIGGER" + + return node; + }) + + // FIXME - tmp branch update + var insertedNodes = [].concat(actions, triggers) + const edges = workflow.branches.map((branch, index) => { + //workflow.branches[index].conditions = [{ + + const edge = { }; + var conditions = workflow.branches[index].conditions + if (conditions === undefined || conditions === null) { + conditions = [] + } + + var label = "" + if (conditions.length === 1) { + label = conditions.length+" condition" + } else if (conditions.length > 1) { + label = conditions.length+" conditions" + } + + edge.data = { + id: branch.id, + _id: branch.id, + source: branch.source_id, + target: branch.destination_id, + label: label, + conditions: conditions, + hasErrors: branch.has_errors + }; + return edge; + }) + + setWorkflow(workflow) + + // Verifies if a branch is valid and skips others + var newedges = [] + for (var key in edges) { + var item = edges[key] + + const sourcecheck = insertedNodes.find(data => data.data.id === item.data.source) + const destcheck = insertedNodes.find(data => data.data.id === item.data.target) + if (sourcecheck === undefined || destcheck === undefined) { + continue + } + + newedges.push(item) + } + + insertedNodes = insertedNodes.concat(newedges) + setElements(insertedNodes) + } + + const removeNode = () => { + setSelectedApp({}) + setSelectedAction({}) + setSelectedActionName("") + + const selectedNode = cy.$(':selected') + if (selectedNode.data() === undefined) { + return + } + + if (selectedNode.data().type === "TRIGGER") { + console.log("Should remove trigger!") + console.log(selectedNode.data()) + const triggerindex = workflow.triggers.findIndex(data => data.id === selectedNode.data().id) + setSelectedTriggerIndex(triggerindex) + if (selectedNode.data().trigger_type === "SCHEDULE") { + setSelectedTrigger(selectedNode.data()) + stopSchedule(selectedNode.data(), triggerindex) + } else if (selectedNode.data().trigger_type === "WEBHOOK") { + setSelectedTrigger(selectedNode.data()) + deleteWebhook(selectedNode.data(), triggerindex) + } else if (selectedNode.data().trigger_type === "EMAIL") { + setSelectedTrigger(selectedNode.data()) + stopMailSub(selectedTrigger, triggerindex) + } + + } + + selectedNode.remove() + setSelectedTrigger({}) + setSelectedTriggerIndex({}) + } + + const stopSchedule = (trigger, triggerindex) => { + alert.info("Stopping schedule") + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/schedule/"+trigger.id, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to delete schedule: " + responseJson.reason) + } else { + alert.success("Successfully stopped schedule") + workflow.triggers[triggerindex].status = "stopped" + trigger.status = "stopped" + setSelectedTrigger(trigger) + setWorkflow(workflow) + saveWorkflow(workflow) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + //const submitSchedule = (id, name, frequency, executionArg) => { + const submitSchedule = (trigger, triggerindex) => { + const cronSplit = workflow.triggers[triggerindex].parameters[0].value.split("*") + if (cronSplit.length <= 5 || cronSplit.length > 6) { + alert.error("Error: Bad cron, example run every 1 minute: */1 * * * *") + return + } + + if (trigger.name.length <= 0) { + alert.error("Error: name can't be empty") + return + } + + alert.info("Attempting to create schedule with name " + trigger.name) + const data = { + "name": trigger.name, + "frequency": workflow.triggers[triggerindex].parameters[0].value, + "execution_argument": workflow.triggers[triggerindex].parameters[1].value, + "id": trigger.id, + } + + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/schedule", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to set schedule: " + responseJson.reason) + } else { + alert.success("Successfully created schedule") + workflow.triggers[triggerindex].status = "running" + trigger.status = "running" + setSelectedTrigger(trigger) + setWorkflow(workflow) + console.log("Should set the status to running and save") + saveWorkflow(workflow) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const setAppSearch = (event) => { + setFilteredApps(apps.filter(app => app.name.includes(event.target.value))) + } + + const appViewStyle = { + marginLeft: "5px", + marginRight: "5px", + display: "flex", + flexDirection: "column", + } + + const scrollStyle = { + marginTop: "10px", + overflow: "scroll", + height: "66vh", + overflowX: "auto", + overflowY: "auto", + } + + const paperAppStyle = { + minHeight: "100px", + maxHeight: "100px", + minWidth: "100%", + maxWidth: "100%", + marginTop: "5px", + color: "white", + backgroundColor: surfaceColor, + cursor: "pointer", + display: "flex", + } + + // All this is stupid lmao + const handleHookHover = () => { + setHookHoverColor(hoverColor) + setAppsHoverColor(hoverOutColor) + setVariablesHoverColor(hoverOutColor) + } + + const handleHookHoverOut = () => { + setHookHoverColor(hoverOutColor) + } + + const handleAppsHover = () => { + setAppsHoverColor(hoverColor) + setHookHoverColor(hoverOutColor) + setVariablesHoverColor(hoverOutColor) + } + + const handleAppsHoverOut = () => { + setAppsHoverColor(hoverOutColor) + } + + const handleVariablesHover = () => { + setVariablesHoverColor(hoverColor) + setHookHoverColor(hoverOutColor) + setAppsHoverColor(hoverOutColor) + } + + const handleVariablesHoverOut = () => { + setVariablesHoverColor(hoverOutColor) + } + + const VariablesView = () => { + const [open, setOpen] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + + if (workflow.workflow_variables === undefined || workflow.workflow_variables === null || workflow.workflow_variables.length === 0) { + return ( +
            +
            +
            + Looks like you don't have any variables yet. +
            +
            + +
            +
            +
            +
            + ) + } + + const paperVariableStyle = { + minHeight: "50px", + maxHeight: "50px", + minWidth: "100%", + maxWidth: "100%", + marginTop: "5px", + color: "white", + backgroundColor: surfaceColor, + cursor: "pointer", + display: "flex", + } + + const menuClick = (event) => { + setOpen(!open) + setAnchorEl(event.currentTarget); + } + + const deleteVariable = (variableName) => { + workflow.workflow_variables = workflow.workflow_variables.filter(data => data.name !== variableName) + setWorkflow(workflow) + } + + const variableScrollStyle = { + marginTop: "10px", + overflow: "scroll", + height: "66vh", + overflowX: "auto", + overflowY: "auto", + flex: "10", + } + + return ( +
            +
            + {workflow.workflow_variables.map(variable=> { + return ( +
            + { + }}> +
            +
            +
            { + setNewVariableName(variable.name) + setNewVariableDescription(variable.description) + setNewVariableValue(variable.value) + setVariablesModalOpen(true)}}> + Name: {variable.name} +
            +
            + + + + { + setOpen(false) + setAnchorEl(null) + }} + > + + { + setOpen(false) + setNewVariableName(variable.name) + setNewVariableDescription(variable.description) + setNewVariableValue(variable.value) + setVariablesModalOpen(true) + }} key={"Edit"}>{"Edit"} + { + deleteVariable(variable.name) + setOpen(false) + }} key={"Delete"}>{"Delete"} + +
            +
            + +
            + ) + })} + +
            +
            + +
            +
            + ) + } + + const HandleLeftView = () => { + var thisview = + if (currentView === "triggers") { + thisview = + } else if (currentView === "variables") { + thisview = + } + + return( +
            +
            { + setLeftViewOpen(false) + setLeftBarSize(50) + }}> + + + +
            + +
            + {thisview} +
            +
            + +
            +
            {setCurrentView("apps")}}> + + + + + + Apps + + +
            +
            {setCurrentView("triggers")}}> + + + + + + Triggers + + +
            +
            {setCurrentView("variables")}}> + + + + + + Variables + + +
            +
            +
            +
            + ) + } + + const TriggersView = () => { + const triggersViewStyle = { + marginLeft: "10px", + marginRight: "10px", + display: "flex", + flexDirection: "column", + } + + // Predefined hurr + var triggers = [{ + "name": "Webhook", + "type": "TRIGGER", + "status": "uninitialized", + "description": "Realtime HTTP trigger", + "trigger_type": "WEBHOOK", + "errors": null, + "large_image": 'data:image/png;base64,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', + "is_valid": true, + "label": "Webhook", + "environment": "onprem", + }, + { + "name": "User Input", + "type": "TRIGGER", + "status": "running", + "description": "Wait for user input", + "trigger_type": "USERINPUT", + "errors": null, + "large_image": '', + "is_valid": true, + "label": "User input", + "environment": "onprem", + }, + { + "name": "Schedule", + "type": "TRIGGER", + "status": "uninitialized", + "description": "Schedule execution time", + "trigger_type": "SCHEDULE", + "errors": null, + "large_image": "data:image/png;base64,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", + "is_valid": true, + "label": "Schedule", + "environment": "onprem", + }, + { + "name": "Email", + "type": "TRIGGER", + "status": "uninitialized", + "description": "Add your email provider", + "trigger_type": "EMAIL", + "errors": null, + "is_valid": true, + "label": "Email", + "environment": "cloud", + "large_image": 'data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoM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', + }] + + return ( +
            +
            + {triggers.map(trigger => { + var imageline = trigger.large_image.length === 0 ? + + : + + return( + {handleTriggerDrag(e, trigger)}} + onStop={(e) => {handleDragStop(e)}} + dragging={false} + position={{ + x: 0, + y: 0, + }} + > + {}}> +
            +
            + + + + {imageline} + + + + +

            {trigger.name}

            +
            + + {trigger.description} + +
            +
            +
            +
            + ) + })} +
            +
            + ) + } + + var newNodeId = "" + const handleTriggerDrag = (e, data) => { + const cycontainer = cy.container() + // Chrome lol + //if (e.srcElement !== undefined && e.srcElement.localName === "canvas") { + if (e.pageX > cycontainer.offsetLeft && e.pageX < cycontainer.offsetLeft+cycontainer.offsetWidth && e.pageY > cycontainer.offsetTop && e.pageY < cycontainer.offsetTop+cycontainer.offsetHeight) { + if (newNodeId.length > 0) { + var currentnode = cy.getElementById(newNodeId) + if (currentnode.length === 0) { + return + } + + currentnode[0].renderedPosition("x", e.pageX-cycontainer.offsetLeft) + currentnode[0].renderedPosition("y", e.pageY-cycontainer.offsetTop) + } else{ + console.log(workflow) + if (workflow.start === "" || workflow.start === undefined) { + alert.error("Define a starting action first.") + return + } + + newNodeId = uuid.v4() + console.log(data) + + const newposition = { + "x": e.pageX-cycontainer.offsetLeft, + "y": e.pageY-cycontainer.offsetTop, + } + + console.log(data) + const newAppData = { + app_name: data.name, + app_version: "1.0.0", + environment: data.environment, + errors: [], + id_: newNodeId, + _id_: newNodeId, + id: newNodeId, + is_valid: true, + label: data.label, + type: data.type, + trigger_type: data.trigger_type, + large_image: data.large_image, + status: "uninitialized", + name: data.name, + isStartNode: false, + position: newposition, + } + + // Can all the data be in here? hmm + const nodeToBeAdded = { + group: "nodes", + data: newAppData, + renderedPosition: newposition, + } + + cy.add(nodeToBeAdded) + + if (workflow.triggers === undefined) { + workflow.triggers = [newAppData] + } else { + workflow.triggers.push(newAppData) + } + + const newEdgeUuid = uuid.v4() + const newbranch = { + "source_id": newNodeId, + "destination_id": workflow.start, + "id_": newEdgeUuid, + "id": newEdgeUuid, + "hasErrors": false, + } + + const newcybranch = { + "source": newNodeId, + "target": workflow.start, + "_id": newEdgeUuid, + "id": newEdgeUuid, + "hasErrors": false, + } + + const edgeToBeAdded = { + group: "edges", + data: newcybranch, + } + + if (data.name !== "User Input") { + workflow.branches.push(newbranch) + cy.add(edgeToBeAdded) + } + + setWorkflow(workflow) + } + } + } + + const handleAppDrag = (e, app) => { + const cycontainer = cy.container() + // Chrome lol + //if (e.srcElement !== undefined && e.srcElement.localName === "canvas") { + if (e.pageX > cycontainer.offsetLeft && e.pageX < cycontainer.offsetLeft+cycontainer.offsetWidth && e.pageY > cycontainer.offsetTop && e.pageY < cycontainer.offsetTop+cycontainer.offsetHeight) { + if (newNodeId.length > 0) { + var currentnode = cy.getElementById(newNodeId) + if (currentnode.length === 0) { + return + } + + currentnode[0].renderedPosition("x", e.pageX-cycontainer.offsetLeft) + currentnode[0].renderedPosition("y", e.pageY-cycontainer.offsetTop) + } else{ + if (app.actions === undefined || app.actions === null || app.actions.length === 0) { + alert.error("App "+app.name+" currently has no actions to perform. Please go to https://shuffler.io/apps to edit it.") + return + } + + newNodeId = uuid.v4() + const actionType = "ACTION" + const actionLabel = getNextActionName(app.name) + var parameters = null + + if (app.actions[0].parameters !== null && app.actions[0].parameters.length > 0) { + parameters = app.actions[0].parameters + } + + var newAppPopup = false + if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) { + console.log("Should make modal popup for new app") + newAppPopup = true + } + + const newAppData = { + app_name: app.name, + app_version: app.app_version, + app_id: app.id, + sharing: app.sharing, + private_id: app.private_id, + environment: "onprem", + errors: [], + id_: newNodeId, + _id_: newNodeId, + id: newNodeId, + is_valid: true, + label: actionLabel, + type: actionType, + name: app.actions[0].name, + parameters: parameters, + isStartNode: false, + large_image: app.large_image, + authentication: [], + } + + console.log(newAppData) + + // FIXME - find the cytoscape offset position + // Can this be done with zoom calculations? + const nodeToBeAdded = { + group: "nodes", + data: newAppData, + renderedPosition: { + x: e.layerX, + y: e.layerY, + } + } + + cy.add(nodeToBeAdded) + + if (workflow.actions === undefined || workflow.actions.length === 0) { + workflow.start = newAppData.id + workflow.actions = [] + newAppData.isStartNode = true + //setStartNode(newAppData.id) + } + + if (workflow.actions.length > 0 && elements.length === 0) { + const actions = workflow.actions.map(action => { + const node = {} + node.position = action.position + node.data = action + + node.data._id = action["id"] + node.data.type = "ACTION" + node.isStartNode = action["id"] === workflow.start + + return node; + }) + + const tmpelements = [].concat(actions) + setElements(tmpelements) + } + + if (workflow.actions.length === 1 && workflow.actions[0].id === workflow.start) { + const newEdgeUuid = uuid.v4() + const newcybranch = { + "source": workflow.start, + "target": newNodeId, + "_id": newEdgeUuid, + "id": newEdgeUuid, + "hasErrors": false, + } + + const edgeToBeAdded = { + group: "edges", + data: newcybranch, + } + console.log("SHOULD STITCH WITH STARTNODE") + cy.add(edgeToBeAdded) + } + + workflow.actions.push(newAppData) + setWorkflow(workflow) + + if (newAppPopup) { + alert.error("SHOULD MAKE USER AUTHENTICATE THE APP OR SET hasError") + alert.info("Remember: set the authentication for the user itself, not the app") + } + } + } + } + + const handleDragStop = (e) => { + newNodeId = "" + } + + const appScrollStyle = { + overflow: "scroll", + maxHeight: bodyHeight-appBarSize-150, + minHeight: bodyHeight-appBarSize-150, + overflowY: "auto", + overflowX: "hidden", + } + + const AppView = () => { + return( +
            +
            +
            + {filteredApps.map(app=> { + // FIXME - add label to apps, as this might be slow with A LOT of apps + var newAppname = app.name + newAppname = newAppname.replace("_", " ") + newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) + + const image = "url("+app.large_image+")" + return( + {handleAppDrag(e, app)}} + onStop={(e) => {handleDragStop(e)}} + dragging={false} + position={{ + x: 0, + y: 0, + }} + > + +
            +
            + + +
            + + + +

            {newAppname}

            +
            + + Description + + + Version: {app.app_version} + +
            + + + + ) + })} +
            +
            +
            + ) + } + + const getNextActionName = (appName) => { + var highest = "" + //label = name + _number + for (var key in workflow.actions) { + const item = workflow.actions[key] + if (item.app_name === appName) { + var number = item.label.split("_") + if (isNaN(number[-1]) && parseInt(number[number.length-1]) > highest) { + highest = number[number.length-1] + } + } + } + + if (highest) { + return appName+"_"+(parseInt(highest)+1) + } else { + return appName+"_"+1 + } + } + + const setNewSelectedAction = (e) => { + const newaction = selectedApp.actions.find(a => a.name === e.target.value) + + selectedAction.name = newaction.name + selectedAction.parameters = newaction.parameters + + // FIXME - this is broken sometimes lol + //var env = environments.find(a => a.name === newaction.environment) + //if ((!env || env === undefined) && selectedAction.environment === undefined ) { + // env = environments[0] + //} + //setSelectedActionEnvironment(env) + + setSelectedAction(selectedAction) + setSelectedActionName(e.target.value) + } + + // APPSELECT at top + // appname & version + // description + // ACTION select + const selectedNameChange = (event) => { + selectedAction.label = event.target.value + setSelectedAction(selectedAction) + } + + const selectedTriggerChange = (event) => { + selectedTrigger.label = event.target.value + setSelectedTrigger(selectedTrigger) + } + + const getParents = (action) => { + var allkeys = [action.id] + var handled = [] + var results = [] + + while(true) { + for (var key in allkeys) { + var currentnode = cy.getElementById(allkeys[key]) + if (handled.includes(currentnode.data().id)) { + continue + } else { + // Get the name / label here too? + handled.push(currentnode.data().id) + results.push(currentnode.data()) + } + + if (currentnode.length === 0) { + continue + } + + const incomingEdges = currentnode.incomers('edge') + if (incomingEdges.length === 0) { + continue + } + + for (var i = 0; i < incomingEdges.length; i++) { + var tmp = incomingEdges[i] + if (!allkeys.includes(tmp.data().source)) { + allkeys.push(tmp.data().source) + } + } + } + if (results.length === allkeys.length) { + break + } + } + + // Remove self + results = results.filter(data => data.id !== action.id) + results = results.filter(data => data.type !== "TRIGGER") + results.push({"label": "Execution Argument", "type": "INTERNAL"}) + return results + } + + // BOLD name: type: required? + // FORM + // Dropdown -> static, action, local env, global env + // VALUE (JSON) + // {data.name}, {data.description}, {data.required}, {data.schema.type} + const AppActionArguments = () => { + const [selectedActionParameters, setSelectedActionParameters] = React.useState([]) + const [selectedVariableParameter, setSelectedVariableParameter] = React.useState() + + useEffect(() => { + if (selectedActionParameters !== null && selectedActionParameters.length === 0) { + setSelectedActionParameters(selectedAction.parameters) + } + + if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && (workflow.workflow_variables !== null && workflow.workflow_variables.length > 0)) { + // FIXME - this is the bad thing + setSelectedVariableParameter(workflow.workflow_variables[0].name) + } + + }) + + const changeActionParameter = (event, count) => { + selectedActionParameters[count].value = event.target.value + selectedAction.parameters = selectedActionParameters + setSelectedAction(selectedAction) + } + + + const changeActionParameterVariable = (fieldvalue, count) => { + setSelectedVariableParameter(fieldvalue) + + // this isn't updated anywhere in the workflow + setSelectedActionName({}) + setSelectedAction({}) + setSelectedTrigger({}) + setSelectedApp({}) + setSelectedEdge({}) + + selectedActionParameters[count].action_field = fieldvalue + selectedAction.parameters = selectedActionParameters + + setSelectedActionName(selectedActionName) + setSelectedApp(selectedApp) + setSelectedAction(selectedAction) + } + + // Sets ACTION_RESULT things + const changeActionParameterActionResult = (fieldvalue, count) => { + //cy.nodes().forEach(function( ele ) { + // if (ele.data()["label"] === fieldvalue) { + // selectedActionParameters[count].action_field = ele.id() + // return + // } + //}); + + selectedActionParameters[count].action_field = fieldvalue + selectedAction.parameters = selectedActionParameters + + setSelectedActionName({}) + setSelectedAction({}) + setSelectedTrigger({}) + setSelectedApp({}) + setSelectedEdge({}) + // FIXME - check if startnode + + // Set value + setSelectedActionName(selectedActionName) + setSelectedApp(selectedApp) + + setSelectedAction(selectedAction) + } + + const changeActionParameterVariant = (data, count) => { + selectedActionParameters[count].variant = data + selectedActionParameters[count].value = "" + + if (data === "ACTION_RESULT") { + var parents = getParents(selectedAction) + if (parents.length > 0) { + selectedActionParameters[count].action_field = parents[0].label + } else { + selectedActionParameters[count].action_field = "" + } + } else if (data === "WORKFLOW_VARIABLE") { + if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) { + selectedActionParameters[count].action_field = workflow.workflow_variables[0].name + } + } + + selectedAction.parameters = selectedActionParameters + + // This is a stupid workaround to make it refresh rofl + setSelectedActionName({}) + setSelectedAction({}) + setSelectedTrigger({}) + setSelectedApp({}) + setSelectedEdge({}) + // FIXME - check if startnode + + // Set value + setSelectedActionName(selectedActionName) + setSelectedApp(selectedApp) + setSelectedAction(selectedAction) + } + + if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters) { + return ( +
            + Arguments + {selectedActionParameters.map((data, count) => { + if (data.variant === "") { + data.variant = "STATIC_VALUE" + } + + var staticcolor = "inherit" + var actioncolor = "inherit" + var varcolor = "inherit" + var multiline = false + if (data.multiline !== undefined && data.multiline !== null && data.multiline === true) { + multiline = true + } + + var placeholder = "Static value" + if (data.example !== undefined && data.example !== null && data.example.length > 0) { + placeholder = data.example + } + + var datafield = + { + changeActionParameter(event, count) + }} + /> + + + // Remap data based on variant + if (data.variant === "STATIC_VALUE") { + staticcolor = "#f85a3e" + } else if (data.variant === "ACTION_RESULT") { + // Gets the parents of the current node + var parents = getParents(selectedAction) + actioncolor = "#f85a3e" + // set the datafield + //var datafieldvalue = "Error: No parents. Action not eligible" + //if (parents.length > 0) { + // datafieldvalue = parents[0].label + //} + const fixedActionText = selectedActionParameters[count].value + + datafield = +
            + + Example: $.body will get "data" from {'{"body": "data"}'}
            } + placeholder="Action variable ($.)" + onChange={(event) => { + changeActionParameter(event, count) + }} + />
            + + } else if (data.variant === "WORKFLOW_VARIABLE") { + varcolor = "#f85a3e" + if (workflow.workflow_variables === null || workflow.workflow_variables === undefined || workflow.workflow_variables.length === 0) { + setCurrentView("variables") + datafield = +
            +
            + Looks like you don't have any variables yet. +
            +
            + +
            +
            + } else { + // FIXME - this is a shitty solution that needs re-renders all the time + datafield = + + } + + } + + var itemColor = "#f85a3e" + if (!data.required) { + itemColor = "#ffeb3b" + } + return ( +
            +
            +
            +
            + {data.name}: +
            +
            { + changeActionParameterVariant("STATIC_VALUE", count) + }}> + static +
            +  |  +
            { + changeActionParameterVariant("ACTION_RESULT", count) + }}> + action +
            +  |  +
            { + changeActionParameterVariant("WORKFLOW_VARIABLE", count) + }}> + var +
            +
            + {datafield} +
            + )})} +
            + ) + } + return null + } + + //height: "100%", + const appApiViewStyle = { + display: "flex", + flexDirection: "column", + backgroundColor: "#1F2023", + color: "white", + paddingRight: 10, + paddingLeft: 10, + minHeight: "100%", + } + + const defineStartnode = () => { + var oldstartnode = cy.getElementById(workflow.start) + if (oldstartnode.length > 0) { + oldstartnode[0].data("isStartNode", false) + var oldnodecnt = workflow.actions.findIndex(a => a.id === workflow.start) + workflow.actions[oldnodecnt].isStartNode = false + } + + var newstartnode = cy.getElementById(selectedAction.id) + if (newstartnode.length > 0) { + newstartnode[0].data("isStartNode", true) + var newnodecnt = workflow.actions.findIndex(a => a.id === selectedAction.id) + workflow.actions[newnodecnt].isStartNode = true + } + + // Find branches with triggers as source nodes + // Move these targets to be the new node + // Set arrows pointing to new startnode with errors + for (var key in workflow.branches) { + var item = workflow.branches[key] + if (item.destination_id === oldstartnode[0].data()["id"]) { + var curbranch = cy.getElementById(item.id) + if (curbranch.length > 0) { + //console.log(curbranch[0].data()) + //curbranch[0].data("target", selectedAction.id) + curbranch[0].data("hasErrors", true) + //workflow.branches[key].destination_id = selectedAction.id + //console.log(curbranch[0].data()) + } + } + } + + workflow.start = selectedAction.id + setWorkflow(workflow) + //setStartNode(selectedAction.id) + } + + const appApiView = Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0 ? +
            +
            +
            +

            {selectedAction.app_name}

            + What are apps? +
            +
            + +
            +
            + +
            + Name +
            + + +
            + Environment: + +
            + {/* FIXME authenticat +
            + +
            + */} + { /* +
            + Authentication: FIXME - Configurations +
            + */ } + +
            +
            + Actions +
            + +
            + + +
            +
            +
            + : null + + + const headerSize = 74 + const rightsidebarStyle = { + position: "fixed", + right: 0, + top: headerSize+1, + height: "100%", + bottom: 0, + minWidth: "350px", + maxWidth: "350px", + borderLeft: "1px solid rgb(91, 96, 100)", + overflow: "scroll", + overflowX: "auto", + overflowY: "auto", + } + + const setTriggerFolderWrapperMulti = event => { + const { options } = event.target; + const value = []; + for (let i = 0, l = options.length; i < l; i += 1) { + if (options[i].selected) { + value.push(options[i].value); + } + } + + if (selectedTrigger.parameters === null) { + selectedTrigger.parameters = [[]] + workflow.triggers[selectedTriggerIndex].parameters = [[]] + } + + // This is a dirty workaround for the static values in the go backend and datastore db + const fixedValue = value.join(splitter) + selectedTrigger.parameters[0] = {"value": fixedValue, "name": "outlookfolder"} + workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": fixedValue, "name": "outlookfolder"} + + // This resets state for some reason (: + setSelectedActionName({}) + setSelectedAction({}) + setSelectedTrigger({}) + setSelectedApp({}) + setSelectedEdge({}) + + // Set value + setSelectedTrigger(selectedTrigger) + setWorkflow(workflow) + }; + + //const setTriggerFolderWrapper = (event) => { + // if (selectedTrigger.parameters === null) { + // selectedTrigger.parameters = [] + // workflow.triggers[selectedTriggerIndex].parameters = [] + // } + + // const folder = triggerFolders.find(a => a.displayName === event.target.value) + // console.log(event.target.value) + // console.log(folder) + // + // if (folder !== undefined) { + // workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": folder.displayName, "name": "outlookfolder", "id": folder.id} + // selectedTrigger.parameters[0] = {"value": folder.displayName, "name": "outlookfolder", "id": folder.id} + // setWorkflow(workflow) + + // // This resets state for some reason (: + // setSelectedActionName({}) + // setSelectedActionEnvironment({}) + // setSelectedAction({}) + // setSelectedTrigger({}) + // setSelectedApp({}) + // setSelectedEdge({}) + + // // Set value + // setSelectedTrigger(selectedTrigger) + + // } else { + // alert.error("Some error occurred with folder "+event.target.value) + // } + //} + + const setTriggerCronWrapper = (value) => { + if (selectedTrigger.parameters === null) { + selectedTrigger.parameters = [] + } + + workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "cron"} + setWorkflow(workflow) + } + + const setTriggerOptionsWrapper = (value) => { + if (selectedTrigger.parameters === null) { + selectedTrigger.parameters = [] + } + + const splitItems = workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",") + console.log(splitItems) + if (splitItems.includes(value)) { + for( var i = 0; i < splitItems.length; i++){ + if (splitItems[i] === value) { + splitItems.splice(i, 1); + } + } + + } else { + splitItems.push(value) + } + + for( var i = 0; i < splitItems.length; i++){ + if (splitItems[i] === "") { + splitItems.splice(i, 1); + } + } + + workflow.triggers[selectedTriggerIndex].parameters[2].value = splitItems.join(",") + + console.log(splitItems) + setWorkflow(workflow) + setLocalFirstrequest(!localFirstrequest) + } + + const setTriggerTextInformationWrapper = (value) => { + if (selectedTrigger.parameters === null) { + selectedTrigger.parameters = [] + } + + workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "alertinfo"} + setWorkflow(workflow) + } + + const setTriggerBodyWrapper = (value) => { + if (selectedTrigger.parameters === null) { + selectedTrigger.parameters = [] + workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "cron"} + } + + workflow.triggers[selectedTriggerIndex].parameters[1] = {"value": value, "name": "execution_argument"} + setWorkflow(workflow) + } + + const AppConditionHandler = (props) => { + const { tmpdata, type } = props; + + if (tmpdata === undefined) { + return tmpdata + } + const [data, ] = useState(tmpdata) + + if (data.variant === "") { + data.variant = "STATIC_VALUE" + } + + var staticcolor = "inherit" + var actioncolor = "inherit" + var varcolor = "inherit" + var multiline = false + if (data.multiline !== undefined && data.multiline !== null && data.multiline === true) { + multiline = true + } + + var placeholder = "Static value" + if (data.example !== undefined && data.example !== null && data.example.length > 0) { + placeholder = data.example + } + + var datafield = + { + changeActionVariable(data.action_field, e.target.value) + }} + /> + + console.log(data) + + // Remap data based on variant + if (data.variant === "STATIC_VALUE") { + staticcolor = "#f85a3e" + } else if (data.variant === "ACTION_RESULT") { + // Gets the parents of the current node + var parents = getParents(workflow.actions.find(a => a.id === selectedEdge["target"])) + actioncolor = "#f85a3e" + // set the datafield + //var datafieldvalue = "Error: No parents. Action not eligible" + //if (parents.length > 0) { + // datafieldvalue = parents[0].label + //} + + datafield = +
            + + { + changeActionVariable(data.action_field, e.target.value) + }} + />
            + + } else if (data.variant === "WORKFLOW_VARIABLE") { + varcolor = "#f85a3e" + if (workflow.workflow_variables === null || workflow.workflow_variables === undefined || workflow.workflow_variables.length === 0) { + setCurrentView("variables") + datafield = +
            +
            + Looks like you don't have any variables yet. +
            +
            + +
            +
            + } else { + // FIXME - this is a shitty solution that needs re-renders all the time + datafield = + + } + + } + + const changeActionVariable = (variable, value) => { + // set the name + data.value = value + data.action_field = variable + + //setConditionValue({}) + + if (type === "source") { + setSourceValue(data) + //setDestinationValue(destinationValue) + } else if (type === "destination") { + setDestinationValue(data) + //setSourceValue(sourceValue) + } + } + + const changeActionParameterVariant = (variant) => { + if (data.variant === variant) { + return + } + + data.variant = variant + data.value = "" + + if (variant === "ACTION_RESULT") { + console.log("SHOULD FIND PARENTS OF EDGE") + + // Uses the target's parents, as the target should be executing the checks (I think) + var parents = getParents(workflow.actions.find(a => a.id === selectedEdge["target"])) + if (parents.length > 0) { + console.log(parents) + data.action_field = parents[0].label + } else { + data.action_field = "" + } + } else if (variant === "WORKFLOW_VARIABLE") { + if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) { + data.action_field = workflow.workflow_variables[0].name + } + } + + setSourceValue({}) + setConditionValue({}) + setDestinationValue({}) + + if (type === "source") { + setSourceValue(data) + setDestinationValue(destinationValue) + } else if (type === "destination") { + setDestinationValue(data) + setSourceValue(sourceValue) + } + } + + return ( +
            +
            +
            +
            + {data.name} +
            +
            { + changeActionParameterVariant("STATIC_VALUE") + }}> + static +
            +  |  +
            { + changeActionParameterVariant("ACTION_RESULT") + }}> + action +
            +  |  +
            { + changeActionParameterVariant("WORKFLOW_VARIABLE") + }}> + var +
            +
            + {datafield} +
            + ) + } + + + const menuItemStyle = { + color: "white", + } + + const conditionsModal = + { + setConditionsModalOpen(false) + setSourceValue({}) + setConditionValue({}) + setDestinationValue({}) + }} + > + +
            Condition
            + +
            + +
            +
            + + { + setVariableAnchorEl(null) + }} + > + { + conditionValue.value = "equals" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"equals"}>equals + { + conditionValue.value = "does not equal" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"does not equal"}>does not equal + { + conditionValue.value = "startswith" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"starts with"}>starts with + { + conditionValue.value = "endswith" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"ends with"}>ends with + { + conditionValue.value = "endswith" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"ends with"}>ends with + { + conditionValue.value = "contains" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"contains"}>contains + { + conditionValue.value = "matches regex" + setConditionValue(conditionValue) + setVariableAnchorEl(null) + }} key={"matches regex"}>matches regex + + +
            +
            + +
            +
            + + + +
            +
            + + const EdgeSidebar = () => { + const ConditionHandler = (condition, index) => { + const [open, setOpen] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + + const deleteCondition = (conditionIndex) => { + //selectedEdge.conditions.splice(conditionIndex, 1) + //setSelectedEdge(selectedEdge) + } + + const paperVariableStyle = { + minHeight: "50px", + maxHeight: "50px", + minWidth: "100%", + maxWidth: "100%", + marginTop: "5px", + color: "white", + backgroundColor: surfaceColor, + cursor: "pointer", + display: "flex", + } + + const menuClick = (event) => { + setOpen(!open) + setAnchorEl(event.currentTarget); + } + + return ( + {}}> +
            +
            +
            { + setSourceValue(condition.source) + setConditionValue(condition.condition) + setDestinationValue(condition.destination) + setConditionsModalOpen(true) + }}> +
            + {condition.source.value} +
            + +
            {}}> + {condition.condition.value} +
            + +
            + {condition.destination.value} +
            +
            +
            + + + + { + setOpen(false) + setAnchorEl(null) + }} + > + { + setOpen(false) + deleteCondition(index) + }} key={"Delete"}>{"Delete"} + +
            +
            + + ) + } + + var injectedData = +
            +
            + + if (selectedEdge.conditions !== undefined && selectedEdge.conditions !== null && selectedEdge.conditions.length > 0) { + injectedData = selectedEdge.conditions.map((condition, index) => { + return ConditionHandler(condition, index) + }) + } + + // FIXME - remove index + const conditionId = uuid.v4() + return( +
            +
            +
            +

            Branch: Conditions - {selectedEdgeIndex}

            + What are conditions? +
            +
            + +
            + Conditions +
            + {injectedData} + + +
            + ) + } + + // 1. GET the trigger authentication data + // 2. Parse the fields that are used (outlook & gmail) + // 3. Parse the folders that are selected + // 4. Start / stop + const EmailSidebar = () => { + if (Object.getOwnPropertyNames(selectedTrigger).length === 0) { + return null + } + + if (workflow.triggers[selectedTriggerIndex] === undefined) { + return null + } + + if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) { + workflow.triggers[selectedTriggerIndex].parameters = [{"value": "No folders selected yet", "name": "outlookfolder"}] + selectedTrigger.parameters = [{"value": "No folders selected yet", "name": "outlookfolder"}] + setWorkflow(workflow) + setSelectedTrigger(selectedTrigger) + } + + const setFolders = () => { + fetch(globalUrl+"/functions/outlook/getFolders?trigger_id="+selectedTrigger.id, { + method: "GET", + headers: {"content-type": "application/json"}, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + throw new Error("No folders :o!") + } + + return response.json() + }) + .then((responseJson) => { + setTriggerFolders(responseJson) + if (workflow.triggers[selectedTriggerIndex].parameters.length === 0 && responseJson.length > 0) { + workflow.triggers[selectedTriggerIndex].parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}] + selectedTrigger.parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}] + setWorkflow(workflow) + setSelectedTrigger(selectedTrigger) + } + }) + .catch(error => { + console.log(error.toString()) + }); + } + + const getTriggerAuth = () => { + fetch(globalUrl+"/api/v1/triggers/"+selectedTrigger.id, { + method: "GET", + headers: {"content-type": "application/json"}, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + throw new Error("No trigger info :o!") + } + + return response.json() + }) + .then((responseJson) => { + setTriggerAuthentication(responseJson) + }) + .catch(error => { + console.log(error.toString()) + }); + } + + // Getting the triggers and the folders if they exist + // This is horrible hahah + if (localFirstrequest) { + getTriggerAuth() + setFolders() + setLocalFirstrequest(false) + } + + const outlookButton = + + + + // FIXME - set everything in here to multifolder etc + var triggerInfo = "SET UP BUT NO TYPE :)" + if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) { + // Should get the folders if they don't already exist + + if (triggerAuthentication.type === "outlook") { + triggerInfo =
            +
            +
            +
            + Login: +
            +
            + {outlookButton} + +
            +
            +
            + Folders: (hold CTRL to select multiple) +
            +
            + } + key={selectedTrigger} + > + {triggerFolders.map(folder => { + var folderItem = + if (folder.childFolderCount > 0) { + // Here to handle subfolders sometime later + folderItem = + + } + + return folderItem + })} + +
            + } else if (triggerAuthentication.type === "gmail") { + triggerInfo = "SPECIAL GMAIL" + } + } + + + // Check + const argumentView = Object.getOwnPropertyNames(triggerAuthentication).length > 0 ? +
            + {triggerInfo} +
            + : +
            +
            +
            +
            + Login to either: +
            +
            + {outlookButton} + +
            + + return( +
            +
            +
            +

            {selectedTrigger.app_name}: {selectedTrigger.status}

            + What are webhooks? +
            +
            + +
            + Name +
            + + +
            + Environment: + +
            + + {argumentView} +
            +
            + +
            + + +
            +
            +
            +
            + ) + } + + const WebhookSidebar = () => { + if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { + if (workflow.triggers[selectedTriggerIndex] === undefined) { + return null + } + + if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) { + workflow.triggers[selectedTriggerIndex].parameters = [] + workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "url", "value": referenceUrl+"webhook_"+selectedTrigger.id} + workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "tmp", "value": "webhook_"+selectedTrigger.id} + setWorkflow(workflow) + } + //const cronValue = "*/15 * * * *" + + return( +
            +
            +
            +

            {selectedTrigger.app_name}: {selectedTrigger.status}

            + What are webhooks? +
            +
            + +
            + Name +
            + + +
            + Environment: + +
            + +
            +
            + Arguments +
            +
            +
            + Webhook URI: +
            +
            + { + //alert.info("Saved URI to clipboard") + console.log("Copy to clipboooooard") + }} + InputProps={{ + style:{ + color: "white", + height: "50px", + marginLeft: "5px", + maxWidth: "95%", + fontSize: "1em", + }, + }} + fullWidth + disabled + defaultValue={workflow.triggers[selectedTriggerIndex].parameters[0].value} + color="primary" + placeholder="defaultValue" + onBlur={(e) => { + setTriggerCronWrapper(e.target.value) + }} + /> +
            +
            +
            + Execution argument: +
            +
            + { + setTriggerBodyWrapper(e.target.value) + }} + /> + +
            + + +
            +
            +
            +
            + ) + } + + return null + } + + //const getTriggerAuth = (trigger_id) => { + // fetch(globalUrl+"/api/v1/triggers/"+trigger_id, { + // method: "GET", + // headers: {"content-type": "application/json"}, + // credentials: "include", + // }) + // .then((response) => response.json()) + // .then((responseJson) => { + // if (responseJson.success) { + // console.log("SUCCESS") + // console.log(responseJson) + // } else { + // console.log("FAIL") + // } + // }) + // .catch(error => { + // console.log(error.toString()) + // }); + //} + + const stopMailSub = (trigger, triggerindex) => { + // DELETE + if (trigger.id === undefined) { + return + } + alert.info("Stopping trigger") + + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/outlook/"+trigger.id, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + throw new Error("Status not 200 for stream results :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson.success) { + alert.success("Successfully stopped trigger") + // Set the status + workflow.triggers[triggerindex].status = "stopped" + trigger.status = "stopped" + setWorkflow(workflow) + setSelectedTrigger(trigger) + saveWorkflow(workflow) + } else { + alert.error("Failed stopping trigger: "+responseJson.reason) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const startMailSub = (trigger, triggerindex) => { + var folders = [] + + const splitItem = workflow.triggers[selectedTriggerIndex].parameters[0].value.split(splitter) + for (var key in splitItem) { + const item = splitItem[key] + const curfolder = triggerFolders.find(a => a.displayName === item) + if (curfolder === undefined) { + alert.error("Something went wrong with outlook folder "+item) + return + } + + folders.push(curfolder.id) + } + + alert.info("Creating outlook subscription with name " + trigger.name) + const data = { + "name": trigger.name, + "folders": folders, + "id": trigger.id, + } + + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/outlook", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to start outlook: " + responseJson.reason) + } else { + alert.success("Successfully started outlook sub") + + workflow.triggers[triggerindex].status = "running" + trigger.status = "running" + setWorkflow(workflow) + setSelectedTrigger(trigger) + saveWorkflow(workflow) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const newWebhook = (trigger) => { + const hookname = trigger.label + if (hookname.length === 0) { + alert.error("Missing name") + return + } + + if (trigger.id.length !== 36) { + alert.error("Missing id") + return + } + + alert.info("Starting webhook") + + const data = { + "name": hookname, + "type": "webhook", + "id": trigger.id, + "workflow": workflow.id, + } + + fetch(globalUrl+"/api/v1/hooks/new", { + method: "POST", + headers: {"content-type": "application/json"}, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success) { + // Set the status + alert.success("Successfully started webhook") + workflow.triggers[selectedTriggerIndex].status = "running" + trigger.status = "running" + setWorkflow(workflow) + setSelectedTrigger(trigger) + saveWorkflow(workflow) + } else { + alert.error("Failed starting webhook: "+responseJson.reason) + } + }) + .catch(error => { + console.log(error.toString()) + }); + } + + const deleteWebhook = (trigger, triggerindex) => { + if (trigger.id === undefined) { + return + } + alert.info("Stopping webhook") + + fetch(globalUrl+"/api/v1/hooks/"+trigger.id+"/delete", { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson.success) { + alert.success("Successfully stopped webhook") + // Set the status + workflow.triggers[triggerindex].status = "stopped" + trigger.status = "stopped" + setWorkflow(workflow) + setSelectedTrigger(trigger) + saveWorkflow(workflow) + } else { + alert.error("Failed stopping webhook: "+responseJson.reason) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const UserinputSidebar = () => { + if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) { + console.log(workflow.triggers[selectedTriggerIndex]) + console.log(selectedTrigger) + if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) { + workflow.triggers[selectedTriggerIndex].parameters = [] + workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "alertinfo", "value": "hello this is an alert"} + + // boolean, + workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "options", "value": "boolean"} + + // email,sms,app ... + workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "type", "value": "email"} + setWorkflow(workflow) + } + + return( +
            +
            +
            +

            {selectedTrigger.app_name}: {selectedTrigger.status}

            + What are schedules? +
            +
            + +
            + Name +
            + + +
            + Environment: + +
            + +
            + Arguments +
            +
            +
            + Information: +
            +
            + { + setTriggerTextInformationWrapper(e.target.value) + }} + /> +
            +
            +
            + Contact options: +
            +
            + + { + setTriggerOptionsWrapper("email") + }} + color="primary" + value="email" + /> + } + label={
            Email
            } + /> + { + setTriggerOptionsWrapper("sms") + }} + color="primary" + value="sms" /> + } + label={
            SMS
            } + /> +
            +
            +
            + ) + } + + return null + } + + const ScheduleSidebar = () => { + if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) { + if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) { + workflow.triggers[selectedTriggerIndex].parameters = [] + workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "cron", "value": "*/15 * * * *"} + workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "execution_argument", "value": '{"example": {"json": "is cool"}}'} + setWorkflow(workflow) + } + + return( +
            +
            +
            +

            {selectedTrigger.app_name}: {selectedTrigger.status}

            + What are schedules? +
            +
            + +
            + Name +
            + + +
            + Environment: + +
            + +
            +
            + Arguments +
            +
            +
            + Cron: +
            +
            + { + setTriggerCronWrapper(e.target.value) + }} + /> +
            +
            +
            + Execution argument: +
            +
            + { + setTriggerBodyWrapper(e.target.value) + }} + /> + +
            + + +
            +
            +
            +
            + ) + } + + return null + } + + const bottomBarStyle = { + position: "fixed", + right: 20, + left: leftBarSize, + bottom: 0, + minWidth: "100%", + marginLeft: 20, + marginBottom: 20, + } + + const topBarStyle= { + position: "fixed", + right: 0, + left: leftBarSize, + top: appBarSize, + minWidth: "100%", + marginLeft: 20, + marginBottom: 20, + } + + const TopCytoscapeBar = () => { + return ( +
            +
            +

            Editing workflow {workflow.name}

            +
            +
            + ) + } + + const BottomCytoscapeBar = () => { + const boxSize = 100 + const executionButton = executionRunning ? + + + + : + + + + + return( +
            + {executionButton} +
            + + { + setExecutionText(e.target.value) + }} + /> + + + + + + + + + + +
            +
            + ) + } + + const RightSideBar = () => { + setLastSaved(false) + if (Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0) { + //console.time('ACTIONSTART'); + return( +
            + {appApiView} +
            + ) + } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { + if (selectedTrigger.trigger_type === "SCHEDULE") { + console.log("SCHEDULE") + return( +
            + +
            + ) + } else if (selectedTrigger.trigger_type === "WEBHOOK") { + console.log("WEBHOOK") + return( +
            + +
            + ) + } else if (selectedTrigger.trigger_type === "EMAIL") { + console.log("EMAIL") + return( +
            + +
            + ) + } else if (selectedTrigger.trigger_type === "USERINPUT") { + console.log("USER INPUT SIDEBAR") + return( +
            + +
            + ) + } else if (selectedTrigger.trigger_type === undefined) { + return null + } else { + console.log("Unable to handle invalid trigger type "+selectedTrigger.trigger_type) + return null + } + } else if (Object.getOwnPropertyNames(selectedEdge).length > 0) { + return( +
            + +
            + ) + } + + return( + null + ) + } + + // This can execute a workflow with firestore. Used for test, as datastore is old and stuff + // Too much work to move everything over alone, so won't touch it for now + // + // + + const leftView = leftViewOpen ?
            + +
            : +
            +
            { + setLeftViewOpen(true) + setLeftBarSize(350) + }}> + + + +
            +
            + + const newView = isLoggedIn ? +
            +
            + {leftView} + { + setCy(incy) + }} + /> +
            + + + +
            + : +
            + TMP FOR NOT LOGGED IN +
            + + const variablesModal = variablesModalOpen ? + { + setNewVariableName("") + setNewVariableDescription("") + setNewVariableValue("") + setVariablesModalOpen(false) + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + }, + }} + > + + Workflow Variable + + setNewVariableName(event.target.value)} + color="primary" + placeholder="Name" + InputProps={{ + style:{ + color: "white" + } + }} + margin="dense" + fullWidth + defaultValue={newVariableName} + /> + setNewVariableDescription(event.target.value)} + color="primary" + placeholder="Description" + margin="dense" + fullWidth + InputProps={{ + style:{ + color: "white" + } + }} + defaultValue={newVariableDescription} + /> + setNewVariableValue(event.target.value)} + rows="6" + multiline + color="primary" + placeholder="Value" + margin="dense" + InputProps={{ + style:{ + color: "white" + } + }} + fullWidth + defaultValue={newVariableValue} + /> + + + + + + + + : null + + + const AuthenticationData = () => { + if (selectedApp.authentication === undefined) { + return null + } + + if (selectedApp.authentication.parameters.length === undefined || + selectedApp.authentication.parameters.length === 0) { + return null + } + + // Yes, it should be possible to have more than one, but.. :) + const currentAuth = selectedApp.authentication.parameters[0] + if (currentAuth.scheme.toLowerCase() === "bearer") { + return
            + NOT IMPLEMENTED + Insert your bearer token for {selectedApp.name} + { + // This data should be written to a KMS, then have the ID point back + }} + /> +
            + } + + return ( +
            + NOT IMPLEMENTED
            + Unknown auth: {currentAuth.scheme} +
            + ) + } + + const authenticationModal = authenticationModalOpen ? + { + setAuthenticationModalOpen(false) + setAppAuthentication({}) + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + }, + }} + > +
            Authentication for {selectedApp.name}
            + + What is this? +
            + + + + + + +
            : null + + const loadedCheck = isLoaded && isLoggedIn && workflowDone ? +
            + {newView} + {variablesModal} + {conditionsModal} + {authenticationModal} +
            + : +
            +
            + + + return ( +
            + {loadedCheck} +
            + ) +} + +export default AngularWorkflow; diff --git a/frontend/src/App.js b/frontend/src/App.js new file mode 100644 index 00000000..550439a1 --- /dev/null +++ b/frontend/src/App.js @@ -0,0 +1,166 @@ +import React, {useState, useEffect} from 'react'; + +import {Route} from 'react-router'; +import {BrowserRouter} from 'react-router-dom'; +import { CookiesProvider } from 'react-cookie'; +import { useCookies } from 'react-cookie'; + +import EditSchedule from "./EditSchedule"; +import Schedules from "./Schedules"; +import Webhooks from "./Webhooks"; +import Workflows from "./Workflows"; +import EditWebhook from "./EditWebhook"; +import AngularWorkflow from "./AngularWorkflow"; +import ForgotPassword from "./ForgotPassword"; +import ForgotPasswordLink from "./ForgotPasswordLink"; + +import Header from './Header'; +import Apps from './Apps'; +import AppCreator from './AppCreator'; +import Contact from './Contact'; +import Oauth2 from './Oauth2'; +import About from "./About"; +import Post from "./Post"; +import Dashboard from "./Dashboard"; +import AdminSetup from "./AdminSetup"; +import Admin from "./Admin"; +import Docs from "./Docs"; +import RegisterLink from "./RegisterLink"; +import LandingPage from "./Landingpage"; +import LandingPageNew from "./LandingpageNew"; +import LoginPage from "./LoginPage"; +import SettingsPage from "./SettingsPage"; + +import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider'; +import { createMuiTheme } from '@material-ui/core/styles'; + +import AlertTemplate from "react-alert-template-basic"; +import { positions, Provider } from "react-alert"; + +// Testing - localhost +const globalUrl = "http://192.168.3.6:5001" + +// Production - backend proxy forwarding in nginx +//const globalUrl = window.location.origin + +const surfaceColor = "#27292D" +const inputColor = "#383B40" + +const theme = createMuiTheme({ + palette: { + primary: { + main: "#f85a3e" + }, + secondary: { + main: '#e8eaf6', + }, + }, + typography: { + useNextVariants: true + } +}); + + +// FIXME - set client side cookies +const App = (message, props) => { + const [userdata, setUserData] = useState({}); + //const [homePage, ] = useState(true); + const [cookies, setCookie, removeCookie] = useCookies([]); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [dataset, setDataset] = useState(false); + const [isLoaded, setIsLoaded] = useState(false); + + useEffect(() => { + if (dataset === false) { + checkLogin() + setDataset(true) + initializeReactGA() + } + }) + + function initializeReactGA() { + } + + console.log(window.location) + if (isLoaded && !isLoggedIn && (!window.location.pathname.startsWith("/login") && (!window.location.pathname.startsWith("/docs") && (!window.location.pathname.startsWith("/adminsetup"))))) { + window.location = "login" + } + + const checkLogin = () => { + var baseurl = globalUrl + fetch(baseurl+"/api/v1/getinfo", { + credentials: "include", + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => response.json()) + .then(responseJson => { + if (responseJson.success === true) { + setUserData(responseJson) + setIsLoggedIn(true) + + // Updating cookie every request + for (var key in responseJson["cookies"]) { + setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, {path: "/"}) + } + } + setIsLoaded(true) + }) + .catch(error => { + setIsLoaded(true) + }); + } + + // Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies) + + const options = { + timeout: 5000, + position: positions.BOTTOM_CENTER + }; + + const includedData = window.location.pathname === "/home" || window.location.pathname === "/features" ? +
            + } /> +
            : +
            +
            + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {window.location.pathname = "/docs/about"}} /> + {window.location.pathname = "/login"}} /> +
            + + //
            + // backgroundColor: "#213243", + // This is a mess hahahah + return ( + + + + + {includedData} + + + + + ); +}; + +export default App; + diff --git a/frontend/src/AppCreator.js b/frontend/src/AppCreator.js new file mode 100644 index 00000000..07944ba8 --- /dev/null +++ b/frontend/src/AppCreator.js @@ -0,0 +1,1232 @@ +import React, {useState, useEffect} from 'react'; +import { makeStyles } from '@material-ui/styles'; +import {BrowserView, MobileView} from "react-device-detect"; + +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; +import Divider from '@material-ui/core/Divider'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormControl from '@material-ui/core/FormControl'; +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogActions from '@material-ui/core/DialogActions'; +import TextField from '@material-ui/core/TextField'; +import Tooltip from '@material-ui/core/Tooltip'; +import CheckCircleIcon from '@material-ui/icons/CheckCircle'; + +import ErrorOutline from '@material-ui/icons/ErrorOutline'; +import { useAlert } from "react-alert"; + +const surfaceColor = "#27292D" +const inputColor = "#383B40" + +const bodyDivStyle = { + margin: "auto", + width: "900px", +} + +const actionListStyle = { + paddingLeft: "10px", + paddingRight: "10px", + paddingBottom: "10px", + paddingTop: "10px", + marginTop: "5px", + backgroundColor: inputColor, + display: "flex", +} + +const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + display: "flex", + flexDirection: "column", + backgroundColor: surfaceColor, +} + +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important" + }, +}); + +// Should be different if logged in :| +const AppCreator = (props) => { + const { globalUrl, isLoaded } = props; + const classes = useStyles(); + const alert = useAlert() + + var upload = "" + const actionNonBodyRequest = ["GET", "HEAD", "DELETE", "CONNECT"] + const actionBodyRequest = ["POST", "PUT", "PATCH",] + const authenticationOptions = ["No authentication", "API key", "Bearer auth", "Basic auth", ] + const apikeySelection = ["Header", "Query",] + + const [name, setName] = useState(""); + const [contact, setContact] = useState(""); + const [file, setFile] = useState(""); + const [fileBase64, setFileBase64] = useState(""); + const [isAppLoaded, setIsAppLoaded] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [description, setDescription] = useState(""); + const [updater, setUpdater] = useState("tmp") + const [baseUrl, setBaseUrl] = useState(""); + const [actionsModalOpen, setActionsModalOpen] = useState(false); + const [authenticationOption, setAuthenticationOption] = useState(authenticationOptions[0]); + const [parameterName, setParameterName] = useState(""); + const [parameterLocation, setParameterLocation] = useState(apikeySelection[0]); + const [urlPath, setUrlPath] = useState(""); + //const [urlPathQueries, setUrlPathQueries] = useState([{"name": "test", "required": false}]); + const [urlPathQueries, setUrlPathQueries] = useState([]); + const [urlPathParameters, ] = useState([]); + const [firstrequest, setFirstrequest] = React.useState(true) + const [, setBasedata] = React.useState({}) + const [actions, setActions] = useState([]) + const [errorCode, setErrorCode] = useState("") + + //const [actions, setActions] = useState([{ + // "name": "Get workflows", + // "description": "Get workflows", + // "url": "/workflows", + // "headers": "", + // "queries": [], + // "paths": [], + // "body": "", + // "errors": ["wutface", "WOAH"], + // "method": actionNonBodyRequest[0], + //}, { + // "name": "Get workflow", + // "description": "Get workflow", + // "url": "/workflows/{id}", + // "headers": "", + // "queries": [], + // "paths": ["id"], + // "body": "", + // "errors": ["wutface", "WOAH"], + // "method": actionNonBodyRequest[0], + //}, + // + //]) + + const [currentActionMethod, setCurrentActionMethod] = useState(actionNonBodyRequest[0]) + const [currentAction, setCurrentAction] = useState({ + "name": "", + "description": "", + "url": "", + "headers": "", + "paths": [], + "queries": [], + "body": "", + "errors": [], + "method": actionNonBodyRequest[0], + }); + + + + useEffect(() => { + if (firstrequest) { + setFirstrequest(false) + if (window.location.pathname.includes("apps/edit")) { + setIsEditing(true) + handleEditApp() + } else { + checkQuery() + } + } + }) + + const handleEditApp = () => { + fetch(globalUrl+"/api/v1/apps/"+props.match.params.appid+"/config", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + window.location.pathname = "/apps" + } + + return response.json() + }) + .then((responseJson) => { + setIsAppLoaded(true) + if (!responseJson.success) { + alert.error("Failed to verify") + } else { + const data = JSON.parse(responseJson.body) + parseOpenapiData(data) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + + // Checks if there is an ID in the query, and gets it if it doesn't exist. + const checkQuery = () => { + var urlParams = new URLSearchParams(window.location.search) + if (!urlParams.has("id")) { + setIsAppLoaded(true) + return + } + + fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + throw new Error("NOT 200 :O") + } + + return response.json() + }) + .then((responseJson) => { + setIsAppLoaded(true) + if (!responseJson.success) { + alert.error("Failed to verify") + } else { + const data = JSON.parse(responseJson.body) + parseOpenapiData(data) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const setFileFromb64 = () => { + //const img = document.getElementById('logo') + //var canvas = document.createElement('canvas') + //var ctx = canvas.getContext('2d') + + //img.onload = function() { + // console.log("LOADED?") + // ctx.drawImage(img, 0, 0) + // const canvasUrl = canvas.toDataURL() + // console.log(canvasUrl) + // setFileBase64(canvasUrl) + //} + } + + // Sets the data up as it should be at later points + const parseOpenapiData = (data) => { + setBasedata(data) + + + setName(data.info.title) + setDescription(data.info.description) + document.title = "Apps - "+data.info.title + + if (data.info.contact != undefined) { + setContact(data.info.contact) + } + + if (data.servers !== undefined && data.servers.length > 0) { + setBaseUrl(data.servers[0].url) + } + + console.log(data) + + // This is annoying (: + var securitySchemes = data.components.securityDefinitions + if (securitySchemes === undefined) { + securitySchemes = data.securitySchemes + } + + if (securitySchemes === undefined) { + securitySchemes = data.components.securitySchemes + } + + if (securitySchemes !== undefined) { + console.log("Am I in here?") + for (const [key, value] of Object.entries(securitySchemes)) { + if (value.scheme === "bearer") { + setAuthenticationOption("Bearer auth") + break + } else if (value.type === "apiKey") { + setAuthenticationOption("API key") + setParameterName(value.name) + setParameterLocation(value.in) + if (!apikeySelection.includes(value.in)) { + alert.error("Might be error in setting up API key authentication") + } + break + } else if (value.scheme === "basic") { + setAuthenticationOption("Basic auth") + break + } + } + } + + // FIXME - headers? + var newActions = [] + for (let [path, pathvalue] of Object.entries(data.paths)) { + for (let [method, methodvalue] of Object.entries(pathvalue)) { + var newaction = { + "name": methodvalue.summary, + "description": methodvalue.description, + "url": path, + "method": method.toUpperCase(), + "headers": "", + "queries": [], + "paths": [], + "body": "", + "errors": [], + } + + //console.log(`${path}: ${method}`); + //console.log(methodvalue) + + for (var key in methodvalue.parameters) { + const parameter = methodvalue.parameters[key] + if (parameter.in === "query") { + var tmpaction = { + "description": parameter.description, + "name": parameter.name, + "required": parameter.required, + "in": "query", + } + + if (parameter.required === undefined) { + tmpaction.required = false + } + + newaction.queries.push(tmpaction) + } else if (parameter.in === "path") { + // FIXME - parse this to the URL too + newaction.paths.push(parameter.name) + } + } + + newActions.push(newaction) + } + } + + console.log(newActions) + setActions(newActions) + } + + const submitApp = () => { + alert.info("Uploading private app " + name) + setErrorCode("") + + // Format the information + const splitBase = baseUrl.split("/") + const host = splitBase[2] + const schemes = [splitBase[0]] + const basePath = "/"+(splitBase.slice(3, )).join("/") + + const data = { + "swagger": "3.0", + "info": { + "title": name, + "description": description, + "version": "1.0", + }, + "servers": [{"url": baseUrl}], + "host": host, + "basePath": basePath, + "schemes": schemes, + "paths": {}, + "editing": isEditing, + "components": { + "securitySchemes": {}, + }, + "image": fileBase64, + "id": props.match.params.appid, + "securityDefinitions": {}, + } + + if (contact === "") { + data.info["contact"] = { + "name": "@frikkylikeme", + "url": "https://twitter.com/frikkylikeme", + "email": "frikky@shuffler.io", + } + } else { + data.info["contact"] = contact + } + + for (var key in actions) { + const item = actions[key] + console.log(item) + if (item.errors.length > 0) { + alert.error("Saving with error in action "+item.name) + } + + if (item.name === undefined && item.description !== undefined) { + item.name = item.description + } + + console.log(data.paths) + console.log(item) + if (data.paths[item.url] === null || data.paths[item.url] === undefined) { + data.paths[item.url] = {} + } + + data.paths[item.url][item.method.toLowerCase()] = { + "responses": { + "default": { + "description": "default", + "schema": {} + } + }, + "summary": item.name, + "description": item.description, + "parameters": [] + } + + if (item.queries.length > 0) { + for (var querykey in item.queries) { + const queryitem = item.queries[querykey] + + var newitem = { + "in": "query", + "name": queryitem.name, + "description": "Generated by shuffler.io OpenAPI", + "required": queryitem.required, + "schema": { + "type": "string", + }, + } + + if (queryitem.description !== undefined) { + newitem.description = queryitem.description + } + + data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) + //console.log(queryitem) + } + } + + if (item.paths.length > 0) { + for (querykey in item.paths) { + const queryitem = item.paths[querykey] + newitem = { + "in": "path", + "name": queryitem, + "description": "Generated by shuffler.io OpenAPI", + "required": true, + "schema": { + "type": "string", + }, + } + + if (queryitem.description !== undefined) { + newitem.description = queryitem.description + } + + data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) + //console.log(queryitem) + } + } + } + + if (authenticationOption === "API key") { + data.components.securitySchemes["ApiKeyAuth"] = { + "type": "apiKey", + "in": parameterLocation.toLowerCase(), + "name": parameterName, + } + } else if (authenticationOption === "Bearer auth") { + data.components.securitySchemes["BearerAuth"] = { + "type": "http", + "scheme": "bearer", + "bearerFormat": "UUID", + } + } else if (authenticationOption === "Basic auth") { + data.components.securitySchemes["BasicAuth"] = { + "type": "http", + "scheme": "basic", + } + } + + console.log(data) + fetch(globalUrl+"/api/v1/verify_openapi", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + //if (response.status !== 200) { + // setErrorCode("An error occurred during validation") + // throw new Error("NOT 200 :O") + //} + + return response.json() + }) + .then((responseJson) => { + console.log(responseJson) + if (!responseJson.success) { + setErrorCode(responseJson.reason) + alert.error("Failed to verify: ") + } else { + // Return? + alert.success("Successfully uploaded openapi") + //window.location = "/apps" + } + }) + .catch(error => { + setErrorCode(error.toString()) + alert.error(error.toString()) + }); + } + + const bearerAuth = authenticationOption === "Bearer auth" ? +
            +

            + + Bearer auth + +

            + Users will be required to submit their API as the header "Authorization: Bearer APIKEY" + +
            + : null + + // Basicauth + const basicAuth = authenticationOption === "Basic auth" ? +
            +

            + + Basic authentication + +

            + Users will be required to submit a valid username and password before using the API + +
            + : null + + // API key + //const verifyBaseUrl = () => { + // if (baseUrl.startsWith("http://") || baseUrl.startsWith("https://")) { + // return true + // } + + // if (baseUrl.endsWith("/")) { + // return true + // } + // + // return false + //} + + //const verifyApiParameter = () => { + // const notAllowed = ["!","#","$","%","&","'","^","+","-",".","_","~","|","]","+","$",] + // for (var key in notAllowed) { + // if (parameterName.includes(notAllowed[key])) { + // return false + // } + // } + + // return true + //} + + const testAction = (index) => { + console.log("Should test action at index "+index) + console.log(actions[index]) + } + + const addPathQuery = () => { + urlPathQueries.push({"name": "", "required": true}) + if (updater === "addupdater") { + setUpdater("updater") + } else { + setUpdater("addupdater") + } + setUrlPathQueries(urlPathQueries) + } + + const flipRequired = (index) => { + urlPathQueries[index].required = !urlPathQueries[index].required + if (updater === "flipupdater") { + setUpdater("updater") + } else { + setUpdater("flipupdater") + } + setUrlPathQueries(urlPathQueries) + + } + + const deletePathQuery = (index) => { + urlPathQueries.splice(index, 1) + if (updater === "deleteupdater") { + setUpdater("updater") + } else { + setUpdater("deleteupdater") + } + setUrlPathQueries(urlPathQueries) + } + + const deleteAction = (index) => { + actions.splice(index, 1) + setCurrentAction({ + "name": "", + "description": "", + "url": "", + "headers": "", + "paths": [], + "queries": [], + "body": "", + "errors": [], + "method": actionNonBodyRequest[0], + }) + + setActions(actions) + } + + const apiKey = authenticationOption === "API key" ? +
            +

            API key

            + Can't be empty. Can't contain any of the following characters: !#$%&'^+-._~|]+$
            } + onChange={e => setParameterName(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> + Field type + + +
            + : null + + const loopQueries = urlPathQueries.length === 0 ? + null : +
            + + Queries + {urlPathQueries.map((data, index) => { + const requiredColor = data.required === true ? "green" : "red" + //const required = data.required === true ?
            {data.required.toString()}
            :
            {flipRequired(index)}} style={{display: "inline", color: "red", cursor: "pointer"}}>{data.required.toString()}
            + return ( + +
            +
            {flipRequired(index)}}> + Required:
            {data.required.toString()}
            +
            + Click required switch
            } + onBlur={(e) => { + urlPathQueries[index].name = e.target.value + setUrlPathQueries(urlPathQueries) + }} + InputProps={{ + style:{ + color: "white", + }, + }} + /> + +
            +
            {deletePathQuery(index)}}> + Delete +
            + + + ) + })} + +
            + + const loopActions = actions.length === 0 ? + null + : +
            + {actions.map((data, index) => { + var error = + + + + // "ERROR: "+data.errors.join("\n") + if (data.errors.length > 0) { + error = + + + + } + + + const url = baseUrl+data.url + return ( + + {error} + +
            { + setCurrentAction(data) + setCurrentActionMethod(data.method) + setUrlPathQueries(data.queries) + setUrlPath(data.url) + setActionsModalOpen(true) + }}> + {data.method} - {url} - {data.name} +
            +
            + +
            {testAction(index)}}> + Test +
            +
            + +
            {deleteAction(index)}}> + Delete +
            +
            +
            + ) + })} +
            + + const setActionField = (field, value) => { + currentAction[field] = value + setCurrentAction(currentAction) + } + + const bodyInfo = actionBodyRequest.includes(currentActionMethod) ? +
            + Body + setActionField("body", e.target.value)} + key={currentAction} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> + +
            + : null + + const addActionToView = (errors) => { + currentAction.errors = errors + currentAction.queries = urlPathQueries + setUrlPathQueries([]) + + console.log(actions) + console.log(currentAction.name) + const actionIndex = actions.findIndex(data => data.name === currentAction.name) + console.log(actionIndex) + if (actionIndex < 0) { + actions.push(currentAction) + } else { + actions[actionIndex] = currentAction + } + + setActions(actions) + } + + const getActionErrors = () => { + var errormessage = [] + if (currentAction.name.length === 0) { + errormessage.push("Name can't be empty") + } + + // Url verification + if (currentAction.url.length === 0) { + errormessage.push("URL path can't be empty.") + } else if (!currentAction.url.startsWith("/")) { + errormessage.push("URL must start with /") + } + + const check = urlPathQueries.findIndex(data => data.name.length === 0) + if (check >= 0) { + errormessage.push("All queries must have a value") + } + + console.log(urlPathParameters) + // const [urlPathParameters, setUrlPathParameters] = useState([]); + + return errormessage + } + + const UrlPathParameters = () => { + if (urlPath.includes("{") && urlPath.includes("}")) { + var values = [] + var tmpWord = "" + var record = false + for (var key in urlPath) { + if (urlPath[key] === "}") { + values.push(tmpWord) + tmpWord = "" + record = false + } + + if (record) { + tmpWord += urlPath[key] + } + + if (urlPath[key] === "{" && urlPath[key-1] === "/") { + record = true + } + } + + if (!currentAction.paths === values) { + currentAction.paths = values + setCurrentAction(currentAction) + } + + return ( +
            + Required parameters: {values.join(", ")} +
            + ) + } + + return null + } + + const newActionModal = + { + setUrlPath("") + setCurrentAction({ + "name": "", + "description": "", + "url": "", + "headers": "", + "paths": [], + "queries": [], + "body": "", + "errors": [], + "method": actionNonBodyRequest[0], + }) + setCurrentActionMethod(apikeySelection[0]) + setUrlPathQueries([]) + setActionsModalOpen(false) + }} + > + +
            New action
            + + Learn more about app creation +
            + Name + setActionField("name", e.target.value)} + key={currentAction} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> +
            + Description + setActionField("description", e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> + +

            Request

            + +
            + URL path + { + setActionField("url", e.target.value) + setUrlPath(e.target.value) + console.log(e.target.value) + }} + helperText={
            The path to use. Must start with /. Add {"{variable}"} to have path variables
            } + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + input: classes.input, + }, + style:{ + color: "white", + }, + }} + /> + + {loopQueries} + +
            + Headers + setActionField("headers", e.target.value)} + helperText={
            Headers that are part of the request
            } + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> + {bodyInfo} + + + + + + +
            + + const actionView = +
            +

            Actions

            + Actions are the tasks performed by an app. Read more about actions and apps + here. +
            + {loopActions} + +
            +
            + + const testView = +
            +

            Test

            + Test an action to see whether it performs in an expected way. +  Click here to learn more about testing. +
            + Test :) +
            +
            + + var image = "" + const editHeaderImage = (event) => { + const file = event.target.value + console.log(file) + const actualFile = event.target.files[0] + const fileObject = URL.createObjectURL(actualFile) + setFile(fileObject) + } + + if (file !== "" && fileBase64 === "") { + const img = document.getElementById('logo') + var canvas = document.createElement('canvas') + var ctx = canvas.getContext('2d') + + img.onload = function() { + // img, x, y, width, height + ctx.drawImage(img, 0, 0) + const canvasUrl = canvas.toDataURL() + console.log(canvasUrl) + setFileBase64(canvasUrl) + } + + //console.log(img.width) + //console.log(img.width) + //canvas.width = img.width + //canvas.height = img.height + } + + //const imageInfo = file.length === 0 ? + //
            + // Upload logo + //
            : + // + + const imageInfo = + + // Random names for type & autoComplete. Didn't research :^) + const landingpageDataBrowser = +
            + +

            General information

            + Click here to learn more about app creation +
            + +
            {upload.click()}}> + upload = ref} onChange={editHeaderImage} /> + {imageInfo} +
            +
            +
            +
            + Name + setName(e.target.value)} + color="primary" + InputProps={{ + style:{ + color: "white", + height: "50px", + fontSize: "1em", + }, + classes: { + notchedOutline: classes.notchedOutline, + }, + }} + /> +
            + Description + setDescription(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> +
            +
            + +

            General API information

            + Base URL + Must start with http(s):// and CANT end with /. Can be empty if you its a variable.
            } + placeholder="https://api.example.com" + onChange={e => setBaseUrl(e.target.value)} + /> + +
            Authentication
            + +
            + {basicAuth} + {bearerAuth} + {apiKey} +
            + {actionView} +
            + + {testView} + + + {errorCode} + +
            + + + const loadedCheck = isLoaded && isAppLoaded && !firstrequest ? +
            + +
            {landingpageDataBrowser}
            + {newActionModal} +
            + + +
            + : +
            +
            + + return( +
            + {loadedCheck} +
            + ) +} +export default AppCreator; diff --git a/frontend/src/Apps.js b/frontend/src/Apps.js new file mode 100644 index 00000000..4d5ab584 --- /dev/null +++ b/frontend/src/Apps.js @@ -0,0 +1,725 @@ +import React, { useEffect} from 'react'; + +import { useInterval } from 'react-powerhooks'; + +import Grid from '@material-ui/core/Grid'; +import Paper from '@material-ui/core/Paper'; +import Divider from '@material-ui/core/Divider'; +import ButtonBase from '@material-ui/core/ButtonBase'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import FormControl from '@material-ui/core/FormControl'; +import Tooltip from '@material-ui/core/Tooltip'; +import YAML from 'yaml' +import {Link} from 'react-router-dom'; + +import CloudDownload from '@material-ui/icons/CloudDownload'; +import { useAlert } from "react-alert"; + +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import CircularProgress from '@material-ui/core/CircularProgress'; + + +const surfaceColor = "#27292D" +const inputColor = "#383B40" + +const Apps = (props) => { + const { globalUrl, isLoggedIn, isLoaded } = props; + + //const [workflows, setWorkflows] = React.useState([]); + const alert = useAlert() + const [selectedApp, setSelectedApp] = React.useState({}); + const [firstrequest, setFirstrequest] = React.useState(true) + const [apps, setApps] = React.useState([]) + const [filteredApps, setFilteredApps] = React.useState([]) + const [validation, setValidation] = React.useState(false) + const [isLoading, setIsLoading] = React.useState(false) + + const [openApi, setOpenApi] = React.useState("") + const [openApiData, setOpenApiData] = React.useState("") + const [appValidation, setAppValidation] = React.useState("") + const [openApiModal, setOpenApiModal] = React.useState(false); + const [openApiModalType, setOpenApiModalType] = React.useState(""); + const [openApiError, setOpenApiError] = React.useState("") + const { start, stop } = useInterval({ + duration: 5000, + startImmediate: false, + callback: () => { + getApps() + } + }); + + useEffect(() => { + if (apps.length <= 0 && firstrequest) { + document.title = "Shuffle - Apps" + setFirstrequest(false) + getApps() + } + }) + + const appViewStyle = { + color: "#ffffff", + width: "100%", + display: "flex", + } + + const paperAppStyle = { + minHeight: 130, + maxHeight: 130, + minWidth: "100%", + maxWidth: "100%", + color: "white", + backgroundColor: surfaceColor, + cursor: "pointer", + display: "flex", + } + + const getApps = () => { + fetch(globalUrl+"/api/v1/workflows/apps", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + } + + return response.json() + }) + .then((responseJson) => { + setApps(responseJson) + setFilteredApps(responseJson) + if (responseJson.length > 0) { + setSelectedApp(responseJson[0]) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const downloadApp = (inputdata) => { + const id = inputdata.id + + alert.info("Preparing download.") + fetch(globalUrl+"/api/v1/apps/"+id+"/config", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + window.location.pathname = "/apps" + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to download file") + } else { + const data = YAML.stringify(YAML.parse(responseJson.body)) + + var name = inputdata.name + name = name.replace(/ /g, "_", -1) + name = name.toLowerCase() + + var blob = new Blob( [ data ], { + type: 'application/octet-stream' + }) + + var url = URL.createObjectURL( blob ) + var link = document.createElement( 'a' ) + link.setAttribute( 'href', url ) + link.setAttribute( 'download', `${name}.yaml` ) + var event = document.createEvent( 'MouseEvents' ) + event.initMouseEvent( 'click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null) + link.dispatchEvent( event ) + //link.parentNode.removeChild(link) + } + }) + .catch(error => { + console.log(error) + alert.error(error.toString()) + }); + } + + // dropdown with copy etc I guess + const appPaper = (data) => { + var boxWidth = "2px" + if (selectedApp.id === data.id) { + boxWidth = "4px" + } + + var boxColor = "orange" + if (data.is_valid) { + boxColor = "green" + } + + var imageline = data.large_image.length === 0 ? + + : + + + // FIXME - add label to apps, as this might be slow with A LOT of apps + var newAppname = data.name + newAppname = newAppname.replace("_", " ") + newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) + + var sharing = "public" + if (!data.sharing) { + sharing = "private" + } + + var valid = "true" + if (!data.valid) { + valid = "false" + } + + if (data.actions === null || data.actions.length === 0) { + valid = "false" + } + + var description = data.description + const maxDescLen = 60 + if (description.length > maxDescLen) { + description = data.description.slice(0, maxDescLen)+"..." + } + + return ( + { + if (selectedApp.id !== data.id) { + setSelectedApp(data) + } + }}> + + + {imageline} + +
            +
            + + + +

            {newAppname}

            +
            +
            + + {description} + +
            + + Sharing: {sharing} + , Valid: {valid} + +
            +
            +
            + {downloadApp(data)}}> + + + + +
            + ) + } + + const dividerColor = "rgb(225, 228, 232)" + const uploadViewPaperStyle = { + minWidth: "100%", + maxWidth: "100%", + color: "white", + backgroundColor: surfaceColor, + display: "flex", + marginBottom: 10, + } + + //const handleFile = (event) =>{ + // const formData = new FormData(); + // formData.append('file', event.target.files[0]); + + // fetch(globalUrl+"/api/v1/workflows/apps/validate", { + // method: 'POST', + // headers: { + // 'Accept': 'application/json', + // }, + // body: formData, + // credentials: "include", + // }) + // .then((response) => { + // if (response.status !== 200) { + // console.log("Status not 200 for apps :O!") + // return + // } + // return response.json() + // }) + // .then((responseJson) => { + // console.log(responseJson) + // }) + // .catch(error => { + // alert.error(error.toString()) + // }); + //} + + const UploadView = () => { + //var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ? + // + // : + // PICTURE + // FIXME - add label to apps, as this might be slow with A LOT of apps + var newAppname = selectedApp.name + if (newAppname !== undefined && newAppname.length > 0) { + newAppname = newAppname.replace("_", " ") + newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) + } else { + newAppname = "" + } + + var description = selectedApp.description + + const url = "/apps/edit/"+selectedApp.id + var editButton = selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? + + : null + + + var deleteButton = (selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated) || (selectedApp.downloaded != undefined && selectedApp.downloaded == true) ? + : null + + + //fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), { + var baseInfo = newAppname.length > 0 ? +
            +

            {newAppname}

            +

            {description}

            +

            {selectedApp.id}

            +

            {selectedApp.privateId}

            + {editButton} + {deleteButton} +
            + : + null + + return( +
            + +
            +

            App creation

            + What are apps? +  - OpenAPI specification +
            + Apps are how you interact with workflows, and are used to execute workflows. They are created with the app creator, using OpenAPI specification or manually in python. +
            +
            + + + + +
            +
            + + +
            + {baseInfo} +
            +
            +
            + ) + } + + const handleSearchChange = (event) => { + const searchfield = event.target.value.toLowerCase() + const newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield)) + setFilteredApps(newapps) + } + + const appView = isLoggedIn ? +
            +
            +
            +

            Upload

            +
            + +
            + +
            +
            +
            +

            Available integrations

            +
            + {isLoading ? : null} + +
            + { + handleSearchChange(event) + }} + /> +
            + {apps.length > 0 ? + filteredApps.length > 0 ? +
            + {filteredApps.map(app => { + return ( + appPaper(app) + ) + })} +
            + : + +

            + Try a broader search term. E.g. "http" or "TheHive" +

            +
            + : + +

            + No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images. +

            +
            + } +
            +
            +
            +
            + : +
            +

            Available integrations

            + + {apps.map(data => { + return ( + appPaper(data) + ) + })} +
            + + // Gets the URL itself (hopefully this works in most cases? + // Will then forward the data to an internal endpoint to validate the api + const getExistingApps = () => { + setValidation(true) + + setIsLoading(true) + start() + + alert.success("Downloading and building apps. Feel free to move around meanwhile.") + var cors = "cors" + fetch(globalUrl+"/api/v1/apps/get_existing", { + method: "GET", + mode: "cors", + headers: { + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + alert.success("Failed loading.") + } else { + response.text().then(function (text) { + console.log("RETURN: ", text) + alert.success("Loaded existing apps!") + }) + } + setIsLoading(false) + stop() + }) + .catch(error => { + alert.error(error.toString()) + }) + } + + // Gets the URL itself (hopefully this works in most cases? + // Will then forward the data to an internal endpoint to validate the api + const validateUrl = () => { + setValidation(true) + + var cors = "cors" + if (openApi.includes("localhost")) { + cors = "no-cors" + } + + fetch(openApi, { + method: "GET", + mode: "cors", + }) + .then((response) => { + response.text().then(function (text) { + validateOpenApi(text) + }) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const deleteApp = (appId) => { + alert.info("Attempting to delete app") + fetch(globalUrl+"/api/v1/apps/"+appId, { + method: 'DELETE', + headers: { + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + alert.success("Successfully deleted app") + } else { + alert.error("Failed deleting app") + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const validateRemote = () => { + setValidation(true) + + fetch(globalUrl+"/api/v1/get_openapi_uri", { + method: 'POST', + headers: { + 'Accept': 'application/json', + }, + body: JSON.stringify(openApi), + credentials: "include", + }) + .then((response) => { + return response.text() + }) + .then((responseText) => { + validateOpenApi(responseText) + setValidation(false) + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const escapeApiData = (apidata) => { + console.log(apidata) + try { + return JSON.stringify(JSON.parse(apidata)) + } catch(error) { + console.log("JSON DECODE ERROR - TRY YAML") + } + + + try { + return JSON.stringify(YAML.parse(apidata)) + } catch(error) { + console.log("YAML DECODE ERROR - TRY SOMETHING ELSE?: "+error) + setOpenApiError(error) + } + + return "" + } + + // Sends the data to backend, which should return a version 3 of the same API + // If 200 - continue, otherwise, there's some issue somewhere + const validateOpenApi = (openApidata) => { + const newApidata = escapeApiData(openApidata) + if (newApidata === "") { + return + } + + fetch(globalUrl+"/api/v1/validate_openapi", { + method: 'POST', + headers: { + 'Accept': 'application/json', + }, + body: newApidata, + credentials: "include", + }) + .then((response) => { + return response.json() + }) + .then((responseJson) => { + setValidation(false) + if (responseJson.success) { + setAppValidation(responseJson.id) + } else { + if (responseJson.reason !== undefined) { + setOpenApiError(responseJson.reason) + } + alert.error("An error occurred in the response") + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const redirectOpenApi = () => { + window.location.href = "/apps/new?id="+appValidation + } + + const errorText = openApiError.length > 0 ?
            Error: {openApiError}
            : null + const circularLoader = validation ? : null + console.log(validation) + const modalView = openApiModal ? + {setOpenApiModal(false)}} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + +
            Create a new integration
            + + Paste in the URI for the OpenAPI + { + setOpenApiError("") + validateRemote() + }}>Validate + }} + onChange={e => setOpenApi(e.target.value)} + helperText={
            Must point to a version 2 or 3 specification.
            } + placeholder="OpenAPI URI" + fullWidth + /> +
            + Example: +
            + https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json +

            or paste the yaml/JSON directly below

            + { + setOpenApiError("") + validateOpenApi(openApiData) + }}>Validate data + }} + onChange={e => setOpenApiData(e.target.value)} + helperText={
            Must point to a version 2 or 3 specification.
            } + placeholder="OpenAPI text" + fullWidth + /> + {errorText} + + + {circularLoader} + + + + +
            + : null + + + const loadedCheck = isLoaded && !firstrequest ? +
            + {appView} + {modalView} +
            + : +
            +
            + + // Maybe use gridview or something, idk + return ( +
            + {loadedCheck} +
            + ) +} + +export default Apps diff --git a/frontend/src/Contact.js b/frontend/src/Contact.js new file mode 100644 index 00000000..97cf84e6 --- /dev/null +++ b/frontend/src/Contact.js @@ -0,0 +1,343 @@ +import React, {useState} from 'react'; +import {BrowserView, MobileView} from "react-device-detect"; + +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; + +import TextField from '@material-ui/core/TextField'; + +const bodyDivStyle = { + margin: "auto", + textAlign: "center", + width: "900px", +} + + + + +// Should be different if logged in :| +const Contact = (props) => { + const { globalUrl, isLoaded, surfaceColor, inputColor } = props; + + const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, + display: "flex", + flexDirection: "column" + } + + const bodyTextStyle = { + color: "#ffffff", + } + + const [firstname, setFirstname] = useState(""); + const [lastname, setLastname] = useState(""); + const [title, setTitle] = useState(""); + const [companyname, setCompanyname] = useState(""); + const [email, setEmail] = useState(""); + const [phone, setPhone] = useState(""); + const [message, setMessage] = useState(""); + + const [formMessage, setFormMessage] = useState(""); + + const submitContact = () => { + const data = { + "firstname": firstname, + "lastname": lastname, + "title": title, + "companyname": companyname, + "email": email, + "phone": phone, + "message": message, + } + console.log(data) + + fetch(globalUrl+"/api/v1/contact", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(response => { + if (response.success === true) { + setFormMessage(response.message) + } else { + setFormMessage("Something went wrong. Please contact frikky@shuffler.io.") + } + console.log(response) + }) + .catch(error => { + console.log(error) + }); + } + + // Random names for type & autoComplete. Didn't research :^) + const landingpageDataBrowser = +
            +
            +

            Contact us

            +

            Lets talk!

            +
            +
            + +

            Contact Details

            +
            + setFirstname(e.target.value)} + /> + setLastname(e.target.value)} + /> +
            +
            + setTitle(e.target.value)} + /> + setCompanyname(e.target.value)} + /> +
            +
            + setEmail(e.target.value)} + /> + setPhone(e.target.value)} + /> +
            +
            +

            Message

            +
            +
            + setMessage(e.target.value)} + /> +
            + +

            {formMessage}

            +
            +
            +
            + + const landingpageDataMobile = +
            +
            +

            Contact us

            +

            Lets talk!

            +
            +
            + +

            Contact Details

            +
            + setFirstname(e.target.value)} + /> +
            +
            + setEmail(e.target.value)} + /> +
            +
            +

            Message

            +
            +
            + setMessage(e.target.value)} + /> +
            + +

            {formMessage}

            +
            +
            +
            + + + const loadedCheck = isLoaded ? +
            + +
            {landingpageDataBrowser}
            +
            + + {landingpageDataMobile} + +
            + : +
            +
            + + return( +
            + {loadedCheck} +
            + ) +} +export default Contact; diff --git a/frontend/src/Dashboard.js b/frontend/src/Dashboard.js new file mode 100644 index 00000000..30e093b1 --- /dev/null +++ b/frontend/src/Dashboard.js @@ -0,0 +1,211 @@ +import React, {useState} from 'react'; +// nodejs library that concatenates classes +import classNames from "classnames"; +// react plugin used to create charts +import { Line, Bar } from "react-chartjs-2"; + +// https://demos.creative-tim.com/black-dashboard-react/?ref=appseed#/admin/dashboard + +// reactstrap components +import { + Button, + ButtonGroup, + Card, + CardHeader, + CardBody, + CardTitle, + DropdownToggle, + DropdownMenu, + DropdownItem, + UncontrolledDropdown, + Label, + FormGroup, + Input, + Table, + Row, + Col, + UncontrolledTooltip +} from "reactstrap"; + +// core components +import { + chartExample1, + chartExample2, + chartExample3, + chartExample4 +} from "./charts.js"; + +// This is the start of a dashboard that can be used. +// What data do we fill in here? Idk +const Dashboard = (props) => { + const [bigChartData, setBgChartData] = useState("data1"); + + document.title = "Shuffle - dashboard" + + const data = +
            + + + + + + +
            Total Shipments
            + Performance + + + + + + + + +
            +
            + +
            + +
            +
            +
            + +
            + + + + +
            Total Shipments
            + + {" "} + 763,215 + +
            + +
            + +
            +
            +
            + + + + +
            Daily Sales
            + + {" "} + 3,500€ + +
            + +
            + +
            +
            +
            + + + + +
            Completed Tasks
            + + 12,100K + +
            + +
            + +
            +
            +
            + +
            +
            + + const dataWrapper = +
            + {data} +
            + + return dataWrapper +} + +export default Dashboard; diff --git a/frontend/src/Docs.js b/frontend/src/Docs.js new file mode 100644 index 00000000..58fac7e9 --- /dev/null +++ b/frontend/src/Docs.js @@ -0,0 +1,226 @@ +import React, {useState, useEffect} from 'react'; + +import Divider from '@material-ui/core/Divider'; +import ReactMarkdown from 'react-markdown'; +import {BrowserView, MobileView} from "react-device-detect"; +import Button from '@material-ui/core/Button'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; + +import {Link} from 'react-router-dom'; + +const Body = { + maxWidth: '1000px', + minWidth: '768px', + margin: 'auto', + display: "flex", + heigth: "100%", + color: "white", + //textAlign: "center", +}; + +const dividerColor = "rgb(225, 228, 232)" + +const SideBar = { + maxWidth: "250px", + flex: "1", +} + +const hrefStyle = { + color: "rgba(255, 255, 255, 0.40)", + textDecoration: "none" +} + +const Docs = (props) => { + const { isLoaded, globalUrl } = props; + + const [data, setData] = useState(""); + const [firstrequest, setFirstrequest] = useState(true); + const [list, setList] = useState([]); + const [listLoaded, setListLoaded] = useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + + function handleClick(event) { + setAnchorEl(event.currentTarget); + } + + function handleClose() { + setAnchorEl(null); + } + + useEffect(() => { + if (firstrequest) { + setFirstrequest(false) + fetchDocList() + fetchDocs() + return + } + }) + + const fetchDocList = () => { + fetch(globalUrl+"/api/v1/docs", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success) { + setList(responseJson.list) + } else { + setList(["error"]) + } + setListLoaded(true) + }) + .catch(error => {}); + } + + const fetchDocs = () => { + fetch(globalUrl+"/api/v1/docs/"+props.match.params.key, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success) { + setData(responseJson.reason) + } else { + setData("# Error\nThis page doesn't exist.") + } + }) + .catch(error => {}); + } + + const markdownStyle = { + color: "rgba(255, 255, 255, 0.65)", + flex: "1", + } + + function Link(props) { + return {props.children} + } + + function Img(props) { + return {props.alt} + } + + //function unicodeToChar(text) { + // return text.replace(/\\u[\dA-F]{4}/gi, + // function (match) { + // return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)); + // } + // ); + //} + + const postDataBrowser = +
            +
            +
              +
            • + +

              Home

              +
              +
            • + {list.map(item => { + const path = "/docs/"+item + const newname = item.charAt(0).toUpperCase()+item.substring(1) + return ( +
            • + +

              {newname}

              +
              +
            • + ) + })} +
            +
            +
            + +
            +
            + + const mobileStyle = { + color: "white", + marginLeft: "15px", + marginRight: "15px", + paddingBottom: "50px", + backgroundColor: "inherit", + } + + const postDataMobile = +
            + + + {list.map(item => { + const path = "/docs/"+item + const newname = item.charAt(0).toUpperCase()+item.substring(1) + return ( + {window.location.pathname = path}}>{newname} + ) + })} + +
            + +
            + + + +
            + + + //const imageModal = + // + // {imageModal} + + + const loadedCheck = isLoaded && listLoaded ? +
            + + {postDataBrowser} + + + {postDataMobile} + +
            + : +
            +
            + + return ( +
            + {loadedCheck} +
            + ) +} + + +export default Docs; diff --git a/frontend/src/EditSchedule.js b/frontend/src/EditSchedule.js new file mode 100644 index 00000000..0c588172 --- /dev/null +++ b/frontend/src/EditSchedule.js @@ -0,0 +1,1391 @@ +import React, { useState, useEffect} from 'react'; + +/* + * It works a little something like this: + * Choose what source and destination action you want + * Select the source field to be used for a required destination field OR + * write a static value for the field (FIXME) OR + * run a generator action for the field (FIXME, these should be e.g. run workflow and get result) + */ + +import Grid from '@material-ui/core/Grid'; +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; + +import ButtonBase from '@material-ui/core/ButtonBase'; +import TextField from '@material-ui/core/TextField'; +import MenuItem from '@material-ui/core/MenuItem'; +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; +import InputLabel from '@material-ui/core/InputLabel'; +import Table from '@material-ui/core/Table'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableRow from '@material-ui/core/TableRow'; + +import SearchIcon from '@material-ui/icons/Search'; +import DeleteIcon from '@material-ui/icons/Delete'; + +import Downshift from 'downshift'; +import deburr from 'lodash/deburr'; + +//import appdata from './appdata'; + + +const EditSchedule = (props) => { + const { globalUrl } = props; + + const [tmpSrcApp, setTmpSrcApp] = useState({}) + const [tmpDstApp, setTmpDstApp] = useState({}) + const [srcApp, setSrcApp] = useState({}) + const [srcAppConfig, setSrcAppConfig] = useState([]) + const [srcAppConfigOpen, setSrcAppConfigOpen] = useState(false) + const [srcAppAction, setSrcAppAction] = useState("") + + const [dstApp, setDstApp] = useState({}) + const [dstAppAction, setDstAppAction] = useState("") + const [dstAppConfig, setDstAppConfig] = useState([]) + const [dstAppConfigOpen, setDstAppConfigOpen] = useState(false) + + // Lets set the real data here + const [selectedSrc, setSelectedSrc] = React.useState(""); + const [, setSelectedSrcData] = React.useState({}); + const [selectedDst, setSelectedDst] = React.useState([]); + + // FIXME + const [suggestions, setSuggestions] = React.useState([]); + const [inputappdata, setInputAppData] = React.useState({}); + + const [scheduleConfig, setScheduleConfig] = React.useState({}) + const [selectedSrcParameters, setSelectedSrcParameters] = React.useState([]) + + const getCurrentSchedule = () => { + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig(responseJson) + }) + .catch(error => { + console.log(error) + }); + } + + const loadAppSuggestions = () => { + fetch(globalUrl+"/api/v1/schedules/apps", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + setSuggestions(responseJson.apps) + }) + .catch(error => { + console.log(error) + }); + } + + // FIXME - this is generated from app selection input with required items + useEffect(() => { + if (Object.getOwnPropertyNames(scheduleConfig).length <= 0) { + getCurrentSchedule() + } + + // Load apps if destination or source is + if (suggestions.length === 0) { + loadAppSuggestions() + } + + // Load everything else + if (Object.getOwnPropertyNames(scheduleConfig).length > 0 && Object.getOwnPropertyNames(scheduleConfig.appinfo.sourceapp).length > 0 && Object.getOwnPropertyNames(srcApp).length <= 0) { + if (scheduleConfig.appinfo.sourceapp.name.length > 0) { + setSrcApp(scheduleConfig.appinfo.sourceapp) + setSrcAppAction(scheduleConfig.appinfo.sourceapp.action) + } + } + + + // Use sourceapp.name&version and sourceapp.action and look for name in inputappdata + // Basically fix everything in this one lol + if (suggestions.length > 0 && srcAppAction.length > 0 && Object.getOwnPropertyNames(scheduleConfig).length > 0 && Object.getOwnPropertyNames(inputappdata).length <= 0) { + // Loops all apps and finds current + for (var key in suggestions) { + var curapp = suggestions[key] + if (curapp.name === srcApp.name) { + break + } + } + + setInputAppData(curapp) + + // Loops the apps actions to find current + for (key in curapp.output) { + var curappaction = curapp.output[key] + if (curappaction.name === srcAppAction) { + break + } + } + + setSelectedSrcParameters(curappaction.outputparameters) + if (curappaction.config !== null && curappaction.config !== undefined) { + setSrcAppConfig(curappaction.config) + } + + // FIXME - set src of all translator nodes to curappaction.outputparameters[0] if not defined + //value={scheduleConfig.translator[count].src.name} + + //console.log(scheduleConfig) + var newtranslator = [] + for (key in scheduleConfig.translator) { + var curtranslator = scheduleConfig.translator[key] + + // Overwrite issues + if (curtranslator.src.name === "") { + curtranslator.src = curappaction.outputparameters[0] + } + + newtranslator.push(curtranslator) + } + + scheduleConfig.translator = newtranslator + setScheduleConfig(scheduleConfig) + } + + if (suggestions.length > 0 && dstAppAction.length <= 0 && Object.getOwnPropertyNames(scheduleConfig).length > 0) { + // Loops all apps and finds current + for (key in suggestions) { + curapp = suggestions[key] + if (curapp.name === dstApp.name) { + break + } + } + + const curAction = scheduleConfig.appinfo.destinationapp.action + + // Loops the apps actions to find current + for (key in curapp.output) { + curappaction = curapp.output[key] + if (curappaction.name === curAction) { + break + } + } + + setDstAppAction(curappaction.name) + if (curappaction.config !== null && curappaction.config !== undefined) { + setDstAppConfig(curappaction.config) + } + } + + + }) + + const getSuggestions = (value, type, { showEmpty = false } = {}) => { + const inputValue = deburr(value.trim()).toLowerCase(); + const inputLength = inputValue.length; + let count = 0; + + return inputLength === 0 && !showEmpty + ? [] + : suggestions.filter(suggestion => { + const keep = + count < 5 && + suggestion.types && + suggestion.types.includes(type) && + suggestion.name.slice(0, inputLength).toLowerCase() === inputValue; + + if (keep) { + count += 1; + } + + return keep; + }); + } + + const setSrcAppWrapper = (currentapps) => { + setSrcApp(currentapps[0]) + if (currentapps[0].output.length > 0) { + selectSrcAction(currentapps[0].output[0].name) + } + } + + const setDstAppWrapper = (currentapps) => { + setDstApp(currentapps[0]) + if (currentapps[0].input.length > 0) { + selectInitialDstAction(currentapps) + } + } + + const renderInput = (type, inputProps) => { + const { InputProps, classes, ref, ...other } = inputProps; + + // Sets the srcapp if string is matching exactly for name + if (InputProps["aria-activedescendant"] === null && InputProps["value"].length > 0) { + const currentapps = suggestions.filter(data => data.name === InputProps["value"]) + if (currentapps.length === 1) { + if (type === "output") { + if (currentapps[0].name !== srcApp.name) { + setSrcAppWrapper(currentapps) + } + } else if (type === "input") { + if (currentapps[0].name !== dstApp.name) { + setDstAppWrapper(currentapps) + } + } + } + } + + return ( +
            + + + + ), + }} + {...other} + /> +
            + ); + } + + const renderSuggestion = (suggestionProps) => { + const { suggestion, index, itemProps, highlightedIndex, selectedItem } = suggestionProps; + const isHighlighted = highlightedIndex === index; + const isSelected = (selectedItem || '').indexOf(suggestion.name) > -1; + + return ( + + {suggestion.name} + + ); + } + + const bodyDivStyle = { + marginLeft: "20px", + marginTop: "50px", + marginRight: "20px", + margin: "auto", + width: "1350px", + display: "flex", + } + + const appActionStyle = { + height: "50px", + marginTop: "10px", + } + + // FIXME - set this + //const setRelationshipsFromSource = () => { + + //} + + //// FIXME - set this + //const setRelationshipsFromGenerator = () => { + + //} + + const selectDstAppAction = (event) => { + if (event.target.value === dstAppAction) { + return + } + + setDstAppAction(event.target.value) + refactorTranslations(event.target.value) + } + + const refactorTranslations = (action) => { + // dstapp is chosen + var found = false + for (var key in dstApp.input) { + var curinput = dstApp.input[key] + if (curinput.name === action) { + found = true + break + } + } + + // Should never happen.. + if (!found) { + return + } + + var tmprelationships = [] + for (key in curinput.inputparameters) { + var curRelation = {"dst": curinput.inputparameters[key], "src": {}} + tmprelationships.push(curRelation) + } + + scheduleConfig["translator"] = tmprelationships + setScheduleConfig(scheduleConfig) + } + + const selectInitialDstAction = (value) => { + var action = value[0].input[0].name + setDstAppAction(action) + + var found = false + for (var key in value) { + var curinput = value[0].input[key] + if (curinput.name === action) { + found = true + break + } + } + + // Should never happen.. + if (!found) { + return + } + + var tmprelationships = [] + for (key in curinput.inputparameters) { + var curRelation = {"dst": curinput.inputparameters[key], "src": {}} + tmprelationships.push(curRelation) + } + + scheduleConfig["translator"] = tmprelationships + setScheduleConfig(scheduleConfig) + } + + + const selectSrcAction = (value) => { + // FIXME - load the config for this action + var found = false + + setSrcAppAction(value) + + var curitem = {} + for (var key in inputappdata.output) { + curitem = inputappdata.output[key] + if (curitem.name === value) { + found = true + break + } + } + + if (!found) { + setSelectedSrcParameters([]) + return + } + + // Generate this for every relation? + setSelectedSrcParameters(curitem.outputparameters) + + //FIXME - check if source has the right attribute + var tmprelationships = [] + key = 0 + for (key in scheduleConfig.translator) { + var curRelation = scheduleConfig.translator[key] + curRelation["src"] = curitem.outputparameters[0] + tmprelationships.push(curRelation) + } + + scheduleConfig["translator"] = tmprelationships + setScheduleConfig(scheduleConfig) + } + + // Wrapper to handle event click + const selectSrcActionWrapper = (event) => { + return selectSrcAction(event.target.value) + } + + const srcappaction = Object.getOwnPropertyNames(srcApp).length > 0 && srcApp.output ? +
            + + + Actions + + + +
            + : null + + const dstappaction = Object.getOwnPropertyNames(dstApp).length > 0 ? +
            + + + Actions + + + +
            + : null + + const downshiftStyle = { + //marginLeft: "20px", + //marginTop: "20px", + //marginRight: "20px", + display: "flex", + width: "100%", + } + + const submitDisabled = selectedSrc.length > 0 && selectedDst.length > 0 + const submitButtonClick = () => { + var newtranslations = [] + var tobeadded = [] + var tobechanged = [] + + // Go find the information again in testdata + + //newrelationship["translator"] = {"src": {}, "dst": {}} + //newrelationship["static"] = {} + + // Reformat relationships + // clean up old relationships + // + + if (Object.getOwnPropertyNames(scheduleConfig.Translator).length <= 0) { + return + } + + // Clean up old translations for specified elements + // FIXME - maybe just send a request with relationships and fix in backend? + // FIXME - there is an issue here for some multifield stuff + // Literally have to verify every single one anyway.. + for (var selected in selectedDst) { + var found = false + var index = 0 + for (var key in scheduleConfig["translator"]) { + if (scheduleConfig["translator"][key]["dst"]["name"] === selectedDst[selected]) { + index = key + found = true + break + } + } + + if (!found) { + tobeadded.push(selectedDst[selected]) + } else { + // Dst can be the + tobechanged.push(index) + } + } + + var tmprelationships = scheduleConfig + + key = 0 + for (key in scheduleConfig["translator"]) { + if (tobechanged.includes(key)) { + var tmprel = scheduleConfig["translator"][key] + tmprel["src"] = {"name": selectedSrc} + tmprelationships["translator"].splice(key, 1) + newtranslations.push(tmprel) + } + } + + key = 0 + for (key in tobeadded) { + tmprel = {"src": {"name": selectedSrc}, "dst": {"name": tobeadded[key]}} + newtranslations.push(tmprel) + } + + // delete deletable keys from copy (new) + // + key = 0 + for (key in newtranslations) { + tmprelationships["translator"].push(newtranslations[key]) + } + + setSelectedSrc("") + setSelectedDst([]) + setSelectedSrcData({}) + + // FIXME - does this work? + scheduleConfig["translator"] = tmprelationships["translator"] + setScheduleConfig(scheduleConfig) + } + + // When it's set, need to find the destination in the row and set static + const setStaticValue = (event, row) => { + console.log("HI") + // FIXME - modify the row first + // FIXME - set generator and source to "nothing" + + console.log(row.dst.name) + console.log(row) + console.log(event.target.value) + var newsrcrow = {"name": "static", "description": "Static value set", "type": "static", "value": event.target.value, "schema": {"type": "string"}} + + var relationshipclone = JSON.parse(JSON.stringify(scheduleConfig)); + for (var key in scheduleConfig["translator"]) { + var curItem = scheduleConfig["translator"][key] + if (curItem.dst.name === row.dst.name) { + break + } + } + + relationshipclone["translator"][key]["src"] = newsrcrow + + scheduleConfig["translator"] = relationshipclone["translator"] + console.log(scheduleConfig) + + // Fuck this :( + // DO DIS IN FRONTEND AND HAVE A SUBMIT BUTTON :( + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(scheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}) + }) + .catch(error => { + console.log(error) + }); + } + + // Rewrites relationships clientside + const setCurrentSrcPropRelation = (event, rowkey) => { + console.log(event, rowkey) + var found = false + + for (var key in selectedSrcParameters) { + var curItem = selectedSrcParameters[key] + if (curItem.name === event.target.value) { + found = true + break + } + } + + // No idea how this would ever happen, but but (: + if (!found) { + return + } + + var row = scheduleConfig.translator[rowkey] + var rowclone = JSON.parse(JSON.stringify(row)); + rowclone.src = curItem + + var relationshipclone = JSON.parse(JSON.stringify(scheduleConfig)); + for (key in scheduleConfig["translator"]) { + curItem = scheduleConfig["translator"][key] + if (curItem.dst.name === row.dst.name) { + break + } + } + + relationshipclone["translator"][key] = rowclone + + scheduleConfig["translator"] = relationshipclone["translator"] + console.log(scheduleConfig) + + // Fuck this :( + // DO DIS IN FRONTEND AND HAVE A SUBMIT BUTTON :( + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(scheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}) + }) + .catch(error => { + console.log(error) + }); + return + } + + + + const relationshipBody = Object.getOwnPropertyNames(scheduleConfig).length > 0 && scheduleConfig["translator"] !== undefined && scheduleConfig.translator.length > 0 ? + + + + + Action + + + Source Field + + + Destination Field + + + Field Type + + + Required + + + Static + + + Generator + + + Transform + + + {scheduleConfig.translator.map((row, count) => { + var buttonIcon = + if (!row.dst.required) { + buttonIcon = + } + + const srcpropsSelector = (Object.getOwnPropertyNames(selectedSrcParameters).length > 0 || Object.getOwnPropertyNames(scheduleConfig.translator).length > 0) && Object.getOwnPropertyNames(srcApp).length > 0 ? + :
            No defined fields
            + + const placeholderValue = row.src.type === "static" ? row.src.value : "... Set a static value" + + return( + + + {buttonIcon} + + {srcpropsSelector} + {row.dst.name} + {row.dst.schema.type} + {row.dst.required} + + { + if (event.key === 'Enter') { + event.preventDefault(); + console.log(`Pressed keyCode ${event.key}`); + setStaticValue(event, row) + } + }} + /> + + INSERT SCRIPT THINGY - Cortex responder? + + ) + })} +
            +
            : null + + + // FIXME - use this + //const submitRelationships = () => { + // // Make an API-call to the backend for verification + // // Return with failures etc, and mark row issues? + // + // var apiUrl = "http://localhost:5000" + // fetch(apiUrl+"/api/v1/schedules", + // { + // method: "POST", + // headers: {"content-type": "application/json"}, + // body: JSON.stringify(scheduleConfig), + // } + // ) + // .then((response) => response.json()) + // .then((responseJson) => { + // console.log(responseJson) + // }) + // .catch((error) => { + // console.log(error); + // }); + //} + + const executeSchedule = () => { + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key+"/execute", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson) + }) + .catch(error => { + console.log(error) + }); + } + + + const submitButton = +
            + +
            + + // Requires src or dst as input + // FIXME - use this shit to edit an app or something + //const editButtonFix = (app, apptype) => { + // if (apptype === "src") { + + // } else if (apptype === "dst") { + + // } + //} + + const editSrcApp = (event) => { + // if tmpsrcapp, show RESET (can be an X or something too) + // if reset is clicked, set source app back to the original + setTmpSrcApp(scheduleConfig.appinfo.sourceapp) + + var tmpscheduleConfig = scheduleConfig + tmpscheduleConfig.appinfo.sourceapp = {} + + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}) + setSrcApp({}) + setSrcAppAction("") + }) + .catch(error => { + console.log(error) + }); + } + + const editDstApp = (event) => { + // if tmpsrcapp, show RESET (can be an X or something too) + // if reset is clicked, set source app back to the original + setTmpDstApp(scheduleConfig.appinfo.destinationapp) + + var tmpscheduleConfig = scheduleConfig + tmpscheduleConfig.appinfo.destinationapp = {} + + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}) + setDstApp({}) + setDstAppAction("") + }) + .catch(error => { + console.log(error) + }); + } + + const scheduleApp = (app, actiondata) => { + const editButton = actiondata === "src" ? + + : + + + + + const configureButton = actiondata === "src" ? + + : + + // FIXME - set src vs dst + return( + + + + + + + {splitter} + + + +
            +

            {app.name}

            +
            +
            + {app.description} +
            +
            + + {app.action} + +
            + {splitter} + +
            + {editButton} +
            +
            + {configureButton} +
            +
            +
            +
            + ) + } + + const splitter =
            + + const dstDownshift = + + {({ + getInputProps, + getItemProps, + getMenuProps, + highlightedIndex, + inputValue, + isOpen, + selectedItem, + }) => ( +
            + {renderInput("input", { + fullWidth: true, + InputProps: getInputProps({ + placeholder: 'Search destination apps', + }), + })} + +
            +
            + {isOpen ? ( + + {getSuggestions(inputValue, "input").map((suggestion, index) => + renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion.name }), + highlightedIndex, + selectedItem, + }), + )} + + ) : null} +
            + {dstappaction} +
            +
            + )} +
            + + + const srcDownshift = + + {({ + getInputProps, + getItemProps, + getMenuProps, + highlightedIndex, + inputValue, + isOpen, + selectedItem, + }) => ( +
            + {renderInput("output", { + fullWidth: true, + InputProps: getInputProps({ + placeholder: 'Search source apps', + }), + })} + +
            +
            + {isOpen ? ( + + {getSuggestions(inputValue, "output").map((suggestion, index) => + renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion.name }), + highlightedIndex, + selectedItem, + }), + )} + + ) : null} +
            + {srcappaction} +
            +
            + )} +
            + + const submitDstApp = (event) => { + var packagedSrc = { + name: dstApp.name, + id: dstApp.id, + description: dstApp.description, + action: dstAppAction, + } + + var tmpscheduleConfig = scheduleConfig + + tmpscheduleConfig.appinfo.destinationapp = packagedSrc + + // Hmm, do this here? Idk + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}) + }) + .catch(error => { + console.log(error) + }); + } + + const submitSrcApp = (event) => { + var packagedSrc = { + name: srcApp.name, + id: srcApp.id, + description: srcApp.description, + action: srcAppAction, + } + + var tmpscheduleConfig = scheduleConfig + + // FIXME - future fred + // + tmpscheduleConfig.appinfo.sourceapp = packagedSrc + + // Hmm, do this here? Idk + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}) + }) + .catch(error => { + console.log(error) + }); + } + + const resetDstApp = (event) => { + var tmpscheduleConfig = scheduleConfig + tmpscheduleConfig.appinfo.destinationapp = tmpDstApp + + // Hmm, do this here? Idk + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}) + setDstApp(tmpSrcApp) + setDstAppAction(tmpDstApp.action) + setTmpDstApp({}) + }) + .catch(error => { + console.log(error) + }); + + } + + const resetSrcApp = (event) => { + var tmpscheduleConfig = scheduleConfig + tmpscheduleConfig.appinfo.sourceapp = tmpSrcApp + + // Hmm, do this here? Idk + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}) + setSrcApp(tmpSrcApp) + setSrcAppAction(tmpSrcApp.action) + setTmpSrcApp({}) + }) + .catch(error => { + console.log(error) + }); + + } + + // Based on some srcthing + const searchGridSrc = + + + Choose Source app (FIXME) - clickable + + {splitter} + + + +
            + {srcDownshift} +
            +
            +
            + {splitter} + +
            + +
            +
            + +
            +
            +
            +
            + + const searchGridDst = + + + Choose Source app (FIXME) - clickable + + {splitter} + + + +
            + {dstDownshift} +
            +
            +
            + {splitter} + +
            + +
            +
            + +
            +
            +
            +
            + + + const srcField = (Object.getOwnPropertyNames(scheduleConfig).length > 0 && Object.getOwnPropertyNames(scheduleConfig.appinfo.sourceapp).length > 0 && scheduleConfig.appinfo.sourceapp.name.length > 0) ? +
            + + {scheduleApp(scheduleConfig.appinfo.sourceapp, "src")} + +
            + : +
            + + {searchGridSrc} + +
            + + const dstField = (Object.getOwnPropertyNames(scheduleConfig).length > 0 && Object.getOwnPropertyNames(scheduleConfig.appinfo.destinationapp).length > 0 && scheduleConfig.appinfo.destinationapp.name.length > 0) ? +
            + + {scheduleApp(scheduleConfig.appinfo.destinationapp, "dst")} + +
            + : +
            + + {searchGridDst} + +
            + + // Have to use array cus of datastore lol (no map[string]string) + const srcModalData = [] + const buildSrcModal = (event, fieldname) => { + var fieldfound = false + for (var key in srcModalData) { + if (srcModalData[key]["key"] === fieldname) { + fieldfound = true + srcModalData[key]["value"] = event.target.value + break + } + } + + if (!fieldfound) { + srcModalData.push({"key": fieldname, "value": event.target.value}) + } + console.log(srcModalData) + } + + // FIXME - verify required fields? + const submitSrcConfig = () => { + scheduleConfig.appinfo.sourceapp.config = srcModalData + setScheduleConfig(scheduleConfig) + setSrcAppConfigOpen(false) + + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(scheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson) + }) + .catch(error => { + console.log(error) + }); + } + + const dstModalData = [] + const buildDstModal = (event, fieldname) => { + var fieldfound = false + for (var key in dstModalData) { + if (dstModalData[key]["key"] === fieldname) { + fieldfound = true + dstModalData[key]["value"] = event.target.value + break + } + } + + if (!fieldfound) { + dstModalData.push({"key": fieldname, "value": event.target.value}) + } + console.log(dstModalData) + } + + // FIXME - verify required fields + const submitDstConfig = () => { + scheduleConfig.appinfo.destinationapp.config = dstModalData + setScheduleConfig(scheduleConfig) + setDstAppConfigOpen(false) + + fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(scheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson) + }) + .catch(error => { + console.log(error) + }); + } + + const dstConfigModal = dstAppConfigOpen ? + {setDstAppConfigOpen(false)}} + > + Source configuration + + Configure {dstApp.name}'s required fields + {dstAppConfig.map(data => ( + {buildDstModal(event, data.name)}} + autofocus + color="primary" + name="searchtext" + placeholder={data.name} + margin="dense" + id={data.name} + label={data.name} + fullWidth + /> + ))} + + + + + + : null + + + // FIXME - load the actual fields! + const srcConfigModal = srcAppConfigOpen ? + {setSrcAppConfigOpen(false)}} + > + Source configuration + + Configure {srcApp.name}'s required fields + {srcAppConfig.map(data => ( + {buildSrcModal(event, data.name)}} + autofocus + color="primary" + name="searchtext" + placeholder={data.name} + margin="dense" + id={data.name} + label={data.name} + fullWidth + /> + ))} + + + + + + : null + + return( +
            + {srcConfigModal} + {dstConfigModal} +
            + {srcField} + {dstField} +
            + +
            + {submitButton} +
            +
            + {relationshipBody} +
            +
            + ) +} + +export default EditSchedule diff --git a/frontend/src/EditWebhook.js b/frontend/src/EditWebhook.js new file mode 100644 index 00000000..6653cb5a --- /dev/null +++ b/frontend/src/EditWebhook.js @@ -0,0 +1,366 @@ +import React, {useState, useEffect} from 'react'; + +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; +import Divider from '@material-ui/core/Divider'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; + +import WebhookImage from './assets/img/webhook.png'; +import KafkaImage from './assets/img/kafka.png'; + +import EditWorkflow from "./EditWorkflow"; + +const EditWebhook = (props) => { + const { globalUrl, isLoaded } = props; + + // FIXME + //const [webhookData, setWebhookData] = useState(webhooktest) + const [webhookData, setWebhookData] = useState({}) + const [workflows, setWorkflows] = useState([]) + const [firstrequest, setFirstrequest] = React.useState(true); + + const [selectedWorkflows, setSelectedWorkflows] = useState([]) + + const getWorkflows = () => { + fetch(globalUrl+"/api/v1/workflows", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!") + } + return response.json() + }) + .then((responseJson) => { + setWorkflows(responseJson) + + }) + .catch(error => { + console.log(error) + }); + } + + const setWebhook = (inputdata) => { + console.log(inputdata) + + fetch(globalUrl+"/api/v1/hooks/"+props.match.params.key, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + body: JSON.stringify(inputdata), + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson) + }) + .catch(error => { + console.log(error) + }); + } + + const getCurrentWebhook = () => { + fetch(globalUrl+"/api/v1/hooks/"+props.match.params.key, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200!") + window.location.pathname = "webhooks" + } + return response.json() + }) + .then((responseJson) => { + if (responseJson.actions === null) { + responseJson.actions = [] + } + + if (responseJson.transforms === null) { + responseJson.transforms = [] + } + + setWebhookData(responseJson) + }) + .catch(error => { + console.log(error) + //window.location.pathname = "webhooks" + }); + } + + useEffect(() => { + if (firstrequest) { + setFirstrequest(false) + getCurrentWebhook() + if (workflows.length <= 0) { + getWorkflows() + } + } + + // After everything is loaded + if (Object.getOwnPropertyNames(webhookData).length > 0 && webhookData.actions.length > 0 && workflows.length > 0 && selectedWorkflows.length === 0) { + // Setting startup actions. making like this in case we want other actions + var tmpActionWorkflows = [] + for (var key in webhookData.actions) { + if (webhookData.actions[key].type === "workflow") { + tmpActionWorkflows.push(webhookData.actions[key]) + } + } + + // Fix duplicates... Meh + var foundWorkflowIds = [] + var tmpWorkflows = [] + for (key in tmpActionWorkflows) { + if (foundWorkflowIds.includes(tmpActionWorkflows[key].id)) { + continue + } + + for (var subkey in workflows) { + if (tmpActionWorkflows[key].id === workflows[subkey]["id_"]) { + console.log(tmpActionWorkflows[key].id, workflows[subkey]["id_"]) + foundWorkflowIds.push(tmpActionWorkflows[key].id) + tmpWorkflows.push(workflows[subkey]) + break + } + } + } + + if (tmpWorkflows.length > 0) { + setSelectedWorkflows(tmpWorkflows) + } + } + }) + + + const hookPicture = Object.getOwnPropertyNames(webhookData).length > 0 && webhookData.type === "webhook" ? + webhook + : + MQ + + const executeHook = (action) => { + fetch(globalUrl+"/api/v1/hooks/"+props.match.params.key+"/"+action, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + setWebhookData({}) + }) + .catch(error => { + console.log(error) + }); + } + + const headerPaperStyle = { + display: "flex", + maxHeight: "800px", + minHeight: "800px", + margin: "10px 30px 10px 10px", + padding: "10px 5px 5px 5px", + flexDirection: "column", + } + + // FIXME - add with counter to change the correct one (not just edit) + const addNewWorkflow = (event) => { + // Verify if it already exists in the array. Returns if it exists + for (var key in selectedWorkflows) { + var item = selectedWorkflows[key] + if (item["id_"] === event.target.value["id_"]) { + return + } + } + + // FIXME - make this possible for all accounts + if (selectedWorkflows.length === 0) { + console.log("ADD FIRST ITEM FOR SELECTEDWORKFLOWS") + console.log(event.target.value) + + // Cleanup previous actions + var newActions = [] + if (webhookData.actions.length > 0) { + for (key in webhookData.actions) { + if (webhookData.actions[key].type === "" || webhookData.actions[key].type === undefined) { + continue + } + + newActions.push(webhookData.actions[key]) + } + } + + // FIXME - how to stringify this better hurr + var formattedWorkflow = { + "type": "workflow", + "name": event.target.value.name, + "id": event.target.value.id_, + "field": "", + } + + // FIXME: patch this n + newActions.push(formattedWorkflow) + console.log(newActions) + + webhookData.actions = newActions + setWebhook(webhookData) + } + + var tmpSelectedWorkflows = [].concat(selectedWorkflows, [event.target.value]) + setSelectedWorkflows(tmpSelectedWorkflows) + } + + // FIXME + // Create a list with + button + // For each, choose the new workflow I wanna add + // Current: JUST ONE + const selectedWorkflowIds = selectedWorkflows.map(data => {return data["id_"]}) + const availableWorkflows = workflows.filter(data => !selectedWorkflowIds.includes(data["id_"])) + + const WorkflowSelect = (counter) => { + if (selectedWorkflows[counter.counter] === undefined) { + return null + } + + console.log(selectedWorkflows[0]) + console.log(selectedWorkflows[0]) + console.log(selectedWorkflows[0]) + console.log(selectedWorkflows[counter.counter]) + console.log(selectedWorkflows[counter.counter].name) + return ( +
            + Workflow select: + +
            + ) + } + + const extraWorkflow = workflows.length > 0 && availableWorkflows.length > 0 ? + : null + + const multiWorkflowSelect = workflows.length > 0 && selectedWorkflows.length > 0 ? +
            + {selectedWorkflows.map((data, count) => ( + + ))} + {extraWorkflow} +
            + : + + const headerInfo = Object.getOwnPropertyNames(webhookData).length > 0 ? +
            + +
            +
            + {hookPicture} +
            +
            +
            +

            Name: {webhookData.info.name}

            +
            +
            +
            +
            + Description: {webhookData.info.description} +
            + Id: {webhookData.id} +
            +
            + Url: {webhookData.info.url} +
            +
            + Type: {webhookData.type} +
            +
            + Status: {webhookData.status} +
            +
            + CHOOSE ACTIONS: + {multiWorkflowSelect} +
            +
            + +
            +
            + +
            +
            + +
            +
            +
            +
            + : null + + // FIXME - needs refresh everytime you add a new workflow + const workflowdata = Object.getOwnPropertyNames(webhookData).length > 0 && selectedWorkflows.length > 0 ? + : null + + const loadedCheck = isLoaded ? +
            +
            + {workflowdata} +
            +
            + {headerInfo} +
            +
            + : +
            +
            + + // FIXME: Use this for testing + // : null + return ( + +
            + {loadedCheck} +
            + ) +} + +export default EditWebhook; diff --git a/frontend/src/EditWorkflow.js b/frontend/src/EditWorkflow.js new file mode 100644 index 00000000..19ef4bfc --- /dev/null +++ b/frontend/src/EditWorkflow.js @@ -0,0 +1,364 @@ +import React, { useState, useEffect} from 'react'; + +import * as cytoscape from 'cytoscape'; +import * as edgehandles from 'cytoscape-edgehandles'; +import CytoscapeComponent from 'react-cytoscapejs'; + +const EditWorkflow = (props) => { + const { inputworkflows, inputtype, inputname} = props; + + + const [elements, setElements] = useState([]) + const [loadedWorkflows, ] = useState(inputworkflows) + + // Setting cy config + const [cystyle, ] = useState( + [{ + selector: 'node', + css: { + 'label': 'data(label)', + 'text-halign': 'right', + 'text-valign': 'center', + 'font-family': 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif', + 'font-weight': 'lighter', + 'font-size': '15px', + 'width': '60px', + 'height': '60px', + 'padding': '10px', + 'margin': '5px', + 'border-width': '2px', + 'background-image': 'url("data:image/png;base64,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")', + } + }, + { + selector: `node[type="ACTION"]`, + css: { + 'shape': 'square', + 'border-color': '#81c784', + }, + }, + { + selector: `node[type="CONDITION"]`, + css: { + 'shape': 'diamond', + 'border-color': '##FFEB3B', + 'padding': '30px' + }, + }, + { + selector: 'node[type="eventAction"]', + css: { + 'background-color': '#edbd21', + }, + }, + { + selector: 'node[type="webhook"]', + css: { + 'border-color': '#81c784', + 'background-color': 'white', + 'background-image': 'url("data:image/png;base64,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")', + }, + }, + { + selector: 'node[type="mq"]', + css: { + 'background-color': '#edbd21', + }, + }, + { + selector: 'node[?isStartNode]', + css: { + 'shape': 'ellipse', + 'border-width': '2px', + 'border-color': '#80deea', + }, + }, + { + selector: 'node[?hasErrors]', + css: { + 'color': '#991818', + 'font-style': 'italic', + }, + }, + { + selector: 'node:selected', + css: { + 'background-color': '#77b0d0', + }, + }, + { + selector: '.success-highlight', + css: { + 'background-color': '#399645', + 'transition-property': 'background-color', + 'transition-duration': '0.5s', + }, + }, + { + selector: '.failure-highlight', + css: { + 'background-color': '#8e3530', + 'transition-property': 'background-color', + 'transition-duration': '0.5s', + }, + }, + { + selector: '.executing-highlight', + css: { + 'background-color': '#ffef47', + 'transition-property': 'background-color', + 'transition-duration': '0.25s', + }, + }, + { + selector: '.awaiting-data-highlight', + css: { + 'background-color': '#f4ad42', + 'transition-property': 'background-color', + 'transition-duration': '0.5s', + }, + }, + { + selector: '$node > node', + css: { + 'padding-top': '10px', + 'padding-left': '10px', + 'padding-bottom': '10px', + 'padding-right': '10px', + 'text-valign': 'top', + 'text-halign': 'center', + }, + }, + { + selector: 'edge', + css: { + 'target-arrow-shape': 'triangle', + 'curve-style': 'bezier', + }, + }, + { + selector: 'edge.executing-highlight', + css: { + 'width': '5px', + 'target-arrow-color': '#ffef47', + 'line-color': '#ffef47', + 'transition-property': 'line-color, width', + 'transition-duration': '0.25s', + }, + }, + { + selector: 'edge.success-highlight', + css: { + 'width': '5px', + 'target-arrow-color': '#399645', + 'line-color': '#399645', + 'transition-property': 'line-color, width', + 'transition-duration': '0.5s', + }, + }, + { + selector: 'edge[?hasErrors]', + css: { + 'target-arrow-color': '#991818', + 'line-color': '#991818', + 'line-style': 'dashed' + }, + }, + { + selector: '.eh-handle', + style: { + 'background-color': '#337ab7', + 'width': '1px', + 'height': '1px', + 'shape': 'triangle', + } + }, + { + selector: '.eh-source', + style: { + 'border-width': '3', + 'border-color': '#337ab7' + } + }, + { + selector: '.eh-target', + style: { + 'border-width': '3', + 'border-color': '#337ab7' + } + }, + { + selector: '.eh-preview, .eh-ghost-edge', + style: { + 'background-color': '#337ab7', + 'line-color': '#337ab7', + 'target-arrow-color': '#337ab7', + 'source-arrow-color': '#337ab7' + } + } +] +) + + + useEffect(() => { + if (elements.length === 0) { + setupGraph() + } + + const cyDummy = cytoscape(); + if (!cyDummy.edgehandles) { cytoscape.use(edgehandles); } + }) + + const setupGraph = () => { + // Convert our selection arrays to a string + //if (!this.loadedWorkflow.actions) { this.loadedWorkflow.actions = []; } + + //setTimeout(() => { + // if (this.consoleArea && this.consoleArea.codeMirror) this.consoleArea.codeMirror.refresh(); + //}); + + // Create the Cytoscape graph + // http://js.cytoscape.org/#style/labels + + // Breaks stuff + //container: document.getElementById('cy'), + + // FIXME - needs refresh + const tmpEdges = loadedWorkflows.map((workflow, count) => { + return workflow.branches.map(branch => { + const edge = { }; + edge.data = { + id: branch.id, + _id: branch.id, + source: branch.source_id, + target: branch.destination_id, + hasErrors: branch.has_errors + }; + return edge; + }); + }) + + // Make the actual actions + var edges = [] + for (var key in tmpEdges) { + for (var subkey in tmpEdges[key]) { + edges.push(tmpEdges[key][subkey]) + } + } + + const tmpActions = loadedWorkflows.map((workflow, count) => { + return workflow.actions.map(action => { + const node = { position: {x: action.position.x, y: action.position.y}} + node.data = { + id: action["id_"], + _id: action["id_"], + label: action.name, + isStartNode: action["id_"] === loadedWorkflows[count].start, + hasErrors: action.has_errors, + type: "ACTION" + }; + return node; + }); + }) + + // Make the actual actions + var actions = [] + for (key in tmpActions) { + for (subkey in tmpActions[key]) { + actions.push(tmpActions[key][subkey]) + } + } + + const tmpConditionals = loadedWorkflows.map((workflow, count) => { + return workflow.conditions.map(condition => { + const node = { position: {x: condition.position.x, y: condition.position.y}} + node.data = { + id: condition.id_, + _id: condition.id_, + label: condition.name, + isStartNode: condition["id_"] === loadedWorkflows[count].start, + hasErrors: condition.has_errors, + type: "CONDITION" + }; + return node; + }); + }) + + // Make the actual actions + var conditionals = [] + for (key in tmpConditionals) { + for (subkey in tmpConditionals[key]) { + conditionals.push(tmpConditionals[key][subkey]) + } + } + + const tmpelements = [].concat(edges, actions, conditionals) + + if (inputtype !== undefined && inputname !== undefined ) { + // Find startnode, find the movement location and push elements down: + // FIXME - generate stuff + const locationvar = 200 + const baseylocation = 100 + const hookid = "GENERATEME" + const hookname = inputname + for (key in tmpelements) { + var item = tmpelements[key] + + if (item.data.isStartNode) { + // Append a webhook item to the view + var shiftlength = 0 + if (item.position.y-locationvar < baseylocation) { + shiftlength = item.position.y-locationvar+(-baseylocation) + if (shiftlength < 0) { + shiftlength = -(shiftlength) + } + } + + const tmpdata = {data: {id: hookid, label: hookname, type: inputtype}, position: {x: item.position.x, y: item.position.y-locationvar}} + const newedge = {data: {source: hookid, target: item.data.id}} + tmpelements.push(tmpdata) + tmpelements.push(newedge) + + if (shiftlength !== 0) { + const newelements = [] + for (key in tmpelements) { + // isNaN? + if (tmpelements[key].position === undefined || tmpelements[key].position.isNaN) { + newelements.push(tmpelements) + continue + } + + var newitem = tmpelements[key] + newitem.position.y = newitem.position.y+shiftlength + } + } + + break + } + } + } + + setElements(tmpelements) + } + + // Set some extra stuff? + var cy; + const cytmp = cytoscape() + cytmp.fit(null, 50); + + return ( +
            + cy = cytmp} + elements={elements} + style={{width: '1000px', height: '1000px'}} + stylesheet={cystyle} + boxSelectionEnabled={false} + autounselectify={false} + wheelSensitivity={0.1} + />; +
            + ) +} + +export default EditWorkflow; diff --git a/frontend/src/Flows.js b/frontend/src/Flows.js new file mode 100644 index 00000000..aa1a35a3 --- /dev/null +++ b/frontend/src/Flows.js @@ -0,0 +1,25 @@ +import React from 'react'; + +const Flows = () => { + return ( +
            +

            Flows

            + +

            + Built to suit any organization +

            + +

            + WAT +

            + +

            +

            + +

            + +
            + ) +} + +export default Flows; diff --git a/frontend/src/FooterNew.js b/frontend/src/FooterNew.js new file mode 100644 index 00000000..aa68d486 --- /dev/null +++ b/frontend/src/FooterNew.js @@ -0,0 +1,54 @@ +import React from 'react'; + +//import List from '@material-ui/core/List'; +//import ListItem from '@material-ui/core/ListItem'; + +//borderTop: "1px solid #385F71" +const FooterStyle = { + right: "0", + left: "0", + bottom: "0", + height: "130px", + backgroundColor: 'rgba(15, 14, 31, 1)', +}; + +const FooterInfo = { + maxWidth: '1150px', + minWidth: '768px', + textAlign: 'center', + margin: 'auto', +}; + +const hrefStyle = { + color: "#bdbdbd", + textDecoration: "none" +} + +const Footer = props => { + return ( +
            +
            + +
            +
            + ); +}; + +const Box = props => { + return( + + ); +}; + +export default Footer; diff --git a/frontend/src/ForgotPassword.js b/frontend/src/ForgotPassword.js new file mode 100644 index 00000000..0385579a --- /dev/null +++ b/frontend/src/ForgotPassword.js @@ -0,0 +1,122 @@ +/* eslint-disable react/no-multi-comp */ +import React, {useState} from 'react'; + +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; + +const bodyDivStyle = { + margin: "auto", + marginTop: "100px", + width: "500px", +} + + +const ForgotPassword = props => { + const { globalUrl, isLoaded, isLoggedIn, surfaceColor, inputColor } = props; + + + const boxStyle = { + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, + } + + const [username, setUsername] = useState("") + const [resetInfo, setResetInfo] = useState("You will receive an email with instructions shortly.") + + const handleValidateForm = () => { + return username.length > 3 + } + + if (isLoggedIn === true) { + window.location.pathname = "/" + } + + const onSubmit = (e) => { + e.preventDefault() + // FIXME - add some check here ROFL + + // Just use this one? + var data = {"username": username} + var baseurl = globalUrl + var url = baseurl+'/api/v1/passwordresetmail'; + fetch(url, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setResetInfo(responseJson["reason"]) + } + }), + ) + .catch(error => { + setResetInfo("Error in userdata: " + error) + }); + } + + const onChangeUser = (e) => { + setUsername(e.target.value) + } + + const data = +
            + +
            +

            Password reset

            +
            + +
            +
            + + +
            +
            + {resetInfo} +
            +
            +
            +
            + + const loadedCheck = isLoaded ? +
            + {data} +
            + : +
            +
            + + return ( +
            + {loadedCheck} +
            + ) +} + +export default ForgotPassword; diff --git a/frontend/src/ForgotPasswordLink.js b/frontend/src/ForgotPasswordLink.js new file mode 100644 index 00000000..33a5b421 --- /dev/null +++ b/frontend/src/ForgotPasswordLink.js @@ -0,0 +1,134 @@ +import React, {useState, useEffect} from 'react'; + +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; + +import TextField from '@material-ui/core/TextField'; + +const bodyDivStyle = { + margin: "auto", + textAlign: "center", + width: "768px", +} + +const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: "#e8eaf6", + display: "flex", + flexDirection: "column" +} + +//const tmpdata = { +// "username": "frikky", +// "firstname": "fred", +// "lastname": "ode", +// "title": "topkek", +// "companyname": "company here", +// "email": "your email pls", +// "phone": "PHONE!!", +//} + +// FIXME - add fetch for data fields +// FIXME - remove tmpdata +// FIXME: Use isLoggedIn :) +const Settings = (props) => { + const { globalUrl, isLoaded, } = props; + + const [newPassword, setNewPassword] = useState(""); + const [newPassword2, setNewPassword2] = useState(""); + const [passwordFormMessage, setPasswordFormMessage] = useState(""); + + const onPasswordChange = () => { + const data = {"newpassword": newPassword, "newpassword2": newPassword2, "reference": props.match.params.key} + const url = globalUrl+'/api/v1/passwordreset'; + fetch(url, { + mode: 'cors', + method: 'POST', + body: JSON.stringify(data), + credentials: 'include', + crossDomain: true, + withCredentials: true, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setPasswordFormMessage(responseJson["reason"]) + } + }), + ) + .catch(error => { + setPasswordFormMessage("Something went wrong.") + }); + } + + // This should "always" have data + useEffect(() => { + }) + + // Random names for type & autoComplete. Didn't research :^) + const landingpageData = +
            + +

            Password Reset

            +
            + setNewPassword(e.target.value)} + /> + setNewPassword2(e.target.value)} + /> +
            + +

            {passwordFormMessage}

            +
            +
            + +const loadedCheck = isLoaded ? +
            + {landingpageData} +
            + : +
            +
            + +return( +
            + {loadedCheck} +
            +) +} +export default Settings; diff --git a/frontend/src/Header.js b/frontend/src/Header.js new file mode 100644 index 00000000..422a859b --- /dev/null +++ b/frontend/src/Header.js @@ -0,0 +1,295 @@ +import React, {useState} from 'react'; +import {BrowserView, MobileView} from "react-device-detect"; + +import {Link} from 'react-router-dom'; + +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; + +import Button from '@material-ui/core/Button'; +import HomeIcon from '@material-ui/icons/Home'; +import Grid from '@material-ui/core/Grid'; + +const hoverColor = "#f85a3e" +const hoverOutColor = "#e8eaf6" + +const Header = props => { + const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded } = props; + + const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); + const [SoarHoverColor, setSoarHoverColor] = useState(hoverOutColor); + const [LoginHoverColor, setLoginHoverColor] = useState(hoverOutColor); + const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor); + const [HelpHoverColor, setHelpHoverColor] = useState(hoverOutColor); + + const hrefStyle = { + color: hoverOutColor, + textDecoration: "none", + } + + // DEBUG HERE + const handleClickLogout = () => { + console.log("SHOULD LOG OUT") + console.log(isLoggedIn) + + // Don't really care about the logout + fetch(globalUrl+"/api/v1/logout", { + credentials: "include", + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(() => { + // Log out anyway + console.log("Hey") + removeCookie("session_token", {path: "/"}) + window.location.pathname = "/" + }) + .catch(error => { + console.log(error) + }); + } + + // Rofl this is weird + const handleDocsHover = () => { + setDocsHoverColor(hoverColor) + } + + const handleDocsHoverOut = () => { + setDocsHoverColor(hoverOutColor) + } + + const handleHomeHover = () => { + setHomeHoverColor(hoverColor) + } + + const handleHelpHover = () => { + setHelpHoverColor(hoverColor) + } + + const handleHelpHoverOut = () => { + setHelpHoverColor(hoverOutColor) + } + + const handleSoarHover = () => { + setSoarHoverColor(hoverColor) + } + + const handleSoarHoverOut = () => { + setSoarHoverColor(hoverOutColor) + } + + const handleHomeHoverOut = () => { + setHomeHoverColor(hoverOutColor) + } + + const handleLoginHover = () => { + setLoginHoverColor(hoverColor) + } + + const handleLoginHoverOut = () => { + setLoginHoverColor(hoverOutColor) + } + + // Should be based on some path + const logoCheck = !homePage ? null : null + + + // Handle top bar or something + const loginTextBrowser = !isLoggedIn ? +
            + + + +
            + + + + + + Shuffle + + +
            + +
            + + +
            + About +
            + +
            +
            +
            + + + +
            Login
            + +
            +
            +
            +
            + : +
            +
            + + + +
            Workflows
            + +
            + + +
            Apps
            + +
            + {/* + + +
            Dashboard
            + +
            + */} + + +
            Docs
            + +
            + {/* + + +
            Configure
            + +
            + */} +
            +
            +
            + + +
            + Logout +
            +
            + {logoCheck} + + + + + + + + + + +
            +
            +
            + + const loginTextMobile = !isLoggedIn ? +
            + + + +
            + + + + + +
            + +
            + + +
            + About +
            + +
            +
            +
            + : +
            +
            + + + +
            Shuffle
            + +
            + + +
            Workflows
            + +
            + + +
            Apps
            + +
            + {/* + + +
            Configure
            + +
            + */} +
            +
            +
            + + +
            + Logout +
            +
            + {logoCheck} + + + + + + +
            +
            +
            + + // + const loadedCheck = isLoaded ? +
            + + {loginTextBrowser} + + + {loginTextMobile} + +
            + : +
            +
            + + //
            + return ( +
            + {loadedCheck} +
            + ); +}; + +export default Header; diff --git a/frontend/src/Hookpost.js b/frontend/src/Hookpost.js new file mode 100644 index 00000000..566a26d4 --- /dev/null +++ b/frontend/src/Hookpost.js @@ -0,0 +1,25 @@ +import React from 'react'; + +const Hooks = () => { + return ( +
            +

            Hooks

            + +

            + Built to suit any organization +

            + +

            + WAT +

            + +

            +

            + +

            + +
            + ) +} + +export default Hooks; diff --git a/frontend/src/Landingpage.js b/frontend/src/Landingpage.js new file mode 100644 index 00000000..1dd2f912 --- /dev/null +++ b/frontend/src/Landingpage.js @@ -0,0 +1,179 @@ +import React, {} from 'react'; + +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; +import Divider from '@material-ui/core/Divider'; +import {BrowserView, MobileView} from "react-device-detect"; +import ScheduleIcon from '@material-ui/icons/Schedule'; +import Web from '@material-ui/icons/Web'; +import AccountTree from '@material-ui/icons/AccountTree'; + +const bodyDivStyle = { + margin: "auto", + marginTop: "75px", + textAlign: "center", + width: "1100px", +} + +const surfaceColor = "#27292D" +const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + height: "400px", + //backgroundColor: "#e8eaf6", + backgroundColor: surfaceColor, + textAlign: "center", + display: "flex", + flexDirection: "column", +} + +const bodyTextStyle = { + color: "#ffffff", +} + +const hrefStyle = { + color: "black", + textDecoration: "none", +} + + +// Should be different if logged in :| +const LandingPage = (props) => { + const { isLoaded} = props; + + const textColor = "#8899A6" + const iconColor = "#1DA1F2" + const iconSize = "8em" + const GridLayout = (header, description, link, icon) => { + return ( + + +
            +

            {header}

            +
            + +
            + {description} +
            +
            + {icon} +
            + +
            +
            + Learn more +
            +
            +
            +
            + ) + } + + const listitems = [ + GridLayout("Simple integrations", "Easily use others' or create your own integration", "/docs/apps", ), + GridLayout("Workflows", "Access the power of automation within minutes, whether its on premise or in the cloud", "/docs/workflows", ), + GridLayout("Realtime actions", "Beat the clock by leveraging our realtime triggers", "/docs/triggers", ), + ] + + // The actual landing page + // {"logo"} + const landingpageDataBrowser = +
            +
            +

            Shuffle

            +

            A general automation solution for Infosec and IT Professionals

            +
            + + + + + + +
            + {listitems.map(item => { + return ( +
            + {item} +
            + ) + })} +
            +
            + + const landingpageDataMobile = +
            +
            +

            Shuffle

            +

            A general automation solution for Infosec and IT Professionals

            + + + +
            +
            +
            + {listitems[0]} +
            +
            + {listitems[1]} +
            +
            + {listitems[2]} +
            + +
            +
            + + + // Reroute if the user is logged in + // const landingSite = isLoggedIn ? :
            {landingpageData}
            + const landingSite =
            {landingpageDataBrowser}
            + + const loadedCheck = isLoaded ? +
            + + {landingSite} + + + {landingpageDataMobile} + +
            + : +
            +
            + + return( +
            + {loadedCheck} +
            + ) +} +export default LandingPage; diff --git a/frontend/src/LandingpageLoggedin.js b/frontend/src/LandingpageLoggedin.js new file mode 100644 index 00000000..178c5930 --- /dev/null +++ b/frontend/src/LandingpageLoggedin.js @@ -0,0 +1,21 @@ +import React, {} from 'react'; + +const bodyDivStyle = { + transform: "translate(-50%, -50%)", + top: "50%", + left: "50%", + position: "absolute", + width: "500px", + color: "white", +} + +// Should be different if logged in :| +const LandingPageLoggedin = (props) => { + + return( +
            + TMP landingpage when logged in +
            + ) +} +export default LandingPageLoggedin; diff --git a/frontend/src/LandingpageNew.js b/frontend/src/LandingpageNew.js new file mode 100644 index 00000000..4ff6f25e --- /dev/null +++ b/frontend/src/LandingpageNew.js @@ -0,0 +1,349 @@ +import React, {useState } from 'react'; + +import Paper from '@material-ui/core/Paper'; +import Card from '@material-ui/core/Card'; +import CardActionArea from '@material-ui/core/CardActionArea'; +import CardMedia from '@material-ui/core/CardMedia'; +import CardContent from '@material-ui/core/CardContent'; +import CardActions from '@material-ui/core/CardActions'; +import Button from '@material-ui/core/Button'; +import Divider from '@material-ui/core/Divider'; +import Grid from '@material-ui/core/Grid'; +import {BrowserView, MobileView} from "react-device-detect"; + +import ScheduleIcon from '@material-ui/icons/Schedule'; +import Web from '@material-ui/icons/Web'; +import AccountTree from '@material-ui/icons/AccountTree'; +import InfoIcon from '@material-ui/icons/Info'; +import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; +import CreateIcon from '@material-ui/icons/Create'; + +const bodyDivStyle = { + margin: "auto", +} + +const surfaceColor = "#27292D" +const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + height: "400px", + //backgroundColor: "#e8eaf6", + backgroundColor: surfaceColor, + textAlign: "center", + display: "flex", + flexDirection: "column", +} + +const bodyTextStyle = { + color: "#ffffff", +} + +const hrefStyle = { + color: "inherit", + textDecoration: "none", +} + + +// Should be different if logged in :| +const LandingPage = (props) => { + const { isLoaded} = props; + + const textColor = "#8899A6" + const iconColor = "#1DA1F2" + const iconSize = "8em" + + const GridLayout = (header, description, link, icon) => { + return ( + + +
            +

            {header}

            +
            + +
            + {description} +
            +
            + {icon} +
            + +
            +
            + Learn more +
            +
            +
            +
            + ) + } + + const listitems = [ + GridLayout("Simple integrations", "Easily use others' or create your own integration", "/docs/features", ), + GridLayout("Workflows", "Access the power of automation within minutes, whether its on premise or in the cloud", "/docs/features", ), + GridLayout("Realtime actions", "Beat the clock by leveraging our realtime triggers", "/docs/features", ), + ] + + // The actual landing page + // {"logo"} + //We start by understanding your unique environment to help identify the right thing to automate. + const secondaryColor = "rgba(167,46,87,1)" + const primaryColor = "rgba(25, 35, 94, 1)" + + const paperStyle = { + flex: 1, + backgroundColor: "inherit", + cursor: "pointer", + } + + const secondaryItemList = [ + { + primaryText: "No time to waste", + secondaryText: "Bring all your applications into a single view, and make them all work together flawlessly!", + image: "/images/time.jpg", + }, { + primaryText: "Get a better overview", + secondaryText: "Don't know what's happening? We'll help you track and act on your most valuable KPI's!", + image: "/images/overview.jpg", + }, { + primaryText: "Conquer your tasks", + secondaryText: "Get access to powerful tools and pre-made workflows to help you crush your teams daily tasks!", + image: "/images/burnout.jpg", + }, + ] + const [image, setImage] = useState(secondaryItemList[0].image); + + const landingpageDataBrowser = +
            +
            + +
            +
            + Shuffle +
            +
            + INFORMATION
            OVERLOAD
            +
            +
            + Everyone run into the same fundamental operational problems. Mailbox chaos, tickets getting out of hand and a constant feeling of being overwhelmed. The good news?
            Shuffle solves them.
            +
            + + + +
            +
            +
            +
            +
            + Automation is just the beginning +
            +
            +
            + {secondaryItemList.map((data, index) => { + const color = image === data.image ? "rgba(255,255,255,1)" : "rgba(255,255,255,0.4)" + return ( +
            setImage(data.image)}> + {data.primaryText} +
            + {data.secondaryText} +
            +
            + ) + })} +
            +
            +
            + +
            +
            +
            +
            + +
            +
            + Learn more about the benefits of Shuffle +
            + +
            +
            +
            +
            +
            + Focus on the work that matters to you +
            +
            + Menial tasks, scattered content, constant copy pasting, waste of talent - there's a smarter way to work. +
            +
            + {window.location.pathname = "/docs/features"}} style={{flex: 1, margin: 10, textAlign: "center"}}> + + + +

            Premade playbooks

            +

            Get your automation done with minimal effort

            +
            +
            + +
            + {window.location.pathname = "/docs/features"}} style={{flex: 1, margin: 10, textAlign: "center"}}> + + + +

            Open frameworks

            +

            Mitre Att&ck, OpenAPI and more!

            +
            +
            + +
            + {window.location.pathname = "/docs/features"}} style={{flex: 1, margin: 10, textAlign: "center"}}> + + + +

            Hundreds of integrations

            +

            Quickly integrate your software applications

            +
            +
            + +
            + {window.location.pathname = "/docs/features"}}> + + + +

            Automated compliance

            +

            Stuck with compliance needs you can't meet?

            +
            +
            + +
            +
            +
            +
            + + const landingpageDataMobile = +
            +
            +

            Shuffle

            +

            A general automation solution for Infosec and IT Professionals

            + + + +
            +
            +
            + {listitems[0]} +
            +
            + {listitems[1]} +
            +
            + {listitems[2]} +
            + +
            +
            + + + // Reroute if the user is logged in + // const landingSite = isLoggedIn ? :
            {landingpageData}
            + const landingSite =
            {landingpageDataBrowser}
            + + const loadedCheck = isLoaded ? +
            + + {landingSite} + + + {landingpageDataMobile} + +
            + : +
            +
            + + return( +
            + {loadedCheck} +
            + ) +} +export default LandingPage; diff --git a/frontend/src/LoginPage.js b/frontend/src/LoginPage.js new file mode 100644 index 00000000..db0a27c0 --- /dev/null +++ b/frontend/src/LoginPage.js @@ -0,0 +1,255 @@ +/* eslint-disable react/no-multi-comp */ +import React, {useState} from 'react'; +import { makeStyles } from '@material-ui/styles'; + +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; + +const hrefStyle = { + color: "white", + textDecoration: "none" +} + +const bodyDivStyle = { + margin: "auto", + marginTop: "100px", + width: "500px", +} + +const surfaceColor = "#27292D" +const inputColor = "#383B40" + +const boxStyle = { + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, +} + +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important" + }, +}); + +const LoginDialog = props => { + const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register } = props; + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [firstRequest, setFirstRequest] = useState(true); + + // Used to swap from login to register. True = login, false = register + + const classes = useStyles(); + // Error messages etc + const [loginInfo, setLoginInfo] = useState(""); + + const handleValidateForm = () => { + return (username.length > 1 && password.length > 8); + } + + if (isLoggedIn === true) { + window.location.pathname = "/workflows" + } + + + const checkAdmin = () => { + const url = globalUrl+'/api/v1/checkusers'; + fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + if (responseJson.reason === "stay") { + window.location.pathname = "/adminsetup" + } + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata: ", error) + }) + } + + if (firstRequest) { + setFirstRequest(false) + checkAdmin() + } + + const onSubmit = (e) => { + e.preventDefault() + // FIXME - add some check here ROFL + + // Just use this one? + var data = {"username": username, "password": password} + var baseurl = globalUrl + if (register) { + var url = baseurl+'/api/v1/login'; + fetch(url, { + mode: 'cors', + method: 'POST', + body: JSON.stringify(data), + credentials: 'include', + crossDomain: true, + withCredentials: true, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + setLoginInfo("Successful login, rerouting") + for (var key in responseJson["cookies"]) { + setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, {path: "/"}) + } + + setIsLoggedIn(true) + window.location.pathname = "/workflows" + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata: " + error) + }); + } else { + url = baseurl+'/api/v1/register'; + fetch(url, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + setLoginInfo("Successful register :)") + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata: ", error) + }); + } + } + + const onChangeUser = (e) => { + setUsername(e.target.value) + } + + const onChangePass = (e) => { + setPassword(e.target.value) + } + + //const onClickRegister = () => { + // if (props.location.pathname === "/login") { + // window.location.pathname = "/register" + // } else { + // window.location.pathname = "/login" + // } + + // setLoginCheck(!register) + //} + + //var loginChange = register ? (

            Want to register? Click here.

            ) : (

            Go back to login? Click here.

            ); + var formtitle = register ?
            Login
            :
            Register
            + + // {formtitle} + + const basedata = +
            + +
            +

            {formtitle}

            + Username +
            + +
            + Password +
            + +
            +
            + + +
            +
            + {loginInfo} +
            +
            +
            +
            + + const loadedCheck = isLoaded ? +
            + {basedata} +
            + : +
            +
            + + return ( +
            + {loadedCheck} +
            + ) +} + +export default LoginDialog; diff --git a/frontend/src/LoginPopup.js b/frontend/src/LoginPopup.js new file mode 100644 index 00000000..232b4976 --- /dev/null +++ b/frontend/src/LoginPopup.js @@ -0,0 +1,139 @@ +/* eslint-disable react/no-multi-comp */ +import React, {useState} from 'react'; + +import DialogTitle from '@material-ui/core/DialogTitle'; +import Dialog from '@material-ui/core/Dialog'; +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; + +const LoginDialog = props => { + const { classes, onClose, open, globalUrl, isLoggedIn, setIsLoggedIn, ...other } = props; + + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + //const [selectedValue, setSelectedValue] = useState(false); + + // Used to swap from login to register. True = login, false = register + const [loginCheck, setLoginCheck] = useState(true); + + // Error messages etc + const [loginInfo, setLoginInfo] = useState(""); + + const handleValidateForm = () => { + return (username.length > 1 && password.length > 8); + } + + const onSubmit = (e) => { + e.preventDefault() + + // Just use this one? + var data = '{"username": "' + username + '", "password": "' + password + '"}'; + var baseurl = globalUrl + if (loginCheck) { + var url = baseurl+'/login'; + fetch(url, { + method: 'POST', + body: data, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + console.log(responseJson) + //console.log(e) + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + setLoginInfo("Successful login :)") + onClose() + setIsLoggedIn(true) + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata") + }); + } else { + url = baseurl+'/register'; + fetch(url, { + method: 'POST', + body: data, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + setLoginInfo("Successful register :)") + onClose() + setIsLoggedIn(true) + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata") + }); + } + } + + const onChangeUser = (e) => { + setUsername(e.target.value) + } + + const onChangePass = (e) => { + setPassword(e.target.value) + } + + const onClickRegister = () => { + setLoginCheck(!loginCheck) + } + + //var loginChange = loginCheck ? (

            Want to register? Click here.

            ) : (

            Go back to login? Click here.

            ); + var formtitle = loginCheck ?
            Login
            :
            Register
            + var formButton = loginCheck ?
            Click to Register
            :
            Click to Login
            + + return ( + + {formtitle} +
            + Username +
            + +
            + Password +
            + +
            +
            + + + +
            + {loginInfo} +
            +
            + +
            +
            + ); +} + +export default LoginDialog; diff --git a/frontend/src/Oauth2.js b/frontend/src/Oauth2.js new file mode 100644 index 00000000..3137d3d1 --- /dev/null +++ b/frontend/src/Oauth2.js @@ -0,0 +1,11 @@ +import React, { } from 'react'; + +const Oauth2 = (props) => { + return ( +
            + tmp +
            + ) +} + +export default Oauth2; diff --git a/frontend/src/Post.js b/frontend/src/Post.js new file mode 100644 index 00000000..11d41646 --- /dev/null +++ b/frontend/src/Post.js @@ -0,0 +1,72 @@ +import React from 'react'; + +const Body = { + maxWidth: '1000px', + minWidth: '768px', + margin: 'auto', + display: "flex", + heigth: "100%", + color: "white", + //textAlign: "center", +}; + +const SideBar = { + maxWidth: "250px", + flex: "1", +} + +const hrefStyle = { + color: "#385f71", + textDecoration: "none" +} + +const Post = (props) => { + const { currentPost, isLoaded } = props; + + const postData = +
            + +
            + {currentPost} +
            +
            + + const loadedCheck = isLoaded ? +
            + {postData} +
            + : +
            +
            + + return ( +
            + {loadedCheck} +
            + ) +} + +export default Post; diff --git a/frontend/src/PrivacyPolicy.js b/frontend/src/PrivacyPolicy.js new file mode 100644 index 00000000..52041010 --- /dev/null +++ b/frontend/src/PrivacyPolicy.js @@ -0,0 +1,122 @@ +import React from 'react'; + +const PrivacyPolicy = () => { + return ( +
            +

            Privacy Policy

            + +

            Effective date: 17.08.2019

            + + +

            We operate the shuffler.io website.

            + +

            This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our service and the choices you have associated with that data.

            + +

            We use your data to provide and improve the service. By using the service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, accessible from shuffler.io

            + + +

            Information Collection And Use

            + +

            We collect several different types of information for various purposes to provide and improve our service to you.

            + +

            Types of Data Collected

            + +

            Personal Data

            + +

            While using our service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you ("Personal Data"). Personally identifiable information may include, but is not limited to:

            + +
              +
            • Cookies and Usage Data
            • +
            + +

            Usage Data

            + +

            We may also collect information how the service is accessed and used ("Usage Data"). This Usage Data may include information such as your computer's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

            + +

            Tracking & Cookies Data

            +

            We use cookies and similar tracking technologies to track the activity on our service and hold certain information.

            +

            Cookies are files with small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Tracking technologies also used are beacons, tags, and scripts to collect and track information and to improve and analyze our service.

            +

            You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent. However, if you do not accept cookies, you may not be able to use some portions of our service.

            +

            Examples of Cookies we use:

            +
              +
            • Session Cookies. We use Session Cookies to operate our service.
            • +
            • Preference Cookies. We use Preference Cookies to remember your preferences and various settings.
            • +
            • Security Cookies. We use Security Cookies for security purposes.
            • +
            + +

            Use of Data

            + +

            Shuffler uses the collected data for various purposes:

            +
              +
            • To provide and maintain the service
            • +
            • To notify you about changes to our service
            • +
            • To allow you to participate in interactive features of our service when you choose to do so
            • +
            • To provide customer care and support
            • +
            • To provide analysis or valuable information so that we can improve the service
            • +
            • To monitor the usage of the service
            • +
            • To detect, prevent and address technical issues
            • +
            + +

            Transfer Of Data

            +

            Your information, including Personal Data, may be transferred to — and maintained on — computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from your jurisdiction.

            +

            If you are located outside Norway and choose to provide information to us, please note that we transfer the data, including Personal Data, to Norway and process it there.

            +

            Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.

            +

            Shuffler will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information.

            + +

            Disclosure Of Data

            + +

            Legal Requirements

            +

            Shuffler may disclose your Personal Data in the good faith belief that such action is necessary to:

            +
              +
            • To comply with a legal obligation
            • +
            • To protect and defend the rights or property of Shuffler
            • +
            • To prevent or investigate possible wrongdoing in connection with the service
            • +
            • To protect the personal safety of users of the service or the public
            • +
            • To protect against legal liability
            • +
            + +

            Security Of Data

            +

            The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.

            + +

            Service Providers

            +

            We may employ third party companies and individuals to facilitate our service ("service Providers"), to provide the service on our behalf, to perform service-related services or to assist us in analyzing how our service is used.

            +

            These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.

            + +

            Analytics

            +

            We may use third-party service Providers to monitor and analyze the use of our service.

            +
              +
            • +

              Google Analytics

              +

              Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our service. This data is shared with other Google services. Google may use the collected data to contextualize and personalize the ads of its own advertising network.

              +

              You can opt-out of having made your activity on the service available to Google Analytics by installing the Google Analytics opt-out browser add-on. The add-on prevents the Google Analytics JavaScript (ga.js, analytics.js, and dc.js) from sharing information with Google Analytics about visits activity.

              For more information on the privacy practices of Google, please visit the Google Privacy & Terms web page: https://policies.google.com/privacy?hl=en

              +
            • +
            + + +

            Links To Other Sites

            +

            Our service may contain links to other sites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.

            +

            We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

            + + +

            Children's Privacy

            +

            Our service does not address anyone under the age of 18 ("Children").

            +

            We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you are aware that your Children has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from children without verification of parental consent, we take steps to remove that information from our servers.

            + + +

            Changes To This Privacy Policy

            +

            We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.

            +

            We will let you know via email and/or a prominent notice on our service, prior to the change becoming effective and update the "effective date" at the top of this Privacy Policy.

            +

            You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

            + + +

            Contact Us

            +

            If you have any questions about this Privacy Policy, please contact us:

            +
              +
            • By email: fredrik_9490@hotmail.com
            • + +
            +
            + ) +} + +export default PrivacyPolicy; diff --git a/frontend/src/RegisterLink.js b/frontend/src/RegisterLink.js new file mode 100644 index 00000000..a4d8456e --- /dev/null +++ b/frontend/src/RegisterLink.js @@ -0,0 +1,93 @@ +import React, {useState, useEffect} from 'react'; + +import Paper from '@material-ui/core/Paper'; + +const bodyDivStyle = { + margin: "auto", + textAlign: "center", + width: "768px", +} + + +//const tmpdata = { +// "username": "frikky", +// "firstname": "fred", +// "lastname": "ode", +// "title": "topkek", +// "companyname": "company here", +// "email": "your email pls", +// "phone": "PHONE!!", +//} + +// FIXME - add fetch for data fields +// FIXME - remove tmpdata +// FIXME: Use isLoggedIn :) +const Settings = (props) => { + const { globalUrl, isLoaded, surfaceColor, } = props; + + const [firstRequest, setFirstRequest] = useState(true); + const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, + color: "white", + display: "flex", + flexDirection: "column" + } + + const registerCall = () => { + const url = globalUrl+'/api/v1/register/'+props.match.params.key + fetch(url, { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => + response.json().then(responseJson => { + console.log(responseJson) + }), + ) + .catch(error => { + console.log("SOMETHING WRONG") + }); + } + + // This should "always" have data + useEffect(() => { + if (firstRequest) { + setFirstRequest(false) + registerCall() + } + }) + + // Random names for type & autoComplete. Didn't research :^) + const landingpageData = +
            + +

            Registration verification

            +

            Thanks for verifying, redirecting you to our login!

            +
            +
            + +const loadedCheck = isLoaded ? +
            + {landingpageData} +
            + : +
            +
            + +return( +
            + {loadedCheck} +
            +) +} +export default Settings; diff --git a/frontend/src/RegisterPage.js b/frontend/src/RegisterPage.js new file mode 100644 index 00000000..7fea6d0b --- /dev/null +++ b/frontend/src/RegisterPage.js @@ -0,0 +1,139 @@ +/* eslint-disable react/no-multi-comp */ +import React, {useState} from 'react'; + +import DialogTitle from '@material-ui/core/DialogTitle'; +import Dialog from '@material-ui/core/Dialog'; +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; + +const LoginDialog = props => { + const { classes, onClose, open, globalUrl, isLoggedIn, setIsLoggedIn, ...other } = props; + + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + //const [selectedValue, setSelectedValue] = useState(false); + + // Used to swap from login to register. True = login, false = register + const [loginCheck, setLoginCheck] = useState(true); + + // Error messages etc + const [loginInfo, setLoginInfo] = useState(""); + + const handleValidateForm = () => { + return (username.length > 1 && password.length > 8); + } + + const onSubmit = (e) => { + e.preventDefault() + + // Just use this one? + var data = '{"username": "' + username + '", "password": "' + password + '"}'; + var baseurl = globalUrl + if (loginCheck) { + var url = baseurl+'/login'; + fetch(url, { + method: 'POST', + body: data, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + console.log(responseJson) + //console.log(e) + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + setLoginInfo("Successful login :)") + onClose() + setIsLoggedIn(true) + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata") + }); + } else { + url = baseurl+'/register'; + fetch(url, { + method: 'POST', + body: data, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]) + } else { + setLoginInfo("Successful register. Please check your mail :)") + onClose() + setIsLoggedIn(true) + } + }), + ) + .catch(error => { + setLoginInfo("Error in userdata") + }); + } + } + + const onChangeUser = (e) => { + setUsername(e.target.value) + } + + const onChangePass = (e) => { + setPassword(e.target.value) + } + + const onClickRegister = () => { + setLoginCheck(!loginCheck) + } + + //var loginChange = loginCheck ? (

            Want to register? Click here.

            ) : (

            Go back to login? Click here.

            ); + var formtitle = loginCheck ?
            Login
            :
            Register
            + var formButton = loginCheck ?
            Click to Register
            :
            Click to Login
            + + return ( + + {formtitle} +
            + Username +
            + +
            + Password +
            + +
            +
            + + + +
            + {loginInfo} +
            +
            + +
            +
            + ); +} + +export default LoginDialog; diff --git a/frontend/src/Schedules.js b/frontend/src/Schedules.js new file mode 100644 index 00000000..89db7527 --- /dev/null +++ b/frontend/src/Schedules.js @@ -0,0 +1,220 @@ +import React, { useEffect} from 'react'; + +import Paper from '@material-ui/core/Paper'; +import Grid from '@material-ui/core/Grid'; +import ButtonBase from '@material-ui/core/ButtonBase'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import Button from '@material-ui/core/Button'; +//import Breadcrumbs from '@material-ui/core/Breadcrumbs'; + +const Schedules = (props) => { + const { globalUrl } = props; + + //const [schedules, setSchedules] = React.useState(scheduledata); + const [schedules, setSchedules] = React.useState({}); + + const getAvailableSchedules = () => { + fetch(globalUrl+"/api/v1/schedules", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + setSchedules(responseJson) + }) + .catch(error => { + console.log(error) + }); + } + + // FIXME - add automated redirection, as empty apps look horrible currently + const newSchedule = () => { + fetch(globalUrl+"/api/v1/schedules/new", { + method: "POST", + headers: {"content-type": "application/json"}, + body: JSON.stringify(), + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson) + setSchedules({}) + }) + .catch(error => { + console.log(error) + }); + } + + const deleteSchedule = (id) => { + if (id === undefined) { + return + } + + fetch(globalUrl+"/api/v1/schedules/"+id+"/delete", { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + setSchedules({}) + }) + .catch(error => { + console.log(error) + }); + } + + // FIXME - use this? + //const getNewScheduleInfo = () => { + // fetch(globalUrl+"/api/v1/schedules", { + // method: 'GET', + // headers: { + // 'Content-Type': 'application/json', + // 'Accept': 'application/json', + // }, + // }) + // .then((response) => response.json()) + // .then((responseJson) => { + // setSchedules(responseJson) + // }) + // .catch(error => { + // console.log(error) + // }); + //} + + useEffect(() => { + if (Object.getOwnPropertyNames(schedules).length <= 0) { + getAvailableSchedules() + } + }) + + + const bodyDivStyle = { + marginLeft: "20px", + marginRight: "20px", + width: "1350px", + minWidth: "1350px", + maxWidth: "1350px", + } + + const scheduleApp = (app) => { + console.log(app) + return( + + + + + + + + + +
            +

            {app.name}

            +
            +
            + {app.description} +
            +
            + + {app.action} + +
            +
            +
            + ) + } + + const splitter =
            + + const hrefStyle = { + color: "#385f71", + textDecoration: "none" + } + + // FIXME - add Schedule modal + const schedulePaper = (schedule) => { + return( +
            + +
            + {scheduleApp(schedule.appinfo.sourceapp)} +
            +
            + ARROW +
            +
            + {scheduleApp(schedule.appinfo.destinationapp)} +
            + {splitter} +
            + + + + + + + + + + + +
            +
            +
            + ) + } + + console.log(schedules) + console.log(schedules) + console.log(schedules.schedules) + const schedulemap = Object.getOwnPropertyNames(schedules).length > 0 && schedules.schedules && schedules.schedules.length > 0 ? +
            + {schedules.schedules.map(data => ( + schedulePaper(data) + ))} +
            + : +
            + +
            + + const scheduleView = Object.getOwnPropertyNames(schedules).length > 0 ? +
            + + {schedulemap} +
            + : null + + // Maybe use gridview or something, idk + return ( +
            + {scheduleView} +
            + ) +} + +export default Schedules diff --git a/frontend/src/Schedulespost.js b/frontend/src/Schedulespost.js new file mode 100644 index 00000000..102144e7 --- /dev/null +++ b/frontend/src/Schedulespost.js @@ -0,0 +1,25 @@ +import React from 'react'; + +const Schedules = () => { + return ( +
            +

            Schedules

            + +

            + Built to suit any organization +

            + +

            + WAT +

            + +

            +

            + +

            + +
            + ) +} + +export default Schedules; diff --git a/frontend/src/SettingsPage.js b/frontend/src/SettingsPage.js new file mode 100644 index 00000000..dad3c89f --- /dev/null +++ b/frontend/src/SettingsPage.js @@ -0,0 +1,457 @@ +import React, {useState, useEffect} from 'react'; + +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; +import Divider from '@material-ui/core/Divider'; + +import TextField from '@material-ui/core/TextField'; + + +//const tmpdata = { +// "username": "frikky", +// "firstname": "fred", +// "lastname": "ode", +// "title": "topkek", +// "companyname": "company here", +// "email": "your email pls", +// "phone": "PHONE!!", +//} + +// FIXME - add fetch for data fields +// FIXME - remove tmpdata +// FIXME: Use isLoggedIn :) +const Settings = (props) => { + const { globalUrl, isLoaded, userdata, surfaceColor, inputColor } = props; + + const [username, setUsername] = useState(""); + const [firstname, setFirstname] = useState(""); + const [lastname, setLastname] = useState(""); + const [title, setTitle] = useState(""); + const [companyname, setCompanyname] = useState(""); + const [email, setEmail] = useState(""); + const [phone, setPhone] = useState(""); + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [newPassword2, setNewPassword2] = useState(""); + + // Used for error messages etc + const [formMessage, ] = useState(""); + const [passwordFormMessage, setPasswordFormMessage] = useState(""); + + const [firstrequest, setFirstRequest] = useState(true) + + const [userInfo, ] = useState(userdata) + const [userSettings, setUserSettings] = useState({}) + + const bodyDivStyle = { + margin: "auto", + textAlign: "center", + width: "1100px", + } + + const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, + display: "flex", + flexDirection: "column" + } + + const onPasswordChange = () => { + const data = {"currentpassword": currentPassword, "newpassword": newPassword, "newpassword2": newPassword2} + const url = globalUrl+'/api/v1/passwordchange'; + fetch(url, { + mode: 'cors', + method: 'POST', + body: JSON.stringify(data), + credentials: 'include', + crossDomain: true, + withCredentials: true, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + setPasswordFormMessage(responseJson["reason"]) + } + }), + ) + .catch(error => { + setPasswordFormMessage("Something went wrong.") + }); + } + + const generateApikey = () => { + fetch(globalUrl+"/api/v1/generateapikey", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } + + return response.json() + }) + .then((responseJson) => { + setUserSettings(responseJson) + }) + .catch(error => { + console.log(error) + }); + } + + const getSettings = () => { + fetch(globalUrl+"/api/v1/getsettings", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } + + return response.json() + }) + .then((responseJson) => { + console.log(responseJson) + setUserSettings(responseJson) + }) + .catch(error => { + console.log(error) + }); + } + + // Gotta be a better way of doing this rofl + const setFields = () => { + if (userInfo.username !== undefined) { + if (userInfo.username.length > 0) { + setUsername(userInfo.username) + } + if (userInfo.firstname.length > 0) { + setFirstname(userInfo.firstname) + } + if (userInfo.lastname.length > 0) { + setLastname(userInfo.lastname) + } + if (userInfo.title.length > 0) { + setTitle(userInfo.title) + } + if (userInfo.companyname.length > 0) { + setCompanyname(userInfo.companyname) + } + if (userInfo.phone.length > 0) { + setPhone(userInfo.phone) + } + if (userInfo.email.length > 0) { + setEmail(userInfo.email) + } + } + } + + // This should "always" have data + useEffect(() => { + if (firstrequest) { + setFirstRequest(false) + getSettings() + } + + if (Object.getOwnPropertyNames(userInfo).length > 0 && (username === "" && email === "")) { + setFields() + } + }) + + // Random names for type & autoComplete. Didn't research :^) + const landingpageData = +
            + +

            APIKEY

            + + + +

            Settings

            +
            + setUsername(e.target.value)} + /> +
            +
            + setFirstname(e.target.value)} + /> + setLastname(e.target.value)} + /> +
            +
            + setTitle(e.target.value)} + /> + setCompanyname(e.target.value)} + /> +
            +
            + setEmail(e.target.value)} + /> + setPhone(e.target.value)} + /> +
            + +

            {formMessage}

            + +

            Password

            +
            + setCurrentPassword(e.target.value)} + /> +
            +
            + setNewPassword(e.target.value)} + /> + setNewPassword2(e.target.value)} + /> +
            + +

            {passwordFormMessage}

            +
            +
            + + const loadedCheck = isLoaded && !firstrequest ? +
            + {landingpageData} +
            + : +
            +
            + + return( +
            + {loadedCheck} +
            + ) +} +export default Settings; diff --git a/frontend/src/SettingsPopup.js b/frontend/src/SettingsPopup.js new file mode 100644 index 00000000..f493b1a5 --- /dev/null +++ b/frontend/src/SettingsPopup.js @@ -0,0 +1,141 @@ +import React, {useState} from 'react'; + +import DialogTitle from '@material-ui/core/DialogTitle'; +import Dialog from '@material-ui/core/Dialog'; +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; +import Divider from '@material-ui/core/Divider'; + + +const SettingsDialog = props => { + const { classes, onClose, settingsOpen, settingsData, globalUrl, isLoggedIn, setIsLoggedIn, ...other } = props; + + const [password1, setPassword1] = useState(""); + const [password2, setPassword2] = useState(""); + const [password3, setPassword3] = useState(""); + + const handleValidateForm = () => { + var passlength = 10 + if (password1 === password2 && password1.length >= passlength && password3.length >= passlength) { + return true + } + + return false + } + + const onChangePass1 = (e) => { + setPassword1(e.target.value) + } + + const onChangePass2 = (e) => { + setPassword2(e.target.value) + } + + const onChangePass3 = (e) => { + setPassword3(e.target.value) + } + + const onSubmitPassReset = () => { + console.log("Should change password") + // Rofl, this can't possibly be typesafe + var data = '{"password1": "'+password1+'", "password2": "'+password2+'", "password3": "'+password3+'"}' + + fetch(globalUrl+"/passwordreset", { + body: data, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson) + if (responseJson.status === true) { + console.log("SUCCESS") + } + }) + .catch(error => { + console.log(error) + }); + } + + //PaperProps={{style: {minWidth: "500px"}} + return( + onClose()} {...other}> + Settings + +
            +

            + Username +

            + {settingsData.username} +
            +
            +

            + ApiKey +

            + +
            + +
            +

            + Change password +

            +
            + +
            +
            + +
            +
            + +
            +
            + + +
            +
            +
            + ); +} + +export default SettingsDialog; diff --git a/frontend/src/Webhooks.js b/frontend/src/Webhooks.js new file mode 100644 index 00000000..33596163 --- /dev/null +++ b/frontend/src/Webhooks.js @@ -0,0 +1,295 @@ +import React, { useEffect} from 'react'; + +import Paper from '@material-ui/core/Paper'; +import Grid from '@material-ui/core/Grid'; +import ButtonBase from '@material-ui/core/ButtonBase'; +import Button from '@material-ui/core/Button'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import TextField from '@material-ui/core/TextField'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; + +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; + +import WebhookImage from './assets/img/webhook.png'; +import KafkaImage from './assets/img/kafka.png'; + +const Webhooks = (props) => { + const { globalUrl, isLoaded } = props; + const validtypes = ["webhook"] + + //const [hooks, setSchedules] = React.useState(hookdata); + const [hooks, setHooks] = React.useState([]); + const [modalOpen, setModalOpen] = React.useState(false); + const [newHookName, setNewHookName] = React.useState(""); + const [newHookDescription, setNewHookDescription] = React.useState(""); + const [newHookType, setNewHookType] = React.useState(""); + const [firstrequest, setFirstrequest] = React.useState(true); + const [, setModalError] = React.useState(""); + + useEffect(() => { + if (firstrequest) { + setFirstrequest(false) + getAvailableHooks() + } + }) + + const newHook = () => { + if (newHookName.length === 0) { + setModalError("Missing name in modal") + return + } + + if (!validtypes.includes(newHookType)) { + setModalError(newHookType + " is not a valid type. Try this: "+validtypes) + } + + fetch(globalUrl+"/api/v1/hooks/new", { + method: "POST", + headers: {"content-type": "application/json"}, + body: JSON.stringify({"name": newHookName, "description": newHookDescription, "type": newHookType}), + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson) + setHooks([]) + }) + .catch(error => { + console.log(error) + }); + } + + const getAvailableHooks = () => { + fetch(globalUrl+"/api/v1/hooks", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + setHooks(responseJson) + }) + .catch(error => { + console.log(error) + // window.location.pathname = "/" + }); + } + + const deleteHook = (id) => { + if (id === undefined) { + return + } + + fetch(globalUrl+"/api/v1/hooks/"+id+"/delete", { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + setHooks([]) + }) + .catch(error => { + console.log(error) + }); + } + + const bodyDivStyle = { + marginLeft: "20px", + marginRight: "20px", + width: "1350px", + minWidth: "1350px", + maxWidth: "1350px", + } + + const hookApp = (app) => { + + // Might be more options, but should be webhook or MQ + const appPicture = app.type === "webhook" ? + webhook + : + MQ + + return( + + + + {appPicture} + + + {splitter} + + + +
            +

            {app.info.name}

            +
            +
            + Desc: {app.info.description} +
            +
            + Status: {app.status} +
            +
            + + {app.action} + +
            +
            +
            + ) + } + + const splitter =
            + + const hrefStyle = { + color: "#385f71", + textDecoration: "none" + } + + // FIXME - add Schedule modal + const hookPaper = (hook) => { + return( +
            + +
            + {hookApp(hook)} +
            + {splitter} +
            + + + + + + + + + + +
            +
            +
            + ) + } + + const modalView = modalOpen ? + {setModalOpen(false)}} + > + Hook configuration + + {setNewHookName(event.target.value)}} + color="primary" + placeholder="Name" + margin="dense" + fullWidth + /> + {setNewHookDescription(event.target.value)}} + color="primary" + placeholder="Description" + margin="dense" + fullWidth + /> + + + + + + + + + : null + + const hookmap = hooks.length > 0 ? +
            + {hooks.map(data => ( + hookPaper(data) + ))} +
            + : +
            + +
            + + const hookView = +
            + + {hookmap} +
            + + const loadedCheck = isLoaded ? +
            + {modalView} + {hookView} +
            + : +
            +
            + + + // Maybe use gridview or something, idk + return ( +
            + {loadedCheck} +
            + ) +} + +export default Webhooks diff --git a/frontend/src/Workflows.js b/frontend/src/Workflows.js new file mode 100644 index 00000000..29c4c6b5 --- /dev/null +++ b/frontend/src/Workflows.js @@ -0,0 +1,831 @@ +import React, { useEffect} from 'react'; +import { useInterval } from 'react-powerhooks'; + +import Grid from '@material-ui/core/Grid'; +import Paper from '@material-ui/core/Paper'; +import Divider from '@material-ui/core/Divider'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import FormControl from '@material-ui/core/FormControl'; +import IconButton from '@material-ui/core/IconButton'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Switch from '@material-ui/core/Switch'; + +//import JSONPretty from 'react-json-pretty'; +//import JSONPrettyMon from 'react-json-pretty/dist/monikai' +import ReactJson from 'react-json-view' + +import { useAlert } from "react-alert"; + +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +const surfaceColor = "#27292D" + +const Workflows = (props) => { + const { globalUrl, isLoggedIn, isLoaded, } = props; + document.title = "Shuffle - Workflows" + + const alert = useAlert() + + const [workflows, setWorkflows] = React.useState([]); + const [selectedWorkflow, setSelectedWorkflow] = React.useState({}); + const [selectedExecution, setSelectedExecution] = React.useState({}); + const [workflowExecutions, setWorkflowExecutions] = React.useState([]); + const [firstrequest, setFirstrequest] = React.useState(true) + const [workflowDone, setWorkflowDone] = React.useState(false) + const [, setTrackingId] = React.useState("") + + const [collapseJson, setCollapseJson] = React.useState(false) + + const [modalOpen, setModalOpen] = React.useState(false); + const [newWorkflowName, setNewWorkflowname] = React.useState(""); + const [newWorkflowDescription, setNewWorkflowDescription] = React.useState(""); + const { start, stop } = useInterval({ + duration: 5000, + startImmediate: false, + callback: () => { + getWorkflowExecution(selectedWorkflow.id) + } + }); + + const getAvailableWorkflows = () => { + fetch(globalUrl+"/api/v1/workflows", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!") + return + } + return response.json() + }) + .then((responseJson) => { + console.log(responseJson) + setSelectedExecution({}) + setWorkflowExecutions([]) + + if (responseJson !== undefined) { + setWorkflows(responseJson) + setWorkflowDone(true) + } else { + if (isLoggedIn) { + alert.error("An error occurred while loading workflows") + } else { + window.location.pathname = "/login" + } + + return + } + + if (responseJson.length > 0){ + setSelectedWorkflow(responseJson[0]) + getWorkflowExecution(responseJson[0].id) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + useEffect(() => { + if (workflows.length <= 0 && firstrequest) { + setFirstrequest(false) + getAvailableWorkflows() + } + }) + + const viewStyle = { + color: "#ffffff", + width: "100%", + display: "flex", + maxWidth: "1768px", + margin: "auto", + maxHeight: "90vh", + } + + const emptyWorkflowStyle = { + paddingTop: "200px", + width: "1024px", + margin: "auto", + } + + const boxStyle = { + padding: "20px 20px 20px 20px", + width: "100%", + height: "250px", + color: "white", + backgroundColor: surfaceColor, + display: "flex", + flexDirection: "column", + } + + + const scrollStyle = { + marginTop: "10px", + overflow: "scroll", + height: "90%", + overflowX: "auto", + overflowY: "auto", + } + + const paperAppStyle = { + minHeight: "100px", + maxHeight: "100px", + minWidth: "100%", + maxWidth: "100%", + marginTop: "5px", + color: "white", + backgroundColor: surfaceColor, + cursor: "pointer", + display: "flex", + } + + const getWorkflowExecution = (id) => { + fetch(globalUrl+"/api/v1/workflows/"+id+"/executions", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } + + return response.json() + }) + .then((responseJson) => { + setWorkflowExecutions(responseJson) + if (responseJson.length > 0 && Object.getOwnPropertyNames(selectedExecution).length === 0) { + setSelectedExecution(responseJson[0]) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const abortExecution = (workflowid, executionid) => { + alert.success("Aborting execution") + fetch(globalUrl+"/api/v1/workflows/"+workflowid+"/executions/"+executionid+"/abort", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } + getWorkflowExecution(workflowid) + + return response.json() + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const executeWorkflow = (id) => { + alert.show("Executing workflow "+id) + setTrackingId(id) + fetch(globalUrl+"/api/v1/workflows/"+id+"/execute", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error(responseJson.reason) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + + if (id === selectedWorkflow.id) { + sleep(2000).then(() => { + stop() + start() + }) + } + } + + function sleep (time) { + return new Promise((resolve) => setTimeout(resolve, time)); + } + + const exportWorkflow = (data) => { + console.log("export") + let dataStr = JSON.stringify(data) + let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); + let exportFileDefaultName = data.name+'.json'; + + let linkElement = document.createElement('a'); + linkElement.setAttribute('href', dataUri); + linkElement.setAttribute('download', exportFileDefaultName); + linkElement.click(); + } + + const copyWorkflow = (data) => { + alert.success("Copying workflow "+data.name) + data.id = "" + data.name = data.name+"_copy" + + fetch(globalUrl+"/api/v1/workflows", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!") + return + } + return response.json() + }) + .then((responseJson) => { + getAvailableWorkflows() + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + + const deleteWorkflow = (id) => { + alert.success("Deleted workflow "+id) + fetch(globalUrl+"/api/v1/workflows/"+id, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for setting workflows :O!") + } + + return response.json() + }) + .then((responseJson) => { + getAvailableWorkflows() + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + // dropdown with copy etc I guess + const WorkflowPaper = (props) => { + const { data } = props; + const [open, setOpen] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + + var boxWidth = "2px" + if (selectedWorkflow.id === data.id) { + boxWidth = "4px" + } + + var boxColor = "orange" + if (data.is_valid) { + boxColor = "green" + } + + const menuClick = (event) => { + setOpen(!open) + setAnchorEl(event.currentTarget); + } + + return ( + { + if (selectedWorkflow.id !== data.id) { + setSelectedWorkflow(data) + getWorkflowExecution(data.id) + } + }}> +
            +
            + + + +
            +

            {data.name}

            +
            +
            + + + + { + setOpen(false) + setAnchorEl(null) + }} + > + + { + copyWorkflow(data) + setOpen(false) + }} key={"copy"}>{"Copy"} + { + exportWorkflow(data) + setOpen(false) + }} key={"export"}>{"Export"} + { + deleteWorkflow(data.id) + setOpen(false) + }} key={"delete"}>{"Delete"} + + +
            +
            +
            + + + + + + +
            +
            +
            +
            + ) + } + + const executionPaper = (data) => { + var boxWidth = "2px" + if (selectedExecution.execution_id === data.execution_id) { + boxWidth = "4px" + } + + var boxColor = "orange" + if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){ + boxColor = "red" + } else if (data.status === "FINISHED") { + boxColor = "green" + } + + var t = new Date(data.started_at*1000) + if (data.workflow.actions === null || data.workflow.actions === undefined ) { + return null + } + + var actions = data.workflow.actions.length + if (data.results !== null) { + var results = data.results.length + } + return ( + { + setSelectedExecution(data) + }}> +
            + + + +
            +

            Status: {data.status}

            + Actions: {results}/{actions} +
            +
            + +
            +
            +
            + + Started: {t.toISOString()} + +
            +
            +
            + + ) + } + + const dividerColor = "rgb(225, 228, 232)" + + const resultPaperAppStyle = { + minHeight: "100px", + minWidth: "100%", + overflow: "hidden", + maxWidth: "100%", + marginTop: "5px", + color: "white", + backgroundColor: surfaceColor, + cursor: "pointer", + display: "flex", + } + + function replaceAll(string, search, replace) { + return string.split(search).join(replace); + } + + const resultsPaper = (data) => { + var boxWidth = "2px" + var boxColor = "orange" + if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){ + boxColor = "red" + } else if (data.status === "FINISHED" || data.status === "SUCCESS") { + boxColor = "green" + } else if (data.status === "SKIPPED" || data.status === "EXECUTING") { + boxColor = "yellow" + } else { + boxColor = "green" + } + + var t = new Date(data.started_at*1000) + var showResult = data.result.trim() + if (showResult.startsWith("{") && showResult.endsWith("}")) { + //showResult = + + showResult = replaceAll(showResult, " None", " \"None\""); + console.log(showResult) + showResult = + } else { + // FIXME - have everything parsed as json, either just for frontend + // or in the backend + /* + const newdata = {"result": data.result} + showResult = + */ + } + + console.log(data) + return ( + {}}> +
            +
            + + + +

            Status: {data.status}

            +
            + + App: {data.action.app_name}, Version: {data.action.app_version} + + + Action: {data.action.name}, Environment: {data.action.environment} + +
            + + Started: {t.toISOString()} + +
            + +
            + + {showResult} + +
            +
            +
            +
            + ) + } + + const resultsHandler = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? +
            + {selectedExecution.results.sort((a, b) => a.started_at - b.started_at).map(data => { + return ( + resultsPaper(data) + ) + })} +
            + : +
            + No results yet +
            + + const resultsLength = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? selectedExecution.results.length : 0 + + const ExecutionDetails = () => { + var starttime = new Date(selectedExecution.started_at*1000) + var endtime = new Date(selectedExecution.started_at*1000) + console.log(selectedExecution) + + const arg = selectedExecution.execution_argument !== undefined && selectedExecution.execution_argument.length > 0 ? +
            + Argument: {selectedExecution.execution_argument} +
            + : null + /* +
            + ID: {selectedExecution.execution_id} +
            + */ + if (Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.workflow.actions !== null) { + return ( +
            +
            + Actions: {selectedExecution.workflow.actions.length} +
            +
            + Results: {resultsLength} +
            +
            + Status: {selectedExecution.status} +
            +
            + Starttime: {starttime.toISOString()} +
            +
            + Finished: {endtime.toISOString()} +
            +
            + Result: {selectedExecution.result} +
            +
            + Last node: {selectedExecution.last_node} +
            + {arg} + + {resultsHandler} +
            + ) + } + return ( +

            + There are no executiondetails yet. Click "execute" to run your first one. +

            + ) + } + + const ExecutionsView = () => { + if (workflowExecutions.length > 0) { + const sortedWorkflows = workflowExecutions.sort((a, b) => a.started_at - b.started_at).reverse() + + return ( +
            + {sortedWorkflows.map(data => { + return ( + executionPaper(data) + ) + })} +
            + ) + } + return ( +

            + There are no executions for this workflow yet +

            + ) + } + + const setNewWorkflow = () => { + if (newWorkflowName.length === 0) { + return + } + var workflowdata = { + "name": newWorkflowName, + "description": newWorkflowDescription, + } + + fetch(globalUrl+"/api/v1/workflows", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(workflowdata), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!") + return + } + return response.json() + }) + .then((responseJson) => { + window.location.pathname = "/workflows/"+responseJson["id"] + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const modalView = modalOpen ? + {setModalOpen(false)}} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + }, + }} + > + +
            New workflow
            + + setNewWorkflowname(event.target.value)} + InputProps={{ + style:{ + color: "white", + }, + }} + color="primary" + placeholder="Name" + margin="dense" + fullWidth + /> + setNewWorkflowDescription(event.target.value)} + InputProps={{ + style:{ + color: "white", + }, + }} + color="primary" + placeholder="Description" + margin="dense" + fullWidth + /> + + + + + + +
            +
            + : null + + + const viewSize = { + workflowView: 1, + executionsView: 1, + executionResults: 2, + } + + const workflowViewStyle = { + flex: viewSize.workflowView, + marginLeft: "10px", + marginRight: "10px", + } + + if (viewSize.workflowView === 0) { + workflowViewStyle.display = "none" + } + + const workflowView = workflows.length > 0 ? +
            +
            +
            +
            +

            Workflows

            +
            +
            +
            + +
            +
            + +
            +
            +
            + + +
            + {workflows.map(data => { + return ( + + ) + })} +
            +
            +
            +
            +
            +

            Executions

            +
            +
            + +
            +
            + +
            + +
            +
            +
            +
            +
            +

            Execution Timeline

            +
            +
            + Collapse results
            + control={ {setCollapseJson(!collapseJson)}} />} + /> +
            +
            + +
            + +
            +
            +
            + : +
            + +
            +

            Welcome to Shuffle!

            +
            +
            +

            + Shuffle is a flexible, easy to use, automation framework allowing users to integrate their services and devices to reduce the amount of manual labor required for those tasks. Click here for more information. +

            +
            +
            + If you want to jump straight into it, click the following button to create your first workflow: +
            +
            + +
            +
            +
            + + const loadedCheck = isLoaded && isLoggedIn && workflowDone ? +
            + {workflowView} + {modalView} +
            + : +
            +
            + + + // Maybe use gridview or something, idk + return ( +
            + {loadedCheck} +
            + ) +} + +export default Workflows diff --git a/frontend/src/appdata.js b/frontend/src/appdata.js new file mode 100644 index 00000000..82772dbb --- /dev/null +++ b/frontend/src/appdata.js @@ -0,0 +1,3 @@ +const Data = [{"name":"hive","is_valid":false,"id":"02828a90-658b-41c4-8726-e31da7e02fe9","id_":"02828a90-658b-41c4-8726-e31da7e02fe9","link":"","app_version":"1.0.0","description":"The Hive app allows for walkoff to generate or close cases in TheHive","environment":"cloud","contact_info":{"name":"FORGE Cyber","url":"https://github.com/"},"actions":[{"description":"creates a hive case","id_":"eb84008d-b0e0-41c6-a9b3-d7a51866bcd4","name":"create_case","node_type":"ACTION","environment":"cloud","parameters":[{"description":"log data to generate custom fields","id_":"b484aba8-0787-42e9-a97a-882a59e02f8f","name":"log_data","required":true,"schema":{"type":"string"}},{"description":"URL of TheHive","id_":"deb0a68d-479a-4472-b547-1eb82fb3888d","name":"url","required":true,"schema":{"type":"string"}},{"description":"API key to access TheHive","id_":"5116b359-c8da-41cf-80a4-4292a1a3c90b","name":"api_key","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"b040890c-5462-458b-8f54-054e6ce2a146","schema":{"type":"object"}}},{"description":"Updates a case data as well as severity","id_":"b7f64744-04e3-4166-990e-70106edc88cf","name":"update_case","node_type":"ACTION","environment":"cloud","parameters":[{"description":"json from trigger","id_":"ac37d451-ff45-4740-b5a7-de389afcb2b1","name":"input","required":true,"schema":{"type":"object"}},{"description":"id for case","id_":"69613390-78c9-4b5d-aa71-b6c4a4750f8f","name":"id","required":true,"schema":{"type":"string"}},{"description":"severity of change","id_":"b04523b0-e74e-44a3-9c51-055c1e53167a","name":"severity","required":true,"schema":{"type":"integer"}},{"description":"URL of TheHive","id_":"e415c1f9-d502-4a5d-9c8a-8cdefe0ebf88","name":"url","required":true,"schema":{"type":"string"}},{"description":"API key to access TheHive","id_":"2aa8e834-b889-467a-9172-373930b013cf","name":"api_key","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Closes a case in TheHive","id_":"240f26ef-5045-4711-94a5-af7a947ac5a4","name":"close_case","node_type":"ACTION","environment":"cloud","parameters":[{"description":"ID of case to close","id_":"71eb8b33-56dc-416f-8ae9-6bee56321f9b","name":"case_id","required":true,"schema":{"type":"string"}},{"description":"URL of TheHive","id_":"a3bbb859-d6fb-45ec-a08f-8895c8f8b0d5","name":"url","required":true,"schema":{"type":"string"}},{"description":"API key to access TheHive","id_":"660ba026-886f-413c-809a-8a48961d9228","name":"api_key","required":true,"schema":{"type":"string"}},{"description":"Resolution status of the case to close.","id_":"6b9604d8-7358-4362-b0f4-0d570a366724","name":"resolution_status","required":true,"schema":{"type":"string"}},{"description":"Impact status of the case to close. The impact status is only captured when resolution status is TruePositive","id_":"11651f4f-4521-4f1b-a2c7-66078f9be886","name":"impact_status","required":true,"schema":{"type":"string"}},{"description":"Tags to add to the case once closed. Comma separated string.","id_":"01a0951f-92f8-485a-b8a2-0664a7e03862","name":"tags","required":true,"schema":{"type":"string"}},{"description":"Explanation of why the case was closed.","id_":"fa67a17b-b6e0-42e6-8988-03a16484c4b0","name":"summary","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"ebba5506-32c8-4bd2-a004-f32c8ec9cdf6","schema":{"type":"object"}}}]},{"name":"walk_off","is_valid":false,"id":"1f52e17e-c11b-4cf4-abdb-a216211a0d27","id_":"1f52e17e-c11b-4cf4-abdb-a216211a0d27","link":"","app_version":"1.0.0","description":"An example of a Walkoff App specification","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Connect to Walkoff","id_":"a9926e7f-18f9-4b93-b805-27cdb5a232ff","name":"connect","node_type":"ACTION","environment":"cloud","parameters":[{"description":"Timeout on the request (in seconds)","id_":"300136af-3c30-4e2b-babe-272f845942a0","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"username","id_":"896de900-9ca1-4108-a034-016e9c6ffe54","name":"username","required":true,"schema":{"type":"string"}},{"description":"password","id_":"e75e75d4-8a6b-4c24-8ffd-61b435683edb","name":"password","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"de121927-243f-4f09-9331-c48ca25e85d1","schema":{"type":"string"}}},{"description":"Disconnect from Walkoff","id_":"acd81f9b-353c-4749-a33a-2091da2d307c","name":"disconnect","node_type":"ACTION","environment":"cloud","parameters":[{"description":"Timeout on the request (in seconds)","id_":"76819a03-5272-4421-99cd-1e53f95963f0","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"refresh token","id_":"b583836e-eff4-4e7c-8430-286bfa81e43f","name":"refresh_token","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"d6cc2470-2dc9-43f9-a2f9-fb123c590426","schema":{"type":"string"}}},{"description":"Gets a list of all the users loaded on the system","id_":"6610a111-687e-45f5-a60d-26048594cdb1","name":"get_all_users","node_type":"ACTION","environment":"cloud","parameters":[{"description":"Timeout on the request (in seconds)","id_":"89b9a459-f756-40d8-b2c0-1a7c57987fee","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"Access Token","id_":"496c7956-964b-4977-88ed-27db81a94cfe","name":"access_token","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"642ecea4-e747-4dfe-9ca7-4f290847c0ed","schema":{"type":"string"}}},{"description":"Gets a list of all the workflows loaded on the system","id_":"7cb34d71-2619-40b1-a565-17274fea924e","name":"get_all_workflows","node_type":"ACTION","environment":"cloud","parameters":[{"description":"Timeout on the request (in seconds)","id_":"e4553287-018d-465a-8599-a605fdb5fb71","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"Access Token","id_":"f9193b36-cc5b-4a5f-babf-7df321837f57","name":"access_token","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"684de06e-b048-4332-88c5-9555ca41e2a1","schema":{"type":"string"}}},{"description":"Executes a workflow","id_":"9aaf7af4-fa4b-45d1-ac99-5722f0487aef","name":"execute_workflow","node_type":"ACTION","environment":"cloud","parameters":[{"description":"ID of the workflow","id_":"2f9c0aaf-82ea-4fab-9c26-99268cc9735e","name":"workflow_id","required":true,"schema":{"type":"string"}},{"description":"Timeout on the request (in seconds)","id_":"35fc2b33-0e03-4b48-9e1c-73aa8a0b4ad7","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"Access Token","id_":"463425b6-ead9-4783-ad40-e04e6d30bc6e","name":"access_token","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"fda59148-a2b1-4180-a34d-8beef5d68929","schema":{"type":"string"}}},{"description":"Log out of Walkoff","id_":"160693dc-38c4-4a7c-a36b-bd529e69118b","name":"shutdown","node_type":"ACTION","environment":"cloud","parameters":[{"description":"refresh Token","id_":"a24a2395-7696-48cc-a1b8-b71c881c1180","name":"refresh_token","required":true,"schema":{"type":"string"}},{"description":"Timeout on the request (in seconds)","id_":"4b0b1d88-203b-4ba3-956f-822def41d0cb","name":"timeout","required":true,"schema":{"type":"number"}}],"returns":{"description":"","id_":"7836185e-786b-4d13-8c9b-b29d89d61155","schema":{"type":"string"}}}]},{"name":"ip_addr_utils","is_valid":false,"id":"4f00d85c-1fc8-4db2-a1d2-fab1eff5d02e","id_":"4f00d85c-1fc8-4db2-a1d2-fab1eff5d02e","link":"","app_version":"1.0.0","description":"An IP address app that will allow users to specify ip addresses and will format them correctly","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Sets the timestamp fro which scipt outputs will be filed under","id_":"5404d09b-08d8-469b-9087-e6b22892188b","name":"set_timestamp","node_type":"ACTION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"8945dbff-0920-4b04-8682-e9086bcae5a4","schema":{"type":"string"}}},{"description":"Converts ip address from CIDR notation to individual IP's for easier integration with other apps.","id_":"d6c39b8b-63b7-4198-82d2-b8a4f3ed95f2","name":"cidr_to_array","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"14a43bde-2a88-4cf7-987c-e7bb62c202c0","name":"ip_array","required":true,"schema":{"type":"array"}}],"returns":{"description":"","id_":"22c7aac2-321e-451a-a9f4-61b9c6656cb7","schema":{"type":"array"}}}]},{"name":"power_shell","is_valid":false,"id":"5b570c0b-4f31-4f2e-93ac-da39b78502bb","id_":"5b570c0b-4f31-4f2e-93ac-da39b78502bb","link":"","app_version":"1.0.0","description":"A power shell app that can run commands on a remote host.","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Sets the timestamp fro which scipt outputs will be filed under","id_":"8f0cb2b7-1cd1-4284-8790-f8234ec9a1f9","name":"set_timestamp","node_type":"ACTION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"b58197d0-42fc-40bc-a40f-b1bea1c44b01","schema":{"type":"string"}}},{"description":"Executes powershell scripts on remote devices (Scripts located in \"scripts\" directory within app).","id_":"cb1fb39a-9cd9-485d-8c2a-e4fe138035e8","name":"exec_command_prompt_from_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"13fd6552-5b2b-4081-89f9-8e9dad5bdea2","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"filename in which scripts will be located","id_":"7a51a4e2-2c7d-4c7f-9aca-dc2d1316f2db","name":"local_file_name","required":true,"schema":{"type":"string"}},{"description":"Username for remote host","id_":"6ea5d033-308b-453e-84ff-416011b459d7","name":"username","required":true,"schema":{"type":"string"}},{"description":"Password for remote host user","id_":"b71eb882-4b22-456e-980d-84cbba55216b","name":"password","required":true,"schema":{"type":"string"}},{"description":"transport type","id_":"78c824c8-97f9-46ea-ba2a-ada98127555b","name":"transport","required":true,"schema":{"type":"string"}},{"description":"whether server certificate should be validated","id_":"ca847f0c-26de-4289-a13b-0aca5ac55d1c","name":"server_cert_validation","required":true,"schema":{"type":"boolean"}},{"description":"Will encrypt the WinRM messages if set to True and \"transport auth\" supports message encryption","id_":"2747d198-2fd2-47a0-8854-4a3dfc5baa77","name":"message_encryption","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Executes the powershell command on remote devices.","id_":"70717155-5377-40d1-989b-3db7b90c5975","name":"exec_command_prompt","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"b151934c-dea4-4ba9-a993-1e527093b6e4","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"list of commands to execute","id_":"fa6386e4-0e45-453e-8510-6d4affdb7e31","name":"commands","required":true,"schema":{"type":"array"}},{"description":"Username for remote host","id_":"a2c56d5d-48eb-455f-8e4b-155855ac4fa4","name":"username","required":true,"schema":{"type":"string"}},{"description":"Password for remote host user","id_":"66b4b42e-bd81-4180-8ff8-84fae5e590f2","name":"password","required":true,"schema":{"type":"string"}},{"description":"transport type","id_":"d0a7b275-8a0f-4421-9b75-7eb3dbc6ff38","name":"transport","required":true,"schema":{"type":"string"}},{"description":"whether server certificate should be validated","id_":"5df9e652-9e3c-437d-b81f-92e8e0702312","name":"server_cert_validation","required":true,"schema":{"type":"boolean"}},{"description":"Will encrypt the WinRM messages if set to True and the transport auth supports message encryption","id_":"64f64521-5ded-4f0b-8590-f9c6395cd98f","name":"message_encryption","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Executes the powershell script on remote devices based on script file passed in.","id_":"d0fabf12-9db6-4a0e-8785-5248b1035051","name":"exec_powershell_script_from_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"09299ae2-38a0-4fb3-b064-fd3597424d67","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"type of shell you want to execute","id_":"3d076cba-93d2-4f58-abfd-1475b354444d","name":"shell_type","required":true,"schema":{"type":"string"}},{"description":"filename in which scripts will be located","id_":"b82484c2-861c-4193-925a-e23bd882938c","name":"local_file_name","required":true,"schema":{"type":"string"}},{"description":"Username for remote host","id_":"79f42b59-2218-4d41-b13e-154b2ed89105","name":"username","required":true,"schema":{"type":"string"}},{"description":"Password for remote host user","id_":"35ac706c-d375-45ab-add5-40d9b846100c","name":"password","required":true,"schema":{"type":"string"}},{"description":"transport type","id_":"aa9a2b80-7836-4810-82fa-3c270d9e18f6","name":"transport","required":true,"schema":{"type":"string"}},{"description":"whether server certificate should be validated","id_":"39cbd4d7-a0cf-4b9c-99bc-5b7c46cfc8df","name":"server_cert_validation","required":true,"schema":{"type":"boolean"}},{"description":"Will encrypt the WinRM messages if set to True and the transport auth supports message encryption","id_":"c7c161e7-c5db-49b8-a7de-b10a203c551b","name":"message_encryption","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Executes the powershell command/script on remote devices.","id_":"98c0cd0f-a05a-4b26-809c-5dbf3ecb791f","name":"exec_powershell_script","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"9e69e9b0-4639-43dd-9b4c-e4cb710ce63c","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"type of shell you want to execute","id_":"6dce7ac6-b0ac-4d48-a79a-e9db795d751b","name":"shell_type","required":true,"schema":{"type":"string"}},{"description":"script in the form of array commands","id_":"700bb930-582e-4cbe-85e9-f5656461ecd5","name":"arguments","required":true,"schema":{"type":"array"}},{"description":"Username for remote host","id_":"22f0f58f-7e3c-43f6-afca-8e6800a6052e","name":"username","required":true,"schema":{"type":"string"}},{"description":"Password for remote host user","id_":"5b53cb8d-eaa4-4d31-8142-31151406100c","name":"password","required":true,"schema":{"type":"string"}},{"description":"transport type","id_":"635e350e-7a4d-4b94-aa0e-3e0a57ba5577","name":"transport","required":true,"schema":{"type":"string"}},{"description":"whether server certificate should be validated","id_":"07ec9b94-f626-42e7-a5a8-6e9b68b7bf7d","name":"server_cert_validation","required":true,"schema":{"type":"boolean"}},{"description":"Will encrypt the WinRM messages if set to True and the transport auth supports message encryption","id_":"1ec509eb-0444-4f08-971e-1da23983d588","name":"message_encryption","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"762934c3-8de3-4261-850f-38b12effc90a","schema":{"type":"string"}}}]},{"name":"ssh","is_valid":false,"id":"68bfeb11-4e8f-4d46-9cf5-5f681be05858","id_":"68bfeb11-4e8f-4d46-9cf5-5f681be05858","link":"","app_version":"1.0.0","description":"Executes ssh shell commands via SSH","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Execute command on remote server with SSH client","id_":"2ec132a9-1c32-42eb-8260-bcfac47901fb","name":"exec_command","node_type":"ACTION","environment":"cloud","parameters":[{"description":"hosts or hostnames of the remote server","id_":"d94d2878-9208-445a-a9b9-852d05ef5b0c","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"port number","id_":"022cd027-b0f2-49ee-a512-da8a07ed2bfc","name":"port","required":true,"schema":{"type":"integer"}},{"description":"json array of arguments","id_":"2d8dff25-dbc9-4656-9de3-73280015eaaf","name":"args","required":true,"schema":{"type":"array"}},{"description":"username to login with","id_":"89145276-3837-4e48-b9ab-0aa76500ba72","name":"username","required":true,"schema":{"type":"string"}},{"description":"password to login with","id_":"564fb3d7-89ff-41df-b3ed-6ba23d7b3f3e","name":"password","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"1395434e-52c2-4428-a1ac-966a315af6e0","schema":{"type":"string"}}},{"description":"Run a local bash command","id_":"baa63db8-c8d6-4dea-b774-81fe4b8dddfb","name":"exec_local_command","node_type":"ACTION","environment":"cloud","parameters":[{"description":"source path of the file to copy","id_":"fe99ea27-1176-4ca5-8de7-a5754f429784","name":"command","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"ac58f084-4234-4380-b438-a2c9239344f2","schema":{"type":"string"}}},{"description":"Copy remote file to remote host using sftp","id_":"87f34206-3b72-4d01-b64a-9bacdf822526","name":"sftp_copy","node_type":"ACTION","environment":"cloud","parameters":[{"description":"source path of the file to copy","id_":"43f07eb3-7e1b-4ea3-8684-862111011394","name":"src_path","required":true,"schema":{"type":"string"}},{"description":"remote path of the file destination","id_":"1a53170b-15b7-4cf8-9dff-9309d56bdacf","name":"dest_path","required":true,"schema":{"type":"string"}},{"description":"host or hostname of the remote server","id_":"4d78b34e-09ae-48c3-9eeb-f5042903bc04","name":"src_host","required":true,"schema":{"type":"string"}},{"description":"port number","id_":"29ddd615-05d3-4531-9869-23ca7b366133","name":"src_port","required":true,"schema":{"type":"integer"}},{"description":"username to login with","id_":"f9db153e-0c9f-4808-9814-9277f56b2f47","name":"src_username","required":true,"schema":{"type":"string"}},{"description":"password to login with","id_":"a6337fa3-0704-49ca-8920-a49229bed77e","name":"src_password","required":true,"schema":{"type":"string"}},{"description":"host or hostname of the remote server","id_":"04399d82-0ef6-4bdc-b563-344df67219a2","name":"dest_host","required":true,"schema":{"type":"string"}},{"description":"port number","id_":"262df217-34fa-4165-a753-e71488099729","name":"dest_port","required":true,"schema":{"type":"integer"}},{"description":"username to login with","id_":"4fde41af-112b-4b33-88dc-ce3219b444f3","name":"dest_username","required":true,"schema":{"type":"string"}},{"description":"password to login with","id_":"d9382c2a-b64c-4ad8-b7ca-488aa8e440fb","name":"dest_password","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"9b484424-f7d7-42ef-b553-f40ec1805806","schema":{"type":"string"}}},{"description":"runs the specified shell script on the remote server(s)","id_":"bb32494e-b808-4d9b-8f9a-f366a9bfd21e","name":"run_shell_script_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"local path of the shell script to run","id_":"41df393d-3435-4e0c-9c7e-dd6531fdc0a6","name":"local_file_name","required":true,"schema":{"type":"string"}},{"description":"hosts of the remote server","id_":"f2bd124e-1dbb-4b87-a12f-ef7f25d6eb2d","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"port number","id_":"eb2a8834-ea05-4d54-8900-e723e2056132","name":"port","required":true,"schema":{"type":"integer"}},{"description":"username to login with","id_":"ba961ec7-2200-420e-9142-7b237c046809","name":"username","required":true,"schema":{"type":"string"}},{"description":"password to login with","id_":"65b6d98a-5b23-4d5d-ae57-022057038bae","name":"password","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"94e55d3b-ef12-4114-8c60-07e3e6417df8","schema":{"type":"string"}}}]},{"name":"Builtin","is_valid":false,"id":"e34f0c67-83e9-443a-b9e7-7b152b4b16f6","id_":"e34f0c67-83e9-443a-b9e7-7b152b4b16f6","link":"","app_version":"1.0.0","description":"Walkoff built-in functions useful in workflow development.","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Takes input from an API Call and triggers the rest of the workflow to beign executing again.","id_":"c3e959c6-2c97-48bb-bf00-4082bf812a5d","name":"Trigger","node_type":"TRIGGER","environment":"cloud","parameters":[],"returns":{"description":"","id_":"6265f75b-e9a7-4d0d-b0e8-7c4da5ac5e65","schema":{"type":"string"}}},{"description":"Takes input from a previous action and chooses which branch to take according to your logic.","id_":"8b260c29-dd20-405d-b1a6-29d2e67d43e5","name":"Condition","node_type":"CONDITION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"a4c44cdd-fd3b-46ef-b151-51c1f686fa40","schema":{"type":"string"}}}]},{"name":"hello_world","is_valid":false,"id":"e66d38eb-19b4-4801-abf2-38248b3b2786","id_":"e66d38eb-19b4-4801-abf2-38248b3b2786","link":"","app_version":"1.0.0","description":"An example of a Walkoff App specification","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Returns Hello World from the hostname the action is run on","id_":"ac0e2250-20b1-46a3-93ec-1718f9973cc4","name":"hello_world","node_type":"ACTION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"66f4cd0e-7a97-4185-a167-3956dcf3f627","schema":{"type":"string"}}},{"description":"Returns a random float between 0.0 and 1.0","id_":"3817fd8b-1370-4ce8-b874-a526e3c204de","name":"random_number","node_type":"ACTION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"c788404e-c637-4704-bf9e-1b861060e97e","schema":{"type":"number"}}},{"description":"returns the outputs from the trigger data if it's in Json format.","id_":"7fae3591-ab32-402d-8dc4-26e8b802c661","name":"repeat_trigger_as_json","node_type":"ACTION","environment":"cloud","parameters":[{"description":"message to hold output from","id_":"0f0a76ba-b590-4150-ae03-02d0e797f7e4","name":"call","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"1ab56d07-0ce1-4fe5-be01-d70337d9d589","schema":{"type":"object"}}},{"description":"Repeats the call parameter","id_":"23984f43-7593-4c7b-81b6-77852e498add","name":"repeat_back_to_me","node_type":"ACTION","environment":"cloud","parameters":[{"description":"message to repeat","id_":"bedf6d97-958c-4acd-b8a9-a72e19c5d54b","name":"call","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"78708871-00b5-49e6-a4b4-d5e265d89f36","schema":{"type":"string"}}},{"description":"Increments the number parameter by 1","id_":"86624c25-bb66-4fc3-b66f-dc8d394f09bd","name":"return_plus_one","node_type":"ACTION","environment":"cloud","parameters":[{"description":"number to increment","id_":"45af1005-e7f0-46c3-ac09-28748f022a17","name":"number","required":true,"schema":{"type":"number"}}],"returns":{"description":"","id_":"c84fc303-4d27-4091-9500-cb8cd83d6f05","schema":{"type":"number"}}},{"description":"Pause execution by the seconds parameter","id_":"bf787802-6039-4010-bc98-2d6d1dbdce21","name":"pause","node_type":"ACTION","environment":"cloud","parameters":[{"description":"seconds to pause for","id_":"9adcc6f4-0559-47c2-9496-02c8fb3fd58a","name":"seconds","required":true,"schema":{"type":"number"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Echo the data parameter","id_":"0a9fec6a-5bf5-4266-a264-dae7fe52c0d5","name":"echo_array","node_type":"ACTION","environment":"cloud","parameters":[{"description":"array to echo","id_":"8ee260ee-f181-40b3-9d10-f1821f03228c","name":"data","required":true,"schema":{"type":"array"}}],"returns":{"description":"","id_":"bde96402-3d66-43d8-a418-8c0859cb4d01","schema":{"type":"array"}}},{"description":"echos the given JSON object","id_":"e0927a9c-0e6c-429a-965b-40d0804c13f3","name":"echo_json","node_type":"ACTION","environment":"cloud","parameters":[{"description":"The data to echo","id_":"ba719062-90a0-42dd-8ef3-eaa304a57667","name":"data","required":true,"schema":{"type":"object"}}],"returns":{"description":"","id_":"fd4f9c20-e4a0-43df-9a14-7bf0046f391f","schema":{"type":"object"}}}]},{"name":"nmap","is_valid":false,"id":"fc3d231c-9437-4ba9-8fe8-8ba199626197","id_":"fc3d231c-9437-4ba9-8fe8-8ba199626197","link":"","app_version":"1.0.0","description":"A simple app to interact with map","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"looks into xml nmap for osfamily","id_":"09840ebb-f72a-43e4-b49e-ab5a10d56d96","name":"parse_xml_for_windows_from_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output as xml filename","id_":"2bfccd6f-bd75-4ad0-b8df-5ff74aa64761","name":"nmap_file","required":true,"schema":{"type":"string"}}],"returns":{"description":"os","id_":"9c6900a5-7e20-4033-9f3e-61ce8b95ab5f","schema":{"type":"array"}}},{"description":"transforms xml nmap results into json","id_":"150e28cb-3333-49ad-859c-b61e2b758ff7","name":"xml_to_json","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output either as xml filename or string","id_":"5fb18897-fe0e-473f-a83d-3c11f3d8b2a1","name":"nmap_out","required":true,"schema":{"type":"string"}},{"description":"whether the previous parameter is a filename or string","id_":"f87d9bd6-e85a-4f6d-8300-902e3ba29347","name":"is_file","required":true,"schema":{"type":"boolean"}}],"returns":{"description":"xml string on nmap output","id_":"1cd901cc-2101-4df1-9759-bf74fcdb7b9b","schema":{"type":"string"}}},{"description":"retrieves the hosts and ports from an nmap scan for use with OpenVAS","id_":"f743dfa4-2b06-4d1b-bbc7-55d34b3ce499","name":"ports_and_hosts_from_json","node_type":"ACTION","environment":"cloud","parameters":[{"description":"json string or filename","id_":"ca686f90-c947-4473-9d39-abc8bc4895fd","name":"nmap_json","required":true,"schema":{"type":"string"}},{"description":"whether or not first input is a filename or not","id_":"e605313e-f9c5-4335-8ce7-d46abd422e68","name":"is_file","required":true,"schema":{"type":"boolean"}}],"returns":{"description":"","id_":"040f32fc-d020-469f-9f4c-47928b076688","schema":{"type":"string"}}},{"description":"Runs an nmap scan, returns results as string or filename","id_":"ada798da-fff9-4ccf-85f2-24e2edb7722a","name":"run_scan","node_type":"ACTION","environment":"cloud","parameters":[{"description":"The target(s) to scan, comma separated values, CIDR supported","id_":"72704c22-a9c9-457f-ae33-e7c17e758e7d","name":"targets","required":true,"schema":{"type":"array"}},{"description":"see nmap manpage -- some options require root","id_":"51ea8e70-adae-47a1-9241-87674b4712c1","name":"options","required":true,"schema":{"type":"string"}}],"returns":{"description":"xml string on nmap output","id_":"9c18b2a2-c023-4102-bd46-ddeb31a5430c","schema":{"type":"array"}}},{"description":"Gets the list of active hosts on a network from an nmap scan","id_":"48b4814f-47ea-4c66-a5db-cacc9cd305b6","name":"get_hosts_from_scan","node_type":"ACTION","environment":"cloud","parameters":[{"description":"The target (or targets in CIDR notation) to scan","id_":"e48d60f7-4590-4dd2-98ce-cd112ab9df2f","name":"targets","required":true,"schema":{"type":"array"}},{"description":"","id_":"2eb29670-701e-4622-99f2-80e4b51cb06e","name":"options","required":true,"schema":{"type":"string"}}],"returns":{"description":"xml string on nmap output","id_":"1f710e7d-ef6d-4a28-8ceb-8cbc49f6e197","schema":{"type":"string"}}},{"description":"looks into xml nmap for osfamily to match Linux","id_":"f5425571-2de1-4a84-9221-3829d118617a","name":"parse_xml_for_linux","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output as xml array","id_":"e2e7d0c7-13e8-49f9-85f2-da8bfc73308e","name":"nmap_arr","required":true,"schema":{"type":"array"}}],"returns":{"description":"os","id_":"785f146f-095d-4008-a068-0c3146fdf4f0","schema":{"type":"array"}}},{"description":"looks into xml nmap for osfamily to match Windows","id_":"cf35f64c-43b4-4f23-ae75-70d912f4c1d5","name":"parse_xml_for_windows","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output as xml array","id_":"ef1ce558-2d2a-4d30-8e96-c4ad4c18fae9","name":"nmap_arr","required":true,"schema":{"type":"array"}}],"returns":{"description":"os","id_":"6d21bd12-c076-459e-970d-cd2f39545efb","schema":{"type":"array"}}},{"description":"looks into xml nmap for osfamily","id_":"2baa37be-59cb-4e8a-a67b-1abae654ce4b","name":"parse_xml_for_linux_from_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output as xml filename","id_":"aa6324a9-efd2-49c7-a181-90f67b39deb9","name":"nmap_file","required":true,"schema":{"type":"string"}}],"returns":{"description":"os","id_":"96b98b54-2272-4065-b427-2eadf140cb12","schema":{"type":"array"}}}]}] + +export default Data; diff --git a/frontend/src/assets/img/default-monochrome.svg b/frontend/src/assets/img/default-monochrome.svg new file mode 100755 index 00000000..0af6da19 --- /dev/null +++ b/frontend/src/assets/img/default-monochrome.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/img/transform.sh b/frontend/src/assets/img/transform.sh new file mode 100755 index 00000000..944902f9 --- /dev/null +++ b/frontend/src/assets/img/transform.sh @@ -0,0 +1,3 @@ +# resize: convert schedule.png -resize 100x100\> schedule100.png +# base64: - cat picture.png | base64 -w 0 +# js insert: data:image/png;base64, diff --git a/frontend/src/charts.js b/frontend/src/charts.js new file mode 100644 index 00000000..4751047f --- /dev/null +++ b/frontend/src/charts.js @@ -0,0 +1,427 @@ +/*! + +========================================================= +* Black Dashboard React v1.1.0 +========================================================= + +* Product Page: https://www.creative-tim.com/product/black-dashboard-react +* Copyright 2020 Creative Tim (https://www.creative-tim.com) +* Licensed under MIT (https://github.com/creativetimofficial/black-dashboard-react/blob/master/LICENSE.md) + +* Coded by Creative Tim + +========================================================= + +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +*/ +// ############################## +// // // Chart variables +// ############################# + +// chartExample1 and chartExample2 options +let chart1_2_options = { + maintainAspectRatio: false, + legend: { + display: false + }, + tooltips: { + backgroundColor: "#f5f5f5", + titleFontColor: "#333", + bodyFontColor: "#666", + bodySpacing: 4, + xPadding: 12, + mode: "nearest", + intersect: 0, + position: "nearest" + }, + responsive: true, + scales: { + yAxes: [ + { + barPercentage: 1.6, + gridLines: { + drawBorder: false, + color: "rgba(29,140,248,0.0)", + zeroLineColor: "transparent" + }, + ticks: { + suggestedMin: 60, + suggestedMax: 125, + padding: 20, + fontColor: "#9a9a9a" + } + } + ], + xAxes: [ + { + barPercentage: 1.6, + gridLines: { + drawBorder: false, + color: "rgba(29,140,248,0.1)", + zeroLineColor: "transparent" + }, + ticks: { + padding: 20, + fontColor: "#9a9a9a" + } + } + ] + } +}; + +// ######################################### +// // // used inside src/views/Dashboard.js +// ######################################### +let chartExample1 = { + data1: canvas => { + let ctx = canvas.getContext("2d"); + + let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); + + gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)"); + gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)"); + gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors + + return { + labels: [ + "JAN", + "FEB", + "MAR", + "APR", + "MAY", + "JUN", + "JUL", + "AUG", + "SEP", + "OCT", + "NOV", + "DEC" + ], + datasets: [ + { + label: "My First dataset", + fill: true, + backgroundColor: gradientStroke, + borderColor: "#1f8ef1", + borderWidth: 2, + borderDash: [], + borderDashOffset: 0.0, + pointBackgroundColor: "#1f8ef1", + pointBorderColor: "rgba(255,255,255,0)", + pointHoverBackgroundColor: "#1f8ef1", + pointBorderWidth: 20, + pointHoverRadius: 4, + pointHoverBorderWidth: 15, + pointRadius: 4, + data: [100, 70, 90, 70, 85, 60, 75, 60, 90, 80, 110, 100] + } + ] + }; + }, + data2: canvas => { + let ctx = canvas.getContext("2d"); + + let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); + + gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)"); + gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)"); + gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors + + return { + labels: [ + "JAN", + "FEB", + "MAR", + "APR", + "MAY", + "JUN", + "JUL", + "AUG", + "SEP", + "OCT", + "NOV", + "DEC" + ], + datasets: [ + { + label: "My First dataset", + fill: true, + backgroundColor: gradientStroke, + borderColor: "#1f8ef1", + borderWidth: 2, + borderDash: [], + borderDashOffset: 0.0, + pointBackgroundColor: "#1f8ef1", + pointBorderColor: "rgba(255,255,255,0)", + pointHoverBackgroundColor: "#1f8ef1", + pointBorderWidth: 20, + pointHoverRadius: 4, + pointHoverBorderWidth: 15, + pointRadius: 4, + data: [80, 120, 105, 110, 95, 105, 90, 100, 80, 95, 70, 120] + } + ] + }; + }, + data3: canvas => { + let ctx = canvas.getContext("2d"); + + let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); + + gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)"); + gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)"); + gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors + + return { + labels: [ + "JAN", + "FEB", + "MAR", + "APR", + "MAY", + "JUN", + "JUL", + "AUG", + "SEP", + "OCT", + "NOV", + "DEC" + ], + datasets: [ + { + label: "My First dataset", + fill: true, + backgroundColor: gradientStroke, + borderColor: "#1f8ef1", + borderWidth: 2, + borderDash: [], + borderDashOffset: 0.0, + pointBackgroundColor: "#1f8ef1", + pointBorderColor: "rgba(255,255,255,0)", + pointHoverBackgroundColor: "#1f8ef1", + pointBorderWidth: 20, + pointHoverRadius: 4, + pointHoverBorderWidth: 15, + pointRadius: 4, + data: [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130] + } + ] + }; + }, + options: chart1_2_options +}; + +// ######################################### +// // // used inside src/views/Dashboard.js +// ######################################### +let chartExample2 = { + data: canvas => { + let ctx = canvas.getContext("2d"); + + let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); + + gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)"); + gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)"); + gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors + + return { + labels: ["JUL", "AUG", "SEP", "OCT", "NOV", "DEC"], + datasets: [ + { + label: "Data", + fill: true, + backgroundColor: gradientStroke, + borderColor: "#1f8ef1", + borderWidth: 2, + borderDash: [], + borderDashOffset: 0.0, + pointBackgroundColor: "#1f8ef1", + pointBorderColor: "rgba(255,255,255,0)", + pointHoverBackgroundColor: "#1f8ef1", + pointBorderWidth: 20, + pointHoverRadius: 4, + pointHoverBorderWidth: 15, + pointRadius: 4, + data: [80, 100, 70, 80, 120, 80] + } + ] + }; + }, + options: chart1_2_options +}; + +// ######################################### +// // // used inside src/views/Dashboard.js +// ######################################### +let chartExample3 = { + data: canvas => { + let ctx = canvas.getContext("2d"); + + let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); + + gradientStroke.addColorStop(1, "rgba(72,72,176,0.1)"); + gradientStroke.addColorStop(0.4, "rgba(72,72,176,0.0)"); + gradientStroke.addColorStop(0, "rgba(119,52,169,0)"); //purple colors + + return { + labels: ["USA", "GER", "AUS", "UK", "RO", "BR"], + datasets: [ + { + label: "Countries", + fill: true, + backgroundColor: gradientStroke, + hoverBackgroundColor: gradientStroke, + borderColor: "#d048b6", + borderWidth: 2, + borderDash: [], + borderDashOffset: 0.0, + data: [53, 20, 10, 80, 100, 45] + } + ] + }; + }, + options: { + maintainAspectRatio: false, + legend: { + display: false + }, + tooltips: { + backgroundColor: "#f5f5f5", + titleFontColor: "#333", + bodyFontColor: "#666", + bodySpacing: 4, + xPadding: 12, + mode: "nearest", + intersect: 0, + position: "nearest" + }, + responsive: true, + scales: { + yAxes: [ + { + gridLines: { + drawBorder: false, + color: "rgba(225,78,202,0.1)", + zeroLineColor: "transparent" + }, + ticks: { + suggestedMin: 60, + suggestedMax: 120, + padding: 20, + fontColor: "#9e9e9e" + } + } + ], + xAxes: [ + { + gridLines: { + drawBorder: false, + color: "rgba(225,78,202,0.1)", + zeroLineColor: "transparent" + }, + ticks: { + padding: 20, + fontColor: "#9e9e9e" + } + } + ] + } + } +}; + +// ######################################### +// // // used inside src/views/Dashboard.js +// ######################################### +const chartExample4 = { + data: canvas => { + let ctx = canvas.getContext("2d"); + + let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); + + gradientStroke.addColorStop(1, "rgba(66,134,121,0.15)"); + gradientStroke.addColorStop(0.4, "rgba(66,134,121,0.0)"); //green colors + gradientStroke.addColorStop(0, "rgba(66,134,121,0)"); //green colors + + return { + labels: ["JUL", "AUG", "SEP", "OCT", "NOV"], + datasets: [ + { + label: "My First dataset", + fill: true, + backgroundColor: gradientStroke, + borderColor: "#00d6b4", + borderWidth: 2, + borderDash: [], + borderDashOffset: 0.0, + pointBackgroundColor: "#00d6b4", + pointBorderColor: "rgba(255,255,255,0)", + pointHoverBackgroundColor: "#00d6b4", + pointBorderWidth: 20, + pointHoverRadius: 4, + pointHoverBorderWidth: 15, + pointRadius: 4, + data: [90, 27, 60, 12, 80] + } + ] + }; + }, + options: { + maintainAspectRatio: false, + legend: { + display: false + }, + + tooltips: { + backgroundColor: "#f5f5f5", + titleFontColor: "#333", + bodyFontColor: "#666", + bodySpacing: 4, + xPadding: 12, + mode: "nearest", + intersect: 0, + position: "nearest" + }, + responsive: true, + scales: { + yAxes: [ + { + barPercentage: 1.6, + gridLines: { + drawBorder: false, + color: "rgba(29,140,248,0.0)", + zeroLineColor: "transparent" + }, + ticks: { + suggestedMin: 50, + suggestedMax: 125, + padding: 20, + fontColor: "#9e9e9e" + } + } + ], + + xAxes: [ + { + barPercentage: 1.6, + gridLines: { + drawBorder: false, + color: "rgba(0,242,195,0.1)", + zeroLineColor: "transparent" + }, + ticks: { + padding: 20, + fontColor: "#9e9e9e" + } + } + ] + } + } +}; + +module.exports = { + chartExample1, // in src/views/Dashboard.js + chartExample2, // in src/views/Dashboard.js + chartExample3, // in src/views/Dashboard.js + chartExample4 // in src/views/Dashboard.js +}; diff --git a/frontend/src/defaultCytoscapeStyle.js b/frontend/src/defaultCytoscapeStyle.js new file mode 100644 index 00000000..f70fda27 --- /dev/null +++ b/frontend/src/defaultCytoscapeStyle.js @@ -0,0 +1,235 @@ +const data = [{ + selector: 'node', + css: { + 'label': 'data(label)', + 'text-valign': 'center', + 'font-family': 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif', + 'font-weight': 'lighter', + 'margin-right': '10px', + 'font-size': '15px', + 'width': '80px', + 'height': '80px', + 'color': 'white', + 'padding': '10px', + 'margin': '5px', + 'border-width': '1px', + 'text-margin-x': '10px', + } + }, + { + selector: 'edge', + css: { + 'target-arrow-shape': 'triangle', + 'target-arrow-color': 'yellow', + 'curve-style': 'unbundled-bezier', + 'label': 'data(label)', + 'text-margin-y': '-15px', + "line-fill": "linear-gradient", + "line-gradient-stop-colors": ["cyan", "yellow"], + "line-gradient-stop-positions": ["0.0", "100"], + }, + }, + { + selector: `node[type="ACTION"]`, + css: { + 'shape': 'square', + 'background-color': '#213243', + 'border-color': '#81c784', + }, + }, + { + selector: `node[?small_image]`, + css: { + 'background-image': 'data(small_image)', + 'text-halign': 'right', + }, + }, + { + selector: `node[?large_image]`, + css: { + 'background-image': 'data(large_image)', + 'text-halign': 'right', + }, + }, + { + selector: `node[type="CONDITION"]`, + css: { + 'shape': 'diamond', + 'border-color': '##FFEB3B', + 'padding': '30px' + }, + }, + { + selector: `node[type="eventAction"]`, + css: { + 'background-color': '#edbd21', + }, + }, + { + selector: `node[type="TRIGGER"]`, + css: { + 'shape': 'octagon', + 'border-color': 'orange', + 'background-color': '#213243', + }, + }, + { + selector: `node[status="running"]`, + css: { + 'border-color': '#81c784', + }, + }, + { + selector: `node[status="stopped"]`, + css: { + 'border-color': 'orange', + }, + }, + { + selector: 'node[type="mq"]', + css: { + 'background-color': '#edbd21', + }, + }, + { + selector: 'node[?isStartNode]', + css: { + 'shape': 'ellipse', + 'border-color': '#80deea', + }, + }, + { + selector: 'node[?hasErrors]', + css: { + 'color': '#991818', + 'font-style': 'italic', + }, + }, + { + selector: 'node:selected', + css: { + 'background-color': '#77b0d0', + }, + }, + { + selector: '.success-highlight', + css: { + 'background-color': '#399645', + 'transition-property': 'background-color', + 'transition-duration': '0.5s', + }, + }, + { + selector: '.failure-highlight', + css: { + 'background-color': '#8e3530', + 'transition-property': 'background-color', + 'transition-duration': '0.5s', + }, + }, + { + selector: '.not-executing-highlight', + css: { + 'background-color': 'grey', + 'border-color': 'grey', + 'transition-property': '#ffef47', + 'transition-duration': '0.25s', + }, + }, + { + selector: '.executing-highlight', + css: { + 'background-color': '#ffef47', + 'border-color': '#ffef47', + 'transition-property': '#ffef47', + 'transition-duration': '0.25s', + }, + }, + { + selector: '.awaiting-data-highlight', + css: { + 'background-color': '#f4ad42', + 'transition-property': 'background-color', + 'transition-duration': '0.5s', + }, + }, + { + selector: '$node > node', + css: { + 'padding-top': '10px', + 'padding-left': '10px', + 'padding-bottom': '10px', + 'padding-right': '10px', + }, + }, + { + selector: 'edge.executing-highlight', + css: { + 'width': '5px', + 'target-arrow-color': '#ffef47', + 'line-color': '#ffef47', + 'transition-property': 'line-color, width', + 'transition-duration': '0.25s', + }, + }, + { + selector: 'edge.success-highlight', + css: { + 'width': '5px', + 'target-arrow-color': '#399645', + 'line-color': '#399645', + 'transition-property': 'line-color, width', + 'transition-duration': '0.5s', + }, + }, + { + selector: 'edge[?hasErrors]', + css: { + 'target-arrow-color': '#991818', + 'line-color': '#991818', + 'line-style': 'dashed' + }, + }, + { + selector: '.eh-handle', + style: { + 'background-color': '#337ab7', + 'width': '1px', + 'height': '1px', + 'shape': 'circle', + 'border-width': '1px', + 'border-color': 'black' + } + }, + { + selector: '.eh-source', + style: { + 'border-width': '3', + 'border-color': '#337ab7' + } + }, + { + selector: '.eh-target', + style: { + 'border-width': '3', + 'border-color': '#337ab7' + } + }, + { + selector: '.eh-preview, .eh-ghost-edge', + style: { + 'background-color': '#337ab7', + 'line-color': '#337ab7', + 'target-arrow-color': '#337ab7', + 'source-arrow-color': '#337ab7' + } + }, + { + selector: 'edge:selected', + css: { + 'target-arrow-color': '#f85a3e', + }, + } + ] + +export default data diff --git a/frontend/src/environmentdata.js b/frontend/src/environmentdata.js new file mode 100644 index 00000000..7551141b --- /dev/null +++ b/frontend/src/environmentdata.js @@ -0,0 +1,3 @@ +const data = [{"name": "cloud", "type": "cloud"}, {"name": "onprem", "type": "onprem"}] + +export default data; diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 00000000..43ec82ff --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,14 @@ +@import url('https://fonts.googleapis.com/css?family=Nunito+Sans'); + +body { + margin: 0; + padding: 0; + font-family: "Nunito Sans", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} diff --git a/frontend/src/index.js b/frontend/src/index.js new file mode 100644 index 00000000..5c4e400f --- /dev/null +++ b/frontend/src/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + + +ReactDOM.render( + + , document.getElementById('root')); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: http://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/frontend/src/scheduledata.js b/frontend/src/scheduledata.js new file mode 100644 index 00000000..49e05e90 --- /dev/null +++ b/frontend/src/scheduledata.js @@ -0,0 +1,30 @@ +const Data = { + "src": { + "name": "Get Tickets", + "description": "Get tickets", + "outputparameters": [{ + "name": "SymptomDescription", + "schema": {"type": "string"}}, + {"name": "DetailedDescription", + "schema": {"type": "string"}}, + {"name": "EventSource", + "schema": {"type": "string"} + }] + }, + "dst": { + "name": "Create alert", + "description": "Create alert in TheHive", + "inputparameters": [{ + "name": "title", + "required": true, + "schema": {"type": "string"}}, + {"name": "description", + "required": true, + "schema": {"type": "string"}}, + {"name": "source", + "required": true, + "schema": {"type": "string"} + }]} +}; + +export default Data; diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.js new file mode 100644 index 00000000..8859a0c6 --- /dev/null +++ b/frontend/src/serviceWorker.js @@ -0,0 +1,127 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://goo.gl/SC7cgQ' + ); + }); + } else { + // Is not local host. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + + // Execute callback + if (config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/frontend/src/webhookdata.js b/frontend/src/webhookdata.js new file mode 100644 index 00000000..1e3a8c55 --- /dev/null +++ b/frontend/src/webhookdata.js @@ -0,0 +1,15 @@ +const data = { + "id":"8ccf0bec1fde018771ab685d2a40bd52", + "info":{ + "url":"", + "name":"testing", + "description":"wut" + }, + "transforms":{}, + "actions": {}, + "type":"webhook", + "status":"uninitialized", + "running":false +} + +export default data; diff --git a/frontend/src/workflowdata.js b/frontend/src/workflowdata.js new file mode 100644 index 00000000..2e2d0254 --- /dev/null +++ b/frontend/src/workflowdata.js @@ -0,0 +1,3 @@ +const data = {"actions":[{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"70574332-da82-cf17-c723-75fa7b8493c2","is_valid":true,"label":"hello_world","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":353.7438792397648,"y":260.6717930890377},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"30522433-56ed-53c3-575d-766e282e1d3e","is_valid":true,"label":"random_number","environment":"cloud","name":"random_number","parameters":null,"position":{"x":458.30040774503794,"y":104.27580103487651},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104","is_valid":false,"label":"hello_world_2","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":414.7256019053981,"y":-140.46450482659628},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"7e6e7a19-4636-cebc-91c4-052a3769a18b","is_valid":true,"label":"hello_world_3","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":83.59752786243806,"y":50.232317715020734},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"edbf927d-5a00-2405-28ed-47982cdf5110","is_valid":true,"label":"hello_world_4","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":-147.30681300186404,"y":89.16690830150289},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"4844a855-1e2b-669d-fc72-5f398321ac5d","is_valid":false,"label":"hello_world_5","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":130.24982593523967,"y":233.8325632286361},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","is_valid":true,"label":"hello_world_6","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":83.551088005629,"y":-105.15867327274223},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"469d8c2b-52ac-e397-9a29-becccd04aed8","is_valid":true,"label":"hello_world_7","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":314.4987657226086,"y":10.167183586257954},"priority":0}],"branches":[{"destination_id":"30522433-56ed-53c3-575d-766e282e1d3e","id":"4bcb9795-94e6-7d5f-2074-0d5b27784e0b","source_id":"70574332-da82-cf17-c723-75fa7b8493c2"},{"destination_id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104","id":"fe0ab8e4-a535-61cd-3c09-8fd3d8e40769","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"469d8c2b-52ac-e397-9a29-becccd04aed8","id":"8b9ee9bc-b0ab-0bb6-af61-46d4594b2663","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","id":"c204d5ef-9cc1-d906-9988-86a624c57783","source_id":"469d8c2b-52ac-e397-9a29-becccd04aed8"},{"destination_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","id":"1ffb3934-60ec-8f80-5cee-3ddc0a37fdb6","source_id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104"},{"destination_id":"edbf927d-5a00-2405-28ed-47982cdf5110","id":"9c7fb048-9d0d-cb84-9ba0-be729af9b4d1","source_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09"},{"destination_id":"edbf927d-5a00-2405-28ed-47982cdf5110","id":"e3ab104e-fc8b-3af5-8daa-bfa57bcf9690","source_id":"7e6e7a19-4636-cebc-91c4-052a3769a18b"},{"destination_id":"7e6e7a19-4636-cebc-91c4-052a3769a18b","id":"b6626081-22dd-3af3-b899-480f60d886ca","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"4844a855-1e2b-669d-fc72-5f398321ac5d","id":"4275cf97-0447-bbda-0c80-ab20d389de1a","source_id":"edbf927d-5a00-2405-28ed-47982cdf5110"}],"conditions":[],"triggers":[],"transforms":[],"description":"asd","id":"2f299808-0f1b-4ae0-97fc-ac17483dfcf7","id":"2f299808-0f1b-4ae0-97fc-ac17483dfcf7","is_valid":true,"name":"test2","start":"70574332-da82-cf17-c723-75fa7b8493c2","owner":{"username":"","id":"","orgs":""},"execution_org":{"name":"","org":"","users":null,"id":""},"workflow_variables":null} + +export default data; diff --git a/frontend/test.sh b/frontend/test.sh new file mode 100755 index 00000000..92433ed2 --- /dev/null +++ b/frontend/test.sh @@ -0,0 +1,17 @@ +#curl -XPOST https://localhost:8443/login -k -d '{"username": "asdasd", "password": "lel"}' + +#curl -XPOST https://localhost:8443/passwordreset -k --cookie "session_token=212921dd-0357-411a-8eb6-8c36786c0ab6" -d '{"password1": "asdASD123aa", "password2": "asdASD123aa", "password3": "asdASD123a"}' + + +# Register user +#curl -XPOST -k https://localhost:8443/register -d '{"username": "test@test.noooooo", "password": "asdASD123a"}' + +# Get queue with apikey +#curl https://localhost:8443/api/v1/apk/queue -k -H "apikey: 8631d3f2-8fcc-44dd-961e-ac358d229408" + +# Get apk scan +#curl -k https://localhost:8443/api/v1/apk/e9d8f6752c6551a68a5dbf1ae4d8b51f -H "apikey: 8631d3f2-8fcc-44dd-961e-ac358d229408" + +# +#curl -k https://localhost:8443/scan/e9d8f6752c6551a68a5dbf1ae4d8b51f +curl -k https://localhost:8443/scan -k diff --git a/functions/README.md b/functions/README.md new file mode 100644 index 00000000..76682e26 --- /dev/null +++ b/functions/README.md @@ -0,0 +1,19 @@ +# Functions +The point of this folder is to make GCP Cloud functions able to run default WALKOFF apps. + +# How it works: +* Subsequent info is based on the appname in main +1. stitcher.go deploys the config to the app part of the website +2. stitcher.go deploys the cloud function based on baseline.py +3. stitcher.go SHOULD deploy the app to dockerhub for onpremise usecases + +## How to fix an appfile (done in stitcher.go) +* Remove walkoff_app_sdk.app_base import +* Remove anything with __name__ == "__main"__ (runner) + +## Stitching order: +* Base imports +* Authorization +* class AppBase +* class +* Runner diff --git a/functions/newworker/Dockerfile b/functions/newworker/Dockerfile new file mode 100644 index 00000000..c361f4e9 --- /dev/null +++ b/functions/newworker/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.7-alpine as base + +FROM base as builder +RUN apk --no-cache add --update alpine-sdk + +RUN mkdir /install +WORKDIR /install + +COPY ./worker/requirements.txt /requirements.txt + +RUN git clone "https://github.com/aio-libs/aioredis.git" +RUN pip install --prefix="/install" ./aioredis +RUN pip install --prefix="/install" --no-deps asteval +RUN pip install --prefix="/install" six +RUN pip install --prefix="/install" -r /requirements.txt + +FROM base + +COPY --from=builder /install /usr/local +COPY ./umpire/common /app/common +COPY ./worker /app/worker + +WORKDIR /app + +CMD python -m worker.worker diff --git a/functions/newworker/async_logger.py b/functions/newworker/async_logger.py new file mode 100644 index 00000000..795a49ad --- /dev/null +++ b/functions/newworker/async_logger.py @@ -0,0 +1,253 @@ +import logging +import asyncio +import sys +import warnings +from logging import StreamHandler, DEBUG, INFO, ERROR, WARNING, CRITICAL, raiseExceptions + + +class AsyncHandler(StreamHandler): + """ An async wrapper around logging.StreamHandler for async log streams like Redis PUB/SUB""" + def __init__(self, stream=None, loop=None): + """ + Initialize the handler. + + If stream is not specified, sys.stderr is used. + """ + super().__init__(stream) + self.loop = loop + + async def flush(self): + """ + Flushes the stream. + """ + await self.stream.flush() + + async def emit(self, record): + """ + Emit a record. + + If a formatter is specified, it is used to format the record. + The record is then written to the stream with a trailing newline. If + exception information is present, it is formatted using + traceback.print_exception and appended to the stream. If the stream + has an 'encoding' attribute, it is used to determine how to do the + output to the stream. + """ + try: + msg = self.format(record) + await self.stream.write(msg + self.terminator) + await self.flush() + except Exception: + self.handleError(record) + + async def handle(self, record): + """ + Conditionally emit the specified logging record. + + Emission depends on filters which may have been added to the handler. + Wrap the actual emission of the record with acquisition/release of + the I/O thread lock. Returns whether the filter passed the record for + emission. + """ + rv = self.filter(record) + if rv: + self.acquire() + try: + await self.emit(record) + finally: + self.release() + return rv + + async def close(self): + if self.stream is not None: + await self.flush() + await self.stream.close() + super().close() + + +class AsyncLogger(logging.Logger): + """ An async wrapper around logging.Logger for async log streams like Redis PUB/SUB """ + def __init__(self, name, level=logging.ERROR, loop=asyncio.get_event_loop()): + super().__init__(name, level=level) + self.loop = loop + + async def debug(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'DEBUG'. + + To pass exception information, use the keyword argument exc_info with + a true value, e.g. + + logger.debug("Houston, we have a %s", "thorny problem", exc_info=1) + """ + if self.isEnabledFor(DEBUG): + await self._log(DEBUG, msg, args, **kwargs) + + async def info(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'INFO'. + + To pass exception information, use the keyword argument exc_info with + a true value, e.g. + + logger.info("Houston, we have a %s", "interesting problem", exc_info=1) + """ + if self.isEnabledFor(INFO): + await self._log(INFO, msg, args, **kwargs) + + async def warning(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'WARNING'. + + To pass exception information, use the keyword argument exc_info with + a true value, e.g. + + logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1) + """ + if self.isEnabledFor(WARNING): + await self._log(WARNING, msg, args, **kwargs) + + async def warn(self, msg, *args, **kwargs): + warnings.warn("The 'warn' method is deprecated, use 'warning' instead", DeprecationWarning, 2) + await self.warning(msg, *args, **kwargs) + + async def error(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'ERROR'. + + To pass exception information, use the keyword argument exc_info with + a true value, e.g. + + logger.error("Houston, we have a %s", "major problem", exc_info=1) + """ + if self.isEnabledFor(ERROR): + await self._log(ERROR, msg, args, **kwargs) + + async def exception(self, msg, *args, exc_info=True, **kwargs): + """ + Convenience method for logging an ERROR with exception information. + """ + await self.error(msg, *args, exc_info=exc_info, **kwargs) + + async def critical(self, msg, *args, **kwargs): + """ + Log 'msg % args' with severity 'CRITICAL'. + + To pass exception information, use the keyword argument exc_info with + a true value, e.g. + + logger.critical("Houston, we have a %s", "major disaster", exc_info=1) + """ + if self.isEnabledFor(CRITICAL): + await self._log(CRITICAL, msg, args, **kwargs) + + fatal = critical + + async def log(self, level, msg, *args, **kwargs): + """ + Log 'msg % args' with the integer severity 'level'. + + To pass exception information, use the keyword argument exc_info with + a true value, e.g. + + logger.log(level, "We have a %s", "mysterious problem", exc_info=1) + """ + if not isinstance(level, int): + if raiseExceptions: + raise TypeError("level must be an integer") + else: + return + if self.isEnabledFor(level): + await self._log(level, msg, args, **kwargs) + + async def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False): + """ + Low-level logging routine which creates a LogRecord and then calls + all the handlers of this logger to handle the record. + """ + sinfo = None + if logging._srcfile: + try: + fn, lno, func, sinfo = self.findCaller(stack_info) + except ValueError: + fn, lno, func = "(unknown file)", 0, "(unknown function)" + else: + fn, lno, func = "(unknown file)", 0, "(unknown function)" + if exc_info: + if isinstance(exc_info, BaseException): + exc_info = (type(exc_info), exc_info, exc_info.__traceback__) + elif not isinstance(exc_info, tuple): + exc_info = sys.exc_info() + record = self.makeRecord(self.name, level, fn, lno, msg, args, + exc_info, func, extra, sinfo) + await self.handle(record) + + async def handle(self, record): + """ + Call the handlers for the specified record. + + This method is used for unpickled records received from a socket, as + well as those created locally. Logger-level filtering is applied. + """ + if (not self.disabled) and self.filter(record): + await self.callHandlers(record) + + async def callHandlers(self, record): + """ + Pass a record to all relevant handlers. + + Loop through all handlers for this logger and its parents in the + logger hierarchy. If no handler was found, output a one-off error + message to sys.stderr. Stop searching up the hierarchy whenever a + logger with the "propagate" attribute set to zero is found - that + will be the last logger whose handlers are called. + """ + c = self + found = 0 + while c: + for hdlr in c.handlers: + found += 1 + if record.levelno >= hdlr.level: + await hdlr.handle(record) + if not c.propagate: + c = None + else: + c = c.parent + if found == 0: + if logging.lastResort: + if record.levelno >= logging.lastResort.level: + logging.lastResort.handle(record) + elif logging.raiseExceptions and not self.manager.emittedNoHandlerWarning: + sys.stderr.write("No handlers could be found for logger" + " \"%s\"\n" % self.name) + self.manager.emittedNoHandlerWarning = True + + async def shutdown(self): + """ + Perform any cleanup actions in the logging system (e.g. flushing + buffers). + + Should be called at application exit. + """ + for handler in reversed(self.handlers): + # errors might occur, for example, if files are locked + # we just ignore them if raiseExceptions is not set + try: + if handler: + try: + # await handler.acquire() # do we need to lock? + await handler.flush() + await handler.close() + except (OSError, ValueError): + # Ignore errors which might be caused + # because handlers have been closed but + # references to them are still around at + # application exit. + pass + except Exception: + pass + finally: + # handler.release() # We need to release if we decide to lock I guess + self.removeHandler(handler) + except Exception: # ignore everything, as we're shutting down + pass diff --git a/functions/newworker/config.py b/functions/newworker/config.py new file mode 100644 index 00000000..873f6f17 --- /dev/null +++ b/functions/newworker/config.py @@ -0,0 +1,73 @@ +import logging +from pathlib import Path +import os + + +logging.basicConfig(level=logging.INFO, format="{asctime} - {name} - {levelname}:{message}", style='{') +logger = logging.getLogger("WALKOFF") +CONFIG_PATH = (Path(__file__).parent / "config.ini").resolve() + + +def sint(value, default): + if not isinstance(default, int): + raise TypeError("Default value must be of integer type") + try: + return int(value) + except (TypeError, ValueError): + return default + + +def sfloat(value, default): + if not isinstance(default, int): + raise TypeError("Default value must be of float type") + try: + return float(value) + except (TypeError, ValueError): + return default + + +class Config: + # Worker options + WORKER_TIMEOUT = os.environ.get("WORKER_TIMEOUT", "30") + API_GATEWAY_URI = os.environ.get("API_GATEWAY_URI", "http://localhost:8001") + WALKOFF_USERNAME = os.environ.get("WALKOFF_USERNAME", '') + WALKOFF_PASSWORD = os.environ.get("WALKOFF_PASSWORD", '') + + # Umpire options + APPS_PATH = os.getenv("APPS_PATH", "../apps") + APP_REFRESH = os.getenv("APP_REFRESH", "60") + SWARM_NETWORK = os.getenv("SWARM_NETWORK", "walkoff_default") + APP_PREFIX = os.getenv("APP_PREFIX", "walkoff_app") + STACK_PREFIX = os.getenv("STACK_PREFIX", "walkoff") + DOCKER_REGISTRY = os.getenv("DOCKER_REGISTRY", "127.0.0.1:5000") + UMPIRE_HEARTBEAT = os.getenv("UMPIRE_HEARTBEAT", "1") + + # Redis options + REDIS_URI = os.getenv("REDIS_URI", "redis://192.168.239.145:6379") + REDIS_EXECUTING_WORKFLOWS = os.getenv("REDIS_EXECUTING_WORKFLOWS", "executing-workflows") + REDIS_PENDING_WORKFLOWS = os.getenv("REDIS_PENDING_WORKFLOWS", "pending-workflows") + REDIS_ABORTING_WORKFLOWS = os.getenv("REDIS_ABORTING_WORKFLOWS", "aborting-workflows") + REDIS_ACTIONS_IN_PROCESS = os.getenv("REDIS_ACTIONS_IN_PROCESS", "actions-in-process") + REDIS_WORKFLOW_QUEUE = os.getenv("REDIS_WORKFLOW_Q", "workflow-queue") + REDIS_WORKFLOWS_IN_PROCESS = os.getenv("REDIS_WORKFLOWS_IN_PROCESS", "workflows-in-process") + REDIS_WORKFLOW_GROUP = os.getenv("REDIS_WORKFLOW_GROUP", "workflow-group") + REDIS_ACTION_RESULTS_GROUP = os.getenv("REDIS_ACTION_RESULTS_GROUP", "action-results-group") + REDIS_WORKFLOW_TRIGGERS_GROUP = os.getenv("REDIS_WORKFLOW_TRIGGERS_GROUP", "workflow-triggers-group") + REDIS_WORKFLOW_CONTROL = os.getenv("REDIS_WORKFLOW_CONTROL", "workflow-control") + REDIS_WORKFLOW_CONTROL_GROUP = os.getenv("REDIS_WORKFLOW_CONTROL_GROUP", "workflow-control-group") + + # Overrides the environment variables for docker-compose and docker commands on the docker machine at 'DOCKER_HOST' + # See: https://docs.docker.com/compose/reference/envvars/ for more information. + # DOCKER_HOST = os.environ.get("DOCKER_HOST", "tcp://ip_of_docker_swarm_manager:2376") + # DOCKER_HOST = os.environ.get("DOCKER_HOST", "unix:///var/run/docker.sock") + # DOCKER_TLS_VERIFY = os.environ.get("DOCKER_TLS_VERIFY", "1") + # DOCKER_CERT_PATH = os.environ.get("DOCKER_CERT_PATH", "/Path/to/certs/for/remote/docker/daemon") + + def get_int(self, key, default): + return sint(getattr(self, key), default) + + def get_float(self, key, default): + return sfloat(getattr(self, key), default) + + +config = Config() diff --git a/functions/newworker/docker_helpers.py b/functions/newworker/docker_helpers.py new file mode 100644 index 00000000..b65f8c8c --- /dev/null +++ b/functions/newworker/docker_helpers.py @@ -0,0 +1,321 @@ +import logging +import os +import re +import json +import copy +import base64 +import tarfile +from io import BytesIO +from pathlib import Path +from contextlib import contextmanager, asynccontextmanager + +import aiodocker +from aiodocker.utils import clean_map +from aiodocker.exceptions import DockerError +import docker +from docker.models.services import _get_create_service_kwargs +from docker.types.services import ServiceMode, Resources, EndpointSpec, RestartPolicy, SecretReference + +from compose.cli.command import get_project as get_compose_project +from compose.utils import timeparse, parse_bytes +from compose.config.environment import Environment + +from config import config +from helpers import sint, sfloat + +logger = logging.getLogger("UMPIRE") + + +class DockerBuildError(Exception): + pass + + +# TODO: Clean a lot of this up and rectify the inconsistencies between the different docker libraries +class ServiceKwargs: + @classmethod + def configure(cls, image, service, secrets=None, mounts=None, **kwargs): + self = ServiceKwargs() + options = service.options + deploy_opts = options.get("deploy", {}) + prefs = deploy_opts.get("placement", {}).get("preferences", {}) + + # Map compose options to service options + self.image = image + self.constraints = deploy_opts.get("placement", {}).get("constraints") + self.preferences = [kv for pref in prefs for kv in pref.items()] + self.container_labels = options.get("labels") + + self.endpoint_spec = EndpointSpec(deploy_opts.get("endpoint_mode"), + {p.published: p.target for p in options.get("ports", [])}) + + self.env = options.get("environment", None) + self.hostname = options.get("hostname") + self.isolation = options.get("isolation") + self.labels = {k: v for k, v in (kv.split('=') for kv in deploy_opts.get("labels", []))} + self.log_driver = options.get("logging", {}).get("driver") + self.log_driver_options = options.get("logging", {}).get("options") + self.mode = ServiceMode(deploy_opts.get("mode", "replicated"), deploy_opts.get("replicas", 1)) + self.networks = [config.SWARM_NETWORK] # Similar to mounts. I don't see the use case but see the issues + + resource_opts = deploy_opts.get("resources", {}) + if resource_opts: + # Unpack any generic_resources defined i.e. gpus and such + reservation_opts = resource_opts.get("reservations", {}) + generic_resources = {} + for generic_resource in reservation_opts.get("generic_resources", {}): + discrete_resource_spec = generic_resource["discrete_resource_spec"] + generic_resources[discrete_resource_spec["kind"]] = discrete_resource_spec["value"] + cpu_limit = sfloat(resource_opts.get("limits", {}).get("cpus"), 0) + cpu_reservation = sfloat(reservation_opts.get("cpus"), 0) + nano_cpu_limit = sint(cpu_limit * 1e9, 0) if cpu_limit is not None else None + nano_cpu_reservation = sint(cpu_reservation * 1e9, 0) if cpu_reservation is not None else None + self.resources = Resources(cpu_limit=nano_cpu_limit, + mem_limit=parse_bytes(resource_opts.get("limits", {}).get("memory", '')), + cpu_reservation=nano_cpu_reservation, + mem_reservation=parse_bytes(reservation_opts.get("memory", '')), + generic_resources=generic_resources) + + restart_opts = deploy_opts.get("restart_policy", {}) + if restart_opts: + # Parse the restart policy + delay = timeparse(restart_opts.get("delay", "0s")) + window = timeparse(restart_opts.get("restart_opts", "0s")) + self.restart_policy = RestartPolicy(condition=restart_opts.get("condition", ), + delay=delay, + max_attempts=sint(restart_opts.get("max_attempts", 0), 0), + window=window) + + self.secrets = secrets + self.mounts = mounts + + # Grab any key word arguments that may have been given + [setattr(self, k, v) for k, v in kwargs.items() if hasattr(self, k)] + + service_kwargs = _get_create_service_kwargs('create', copy.copy(self.__dict__)) + + # This is needed because aiodocker assumes the Env is a dictionary for some reason... + if self.env is not None: + service_kwargs["task_template"]["ContainerSpec"]["Env"] = self.env + + return service_kwargs + + +async def create_secret(client, name, data): + data = base64.b64encode(data) + data = data.decode("ascii") + body = {"Data": data, "Name": name} + headers = {"Content-Type": "application/json"} + resp = await client._query("secrets/create", "POST", data=json.dumps(body), headers=headers) + return await resp.json() + + +async def update_service(client, service_id, version, *, image=None, rollback=None, mode=None): + if image is None and rollback is False: + raise ValueError("You need to specify an image.") + + inspect_service = await client.services.inspect(service_id) + spec = inspect_service["Spec"] + + if mode is not None: + spec["Mode"] = mode + + if image is not None: + spec["TaskTemplate"]["ContainerSpec"]["Image"] = image + + params = {"version": version} + if rollback is True: + params["rollback"] = "previous" + + data = json.dumps(clean_map(spec)) + + await client._query_json( + "services/{service_id}/update".format(service_id=service_id), + method="POST", + data=data, + params=params, + ) + return True + + +async def get_secret(client: aiodocker.Docker, secret_id): + resp = await client._query(f"secrets/{secret_id}") + print(resp) + print(resp) + return await resp.json() + + +async def delete_secret(client: aiodocker.Docker, secret_id): + await client._query(f"secrets/{secret_id}", "DELETE") + + +async def get_nodes(client: aiodocker.Docker): + resp = await client._query("nodes") + return await resp.json() + + +async def get_tasks(client: aiodocker.Docker, params): + resp = await client._query("tasks" + '?' + params) + return await resp.json() + + +def normalize_name(name, delimiter=''): + """ Super arbitrary naming convention for docker images/services... """ + return re.sub(r'[^-_a-z0-9]', delimiter, name.lower()) + + +def get_project(path): + project = get_compose_project(path, environment=load_docker_env(), project_name=config.APP_PREFIX) + project.path = path # we'll add this in to refresh the project later + return project + + +def load_docker_env(): + # TODO: remove this since it is likely no longer needed + environment = os.environ + # environment.update({key: val for key, val in config["DOCKER_ENV"].items()}) + return Environment(environment) + + +async def get_service(docker_client, service_id): + try: + s = await docker_client.services.inspect(service_id) + return {'id': s["ID"], 'version': s['Version']['Index']} + except DockerError: + return {} + + +async def remove_service(docker_client, service): + try: + return await docker_client.services.delete(service) + except DockerError: + logger.error(f"Could not delete {service}.") + return False + + +async def get_replicas(docker_client, service): + """ + Gets the running and desired replica counts for the given service ID + :param service: The docker id of the service + :return: a dictionary giving the number of "running" and "desired" replicas + """ + tasks = await docker_client.tasks.list(filters={"service": [service]}) + desired = sum([t["DesiredState"] == "running" for t in tasks]) + running = sum([t["Status"]["State"] == "running" for t in tasks]) + return {"running": running, "desired": desired} + + +async def get_containers(docker_client, service, short_ids=False): + """ + Gets the running containers the given service ID + :param service: The docker id of the service + :return: a set of the running containers + """ + def get_container_id(task_spec): + return task_spec["Status"]["ContainerStatus"]["ContainerID"] + + def get_state(task_spec): + return task_spec["Status"]["State"] + + def has_container(task_spec): + return task_spec["Status"].get("ContainerStatus") is not None + + tasks = await docker_client.tasks.list(filters={"service": [service]}) + + if short_ids: + return set(get_container_id(t)[:12] for t in tasks if get_state(t) == "running" and has_container(t)) + return set(get_container_id(t) for t in tasks if get_state(t) == "running" and has_container(t)) + + + +async def load_secrets(docker_client, project): + service = project.services[0] + secret_references = [] + for service_secret in service.secrets: + secret = service_secret["secret"] + filename = service_secret.get("file", secret.source) + + # Compose doesn't parse external secrets so we'll assume there is one and build if it doesn't exist + try: + secret_id = await get_secret(docker_client, secret.source) + + except (AttributeError, DockerError): + with open(filename, 'rb') as fp: + data = fp.read() + secret_id = (await create_secret(docker_client, name=secret.source, data=data)).get("ID") + + if secret_id is not None: + secret_references.append(SecretReference(secret_id=secret_id, secret_name=secret.source, + uid=secret.uid, gid=secret.gid, mode=secret.mode)) + return secret_references + +def connect_to_docker(): + client = docker.from_env(environment=load_docker_env()) + try: + if client.ping(): + logger.debug(f"Connected to Docker Engine: v{client.version()['Version']}") + return client + except docker.errors.APIError as e: + logger.error(f"Docker API error during connect: {e}") + + +@asynccontextmanager +async def connect_to_aiodocker(): + client = aiodocker.Docker() + try: + + if (await client._query("_ping")).status == 200: + resp = await client._query("version") + version = (await resp.json())["Version"] + logger.debug(f"Connected to Docker Engine: v{version}") + yield client + finally: + await client.close() + logger.info("Docker connection closed.") + + +@contextmanager +def docker_context(path, dirs=None): + """ + Tars and compresses the given docker context in memory. Useful for sending contexts to `docker build` commands. + :param path: str or pathlib.Path object representing the path of the context + :param dirs: white list of directories under path to grab + :return: an in memory tar of the context + """ + if not isinstance(path, Path): + try: + path = Path(path) + except (ValueError, NotImplementedError): + logger.exception(f"Error accessing path: \"{path}\"") + return + + fileobj = BytesIO() + tar = tarfile.open(fileobj=fileobj, mode="w") + + # If a list of subdirectories is listed, only grab them + if dirs is not None: + for d in dirs: + tar.add(path / d, arcname=d) + else: + tar.add(path, arcname='') + tar.close() + try: + fileobj.seek(0) # must go back to start of file after tarfile writes to it + yield fileobj + finally: + fileobj.close() + + +async def stream_docker_log(log_stream): + async for line in log_stream: + if "stream" in line and line["stream"].strip(): + print(line["stream"].strip()) + logger.debug(line["stream"].strip()) + elif "status" in line: + print(line["status"].strip()) + logger.debug(line["status"].strip()) + elif "error" in line: + print(line["error"].strip()) + logger.error(line["error"].strip()) + raise DockerBuildError + else: + print(line) diff --git a/functions/newworker/helpers.py b/functions/newworker/helpers.py new file mode 100644 index 00000000..108547be --- /dev/null +++ b/functions/newworker/helpers.py @@ -0,0 +1,101 @@ +import logging +from config import config + +import aiohttp +import requests + +from message_types import(message_dumps, NodeStatusMessage, WorkflowStatusMessage, + StatusEnum, JSONPatch, JSONPatchOps) + +logger = logging.getLogger("WALKOFF") + +HEX_CHARS = 'abcdefABCDEF0123456789' +UUID_GLOB = "-".join((f"[{HEX_CHARS}]" * i for i in (8, 4, 4, 4, 12))) +UUID_REGEX = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}" + + +def sint(value, default): + if not isinstance(default, int): + raise TypeError("Default value must be of integer type") + try: + return int(value) + except (TypeError, ValueError): + return default + + +def sfloat(value, default): + if not isinstance(default, int): + raise TypeError("Default value must be of float type") + try: + return float(value) + except (TypeError, ValueError): + return default + + +async def get_walkoff_auth_header(session, token=None, timeout=5*60): + url = config.API_GATEWAY_URI.rstrip('/') + '/api' + + # TODO: make this secure and don't use default admin user + if token is None: + async with session.post(url + "/auth", json={"username": config.WALKOFF_USERNAME, + "password": config.WALKOFF_PASSWORD}, timeout=timeout) as resp: + resp_json = await resp.json() + #token = resp_json["refresh_token"] + token = "refresh" + logger.debug("Successfully logged into WALKOFF") + + headers = {"Authorization": f"Bearer {token}"} + async with session.post(url + "/auth/refresh", headers=headers, timeout=timeout) as resp: + resp_json = await resp.json() + #access_token = resp_json["access_token"] + access_token = "access" + logger.debug("Successfully refreshed WALKOFF JWT") + + return {"Authorization": f"Bearer {access_token}"}, token + + +def make_patch(message, root, op, value_only=False, white_list=None, black_list=None): + if white_list is None and black_list is None: + raise ValueError("Either white_list or black_list must be provided") + + if white_list is not None and black_list is not None: + raise ValueError("Either white_list or black_list must be provided, not both") + + # convert blacklist to whitelist and grab those attrs from the message + white_list = set(message.__slots__).difference(black_list) if black_list is not None else white_list + + if value_only and len(white_list) != 1: + raise ValueError("value_only can only be set if a single key is in white_list") + + if value_only: + (key,) = white_list + values = getattr(message, key) + else: + values = {k: getattr(message, k) for k in message.__slots__ if k in white_list} + + return JSONPatch(op, path=root, value=values) + + +def get_patches(message): + patches = [] + if isinstance(message, NodeStatusMessage): + root = f"/node_statuses/{message.node_id}" + if message.status == StatusEnum.EXECUTING: + patches.append(make_patch(message, root, JSONPatchOps.ADD, black_list={"result", "completed_at"})) + + else: + patches.append(make_patch(message, root, JSONPatchOps.REPLACE, black_list={})) + + elif isinstance(message, WorkflowStatusMessage): + if message.status == StatusEnum.EXECUTING: + for key in [attr for attr in message.__slots__ if getattr(message, attr)]: + patches.append(make_patch(message, f"/{key}", JSONPatchOps.REPLACE, value_only=True, + white_list={f"{key}"})) + + elif message.status == StatusEnum.COMPLETED or message.status == StatusEnum.ABORTED: + patches.append(make_patch(message, f"/status", JSONPatchOps.REPLACE, value_only=True, + white_list={"status"})) + patches.append(make_patch(message, f"/completed_at", JSONPatchOps.REPLACE, value_only=True, + white_list={"completed_at"})) + + return patches diff --git a/functions/newworker/main.py b/functions/newworker/main.py new file mode 100644 index 00000000..0e4192e3 --- /dev/null +++ b/functions/newworker/main.py @@ -0,0 +1,583 @@ +import asyncio +import logging +import json +import sys +import os +import signal +import requests +import os +import time +from collections import deque +from inspect import getcoroutinelocals + +from google.cloud import pubsub + +import aiohttp +import aioredis + +from message_types import message_dumps, message_loads, NodeStatusMessage, WorkflowStatusMessage, StatusEnum +from helpers import get_walkoff_auth_header +from redis_helpers import connect_to_redis_pool, xdel, deref_stream_message +from workflow_types import (Node, Action, Condition, Transform, Parameter, Trigger, + ParameterVariant, Workflow, workflow_dumps, workflow_loads, ConditionException) + +logging.basicConfig(level=logging.INFO, format="{asctime} - {name} - {levelname}:{message}", style='{') +logger = logging.getLogger("WORKER") +# logging.getLogger("asyncio").setLevel(logging.DEBUG) +# logger.setLevel(logging.DEBUG) + +CONTAINER_ID = ""#os.getenv("HOSTNAME") +APIKEY = ""#os.getenv("FUNCTION_APIKEY") + +# FIXME +#apiurl = "http://localhost:5001" +apiurl = "https://shuffler.io" + +class Worker: + def __init__(self, workflow: Workflow = None, start_action: str = None, redis: aioredis.Redis = None, + session: aiohttp.ClientSession = None): + self.workflow = workflow + self.start_action = start_action if start_action is not None else self.workflow.start + self.results_stream = f"{workflow.execution_id}:results" + self.parallel_accumulator = {} + self.accumulator = {} + self.parallel_in_process = {} + self.in_process = {} + self.redis = redis + self.streams = set() + self.scheduling_tasks = set() + self.results_getter_task = None + self.parallel_tasks = set() + self.workflow_tasks = set() + self.execution_task = None + self.session = session + self.token = None + self.parent_map = {} + self.cancelled = [] + self.results = {} + + self.execution_id = "" + self.workflow_id = "" + self.id = "" + self.locations = [] + self.project_id = "" + self.authorization = "" + self.start_id = start_action.id if start_action is not None else "" + + async def cancel_subgraph(self, node): + """ + Cancels the task related to the current node as well as the tasks related to every child of that node. + Also removes them from the worker's internal in_process queue. + """ + # dependents = self.workflow.get_dependents(node) + cancelled_tasks = set() + + self.cancelled.append(node.id) + to_cancel = await self.cancel_helper(node, [node.id]) + + for task in self.scheduling_tasks: + for _, arg in getcoroutinelocals(task._coro).items(): + if isinstance(arg, Node): + if arg.id in to_cancel: + self.in_process.pop(arg.id) + self.accumulator[arg.id] = None + self.cancelled.append(arg.id) + task.cancel() + cancelled_tasks.add(task) + + await asyncio.gather(*cancelled_tasks, return_exceptions=True) + + # This is a very specific one, that might be fucked up by an action named the same thing. + # Its this way because of a weird translation from Triggers to Actions that didn't + # really work very well + def handle_user_input_node(self, node): + print("Handle user input. Params: %d!" % len(node.parameters)) + + data = "" + options = "" + actiontypes = [] + for parameter in node.parameters: + print("Param: %s" % parameter) + if parameter.name == "alertinfo": + data = parameter.value + elif parameter.name == "options": + options = parameter.value + elif parameter.name == "type": + actiontypes = parameter.value.split(",") + + print("Data: ", data) + print("Options: ", options) + print("Types: ", actiontypes) + + executed = False + headers = { + "Authorization": "Bearer %s" % APIKEY, + "Content-Type": "application/json", + } + + for actiontype in actiontypes: + if actiontype == "email": + print("SEND EMAIL!") + + #apiurl = "http://localhost:5001" + mailurl = "%s/functions/sendmail" % apiurl + data = { + "targets": ["frikky@shuffler.io"], + "body": data, + "subject": "Shuffle alert requires input!", + "type": "User input", + "sender_company": "Shuffle", + "reference_execution": self.execution_id, + "workflow_id": self.workflow_id, + "execution_type": options, + "start": node.id, + } + + # Add it to actionResult here because of start time! + params = self.dereference_params_pubsub(node) + + ret = requests.post(mailurl, headers=headers, json=data) + logger.debug("Ret: %s" % ret.text) + logger.debug("Status: %d" % ret.status_code) + + if ret.status_code == 200 or ret.status_code == 201: + executed = True + elif actiontype.lower() == "sms": + print("Handle SMS!") + executed = True + + if executed: + actionurl = "%s/api/v1/streams" % apiurl + action = { + "name": node.name, + "app_name": node.app_name, + "app_version": node.app_version, + "label": node.label, + "environment": node.environment, + "id": node.id, + } + + action_result = { + "action": action, + "authorization": self.authorization, + "execution_id": self.execution_id, + "result": "", + "started_at": int(time.time()), + "status": "WAITING", + } + + + actionret = requests.post(actionurl, headers=headers, json=action_result) + logger.debug("Actionret: %d", actionret.status_code) + logger.debug("Actionret: %s", actionret.text) + + print("SHOULD KILL THE EXECUTION (stop this branch)!") + + def execute_workflow_pubsub(self): + """ + Do a simple BFS to visit and schedule each node in the workflow. We assume every node will run and thus preemptively schedule them all. We will clean up any nodes that will not run due to conditions or triggers + """ + visited = {self.start_action} + queue = deque([self.start_action]) + self.scheduling_tasks = set() + while queue: + node = queue.pop() + logger.debug("NODE INFO: %s, %s, %s, %s" % (node.name, node.app_name, node.app_version, node.label)) + parents = {n.id: n for n in self.workflow.predecessors(node)} if node is not self.start_action and node.id is not self.workflow.start else {} + children = {n.id: n for n in self.workflow.successors(node)} + + for parent_id in parents: + if node.id not in self.parent_map.keys(): + self.parent_map[node.id] = 1 + else: + self.parent_map[node.id] = self.parent_map[node.id] + 1 + + self.in_process[node.id] = node + + if isinstance(node, Action): + node.execution_id = self.workflow.execution_id # the app needs this as a key for the redis queue + + # Custom for trigger actions + if node.name == "User Input" and node.app_name == "User Input": + logger.info("Handling user input!") + + # Skipping new nodes + if self.start_id != node.id: + self.handle_user_input_node(node) + break + else: + logger.info("Skipping user input as its start node!") + + print("NAME: %s, ENV: %s, LABEL" % (node.name, node.environment)) + if node.environment == "cloud": + self.scheduling_tasks.add(self.schedule_node_pubsub(node, parents, children)) + + print("EXIT NAME: %s, ENV: %s, LABEL" % (node.name, node.environment)) + for child in sorted(children.values(), reverse=True): + if child not in visited: + queue.appendleft(child) + visited.add(child) + + # Checks whether all actions are finished + finished = self.get_action_results_pubsub() + if finished: + print("Got finished and will return!") + break + + def dereference_params_pubsub(self, action: Action): + param_ret = [] + global_vars = {} + + print(action.parameters) + for param in action.parameters: + data = {"value": param.value, "name": param.name, "action_field": param.action_field, "variant": "STATIC_VALUE"} + + if param.variant == ParameterVariant.STATIC_VALUE: + data["variant"] = "STATIC_VALUE" + elif param.variant == ParameterVariant.ACTION_RESULT: + data["variant"] = "ACTION_RESULT" + elif param.variant == ParameterVariant.WORKFLOW_VARIABLE: + data["variant"] = "WORKFLOW_VARIABLE" + elif param.variant == ParameterVariant.GLOBAL: + data["variant"] = "GLOBAL" + else: + logger.error(f"Unable to dereference parameter:{param} for action:{action}") + break + + param_ret.append(data) + + return param_ret + + def abort(self): + logger.info("ABORTING %s BECAUSE OF ERROR WITH FUNCTION EXECUTION" % self.execution_id) + url = f"{apiurl}/api/v1/workflows/{self.workflow_id}/executions/{self.execution_id}/abort" + headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": f"Bearer {APIKEY}" + } + + ret = requests.get(url, headers=headers, timeout=5) + logger.info("Aborted with status: %d and text:\n%s" % (ret.status_code, ret.text)) + sys.exit(0) + + def schedule_node_pubsub(self, node, parents, children): + """ Waits until all dependencies of an action are met and then schedules the action """ + logger.info(f"Scheduling node {node.id} ({node.name})...") + + logger.info(self.accumulator) + while not all(parent.id in self.accumulator for parent in parents.values()): + time.sleep(1) + #await asyncio.sleep(0) + + logger.info(f"Node {node.id} ({node.name}) ready to execute.") + + # node has more than one parent, check if both parent nodes have been cancelled + if len(parents) > 1: + count = 0 + for parent in parents: + if parent in self.cancelled: + count = count + 1 + + if count == self.parent_map[node.id]: + self.cancel_subgraph(node) + + print(type(node)) + + if isinstance(node, Action): + print("NODE: %s" % node) + params = self.dereference_params_pubsub(node) + print("PARAMS: %s" % params) + + # Added authorization to send to function + message = { + "parameters": params, + "execution_id": self.execution_id, + "authorization": self.authorization, + "node_project": self.project_id, + "name": node.name, + "app_name": node.app_name, + "app_version": node.app_version, + "id": node.id, + "label": node.name, + } + + headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": f"Bearer {APIKEY}" + } + + # Uses version for production apps, but ID for private apps + functionname = f"{node.app_name}-{node.app_version}" + if not node.sharing: + functionname = f"{node.app_name}-{node.private_id}" + + print(f"Functionname (pre): {functionname}") + + functionname = functionname.replace("_", "-") + functionname = functionname.replace(":", "-") + functionname = functionname.replace(".", "-") + functionname = functionname.replace(" ", "-") + + print(f"Functionname (post): {functionname}") + + logger.info(self.locations) + logger.info(self.project_id) + for location in self.locations: + url = f"https://{location}-{self.project_id}.cloudfunctions.net/{functionname}" + + #print(message) + try: + ret = requests.post(url, headers=headers, json=message) + + # If any error at all, just quit the entire thing (abort) + if ret.status_code == 500 or ret.status_code == 401: + logger.info("Status: %d. There is an error with ret when starting %s. Should cancel execution and exit. RAW: %s" % (ret.status_code, url, ret.text)) + self.abort() + except requests.exceptions.ReadTimeout as e: + logger.debug(e) + logger.info("There is an error with ret (readtimeout). Should cancel execution and exit.") + self.abort() + except requests.exceptions.ConnectionError as e: + logger.debug(e) + logger.info("There is an error with ret (connectionerror). Should cancel execution and exit.") + self.abort() + + #logger.debug(ret.text) + logger.debug(ret.status_code) + + # FIXME - only in one location, e.g. eu-west? + break + + group = f"{node.app_name}:{node.app_version}" + stream = f"{node.execution_id}:{group}" + + logger.info(f"Scheduled {node}") + + + def get_action_results_pubsub(self): + """ Continuously monitors the results queue until all scheduled actions have been completed """ + results_stream = f"{self.workflow.execution_id}:results" + + # 1. Get the results for the workflowexecution. POST with authorization and ID should do the trick + # 2. Check whether the whole thing is still executing + # 3. Check whether the status of self.in_process is updated, if so, remove it from in progress + # 4. Schedule the next nodes somehow + print(len(self.in_process), len(self.parallel_in_process)) + print(self.in_process, len(self.parallel_in_process)) + + url = f"{apiurl}/api/v1/streams/results" + #if self.project_id != "": + # url = f"https://{self.project_id}.appspot.com/api/v1/streams/results" + # + + headers = {"Content-Type": "application/json"} + + # Uses workflow specific authorization generated for priviliged access + message = {"authorization": self.authorization, "execution_id": self.execution_id} + + sleeptime = 2 + logger.info(url) + + logger.info(f"Waiting {sleeptime} seconds for new updates in the nodestream...") + while len(self.in_process) > 0 or len(self.parallel_in_process) > 0: + # Ask for all nodes, and check every single one that's in progress + print("Items in process: %s" % self.in_process) + ret = requests.post(url, headers=headers, json=message) + if ret.status_code != 200: + logger.exception("Something went wrong getting workflow status for %s with auth %s. Raw: %s. Status: %d" % (self.execution_id, self.authorization, ret.text, ret.status_code)) + time.sleep(sleeptime) + continue + + # PAUSED, AWAITING_DATA, PENDING, COMPLETED, ABORTED, EXECUTING, SUCCESS, FAILURE + # FIXME - have this? + if ret.json()["status"] == "FINISHED" or ret.json()["status"] == "ABORTED" or ret.json()["status"] == "FAILURE": + print("Entire thing is done with status %s - exiting" % ret.json()["status"]) + return True + + self.results = ret.json() + + # FIXME - REMOVE COMMENTS + # FIXME - This might be wrong for multiple reasons + if self.results.get("results") == "" or self.results.get("results") == None: + print("Couldn't find results in results - getting new") + logger.info(self.results) + self.results["results"] = [] + #print("IS IT DONE? - RETURNING TRUE") + #return + + for node_message in self.results["results"]: + # Ensure that the received NodeStatusMessage is for an action we launched + #print(node_message) + #print(self.in_process) + # FIXME - might be an issue with same kind of node with same ID here + if node_message["action"]["id"] in self.in_process: + if node_message["status"] == "EXECUTING": + logger.info(f"Got EXECUTING result for: {node_message['action']['name']}-{node_message['execution_id']}") + elif node_message["status"] == "WAITING": + # This is just for user-inputted items + logger.info("Should only be here the SECOND time around (after user inputted)!") + logger.info(f"Got WAITING result for: {node_message['action']['name']}-{node_message['execution_id']}. Updating it to SUCCESS now that a user continued.") + self.accumulator[node_message["action"]["id"]] = "SUCCESS" + self.in_process.pop(node_message["action"]["id"], None) + + logger.debug("start_id: %s, node.id: %s", self.start_id, node_message["action"]["id"]) + if self.start_id == node_message["action"]["id"]: + logger.info("HANDLING USER INPUT AS START NODE - SETTING TO SUCCESS!") + # Check if its the same, then update it to success + headers = { + "Authorization": "Bearer %s" % APIKEY, + "Content-Type": "application/json", + } + + # Set it to successful here? + actionurl = "%s/api/v1/streams" % apiurl + action_result = node_message + action_result["status"] = "SUCCESS" + action_result["authorization"] = self.authorization + action_result["completed_at"] = int(time.time()) + action_result["result"] = "User clicked continue!" + actionret = requests.post(actionurl, headers=headers, json=action_result) + elif node_message["status"] == "SKIPPED": + # FIXME - handle SKIPPED - these are + logger.info(f"GOT SKIPPEED result for: {node_message['action']['name']}-{node_message['execution_id']}") + + elif node_message["status"] == "SUCCESS": + # Adds the data to accumulator with success AND + # removes the successful ones, which breaks the loop + self.accumulator[node_message["action"]["id"]] = node_message["result"] + logger.info(f"Worker received result for: {node_message['action']['name']}-{node_message['execution_id']}: {node_message['result']}") + self.in_process.pop(node_message["action"]["id"], None) + elif node_message["status"] == "FAILURE": + self.accumulator[node_message["action"]["id"]] = node_message["result"] + + # FIXME - cancel nodes + #await self.cancel_subgraph(self.workflow.nodes[node_message.node_id]) # kill the children! + logger.info(f"Worker received error \"{node_message['result']}\" for: {node_message['action']['name']}-" + f"{node_message['execution_id']}") + + else: + logger.error(f"Unknown message status received: {node_message}") + node_message = None + + time.sleep(sleeptime) + + return False + +def abort(message, workflow_id, execution_id): + logger.info("ABORTING %s BECAUSE OF ERROR WITH FUNCTION STARTUP" % execution_id) + logger.info("Message: %s" % message) + url = f"{apiurl}/api/v1/workflows/{workflow_id}/executions/{execution_id}/abort" + headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": f"Bearer {APIKEY}" + } + + ret = requests.get(url, headers=headers, timeout=5) + logger.info("Aborted with status: %d and text:\n%s" % (ret.status_code, ret.text)) + sys.exit(0) + +def run_function(message): + messagedata = message + + # Raise exception? + if messagedata["type"] != "workflow": + return f"Wrong type" % e, 500 + + # Required fields + execution_id = messagedata["execution_id"] + workflow_id = messagedata["workflow_id"] + + # FIXME - add exception handler -> abort + workflow = workflow_loads(json.dumps(messagedata["workflow"])) + + id = messagedata["workflow"]["id"] + locations = messagedata["locations"] + project_id = messagedata["project_id"] + authorization = messagedata["authorization"] + execution_id = messagedata["execution_id"] + workflow_id = messagedata["workflow_id"] + + logger.info("Exec_id: %s, authorization: %s" % (execution_id, authorization)) + + if execution_id == None: + logger.info("NO EXECUTION ID") + abort("NO EXECUTION ID", workflow_id, execution_id) + + if len(locations) <= 0: + logger.info("NO LOCATIONS") + abort("NO LOCATIONS", workflow_id, execution_id) + if not project_id: + logger.info("NO PROJECT_ID") + abort("NO PROJECT_ID", workflow_id, execution_id) + if not authorization: + logger.info("NO AUTHORIZATION") + abort("NO AUTHORIZATION", workflow_id, execution_id) + if not workflow_id: + logger.info("NO workflow_id") + abort("NO WORKFLOW_ID", workflow_id, execution_id) + + worker = Worker(workflow) + worker.locations = locations + worker.execution_id = execution_id + worker.id = id + worker.project_id = project_id + worker.authorization = authorization + worker.workflow_id = workflow_id + + try: + worker.start_id = messagedata["start"] + logger.debug("Start node is %s!" % messagedata["start"]) + except KeyError: + try: + worker.start_id = messagedata["workflow"]["start"] + except KeyError: + pass + + logger.info("STARTING EXECUTION TASK FOR %s" % execution_id) + try: + worker.execution_task = worker.execute_workflow_pubsub() + except Exception as e: + logger.error("Execution exception: %s" % e) + abort(e, workflow_id, execution_id) + + # def abort(self): + + logger.info(worker.execution_task) + return f"OK", 200 + +def authorization(data, context): + logger.info("JUST STARTED") + + # Rofl + import base64 + data = base64.b64decode(data['data']).decode('utf-8') + return main(data) + +def main(data): + import argparse + + LOG_LEVELS = ("debug", "info", "error", "warn", "fatal", "DEBUG", "INFO", "ERROR", "WARN", "FATAL") + parser = argparse.ArgumentParser() + parser.add_argument("--log-level", dest="log_level", choices=LOG_LEVELS, default="DEBUG") + parser.add_argument("--debug", "-d", dest="debug", action="store_true", + help="Enables debug level logging for the umpire as well as asyncio debug mode.") + args = parser.parse_args() + + logger.setLevel(args.log_level.upper()) + logger.info("STARTED") + + if isinstance(data, str): + data = json.loads(data) + + return run_function(data) + +def test(): + # Used for testing + with open("data.json", "r") as tmp: + print(main(tmp.read())) + +if __name__ == "__main__": + test() diff --git a/functions/newworker/message_types.py b/functions/newworker/message_types.py new file mode 100644 index 00000000..23df7397 --- /dev/null +++ b/functions/newworker/message_types.py @@ -0,0 +1,214 @@ +import enum +import json +import datetime + + +def message_dumps(obj): + return json.dumps(obj, cls=MessageJSONEncoder) + + +def message_loads(obj): + return json.loads(obj, cls=MessageJSONDecoder) + + +def message_dump(obj, fp): + return json.dump(obj, fp, cls=MessageJSONEncoder) + + +def message_load(obj): + return json.load(obj, cls=MessageJSONDecoder) + + +class MessageJSONDecoder(json.JSONDecoder): + """ A custom decoder for decoding JSON strings to Message types. """ + + def __init__(self, *args, **kwargs): + json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs) + + def object_hook(self, o): + if "result" in o and "app_name" in o: + o["status"] = StatusEnum[o["status"]] + return NodeStatusMessage(**o) + + elif "workflow_id" in o and "execution_id" in o: + o["status"] = StatusEnum[o["status"]] + return WorkflowStatusMessage(**o) + + elif "trigger_data" in o: + return TriggerMessage(**o) + + else: + return o + + +class MessageJSONEncoder(json.JSONEncoder): + """ A custom encoder for encoding Message types to JSON strings. """ + + def default(self, o): + if isinstance(o, NodeStatusMessage): + r = {"name": o.name, "node_id": o.node_id, "label": o.label, "app_name": o.app_name, + "execution_id": o.execution_id, "result": o.result, "status": o.status, + "started_at": o.started_at, "completed_at": o.completed_at, "combined_id": o.combined_id, + "parameters": o.parameters} + + try: + json.dumps(o.result) + except (TypeError, ValueError): + r["result"] = f"Node returned result of type '{type(o.result)}' which is not JSON serializable." + r["status"] = StatusEnum.FAILURE + finally: + return r + + elif isinstance(o, WorkflowStatusMessage): + return {"execution_id": o.execution_id, "workflow_id": o.workflow_id, "name": o.name, "status": o.status, + "started_at": o.started_at, "completed_at": o.completed_at, "user": o.user} + + elif isinstance(o, TriggerMessage): + return {"trigger_data": o.trigger_data} + + elif isinstance(o, JSONPatch): + if o.op in JSONPatchOps: + return {k: getattr(o, k, None) for k in o.__slots__ if getattr(o, k, None) is not None} + else: + raise ValueError("Improper JSON Patch operation") + + elif isinstance(o, StatusEnum): + return o.value + + elif isinstance(o, JSONPatchOps): + return o.value.lower() + + elif isinstance(o, JSONPatch): + return {k: getattr(o, k, None) for k in o.__slots__ if getattr(o, k, None) is not None} + + elif isinstance(o, datetime.datetime): + return str(o) + + else: + return o + + +class JSONPatch: + __slots__ = ("op", "path", "value", "from_") + + def __init__(self, op=None, path=None, value=None, from_=None): + self.op = op + self.path = path + self.value = value + self.from_ = from_ + + +class JSONPatchOps(enum.Enum): + TEST = "TEST" + REMOVE = "REMOVE" + ADD = "ADD" + REPLACE = "REPLACE" + MOVE = "MOVE" + COPY = "COPY" + + +class StatusEnum(enum.Enum): + """ Holds statuses used for Workflow and Action status messages """ + PAUSED = "PAUSED" # not currently implemented but may be if we see a use case + AWAITING_DATA = "AWAITING_DATA" # possibly for triggers? + PENDING = "PENDING" + COMPLETED = "COMPLETED" + ABORTED = "ABORTED" + EXECUTING = "EXECUTING" + SUCCESS = "SUCCESS" + FAILURE = "FAILURE" + + +class WorkflowStatusMessage(object): + """ Class that formats a WorkflowStatusMessage message """ + __slots__ = ("execution_id", "workflow_id", "name", "status", "started_at", "completed_at", "user") + + def __init__(self, execution_id, workflow_id, name, started_at=None, completed_at=None, status=None, user=None): + self.execution_id = execution_id + self.workflow_id = workflow_id + self.name = name + self.status = status + self.started_at = started_at + self.completed_at = completed_at + self.user = user + + @classmethod + def execution_pending(cls, execution_id, workflow_id, name, user=None): + return cls(execution_id, workflow_id, name, status=StatusEnum.PENDING, user=user) + + @classmethod + def execution_started(cls, execution_id, workflow_id, name, user=None): + start_time = datetime.datetime.now() + return cls(execution_id, workflow_id, name, started_at=start_time, status=StatusEnum.EXECUTING, user=user) + + @classmethod + def execution_completed(cls, execution_id, workflow_id, name, user=None): + end_time = datetime.datetime.now() + return cls(execution_id, workflow_id, name, completed_at=end_time, status=StatusEnum.COMPLETED, user=user) + + @classmethod + def execution_aborted(cls, execution_id, workflow_id, name, user=None): + end_time = datetime.datetime.now() + return cls(execution_id, workflow_id, name, completed_at=end_time, status=StatusEnum.ABORTED, user=user) + + +class NodeStatusMessage(object): + """ Class that formats a NodeStatusMessage message. """ + __slots__ = ("name", "node_id", "label", "app_name", "execution_id", "parameters", "combined_id", "result", + "status", "started_at", "completed_at") + + def __init__(self, name, node_id, label, app_name, execution_id, combined_id=None, parameters=None, result=None, + status=None, started_at=None, completed_at=None): + self.name = name + self.node_id = node_id + self.label = label + self.app_name = app_name + self.execution_id = execution_id + self.combined_id = combined_id if combined_id is not None else ':'.join((node_id, execution_id)) + + self.result = result + self.parameters = parameters + self.status = status + self.started_at = started_at + self.completed_at = completed_at + + @classmethod + def from_node(cls, node, execution_id, result=None, status=None, started_at=None, completed_at=None, parameters=None): + return cls(node.name, node.id, node.label, node.app_name, execution_id, result=result, + status=status, started_at=started_at, completed_at=completed_at, parameters=parameters) + + @classmethod + def pending_from_node(cls, node, execution_id, parameters=None): + return NodeStatusMessage.from_node(node, execution_id, status=StatusEnum.PENDING, parameters=parameters) + + @classmethod + def executing_from_node(cls, node, execution_id, parameters=None): + started_at = datetime.datetime.now() + return NodeStatusMessage.from_node(node, execution_id, started_at=started_at, status=StatusEnum.EXECUTING, + parameters=parameters) + + @classmethod + def success_from_node(cls, node, execution_id, result, parameters=None): + completed_at = datetime.datetime.now() + return NodeStatusMessage.from_node(node, execution_id, result=result, completed_at=completed_at, + status=StatusEnum.SUCCESS, parameters=parameters) + + @classmethod + def failure_from_node(cls, node, execution_id, result, parameters=None): + completed_at = datetime.datetime.now() + return NodeStatusMessage.from_node(node, execution_id, result=result, completed_at=completed_at, + status=StatusEnum.FAILURE, parameters=parameters) + + @classmethod + def aborted_from_node(cls, node, execution_id, parameters=None): + completed_at = datetime.datetime.now() + return NodeStatusMessage.from_node(node, execution_id, result=None, completed_at=completed_at, + status=StatusEnum.ABORTED, parameters=parameters) + + +class TriggerMessage(object): + """ Class that formats a TriggerMessage. """ + __slots__ = ("trigger_data",) + + def __init__(self, trigger_data): + self.trigger_data = trigger_data diff --git a/functions/newworker/redis_helpers.py b/functions/newworker/redis_helpers.py new file mode 100644 index 00000000..a27d07b1 --- /dev/null +++ b/functions/newworker/redis_helpers.py @@ -0,0 +1,40 @@ +import logging +from contextlib import asynccontextmanager + +import aioredis + +logger = logging.getLogger("WALKOFF") + + +@asynccontextmanager +async def connect_to_redis_pool(redis_uri) -> aioredis.Redis: + # Redis client bound to pool of connections (auto-reconnecting). + redis = await aioredis.create_redis_pool(redis_uri) + try: + yield redis + finally: + # gracefully close pool + redis.close() + await redis.wait_closed() + logger.info("Redis connection pool closed.") + + +def deref_stream_message(message): + try: + key, value = message[0][-1].popitem() + stream = message[0][0] + id = message[0][1] + return (key, value), stream, id + + except: + logger.exception("Stream message formatted incorrectly.") + + +def xlen(redis: aioredis.Redis, key): + """Returns the number of entries inside a stream.""" + return redis.execute(b'XLEN', key) + + +def xdel(redis: aioredis.Redis, stream, id): + """ Deletes id from stream. Returns the number of items deleted. """ + return redis.execute(b'XDEL', stream, id) diff --git a/functions/newworker/requirements.txt b/functions/newworker/requirements.txt new file mode 100644 index 00000000..55045789 --- /dev/null +++ b/functions/newworker/requirements.txt @@ -0,0 +1,12 @@ +aiodns +aiodocker +aioredis +aiohttp +cchardet +docker +docker-compose +pyyaml +sqlalchemy +asteval +argparse +google-cloud-pubsub diff --git a/functions/newworker/workflow_types.py b/functions/newworker/workflow_types.py new file mode 100644 index 00000000..8fbb1b55 --- /dev/null +++ b/functions/newworker/workflow_types.py @@ -0,0 +1,548 @@ +import uuid +import json +import enum +import logging +from operator import attrgetter, itemgetter +from collections import namedtuple, deque +from asteval import Interpreter, make_symbol_table + +logger = logging.getLogger("WALKOFF") + + +def workflow_dumps(obj): + return json.dumps(obj, cls=WorkflowJSONEncoder) + + +def workflow_loads(obj): + return json.loads(obj, cls=WorkflowJSONDecoder) + + +def workflow_dump(obj, fp): + return json.dump(obj, fp, cls=WorkflowJSONEncoder) + + +def workflow_load(obj, fp): + return json.load(obj, fp, cls=WorkflowJSONDecoder) + + +def attrs_equal(self, other): + attr_getters = (attrgetter(attr) for attr in self.__slots__) + return all(attr_getter(self) == attr_getter(other) for attr_getter in attr_getters) + + +class ConditionException(Exception): + pass + + +class WorkflowJSONDecoder(json.JSONDecoder): + def __init__(self, *args, **kwargs): + super().__init__(object_hook=self.object_hook, *args, **kwargs) + self.nodes = {} + self.branches = set() + + def object_hook(self, o): + if "x" in o and "y" in o: + return Point(**o) + + elif "parameters" in o and "priority" in o: + node = Action(**o) + self.nodes[node.id] = node + return node + + elif "variant" in o: + try: + o["variant"] = ParameterVariant[o["variant"]] + return Parameter(**o) + except KeyError: + o["variant"] = "STATIC_VALUE" + return Parameter(**o) + + elif "source_id" in o and "destination_id" in o: + self.branches.add(Branch(source_id=o["source_id"], destination_id=o["destination_id"], id=o["id"])) + + elif "conditional" in o: + node = Condition(**o) + self.nodes[node.id] = node + return node + + elif "transform" in o: + node = Transform(**o) + self.nodes[node.id] = node + return node + + elif "trigger_schema" in o: + node = Trigger(**o) + self.nodes[node.id] = node + return node + + elif "description" in o and "value" in o: + return Variable(**o) + + elif "actions" in o and "branches" in o: + branches = {Branch(self.nodes[b.source_id], self.nodes[b.destination_id], b.id) for b in self.branches} + + try: + workflow_variables = {var.id: var for var in o["workflow_variables"]} + except: + workflow_variables = {} + if o["workflow_variables"] != None: + for var in o["workflow_variables"]: + workflow_obj = Variable(id=var["id"], name=var["name"], value=var["value"]) + workflow_variables[workflow_obj.id] = workflow_obj + + start = self.nodes[o["start"]] + o["branches"] = branches + o["workflow_variables"] = workflow_variables + o["start"] = start + return Workflow(**o) + + else: + return o + + +class WorkflowJSONEncoder(json.JSONEncoder): + """ A custom encoder for encoding Workflow types to JSON strings. + Note: JSON encoded strings of our custom objects are lossy...for now. + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.workflow = {} + + def default(self, o): + if isinstance(o, Workflow): + # Unpack the adjacency matrix into edges + branches = [{"source_id": src.id, "destination_id": dst.id} for src, dsts in o.edges.items() + for dst in dsts] + branches.sort(key=itemgetter("source_id", "destination_id")) + actions = [action for action in o.actions] + triggers = [trigger for trigger in o.triggers] + workflow_variables = list(o.workflow_variables.values()) + return {"id": o.id, "execution_id": o.execution_id, "name": o.name, "start": o.start.id, + "actions": actions, "branches": branches, + "triggers": triggers, "workflow_variables": workflow_variables, "is_valid": o.is_valid, + "errors": None} + + elif isinstance(o, Action): + position = {"x": o.position.x, "y": o.position.y} + return {"id": o.id, "name": o.name, "app_name": o.app_name, "app_version": o.app_version, + "label": o.label, "position": position, "parameters": o.parameters, "priority": o.priority, + "execution_id": o.execution_id} + + elif isinstance(o, Condition): + position = {"x": o.position.x, "y": o.position.y} + return {"id": o.id, "name": o.name, "app_name": o.app_name, "app_version": o.app_version, + "label": o.label, "position": position, "conditional": o.conditional} + + elif isinstance(o, Transform): + position = {"x": o.position.x, "y": o.position.y} + return {"id": o.id, "name": o.name, "app_name": o.app_name, "app_version": o.app_version, + "label": o.label, "position": position, "transform": o.transform, "parameter": o.parameter} + + elif isinstance(o, Trigger): + position = {"x": o.position.x, "y": o.position.y} + return {"id": o.id, "name": o.name, "app_name": o.app_name, "app_version": o.app_version, + "label": o.label, "position": position, "trigger_schema": o.trigger_schema} + + elif isinstance(o, Parameter): + return {"name": o.name, "variant": o.variant, "value": o.value, "id": o.id} + + elif isinstance(o, ParameterVariant): + return o.value + + elif isinstance(o, Variable): + return {"description": o.description, "id": o.id, "name": o.name, "value": o.value} + + else: + return o + + +Point = namedtuple("Point", ("x", "y")) +Branch = namedtuple("Branch", ("source_id", "destination_id", "id")) +ParentSymbol = namedtuple("ParentSymbol", "result") # used inside conditions to further mask the parent node attrs +ChildSymbol = namedtuple("ChildSymbol", "id") # used inside conditions to further mask the child node attrs + + +class ParameterVariant(enum.Enum): + STATIC_VALUE = "STATIC_VALUE" + ACTION_RESULT = "ACTION_RESULT" + WORKFLOW_VARIABLE = "WORKFLOW_VARIABLE" + GLOBAL = "GLOBAL" + + +class Parameter: + __slots__ = ("name", "value", "selection", "variant", "id", "errors", "parallelized", "description", "required", "schema", "action_field", "multiline", "example") + + def __init__(self, name, parallelized=False, selection=[], id=None, value=None, variant=None, errors=None, description="", required=False, schema={}, action_field="", multiline=False, example=""): + self.id = id + self.name = name + self.description = description + self.required = required + self.parallelized = parallelized + self.selection = selection + self.value = value + self.variant = variant + self.schema = schema + self.errors = errors + self.action_field = action_field + self.multiline = multiline + self.example = example + + def __str__(self): + return f"Parameter-{self.name}:{self.value}" + + def __eq__(self, other): + if isinstance(other, Parameter) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + +class Variable: + """ + A lightweight class representing a WALKOFF WorkflowVariable or Global + """ + __slots__ = ("id", "name", "value", "description") + + def __init__(self, id, name, value, description=None): + self.id = id + self.name = name + self.value = value + self.description = description + + def __eq__(self, other): + if isinstance(other, self.__class__) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + +class Node: + __slots__ = ("id", "name", "app_name", "app_version", "label", "position", "priority", "errors", "is_valid", "parameters") + + def __init__(self, name, position: Point, label, app_name, app_version, parameters=[], id=None, errors=None, is_valid=True, environment="cloud"): + self.id = id if id is not None else str(uuid.uuid4()) + self.is_valid = is_valid # ToDo: Is this neccessary? + self.name = name + self.environment = environment + self.app_name = app_name + self.app_version = app_version + self.label = label + self.parameters = parameters + self.position = position + self.errors = errors if errors is not None else [] + + if hasattr(self, "priority"): + msg = f"Call super().__init__() prior to setting self.priority in Node subclass {self.__class__.__name__}" + logger.warning(msg) + else: + self.priority = 3 # initialize this to mid level for non-Action node types + + def __repr__(self): + return f"Node-{self.id}" + + def __str__(self): + return f"Node-{self.label}" + + def __gt__(self, other): + return self.priority > other.priority + + def __eq__(self, other): + if isinstance(other, self.__class__) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + +class Action(Node): + __slots__ = ("parameters", "execution_id", "parallelized", "environment", "authentication", "sharing", "private_id") + + def __init__(self, name, position, app_name, app_version, label, priority, environment, sharing=False, private_id="", verified=False, parallelized=False, parameters=None, id=None, execution_id=None, errors=None, is_valid=None, authentication=[], app_id="", **kwargs): + super().__init__(name, position, label, app_name, app_version, id=id, errors=errors, is_valid=is_valid) + self.parameters = parameters if parameters is not None else list() + self.parallelized = parallelized + self.priority = priority + self.execution_id = execution_id + self.authentication = authentication + + self.sharing = sharing + self.private_id = private_id + + def __str__(self): + return f"Action: {self.label}::{self.id}" + + def __repr__(self): + return f"Action: {self.label}::{self.id}" + + def __eq__(self, other): + if isinstance(other, self.__class__) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + +class Condition(Node): + __slots__ = ("conditional",) + + def __init__(self, name, position: Point, app_name, app_version, label, conditional, id=None, errors=None, + is_valid=None): + super().__init__(name, position, label, app_name, app_version, id, errors, is_valid) + self.conditional = conditional + self.priority = 3 # Conditions have a fixed, mid valued priority + + def __str__(self): + return f"Condition: {self.label}::{self.id}" + + def __repr__(self): + return f"Condition: {self.label}::{self.id}" + + def __eq__(self, other): + if isinstance(other, self.__class__) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + @staticmethod + def format_node_names(nodes): + # We need to format space delimited names into underscore delimited names + names_to_modify = {node.label for node in nodes.values() if node.label.count(' ') > 0} + formatted_nodes = {} + for node in nodes.values(): + formatted_name = node.label.strip().replace(' ', '_') + + if formatted_name in names_to_modify: # we have to check for a name conflict as described above + logger.error(f"Error processing condition. {node.label} or {formatted_name} must be renamed.") + + formatted_nodes[formatted_name] = node + return formatted_nodes + + def __call__(self, parents, children, accumulator) -> str: + parent_symbols = {k: ParentSymbol(accumulator[v.id]) for k, v in self.format_node_names(parents).items()} + children_symbols = {k: ChildSymbol(v.id) for k, v in self.format_node_names(children).items()} + syms = make_symbol_table(use_numpy=False, **parent_symbols, **children_symbols) + aeval = Interpreter(usersyms=syms, no_for=True, no_while=True, no_try=True, no_functiondef=True, no_ifexp=True, + no_listcomp=True, no_augassign=True, no_assert=True, no_delete=True, no_raise=True, + no_print=True, use_numpy=False, builtins_readonly=True, + readonly_symbols=children_symbols.keys()) + + aeval(self.conditional) + child_id = getattr(aeval.symtable.get("selected_node", None), "id", None) + + if len(aeval.error) > 0: + raise ConditionException + + return child_id + + +class Trigger(Node): + __slots__ = ("trigger_schema",) + + def __init__(self, name, position: Point, app_name, app_version, label, trigger_schema, id=None, errors=None, + is_valid=None): + super().__init__(name, position, label, app_name, app_version, id, errors, is_valid) + self.trigger_schema = trigger_schema + + def __str__(self): + return f"Trigger: {self.label}::{self.id}" + + def __repr__(self): + return f"Trigger: {self.label}::{self.id}" + + def __eq__(self, other): + if isinstance(other, self.__class__) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + def __call__(self, data): + """ A trigger simply echos the data it was given """ + result = data.trigger_data + logger.debug(f"Executed {self.name}-{self.id} with result: {result}") + return result + + +class Transform(Node): + __slots__ = ("transform", "parameter") + + def __init__(self, name, position: Point, app_name, app_version, label, transform, parameter=None, id=None, + errors=None, is_valid=None): + super().__init__(name, position, label, app_name, app_version, id, errors, is_valid) + self.transform = transform.lower() + self.parameter = parameter + self.priority = 3 # Transforms have a fixed, mid valued priority + + def __str__(self): + return f"Transform: {self.label}::{self.id}" + + def __repr__(self): + return f"Transform: {self.label}::{self.id}" + + def __eq__(self, other): + if isinstance(other, self.__class__) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + def __call__(self, data): + """ Execute an action and ship its result """ + logger.debug(f"Attempting execution of: {self.name}-{self.id}") + transform = f"_{self.__class__.__name__}__{self.transform}" + if hasattr(self, transform): + if self.parameter is None: + result = getattr(self, transform)(data=data) + else: + result = getattr(self, transform)(self.parameter, data=data) + logger.debug(f"Executed {self.name}-{self.id} with result: {result}") + return result + else: + logger.error(f"{self.__class__.__name__} has no method {self.transform}") + + # TODO: add JSON to CSV parsing and vice versa. + def __get_value_at_index(self, index, data=None): + return data[index] + + def __get_value_at_key(self, key, data=None): + return data[key] + + def __split_string_to_array(self, delimiter=' ', data=None): + return data.split(delimiter) + + +class DiGraph: + __slots__ = ("nodes", "edges", "rev_adjacency") + + def __init__(self, nodes, edges): + self.nodes = {} + self.add_nodes(nodes) + self.edges = {node: set() for node in self.nodes.values()} + self.rev_adjacency = {} # all edges inverted for quickly getting parents of a node + self.add_edges(edges) + + def __eq__(self, other): + if isinstance(other, self.__class__) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + def add_edges(self, edges): + try: + iter(edges) # check we got an iterable + if callable(getattr(edges, "items", None)): # check if it's a dictionary + for src, dest in edges.items(): + if src in self.edges: + self.edges[src].add(dest) + else: # This edge introduces new nodes so lets add them + self.nodes[src.id] = src + self.nodes[dest.id] = dest + self.edges[src] = {dest} + if dest in self.rev_adjacency: + self.edges[dest].add(src) + else: + self.edges[dest] = {src} + else: # it's a different iterable + for edge in edges: + if not (isinstance(edge, Branch) or (isinstance(edge, tuple) and not len(edge) == 2)): + raise TypeError # it must be an iterable of (src, dest) edges + src = edge[0] + dest = edge[1] + if src in self.edges: + self.edges[src].add(dest) + else: + self.edges[src] = {dest} + + if dest in self.rev_adjacency: + self.rev_adjacency[dest].add(src) + else: + self.rev_adjacency[dest] = {src} + except TypeError: + return + + def add_edge(self, src, dest): + self.add_edges({src, dest}) + + def add_nodes(self, nodes): + self.nodes = {node.id: node for node in nodes} + + def add_node(self, node): + return self.add_nodes([node]) + + def successors(self, node): + return self.edges[node] + + def predecessors(self, node): + return self.rev_adjacency[node] + + +# TODO: Maybe look into pooling nodes/branches and sharing them across a workflow to save memory? +class Workflow(DiGraph): + __slots__ = ("start", "id", "id", "is_valid", "name", "execution_id", "workflow_variables", + "triggers", "actions", "errors", "description", "tags", "owner", "org", "execution_org", "schedules", "sharing") + + def __init__(self, name, start, actions: [Action], branches: [Branch], workflow_variables=[], + triggers=[], id=None, execution_id=None, + is_valid=None, errors=None, description=None, tags=None, owner={}, org={}, execution_org={}, schedules=[], sharing="private"): + super().__init__(nodes=[*actions, *triggers], edges=branches) + + self.start = start + self.id = id if id is not None else str(uuid.uuid4()) + self.is_valid = is_valid if is_valid is not None else self.validate() + self.name = name + self.execution_id = execution_id + self.workflow_variables = workflow_variables if workflow_variables is not None else [] + self.triggers = triggers + self.actions = actions + self.errors = errors if errors is not None else [] + self.description = description + self.owner = owner + self.org = org + self.execution_org = execution_org + self.schedules=schedules + self.tags = tags if tags is not None else [] + + def __eq__(self, other): + if isinstance(other, self.__class__) and self.__slots__ == other.__slots__: + return attrs_equal(self, other) + return False + + def __hash__(self): + return hash(id(self)) + + def validate(self): + # TODO: add in workflow validation from old implementation + return True + + @staticmethod + def dereference_environment_variables(data): + return {ev["id"]: (ev["name"], ev["value"]) for ev in data.get("environment_variables", [])} + + def get_dependents(self, node): + """ + BFS to get all nodes dependent on the current node. This includes the current node. + """ + visited = {node} + queue = deque([node]) + + while queue: + node = queue.pop() + children = set(self.successors(node)) + for child in children: + if child not in visited: + queue.appendleft(child) + visited.add(child) + + return visited diff --git a/functions/onprem/README.md b/functions/onprem/README.md new file mode 100644 index 00000000..51e5782d --- /dev/null +++ b/functions/onprem/README.md @@ -0,0 +1,40 @@ +# ONPREM code +* Onprem means it's supposed to be ran on a server of the customer, and not in the cloud. These are tweaked to hit the API and look for new work throughout workflows. Everything is handled by the initial main.go, which launches the others. + +## orborus.go - Handles NEW workflows - Same as WALKOFF UMPIRE +* Executes and controls the docker environment used by workers. +* A worker is deployed for every execution. +* The apps are responsible for callbacks to the backend themselves. +* After the worker is deployed / running, the execution ID is removed from the workflowqueue API. + +# worker/worker.go - one for each workflow requiring onprem stuff +* Handles a workflow from start to finish as long as the action ID. +* Starting and stopping APPS in docker. + +# app_sdk +* The new APP sdk based on https://github.com/nsacyber/WALKOFF/tree/1.0.0-alpha.1/app_sdk +* Fully functional with WALKOFF apps, which means its also functional with Cloud Function apps (these are now essentially the same with a few small tweaks) + +# Images - all valid images are located here currently +https://hub.docker.com/r/frikky/shuffle + +## Setup with Dockerhub +Requred - access to: https://hub.docker.com/r/docker/frikky/shuffle/general +Login: +``` +docker login +``` + +Update worker: +``` +cd worker +docker build . -t frikky/shuffle:worker +docker push frikky/shuffle:worker +``` + +Update app_sdk: +``` +cd app_sdk +docker build . -t frikky/shuffle:app_sdk +docker push frikky/shuffle:app_sdk +``` diff --git a/functions/onprem/app_sdk/Dockerfile b/functions/onprem/app_sdk/Dockerfile new file mode 100644 index 00000000..44294a5b --- /dev/null +++ b/functions/onprem/app_sdk/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.7-alpine as base + +FROM base as builder +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +RUN mkdir /install +WORKDIR /install + +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +FROM base + +COPY --from=builder /install /usr/local +COPY __init__.py /app/walkoff_app_sdk/__init__.py +COPY app_base.py /app/walkoff_app_sdk/app_base.py diff --git a/functions/onprem/app_sdk/README.md b/functions/onprem/app_sdk/README.md new file mode 100644 index 00000000..0e7362c6 --- /dev/null +++ b/functions/onprem/app_sdk/README.md @@ -0,0 +1,3 @@ +# app_sdk +This is the SDK used for apps to behave like they should. +To change it in the backend, upload it to Buckets/shuffler.appspot.com/generated_apps/baseline. diff --git a/functions/onprem/app_sdk/__init__.py b/functions/onprem/app_sdk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/functions/onprem/app_sdk/app_base.py b/functions/onprem/app_sdk/app_base.py new file mode 100644 index 00000000..795c0f2a --- /dev/null +++ b/functions/onprem/app_sdk/app_base.py @@ -0,0 +1,437 @@ +import os +import sys +import time +import json +import logging +import requests + +class AppBase: + """ The base class for Python-based apps in Shuffle, handles logging and callbacks configurations""" + __version__ = None + app_name = None + + def __init__(self, redis=None, logger=None, console_logger=None):#, docker_client=None): + self.logger = logger if logger is not None else logging.getLogger("AppBaseLogger") + self.redis=redis + self.console_logger = logger if logger is not None else logging.getLogger("AppBaseLogger") + + # apikey is for the user / org + # authorization is for the specific workflow + self.url = os.getenv("CALLBACK_URL", "https://shuffler.io") + self.action = os.getenv("ACTION", "") + self.apikey = os.getenv("FUNCTION_APIKEY", "") + self.authorization = os.getenv("AUTHORIZATION", "") + self.current_execution_id = os.getenv("EXECUTIONID", "") + + if len(self.action) == 0: + print("ACTION env not defined") + sys.exit(0) + if len(self.apikey) == 0: + print("FUNCTION_APIKEY env not defined") + sys.exit(0) + if len(self.authorization) == 0: + print("AUTHORIZATION env not defined") + sys.exit(0) + if len(self.current_execution_id) == 0: + print("EXECUTIONID env not defined") + sys.exit(0) + + if isinstance(self.action, str): + self.action = json.loads(self.action) + + async def execute_action(self, action): + # FIXME - add request for the function STARTING here. Use "results stream" or something + # PAUSED, AWAITING_DATA, PENDING, COMPLETED, ABORTED, EXECUTING, SUCCESS, FAILURE + + # !!! Let this line stay - its used for some horrible codegeneration / stitching !!! # + #STARTCOPY + stream_path = "/api/v1/streams" + action_result = { + "action": action, + "authorization": self.authorization, + "execution_id": self.current_execution_id, + "result": "", + "started_at": int(time.time()), + "status": "EXECUTING" + } + self.logger.info("ACTION RESULT: %s", action_result) + + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer %s" % self.apikey + } + + # Add async logger + # self.console_logger.handlers[0].stream.set_execution_id() + self.logger.info("Before initial stream result") + try: + ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result) + self.logger.info("Workflow: %d" % ret.status_code) + if ret.status_code != 200: + self.logger.info(ret.text) + except requests.exceptions.ConnectionError as e: + print("Connectionerror: %s" % e) + return + self.logger.info("AFTER initial stream result") + + # Verify whether there are any parameters with ACTION_RESULT required + # If found, we get the full results list from backend + + fullexecution = {} + try: + tmpdata = { + "authorization": self.authorization, + "execution_id": self.current_execution_id + } + + self.logger.info("Auth: %s", tmpdata) + + self.logger.info("Before FULLEXEC stream result") + ret = requests.post( + "%s/api/v1/streams/results" % (self.url), + headers=headers, + json=tmpdata + ) + + if ret.status_code == 200: + fullexecution = ret.json() + else: + self.logger.info("Error: Data: ", ret.json()) + self.logger.info("Error with status code for results. Crashing because ACTION_RESULTS or WORKFLOW_VARIABLE can't be handled. Status: %d" % ret.status_code) + return + except requests.exceptions.ConnectionError as e: + self.logger.info("Connectionerror: %s" % e) + return + + self.logger.info("AFTER FULLEXEC stream result") + + def parse_params(action, fullexecution, parameter): + jsonparsevalue = "$." + if parameter["variant"] == "WORKFLOW_VARIABLE": + for item in fullexecution["workflow"]["workflow_variables"]: + if parameter["action_field"] == item["name"]: + parameter["value"] = item["value"] + break + elif parameter["variant"] == "ACTION_RESULT": + # FIXME - calculate value based on action_field and $if prominent + # FIND THE RIGHT LABEL + # GET THE LABEL'S RESULT + + tmpvalue = "" + print(parameter["action_field"]) + + if parameter["action_field"] == "Execution Argument": + tmpvalue = fullexecution["execution_argument"] + else: + self.logger.info("WORKFLOW EXEC BELOW") + self.logger.info(fullexecution) + self.logger.info(fullexecution["results"]) + self.logger.info(fullexecution["workflow"]["actions"]) + self.logger.info("ACTIONS ABOVE") + # redundancy.. + + tmpid = "" + for item in fullexecution["workflow"]["actions"]: + if item["label"] == parameter["action_field"]: + tmpid = item["id"] + + if not tmpid: + self.logger.error("Value not found for that id: %s. Exiting" % parameter["action_field"]) + raise Exception("Value for %s was not found in workflow actions" % parameter["action_field"]) + + for subresult in fullexecution["results"]: + if subresult["action"]["id"] == tmpid: + tmpvalue = subresult["result"] + break + + if not tmpvalue: + self.logger.error("Value not found for label %s. Exiting" % parameter["action_field"]) + raise Exception("Value for %s was not found" % parameter["action_field"]) + + # Override locally with JSON data + if parameter["value"].startswith(jsonparsevalue): + parsersplit = parameter["value"].split(".") + + # Convert to json here + self.logger.info("JSON HANDLING: %s" % tmpvalue) + tmpvalue = tmpvalue.replace("\'", "\"") + try: + if isinstance(tmpvalue, str): + newtmp = json.loads(tmpvalue) + except json.decoder.JSONDecodeError as e: + raise Exception("JSON error: %s" % e) + + try: + #previousvalue = parsersplit[1] + for value in parsersplit[1:]: + # Might need to be recursive here, because it can go + # multiple layers ($.result.#.test.users.#.name) + # That would give executions of: + # 1 + result.length + users.length + # This is also just for one param + #if parsersplit[1:][count] == "#": + if value == "#": + # This means we already have an array + # for item in newtmp: + self.logger.info("THERE SHOULD BE A LOOP HERE") + # This works, but it needs to be split into multiples hurr + # Whenever there is a loop, there is a need to + # check whether there are more loops, then do + # recursion to all the bottom leaves + + #paramnamevalue.append(newtmp + newtmp = newtmp[0] + # Choose numero uno which will then be handled by the next again + # params[parameter["name"]].append(value.nextitem) + else: + newtmp = newtmp[value] + except KeyError as e: + return "KeyError: %s" % e, "" + except IndexError as e: + return "IndexError: %s" % e, "" + + parameter["value"] = str(newtmp) + else: + parameter["value"] = tmpvalue + + return "", parameter["value"] + + def run_validation(sourcevalue, check, destinationvalue): + self.logger.info("Checking %s %s %s" % (sourcevalue, check, destinationvalue)) + + if check == "=" or check.lower() == "equals": + if sourcevalue.lower() == destinationvalue.lower(): + return True + elif check == "!=" or check.lower() == "does not equal": + if sourcevalue.lower() != destinationvalue.lower(): + return True + elif check.lower() == "startswith": + if sourcevalue.lower().startswith(destinationvalue.lower()): + return True + elif check.lower() == "endswith": + if sourcevalue.lower().endswith(destinationvalue.lower()): + return True + elif check.lower() == "contains": + if destinationvalue.lower() in sourcevalue.lower(): + return True + else: + self.logger.info("Condition: can't handle %s yet. Setting to true" % check) + + return False + + def check_branch_conditions(action, fullexecution): + # relevantbranches = workflow.branches where destination = action + try: + if fullexecution["workflow"]["branches"] == None or len(fullexecution["workflow"]["branches"]) == 0: + return True, "" + except KeyError: + return True, "" + + relevantbranches = [] + for branch in fullexecution["workflow"]["branches"]: + if branch["destination_id"] != action["id"]: + continue + + self.logger.info("Relevant branch: %s" % branch) + + # Remove anything without a condition + try: + if (branch["conditions"]) == 0 or branch["conditions"] == None: + continue + except KeyError: + continue + + self.logger.info("Relevant conditions: %s" % branch["conditions"]) + successful_conditions = [] + failed_conditions = [] + for condition in branch["conditions"]: + self.logger.info("Getting condition value of %s" % condition) + + # Parse all values first here + sourcevalue = condition["source"]["value"] + if condition["source"]["variant"] == "" or condition["source"]["variant"]== "STATIC_VALUE": + condition["source"]["variant"]= "STATIC_VALUE" + else: + check, sourcevalue = parse_params(action, fullexecution, condition["source"]) + if check: + return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check) + + print(sourcevalue) + destinationvalue = condition["destination"]["value"] + if condition["destination"]["variant"]== "" or condition["destination"]["variant"]== "STATIC_VALUE": + condition["destination"]["variant"] = "STATIC_VALUE" + else: + check, destinationvalue = parse_params(action, fullexecution, condition["destination"]) + if check: + return False, "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check) + + available_checks = [ + "=", + "equals", + "!=", + "does not equal", + ">", + "larger than", + "<", + "less than", + ">=", + "<=", + "startswith", + "endswith", + "contains", + "re", + "matches regex", + ] + + # FIXME - what should I do here? + if not condition["condition"]["value"] in available_checks: + self.logger.info("Skipping %s %s %s because %s is invalid." % (sourcevalue, condition["condition"]["value"], destinationvalue, condition["condition"]["value"])) + continue + + #print(destinationvalue) + if not run_validation(sourcevalue, condition["condition"]["value"], destinationvalue): + self.logger.info("Failed condition check for %s %s %s." % (sourcevalue, condition["condition"]["value"], destinationvalue)) + return False, "Failed condition: %s %s %s" % (sourcevalue, condition["condition"]["value"], destinationvalue) + + + # Make a general parser here, at least to get param["name"] = param["value"] in maparameter[string]string + #for condition in branch.conditons: + + return True, "" + + # Checks whether conditions are met, otherwise set + branchcheck, tmpresult = check_branch_conditions(action, fullexecution) + if not branchcheck: + self.logger.info("Failed one or more branch conditions.") + action_result["result"] = tmpresult + action_result["status"] = "SKIPPED" + try: + ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result) + self.logger.info("Result: %d" % ret.status_code) + if ret.status_code != 200: + self.logger.info(ret.text) + except requests.exceptions.ConnectionError as e: + self.logger.exception(e) + + return + + # Replace name cus there might be issues + # Not doing lower() as there might be user-made functions + actionname = action["name"] + if " " in actionname: + actionname.replace(" ", "_", -1) + #if action.generated: + # actionname = actionname.lower() + + # Runs the actual functions + try: + func = getattr(self, actionname, None) + if func == None: + self.logger.debug("Failed executing %s because func is None." % actionname) + action_result["status"] = "FAILURE" + action_result["result"] = "Function %s doesn't exist." % actionname + elif callable(func): + try: + if len(action["parameters"]) < 1: + result = await func() + else: + # Potentially parse JSON here + # FIXME - add potential authentication as first parameter(s) here + # params[parameter["name"]] = parameter["value"] + #print(fullexecution["authentication"] + # What variables are necessary here tho hmm + + params = {} + try: + for item in action["authentication"]: + print(key, value) + params[item["key"]] = item["value"] + except KeyError: + pass + #action["authentication"] + + # calltimes is used to handle forloops in the app itself. + # 2 kinds of loop - one in gui with one app each, and one like this, + # which is super fast, but has a bad overview (potentially good tho) + calltimes = 1 + result = "" + paramiter = [] + for parameter in action["parameters"]: + #self.logger.info(parameter) + #print(fullexecution) + + + check, value = parse_params(action, fullexecution, parameter) + if check: + raise Exception(check) + + params[parameter["name"]] = value + # p["value"] + + # FIXME - this is horrible, but works for now + #for i in range(calltimes): + result += await func(**params) + + action_result["status"] = "SUCCESS" + action_result["result"] = str(result) + if action_result["result"] == "": + action_result["result"] = result + + self.logger.debug(f"Executed {action['label']}-{action['id']} with result: {result}") + self.logger.debug(f"Data: %s" % action_result) + except TypeError as e: + action_result["status"] = "FAILURE" + action_result["result"] = "TypeError: %s" % str(e) + else: + print("Not callable?") + self.logger.error(f"App {self.__class__.__name__}.{action['name']} is not callable") + action_result["status"] = "FAILURE" + action_result["result"] = "Function %s is not callable." % actionname + + except Exception as e: + print(f"Failed to execute: {e}") + self.logger.exception(f"Failed to execute {e}-{action['id']}") + action_result["status"] = "FAILURE" + action_result["result"] = "Exception: %s" % e + + action_result["completed_at"] = int(time.time()) + + # I wonder if this actually works + self.logger.info("Before last stream result") + try: + ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result) + self.logger.info("Result: %d" % ret.status_code) + if ret.status_code != 200: + self.logger.info(ret.text) + except requests.exceptions.ConnectionError as e: + self.logger.exception(e) + return + except TypeError as e: + self.logger.exception(e) + action_result["status"] = "FAILURE" + action_result["result"] = "POST error: %s" % e + self.logger.info("Before typeerror stream result") + ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result) + self.logger.info("Result: %d" % ret.status_code) + if ret.status_code != 200: + self.logger.info(ret.text) + + return + + + #STOPCOPY + # !!! Let the above line stay - its used for some horrible codegeneration / stitching !!! # + + @classmethod + async def run(cls): + """ Connect to Redis and HTTP session, await actions """ + logging.basicConfig(format="{asctime} - {name} - {levelname}:{message}", style='{') + logger = logging.getLogger(f"{cls.__name__}") + logger.setLevel(logging.DEBUG) + + app = cls(redis=None, logger=logger, console_logger=logger) + + # Authorization for the app/function to control the workflow + # Function will crash if its wrong, which it probably should. + + await app.execute_action(app.action) diff --git a/functions/onprem/app_sdk/requirements.txt b/functions/onprem/app_sdk/requirements.txt new file mode 100644 index 00000000..804abb1b --- /dev/null +++ b/functions/onprem/app_sdk/requirements.txt @@ -0,0 +1,2 @@ +requests +urllib3 diff --git a/functions/onprem/app_sdk/update_dockerhub b/functions/onprem/app_sdk/update_dockerhub new file mode 100644 index 00000000..27ce940b --- /dev/null +++ b/functions/onprem/app_sdk/update_dockerhub @@ -0,0 +1,4 @@ +#!/bin/bash +docker rmi frikky/shuffle:app_sdk +docker build . -t frikky/shuffle:app_sdk +docker push frikky/shuffle:app_sdk diff --git a/functions/onprem/orborus/Dockerfile b/functions/onprem/orborus/Dockerfile new file mode 100644 index 00000000..1a15a2e6 --- /dev/null +++ b/functions/onprem/orborus/Dockerfile @@ -0,0 +1,14 @@ +from golang as builder + +RUN mkdir /app +WORKDIR /app +COPY orborus.go /app/orborus.go + +RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client + +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus . + +from scratch +COPY --from=builder /app/ / + +CMD ["./orborus"] diff --git a/functions/onprem/orborus/build.sh b/functions/onprem/orborus/build.sh new file mode 100644 index 00000000..3110e77a --- /dev/null +++ b/functions/onprem/orborus/build.sh @@ -0,0 +1,4 @@ +docker rmi frikky/shuffle:orborus --force + +docker build . -t frikky/shuffle:orborus +docker push frikky/shuffle:orborus diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go new file mode 100644 index 00000000..cbea6d9b --- /dev/null +++ b/functions/onprem/orborus/orborus.go @@ -0,0 +1,425 @@ +package main + +/* + Orborus exists to listen for new workflow executions and deploy workers. +*/ + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + dockerclient "github.com/docker/docker/client" + //network "github.com/docker/docker/api/types/network" + //natting "github.com/docker/go-connections/nat" +) + +var baseUrl = os.Getenv("BASE_URL") +var baseimagename = "frikky/shuffle" +var dockerApiVersion = os.Getenv("DOCKER_API_VERSION") +var environment = os.Getenv("ENVIRONMENT_NAME") +var orgId = os.Getenv("ORG_ID") +var workerTimeout = 600 + +type ExecutionRequestWrapper struct { + Data []ExecutionRequest `json:"data"` +} + +type ExecutionRequest struct { + ExecutionId string `json:"execution_id"` + WorkflowId string `json:"workflow_id"` + Authorization string `json:"authorization"` + ExecutionArgument string `json:"execution_argument"` + Environments []string `json:"environments"` + Status string `json:"status"` +} + +// Deploys the internal worker whenever something happens +func deployWorker(cli *dockerclient.Client, image string, identifier string, env []string) error { + // Binds is the actual "-v" volume. + hostConfig := &container.HostConfig{ + LogConfig: container.LogConfig{ + Type: "json-file", + Config: map[string]string{}, + }, + Binds: []string{ + "/var/run/docker.sock:/var/run/docker.sock:rw", + }, + } + + // ROFL: https://docker-py.readthedocs.io/en/1.4.0/volumes/ + config := &container.Config{ + Image: image, + Env: env, + } + //Volumes: map[string]struct{}{ + // "/var/run/docker.sock": {}, + //}, + + cont, err := cli.ContainerCreate( + context.Background(), + config, + hostConfig, + nil, + identifier, + ) + + if err != nil { + log.Println(err) + return err + } + + cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{}) + log.Printf("Container %s is created", cont.ID) + return nil +} + +func stopWorker(containername string) error { + ctx := context.Background() + + cli, err := dockerclient.NewEnvClient() + if err != nil { + log.Println("Unable to create docker client") + return err + } + + // containers, err := cli.ContainerList(ctx, types.ContainerListOptions{ + // All: true, + // }) + + if err := cli.ContainerStop(ctx, containername, nil); err != nil { + log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err) + } + + removeOptions := types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + } + + if err := cli.ContainerRemove(ctx, containername, removeOptions); err != nil { + log.Printf("Unable to remove container: %s", err) + } + + return nil +} + +func initializeImages(dockercli *dockerclient.Client) { + ctx := context.Background() + + // check whether theyre the same first + images := []string{ + fmt.Sprintf("docker.io/%s:app_sdk", baseimagename), + fmt.Sprintf("docker.io/%s:worker", baseimagename), + } + + pullOptions := types.ImagePullOptions{} + for _, image := range images { + reader, err := dockercli.ImagePull(ctx, image, pullOptions) + if err != nil { + log.Printf("Failed getting %s", image) + continue + } + + io.Copy(os.Stdout, reader) + log.Printf("Successfully downloaded and built %s", image) + } +} + +// Initial loop etc +func main() { + zombiecheck() + log.Println("Setting up execution environment") + + //FIXME + if baseUrl == "" { + baseUrl = "https://shuffler.io" + //baseUrl = "http://localhost:5001" + } + + if orgId == "" { + log.Printf("Org not defined. Set variable ORG_ID based on your org") + os.Exit(3) + } + + log.Printf("Running towards %s with Org %s", baseUrl, orgId) + + if environment == "" { + environment = "onprem" + log.Printf("Defaulting to environment name %s. Set environment variable ENVIRONMENT_NAME to change. This should be the same as in the frontend action.", environment) + } + + // FIXME - during init, BUILD and/or LOAD worker and app_sdk + // Build/load app_sdk so it can be loaded as 127.0.0.1:5000/walkoff_app_sdk + dockercli, err := dockerclient.NewEnvClient() + if err != nil { + fmt.Println("Unable to create docker client") + os.Exit(3) + } + + log.Printf("--- Setting up Docker environment. Downloading worker and App SDK! ---") + initializeImages(dockercli) + workerImage := fmt.Sprintf("%s:worker", baseimagename) + + log.Printf("--- Finished configuring docker environment ---\n") + + // FIXME - time limit + sleepTime := 10 + client := &http.Client{} + + fullUrl := fmt.Sprintf("%s/api/v1/workflows/queue", baseUrl) + req, err := http.NewRequest( + "GET", + fullUrl, + nil, + ) + + if err != nil { + log.Printf("Failed making request builder: %s", err) + os.Exit(3) + } + + zombiecounter := 0 + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Org-Id", orgId) + log.Printf("Getting data from %s", fullUrl) + hasStarted := false + for { + //log.Printf("Prerequest") + newresp, err := client.Do(req) + //log.Printf("Postrequest") + if err != nil { + log.Printf("Failed making request: %s", err) + zombiecounter += 1 + if zombiecounter*sleepTime > workerTimeout { + zombiecheck() + zombiecounter = 0 + } + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + // FIXME - add check for StatusCode + if newresp.StatusCode != 200 { + if hasStarted { + log.Printf("Bad statuscode: %d", newresp.StatusCode) + } + } else { + hasStarted = true + } + + body, err := ioutil.ReadAll(newresp.Body) + if err != nil { + log.Printf("Failed reading body: %s", err) + zombiecounter += 1 + if zombiecounter*sleepTime > workerTimeout { + zombiecheck() + zombiecounter = 0 + } + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + var executionRequests ExecutionRequestWrapper + err = json.Unmarshal(body, &executionRequests) + if err != nil { + log.Printf("Failed executionrequest in queue unmarshaling: %s", err) + sleepTime = 10 + zombiecounter += 1 + if zombiecounter*sleepTime > workerTimeout { + zombiecheck() + zombiecounter = 0 + } + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + if hasStarted && len(executionRequests.Data) > 0 { + log.Println(string(body)) + } + + if len(executionRequests.Data) == 0 { + zombiecounter += 1 + if zombiecounter*sleepTime > workerTimeout { + zombiecheck() + zombiecounter = 0 + } + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + // New, abortable version. Should check executionid and remove everything else + var toBeRemoved ExecutionRequestWrapper + for _, execution := range executionRequests.Data { + log.Println(execution.ExecutionArgument) + if execution.Status == "ABORT" || execution.Status == "FAILED" { + log.Printf("Executionstatus issue: ", execution.Status) + } + // Now, how do I execute this one? + // FIXME - if error, check the status of the running one. If it's bad, send data back. + containerName := fmt.Sprintf("worker-%s", execution.ExecutionId) + env := []string{ + fmt.Sprintf("AUTHORIZATION=%s", execution.Authorization), + fmt.Sprintf("EXECUTIONID=%s", execution.ExecutionId), + fmt.Sprintf("DOCKER_API_VERSION=%s", dockerApiVersion), + fmt.Sprintf("ENVIRONMENT_NAME=%s", environment), + fmt.Sprintf("BASE_URL=%s", baseUrl), + } + + err = deployWorker(dockercli, workerImage, containerName, env) + if err != nil { + stats, err := dockercli.ContainerInspect(context.Background(), containerName) + if err != nil { + log.Printf("Failed checking worker %s", execution.ExecutionId) + continue + } + + containerStatus := stats.ContainerJSONBase.State.Status + if containerStatus != "running" { + log.Printf("Status of %s is %s. Should be running. Will reset", containerName, containerStatus) + err = stopWorker(containerName) + if err != nil { + log.Printf("Failed stopping worker %s", execution.ExecutionId) + continue + } + + err = deployWorker(dockercli, workerImage, containerName, env) + if err != nil { + log.Printf("Failed executing worker %s in state %s", execution.ExecutionId, containerStatus) + } + } else { + // Should basically never hit here rofl + log.Printf("ERROR: I HAVE NO IDEA WHAT WENT WRONG. CHECK %s", containerName) + } + } + + log.Printf("%s is deployed and to being removed from queue.", execution.ExecutionId) + zombiecounter += 1 + toBeRemoved.Data = append(toBeRemoved.Data, execution) + } + + // Removes handled workflows (worker is made) + if len(toBeRemoved.Data) > 0 { + confirmUrl := fmt.Sprintf("%s/api/v1/workflows/queue/confirm", baseUrl) + + data, err := json.Marshal(toBeRemoved) + if err != nil { + log.Printf("Failed removal marshalling: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + result, err := http.NewRequest( + "POST", + confirmUrl, + bytes.NewBuffer([]byte(data)), + ) + + if err != nil { + log.Printf("Failed building confirm request: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + result.Header.Add("Content-Type", "application/json") + result.Header.Add("Org-Id", orgId) + + resultResp, err := client.Do(result) + if err != nil { + log.Printf("Failed making confirm request: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + body, err := ioutil.ReadAll(resultResp.Body) + if err != nil { + log.Printf("Failed reading confirm body: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + log.Println(string(body)) + + // FIXME - remove these + //log.Println(string(body)) + //log.Println(resultResp) + if len(toBeRemoved.Data) == len(executionRequests.Data) { + log.Println("Should remove ALL!") + } else { + log.Printf("NOT IMPLEMENTED: Should remove %d workflows from backend because they're executed!", len(toBeRemoved.Data)) + } + } + + time.Sleep(time.Duration(sleepTime) * time.Second) + } +} + +// FIXME - add this to remove exited workers +// Should it check what happened to the execution? idk +func zombiecheck() error { + log.Println("Running zombiecheck") + ctx := context.Background() + + dockercli, err := dockerclient.NewEnvClient() + if err != nil { + log.Println("Unable to create docker client") + return err + } + + containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{ + All: true, + }) + + stopContainers := []string{} + removeContainers := []string{} + for _, container := range containers { + for _, name := range container.Names { + // FIXME - add name_version_uid_uid regex check as well + if !strings.HasPrefix(name, "/worker") { + continue + } + + if container.State != "running" { + removeContainers = append(removeContainers, container.ID) + } + + // stopcontainer & removecontainer + currenttime := time.Now().Unix() + if container.State == "running" && currenttime-container.Created > int64(workerTimeout) { + stopContainers = append(stopContainers, container.ID) + } + } + } + + // FIXME - add killing of apps with same execution ID too + for _, containername := range stopContainers { + if err := dockercli.ContainerStop(ctx, containername, nil); err != nil { + log.Printf("Unable to stop container: %s", err) + } else { + log.Printf("Stopped container %s", containername) + } + } + + removeOptions := types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + } + + for _, containername := range removeContainers { + if err := dockercli.ContainerRemove(ctx, containername, removeOptions); err != nil { + log.Printf("Unable to remove container: %s", err) + } else { + log.Printf("Removed container %s", containername) + } + } + + return nil +} diff --git a/functions/onprem/orborus/run.sh b/functions/onprem/orborus/run.sh new file mode 100644 index 00000000..753d1d2b --- /dev/null +++ b/functions/onprem/orborus/run.sh @@ -0,0 +1,5 @@ +docker run \ + --env ORG_ID=$ORG_ID \ + --env BASE_URL=$BASE_URL \ + -v /var/run/docker.sock:/var/run/docker.sock \ + frikky/shuffle:orborus diff --git a/functions/onprem/worker/Dockerfile b/functions/onprem/worker/Dockerfile new file mode 100644 index 00000000..2f8971f8 --- /dev/null +++ b/functions/onprem/worker/Dockerfile @@ -0,0 +1,21 @@ +#from golang as builder +# +#RUN mkdir /app +#WORKDIR /app +#COPY worker.go /app/worker.go +# +#RUN go get github.com/docker/docker/api/types +#RUN go get github.com/docker/docker/api/types/container +#RUN go get -u github.com/docker/docker/client +# +#RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker . +# + +# THis is a workaround until I get docker/docker to build in a dockerfile +# PS: This is tricky to google. +# Might not work on some machines. +from scratch +#COPY --from=builder /app/ / +COPY worker.bin /worker.bin + +CMD ["./worker.bin"] diff --git a/functions/onprem/worker/run b/functions/onprem/worker/run new file mode 100644 index 00000000..3f1f1f0a --- /dev/null +++ b/functions/onprem/worker/run @@ -0,0 +1,15 @@ +echo "Compiling program" +CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin . + +echo "Fixing docker env" +docker rmi frikky/shuffle:worker --force +docker build . -t frikky/shuffle:worker +docker push frikky/shuffle:worker + +#docker run \ +# --env "AUTHORIZATION=ASD" \ +# --env "DOCKER_API_VERSION=1.39" \ +# --env "EXECUTIONID=ASD" \ +# --env "BASE_URI=$BASE_URI" \ +# -v /var/run/docker.sock:/var/run/docker.sock \ +# frikky/shuffle:worker diff --git a/functions/onprem/worker/worker.bin b/functions/onprem/worker/worker.bin new file mode 100755 index 00000000..ae579699 Binary files /dev/null and b/functions/onprem/worker/worker.bin differ diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go new file mode 100644 index 00000000..310a7f1d --- /dev/null +++ b/functions/onprem/worker/worker.go @@ -0,0 +1,814 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + //"io" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + dockerclient "github.com/docker/docker/client" +) + +var environment = os.Getenv("ENVIRONMENT_NAME") +var baseUrl = os.Getenv("BASE_URL") +var baseimagename = "frikky/shuffle" + +type Condition struct { + AppName string `json:"app_name"` + AppVersion string `json:"app_version"` + Conditional string `json:"conditional"` + Errors []string `json:"errors"` + ID string `json:"id"` + IsValid bool `json:"is_valid"` + Label string `json:"label"` + Name string `json:"name"` + Position struct { + X float64 `json:"x"` + Y float64 `json:"y"` + } `json:"position"` +} + +type User struct { + Username string `datastore:"Username"` + Password string `datastore:"password,noindex"` + Session string `datastore:"session,noindex"` + Verified bool `datastore:"verified,noindex"` + ApiKey string `datastore:"apikey,noindex"` + Id string `datastore:"id" json:"id"` + Orgs string `datastore:"orgs" json:"orgs"` +} + +type Org struct { + Name string `json:"name"` + Org string `json:"org"` + Users []User `json:"users"` + Id string `json:"id"` +} + +// FIXME: Generate a callback authentication ID? +type WorkflowExecution struct { + Type string `json:"type"` + Status string `json:"status"` + ExecutionId string `json:"execution_id"` + ExecutionArgument string `json:"execution_argument"` + WorkflowId string `json:"workflow_id"` + LastNode string `json:"last_node"` + Authorization string `json:"authorization"` + Result string `json:"result"` + StartedAt int64 `json:"started_at"` + CompletedAt int64 `json:"completed_at"` + ProjectId string `json:"project_id"` + Locations []string `json:"locations"` + Workflow Workflow `json:"workflow"` + Results []ActionResult `json:"results"` +} + +// Added environment for location to execute +type Action struct { + AppName string `json:"app_name" datastore:"app_name"` + AppVersion string `json:"app_version" datastore:"app_version"` + Errors []string `json:"errors" datastore:"errors"` + ID string `json:"id" datastore:"id"` + IsValid bool `json:"is_valid" datastore:"is_valid"` + IsStartNode bool `json:"isStartNode" datastore:"isStartNode"` + Label string `json:"label" datastore:"label"` + Environment string `json:"environment" datastore:"environment"` + Name string `json:"name" datastore:"name"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"` + Position struct { + X float64 `json:"x" datastore:"x"` + Y float64 `json:"y" datastore:"y"` + } `json:"position"` + Priority int `json:"priority" datastore:"priority"` +} + +type Branch struct { + DestinationID string `json:"destination_id" datastore:"destination_id"` + ID string `json:"id" datastore:"id"` + SourceID string `json:"source_id" datastore:"source_id"` + HasError bool `json:"has_errors" datastore: "has_errors"` +} + +type Schedule struct { + Name string `json:"name" datastore:"name"` + Frequency string `json:"frequency" datastore:"frequency"` + ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"` + Id string `json:"id" datastore:"id"` +} + +type Trigger struct { + AppName string `json:"app_name" datastore:"app_name"` + Status string `json:"status" datastore:"status"` + AppVersion string `json:"app_version" datastore:"app_version"` + Errors []string `json:"errors" datastore:"errors"` + ID string `json:"id" datastore:"id"` + IsValid bool `json:"is_valid" datastore:"is_valid"` + IsStartNode bool `json:"isStartNode" datastore:"isStartNode"` + Label string `json:"label" datastore:"label"` + SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"` + LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false` + Environment string `json:"environment" datastore:"environment"` + TriggerType string `json:"trigger_type" datastore:"trigger_type"` + Name string `json:"name" datastore:"name"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"` + Position struct { + X float64 `json:"x" datastore:"x"` + Y float64 `json:"y" datastore:"y"` + } `json:"position"` + Priority int `json:"priority" datastore:"priority"` +} + +type Workflow struct { + Actions []Action `json:"actions" datastore:"actions"` + Branches []Branch `json:"branches" datastore:"branches"` + Triggers []Trigger `json:"triggers" datastore:"triggers"` + Schedules []Schedule `json:"schedules" datastore:"schedules"` + Errors []string `json:"errors,omitempty" datastore:"errors"` + Tags []string `json:"tags,omitempty" datastore:"tags"` + ID string `json:"id" datastore:"id"` + IsValid bool `json:"is_valid" datastore:"is_valid"` + Name string `json:"name" datastore:"name"` + Description string `json:"description" datastore:"description"` + Start string `json:"start" datastore:"start"` + Owner string `json:"owner" datastore:"owner"` + Sharing string `json:"sharing" datastore:"sharing"` + Org []Org `json:"org,omitempty" datastore:"org"` + ExecutingOrg Org `json:"execution_org,omitempty" datastore:"execution_org"` + WorkflowVariables []struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id"` + Name string `json:"name" datastore:"name"` + Value string `json:"value" datastore:"value"` + } `json:"workflow_variables" datastore:"workflow_variables"` +} + +type ActionResult struct { + Action Action `json:"action" datastore:"action"` + ExecutionId string `json:"execution_id" datastore:"execution_id"` + Authorization string `json:"authorization" datastore:"authorization"` + Result string `json:"result" datastore:"result"` + StartedAt int64 `json:"started_at" datastore:"started_at"` + CompletedAt int64 `json:"completed_at" datastore:"completed_at"` + Status string `json:"status" datastore:"status"` +} + +type WorkflowApp struct { + Name string `json:"name" yaml:"name" required:true datastore:"name"` + IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` + ID string `json:"id" yaml:"id" required:false datastore:"id"` + Link string `json:"link" yaml:"link" required:false datastore:"link"` + AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"` + Description string `json:"description" datastore:"description" required:false yaml:"description"` + Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"` + ContactInfo struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Url string `json:"url" datastore:"url" yaml:"url"` + } `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false` + Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"` +} + +// Name = current field +// action_field is the field that it's set to +// value, if Variant = ACTION_RESULT = the second field thingy, which will be +type WorkflowAppActionParameter struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id"` + Name string `json:"name" datastore:"name"` + Value string `json:"value" datastore:"value"` + ActionField string `json:"action_field" datastore:"action_field"` + Variant string `json:"variant", datastore:"variant"` + Required bool `json:"required" datastore:"required"` + Schema struct { + Type string `json:"type" datastore:"type"` + } `json:"schema"` +} + +type WorkflowAppAction struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id"` + Name string `json:"name" datastore:"name"` + NodeType string `json:"node_type" datastore:"node_type"` + Environment string `json:"environment" datastore:"environment"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"` + Returns struct { + Description string `json:"description" datastore:"returns"` + ID string `json:"id" datastore:"id"` + Schema struct { + Type string `json:"type" datastore:"type"` + } `json:"schema" datastore:"schema"` + } `json:"returns" datastore:"returns"` +} + +// removes every container except itself (worker) +func shutdown(executionId string) { + dockercli, err := dockerclient.NewEnvClient() + if err != nil { + log.Printf("Unable to create docker client: %s", err) + shutdown(executionId) + } + + containerOptions := types.ContainerListOptions{ + All: true, + } + + containers, err := dockercli.ContainerList(context.Background(), containerOptions) + if err != nil { + panic(err) + } + _ = containers + + for _, container := range containers { + for _, name := range container.Names { + if strings.Contains(name, executionId) { + // FIXME - reinstate - not here for debugging + //err = removeContainer(container.ID) + //if err != nil { + // log.Printf("Failed removing %s before shutdown.", name) + //} + + break + } + } + + } + + // FIXME: Add an API call to the backend + workflowid := "d0496ad4-d682-4506-bbf9-f926358a4b2a" + fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/executions/%s/abort", baseUrl, workflowid, executionId) + log.Printf("ShutdownURL: %s", fullUrl) + req, err := http.NewRequest( + "GET", + fullUrl, + nil, + ) + + if err != nil { + log.Println("Failed building request: %s", err) + } + + client := &http.Client{} + _, err = client.Do(req) + if err != nil { + log.Printf("Failed abort request: %s", err) + } + + log.Printf("Finished shutdown.") + os.Exit(3) +} + +// Deploys the internal worker whenever something happens +func deployApp(cli *dockerclient.Client, image string, identifier string, env []string) error { + hostConfig := &container.HostConfig{ + LogConfig: container.LogConfig{ + Type: "json-file", + Config: map[string]string{}, + }, + } + + config := &container.Config{ + Image: image, + Env: env, + } + + cont, err := cli.ContainerCreate( + context.Background(), + config, + hostConfig, + nil, + identifier, + ) + + if err != nil { + log.Println(err) + return err + } + + cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{}) + fmt.Printf("\n") + log.Printf("Container %s is created", cont.ID) + return nil +} + +func removeContainer(containername string) error { + ctx := context.Background() + + cli, err := dockerclient.NewEnvClient() + if err != nil { + log.Printf("Unable to create docker client: %s", err) + return err + } + + // FIXME - ucnomment + // containers, err := cli.ContainerList(ctx, types.ContainerListOptions{ + // All: true, + // }) + + _ = ctx + _ = cli + //if err := cli.ContainerStop(ctx, containername, nil); err != nil { + // log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err) + //} + + removeOptions := types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + } + + // FIXME - remove comments etc + _ = removeOptions + //if err := cli.ContainerRemove(ctx, containername, removeOptions); err != nil { + // log.Printf("Unable to remove container: %s", err) + //} + + return nil +} + +func handleExecution(client *http.Client, req *http.Request, workflowExecution WorkflowExecution) error { + // if no onprem runs (shouldn't happen, but extra check), exit + // if there are some, load the images ASAP for the app + dockercli, err := dockerclient.NewEnvClient() + if err != nil { + log.Printf("Unable to create docker client: %s", err) + shutdown(workflowExecution.ExecutionId) + } + + onpremApps := []string{} + startAction := workflowExecution.Workflow.Start + sleepTime := 5 + toExecuteOnprem := []string{} + parents := map[string][]string{} + children := map[string][]string{} + + // source = parent, dest = child + // parent can have more children, child can have more parents + for _, branch := range workflowExecution.Workflow.Branches { + parents[branch.DestinationID] = append(parents[branch.DestinationID], branch.SourceID) + children[branch.SourceID] = append(children[branch.SourceID], branch.DestinationID) + } + + for _, action := range workflowExecution.Workflow.Actions { + if action.Environment != environment { + continue + } + + toExecuteOnprem = append(toExecuteOnprem, action.ID) + + actionName := fmt.Sprintf("%s:%s_%s", baseimagename, action.AppName, action.AppVersion) + found := false + for _, app := range onpremApps { + if actionName == app { + found = true + } + } + + if !found { + onpremApps = append(onpremApps, actionName) + } + } + + if len(onpremApps) == 0 { + return errors.New("No apps to handle onprem") + } + + pullOptions := types.ImagePullOptions{} + for _, image := range onpremApps { + log.Printf("Image: %s", image) + if strings.Contains(image, " ") { + image = strings.ReplaceAll(image, " ", "-") + } + + reader, err := dockercli.ImagePull(context.Background(), image, pullOptions) + if err != nil { + log.Printf("Failed getting %s. The app is missing or some other issue", image) + //shutdown(workflowExecution.ExecutionId) + } + + //io.Copy(os.Stdout, reader) + _ = reader + log.Printf("Successfully downloaded and built %s", image) + } + + // Process the parents etc. How? + // while queue: + // while len(self.in_process) > 0 or len(self.parallel_in_process) > 0: + // check if its their own turn to continue + // visited = {self.start_action} + visited := []string{} + nextActions := []string{} + queueNodes := []string{} + + for { + //if len(queueNodes) > 0 { + // log.Println(queueNodes) + // nextActions = queueNodes + //} else { + // nextActions := []string{} + //} + // FIXME - this might actually work, but probably not + //queueNodes = []string{} + + if len(workflowExecution.Results) == 0 { + nextActions = []string{startAction} + } else { + for _, item := range workflowExecution.Results { + visited = append(visited, item.Action.ID) + nextActions = children[item.Action.ID] + // FIXME: check if nextActions items are finished? + } + } + + if len(nextActions) == 0 { + log.Println("No next action. Finished?") + //shutdown(workflowExecution.ExecutionId) + } + + for _, node := range nextActions { + nodeChildren := children[node] + for _, child := range nodeChildren { + if !arrayContains(queueNodes, child) { + queueNodes = append(queueNodes, child) + } + } + } + + //log.Println(queueNodes) + + // IF NOT VISITED && IN toExecuteOnPrem + // SKIP if it's not onprem + // FIXME: Find next node(s) + //for _, result := range workflowExecution.Results { + // log.Println(result.Status) + //} + + for _, nextAction := range nextActions { + action := getAction(workflowExecution, nextAction) + // FIXME - remove this. Should always need to be valid. + //if action.IsValid == false { + // log.Printf("%#v", action) + // log.Printf("Action %s (%s) isn't valid. Exiting, BUT SHOULD CALLBACK TO SET FAILURE.", action.ID, action.Name) + // os.Exit(3) + //} + + // check visited and onprem + if arrayContains(visited, nextAction) { + log.Printf("ALREADY VISITIED: %s", nextAction) + continue + } + + // Not really sure how this edgecase happens. + + // FIXME + // Execute, as we don't really care if env is not set? IDK + if action.Environment != environment { //&& action.Environment != "" { + log.Printf("Bad environment: %s", action.Environment) + continue + } + + // check whether the parent is finished executing + //log.Printf("%s has %d parents", nextAction, len(parents[nextAction])) + + continueOuter := true + if action.IsStartNode { + continueOuter = false + } else if len(parents[nextAction]) > 0 { + // FIXME - wait for parents to finishe executing + fixed := 0 + for _, parent := range parents[nextAction] { + parentResult := getResult(workflowExecution, parent) + if parentResult.Status == "FINISHED" || parentResult.Status == "SUCCESS" { + fixed += 1 + } + } + + if fixed == len(parents[nextAction]) { + continueOuter = false + } + } else { + continueOuter = false + } + + if continueOuter { + log.Printf("Parents of %s aren't finished: %s", nextAction, strings.Join(parents[nextAction], ", ")) + continue + } + + // get action status + actionResult := getResult(workflowExecution, nextAction) + if actionResult.Action.ID == action.ID { + log.Printf("%s already has status %s.", action.ID, actionResult.Status) + continue + } else { + log.Printf("%s:%s has no status result yet. Should execute.", action.Name, action.ID) + } + + appname := action.AppName + appversion := action.AppVersion + appname = strings.Replace(appname, ".", "-", -1) + appversion = strings.Replace(appversion, ".", "-", -1) + + image := fmt.Sprintf("%s:%s_%s", baseimagename, action.AppName, action.AppVersion) + if strings.Contains(image, " ") { + image = strings.ReplaceAll(image, " ", "-") + } + + identifier := fmt.Sprintf("%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId) + if strings.Contains(identifier, " ") { + identifier = strings.ReplaceAll(identifier, " ", "-") + } + + // FIXME - check whether it's running locally yet too + stats, err := dockercli.ContainerInspect(context.Background(), identifier) + if err != nil || stats.ContainerJSONBase.State.Status != "running" { + // REMOVE + if err == nil { + log.Printf("Status: %s, should kill: %s", stats.ContainerJSONBase.State.Status, identifier) + err = removeContainer(identifier) + if err != nil { + log.Printf("Error killing container: %s", err) + } + } else { + //log.Printf("WHAT TO DO HERE?: %s", err) + } + } else if stats.ContainerJSONBase.State.Status == "running" { + continue + } + + if len(action.Parameters) == 0 { + action.Parameters = []WorkflowAppActionParameter{} + } + + if len(action.Errors) == 0 { + action.Errors = []string{} + } + + // marshal action and put it in there rofl + log.Printf("Time to execute %s with app %s:%s, function %s, env %s with %d parameters.", action.ID, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters)) + actionData, err := json.Marshal(action) + if err != nil { + log.Printf("Failed unmarshalling action: %s", err) + continue + } + + //log.Println(string(actionData)) + // FIXME - add proper FUNCTION_APIKEY from user definition + env := []string{ + fmt.Sprintf("ACTION=%s", string(actionData)), + fmt.Sprintf("EXECUTIONID=%s", workflowExecution.ExecutionId), + fmt.Sprintf("FUNCTION_APIKEY=%s", "asdasd"), + fmt.Sprintf("AUTHORIZATION=%s", workflowExecution.Authorization), + fmt.Sprintf("CALLBACK_URL=%s", baseUrl), + } + + err = deployApp(dockercli, image, identifier, env) + if err != nil { + log.Printf("Failed deploying %s from image %s: %s", identifier, image, err) + log.Printf("Should send status and exit the entire thing?") + //shutdown(workflowExecution.ExecutionId) + } + + visited = append(visited, action.ID) + //log.Printf("%#v", action) + } + + //log.Println(nextAction) + //log.Println(startAction, children[startAction]) + + // FIXME - new request here + // FIXME - clean up stopped (remove) containers with this execution id + newresp, err := client.Do(req) + if err != nil { + log.Printf("Failed making request: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + body, err := ioutil.ReadAll(newresp.Body) + if err != nil { + log.Printf("Failed reading body: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + if newresp.StatusCode != 200 { + log.Printf("Err: %s\nStatusCode: %d", string(body), newresp.StatusCode) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + err = json.Unmarshal(body, &workflowExecution) + if err != nil { + log.Printf("Failed workflowExecution unmarshal: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" { + log.Printf("Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId) + shutdown(workflowExecution.ExecutionId) + } + + log.Printf("Status: %s, Results: %d, actions: %d", workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) + if workflowExecution.Status != "EXECUTING" { + log.Printf("Exiting as worker execution has status %s!", workflowExecution.Status) + shutdown(workflowExecution.ExecutionId) + } + + if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) { + shutdownCheck := true + ctx := context.Background() + for _, result := range workflowExecution.Results { + if result.Status == "EXECUTING" { + // Cleaning up executing stuff + shutdownCheck = false + // Check status + + containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{ + All: true, + }) + if err != nil { + log.Printf("Failed listing containers: %s", err) + continue + } + + stopContainers := []string{} + removeContainers := []string{} + for _, container := range containers { + for _, name := range container.Names { + if !strings.Contains(name, result.Action.ID) { + continue + } + + if container.State != "running" { + removeContainers = append(removeContainers, container.ID) + stopContainers = append(stopContainers, container.ID) + } + } + } + + // FIXME - add killing of apps with same execution ID too + // FIXME - stahp + //for _, containername := range stopContainers { + // if err := dockercli.ContainerStop(ctx, containername, nil); err != nil { + // log.Printf("Unable to stop container: %s", err) + // } else { + // log.Printf("Stopped container %s", containername) + // } + //} + + removeOptions := types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + } + + _ = removeOptions + + // FIXME - this + //for _, containername := range removeContainers { + // if err := dockercli.ContainerRemove(ctx, containername, removeOptions); err != nil { + // log.Printf("Unable to remove container: %s", err) + // } else { + // log.Printf("Removed container %s", containername) + // } + //} + + // FIXME - send POST request to kill the container + log.Printf("Should remove (POST request) stopped containers") + //ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result) + } + } + + if shutdownCheck { + log.Println("BREAKING BECAUSE RESULTS IS SAME LENGTH AS ACTIONS. SHOULD CHECK ALL RESULTS FOR WHETHER THEY'RE DONE") + shutdown(workflowExecution.ExecutionId) + } + } + time.Sleep(time.Duration(sleepTime) * time.Second) + } + + return nil +} + +func arrayContains(visited []string, id string) bool { + found := false + for _, item := range visited { + if item == id { + found = true + } + } + + return found +} + +func getResult(workflowExecution WorkflowExecution, id string) ActionResult { + for _, actionResult := range workflowExecution.Results { + if actionResult.Action.ID == id { + return actionResult + } + } + + return ActionResult{} +} + +func getAction(workflowExecution WorkflowExecution, id string) Action { + for _, action := range workflowExecution.Workflow.Actions { + if action.ID == id { + return action + } + } + + return Action{} +} + +// Initial loop etc +func main() { + log.Printf("Setting up worker environment") + + sleepTime := 5 + client := &http.Client{} + authorization := os.Getenv("AUTHORIZATION") + executionId := os.Getenv("EXECUTIONID") + + if len(authorization) == 0 { + log.Println("No AUTHORIZATION key set in env") + shutdown(executionId) + } + + if len(executionId) == 0 { + log.Println("No EXECUTIONID key set in env") + shutdown(executionId) + } + + // FIXME - tmp + data := fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, executionId, authorization) + fullUrl := fmt.Sprintf("%s/api/v1/streams/results", baseUrl) + req, err := http.NewRequest( + "POST", + fullUrl, + bytes.NewBuffer([]byte(data)), + ) + + if err != nil { + log.Println("Failed making request builder") + shutdown(executionId) + } + + for { + newresp, err := client.Do(req) + if err != nil { + log.Printf("Failed request: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + body, err := ioutil.ReadAll(newresp.Body) + if err != nil { + log.Printf("Failed reading body: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + if newresp.StatusCode != 200 { + log.Printf("Err: %s\nStatusCode: %d", string(body), newresp.StatusCode) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + var workflowExecution WorkflowExecution + err = json.Unmarshal(body, &workflowExecution) + if err != nil { + log.Printf("Failed workflowExecution unmarshal: %s", err) + time.Sleep(time.Duration(sleepTime) * time.Second) + continue + } + + if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" { + log.Printf("Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId) + shutdown(executionId) + } + + if workflowExecution.Status == "EXECUTING" || workflowExecution.Status == "RUNNING" { + //log.Printf("Status: %s", workflowExecution.Status) + err = handleExecution(client, req, workflowExecution) + if err != nil { + log.Printf("Workflow %s is finished: %s", workflowExecution.ExecutionId, err) + shutdown(executionId) + } + } else { + log.Printf("Workflow %s has status %s. Exiting worker.", workflowExecution.ExecutionId, workflowExecution.Status) + shutdown(executionId) + } + + //log.Println(string(body)) + time.Sleep(time.Duration(sleepTime) * time.Second) + } +} diff --git a/functions/static_baseline.py b/functions/static_baseline.py new file mode 100644 index 00000000..152131cd --- /dev/null +++ b/functions/static_baseline.py @@ -0,0 +1,76 @@ +import os +import sys +import time +import logging +import requests + +# Goal here: +# * Make an app from WALKOFF able to run without app_base.py from WALKOFF +# # How: +# * Make it rely 100% on INPUT throug HTTP invocations instead of redis READS +# # But really, how? +# * Make a WORKER that reads the queue, and reuses a function + +# Here to get it global +apikey = "" +try: + apikey = os.environ["FUNCTION_APIKEY"] +except KeyError: + pass + +# Authorize the execution +def authorization(request): + # This is basically my issue, but it enforces the use of an internal API key for execution + try: + apikey = os.environ["FUNCTION_APIKEY"] + except KeyError: + return f"Internal server error", 500 + + + # Check API key from ENV authentication + authentication = request.headers.get("Authorization") + if authentication == None: return f"Unauthorized", 401 + + apikey_split = authentication.split(" ") + if apikey_split[0] != "Bearer" or len(apikey_split) != 2: + return f"Apikey error", 401 + + if apikey != apikey_split[1]: + return f"Unauthorized", 401 + + return run(request) + +class AppBase: + """ The base class for Python-based Walkoff applications, handles Redis and logging configurations. """ + __version__ = None + app_name = None + + def __init__(self, redis=None, logger=None, console_logger=None):#, docker_client=None): + self.logger = logger if logger is not None else logging.getLogger("AppBaseLogger") + self.redis=redis + self.console_logger=console_logger + self.current_execution_id = None + self.url = "https://shuffler.io" + self.apikey = apikey + + @classmethod + async def run(cls, action): + """ Connect to Redis and HTTP session, await actions """ + logging.basicConfig(format="{asctime} - {name} - {levelname}:{message}", style='{') + logger = logging.getLogger(f"{cls.__name__}") + logger.setLevel(logging.DEBUG) + + app = cls(redis=None, logger=logger, console_logger=logger) + + # Authorization for the app/function to control the workflow + # Function will crash if its wrong, which it probably should. + + await app.execute_action(action) + + async def execute_action(self, action): + # FIXME - add request for the function STARTING here. Use "results stream" or something + # PAUSED, AWAITING_DATA, PENDING, COMPLETED, ABORTED, EXECUTING, SUCCESS, FAILURE + + self.authorization = action["authorization"] + self.execution_id = action["execution_id"] + self.current_execution_id = action["execution_id"] diff --git a/functions/stitcher.go b/functions/stitcher.go new file mode 100644 index 00000000..1b752f77 --- /dev/null +++ b/functions/stitcher.go @@ -0,0 +1,702 @@ +package main + +import ( + "archive/zip" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" + "strings" + + "archive/tar" + "cloud.google.com/go/storage" + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "google.golang.org/api/cloudfunctions/v1" + "gopkg.in/yaml.v2" +) + +var gceProject = "shuffler" +var bucketName = "shuffler.appspot.com" + +type WorkflowAppActionParameter struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id"` + Name string `json:"name" datastore:"name"` + Example string `json:"example" datastore:"example"` + Value string `json:"value" datastore:"value"` + Multiline bool `json:"multiline" datastore:"multiline"` + ActionField string `json:"action_field" datastore:"action_field"` + Variant string `json:"variant", datastore:"variant"` + Required bool `json:"required" datastore:"required"` + Schema struct { + Type string `json:"type" datastore:"type"` + } `json:"schema"` +} + +type Authentication struct { + Required bool `json:"required" datastore:"required" yaml:"required" ` + Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"` +} + +type AuthenticationParams struct { + Description string `json:"description" datastore:"description" yaml:"description"` + ID string `json:"id" datastore:"id" yaml:"id"` + Name string `json:"name" datastore:"name" yaml:"name"` + Example string `json:"example" datastore:"example" yaml:"example"` + Value string `json:"value" datastore:"value" yaml:"value"` + Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` + Required bool `json:"required" datastore:"required" yaml:"required"` +} + +type WorkflowApp struct { + Name string `json:"name" yaml:"name" required:true datastore:"name"` + IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` + ID string `json:"id" yaml:"id" required:false datastore:"id"` + Link string `json:"link" yaml:"link" required:false datastore:"link"` + AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"` + Description string `json:"description" datastore:"description" required:false yaml:"description"` + Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"` + Sharing bool `json:"sharing" datastore:"sharing" yaml:"sharing"` + SmallImage string `json:"small_image" datastore:"small_image" required:false yaml:"small_image"` + LargeImage string `json:"large_image" datastore:"large_image" yaml:"large_image" requred:false` + ContactInfo struct { + Name string `json:"name" datastore:"name" yaml:"name"` + Url string `json:"url" datastore:"url" yaml:"url"` + } `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false` + Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"` + Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"` +} + +type AuthenticationStore struct { + Key string `json:"key" datastore:"key"` + Value string `json:"value" datastore:"value"` +} + +type WorkflowAppAction struct { + Description string `json:"description" datastore:"description"` + ID string `json:"id" datastore:"id"` + Name string `json:"name" datastore:"name"` + NodeType string `json:"node_type" datastore:"node_type"` + Environment string `json:"environment" datastore:"environment"` + Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"` + Authentication []AuthenticationStore `json:"authentication" datastore:"authentication"` + Returns struct { + Description string `json:"description" datastore:"returns"` + ID string `json:"id" datastore:"id"` + Schema struct { + Type string `json:"type" datastore:"type"` + } `json:"schema" datastore:"schema"` + } `json:"returns" datastore:"returns"` +} + +func getRunner(classname string) string { + return fmt.Sprintf(` +# Run the actual thing after we've checked params +def run(request): + action = request.get_json() + print(action) + print(type(action)) + authorization_key = action.get("authorization") + current_execution_id = action.get("execution_id") + + if action and "name" in action and "app_name" in action: + asyncio.run(%s.run(action), debug=True) + return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' + else: + return f'Invalid action' + + `, classname) +} + +// Could use some kind of linting system too for this, but meh +func formatAppfile(filedata []byte) (string, []byte) { + lines := strings.Split(string(filedata), "\n") + + newfile := []string{} + classname := "" + for _, line := range lines { + if strings.Contains(line, "walkoff_app_sdk") { + continue + } + + // Remap logging. CBA this right now + // This issue also persists in onprem apps because of await thingies.. :( + // FIXME + if strings.Contains(line, "console_logger") && strings.Contains(line, "await") { + continue + //line = strings.Replace(line, "console_logger", "logger", -1) + //log.Println(line) + } + + // Might not work with different import names + // Could be fucked up with spaces everywhere? Idk + if strings.Contains(line, "class") && strings.Contains(line, "(AppBase)") { + items := strings.Split(line, " ") + if len(items) > 0 && strings.Contains(items[1], "(AppBase)") { + classname = strings.Split(items[1], "(")[0] + } else { + log.Println("Something wrong :( (horrible programming right here)") + os.Exit(3) + } + } + + if strings.Contains(line, "if __name__ ==") { + break + } + + // asyncio.run(HelloWorld.run(), debug=True) + + newfile = append(newfile, line) + } + + filedata = []byte(strings.Join(newfile, "\n")) + return classname, filedata +} + +// https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang +func Copy(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() +} +func ZipFiles(filename string, files []string) error { + newZipFile, err := os.Create(filename) + if err != nil { + return err + } + defer newZipFile.Close() + + zipWriter := zip.NewWriter(newZipFile) + defer zipWriter.Close() + + // Add files to zip + for _, file := range files { + zipfile, err := os.Open(file) + if err != nil { + return err + } + defer zipfile.Close() + + // Get the file information + info, err := zipfile.Stat() + if err != nil { + return err + } + + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + // Using FileInfoHeader() above only uses the basename of the file. If we want + // to preserve the folder structure we can overwrite this with the full path. + filesplit := strings.Split(file, "/") + if len(filesplit) > 1 { + header.Name = filesplit[len(filesplit)-1] + } else { + header.Name = file + } + + // Change to deflate to gain better compression + // see http://golang.org/pkg/archive/zip/#pkg-constants + header.Method = zip.Deflate + + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return err + } + if _, err = io.Copy(writer, zipfile); err != nil { + return err + } + } + + return nil +} + +func getAppbase(filepath string) []string { + appBase, err := ioutil.ReadFile(filepath) + if err != nil { + log.Printf("Readerror: %s", err) + os.Exit(1) + } + + record := false + validLines := []string{} + for _, line := range strings.Split(string(appBase), "\n") { + if strings.Contains(line, "#STOPCOPY") { + log.Println("Stopping copy") + break + } + + if record { + validLines = append(validLines, line) + } + + if strings.Contains(line, "#STARTCOPY") { + log.Println("Starting copy") + record = true + } + } + + return validLines +} + +// Puts together ./static_baseline.py, onprem/app_sdk_app_base.py and the +// appcode in a generated_app folder based on appname+version +func stitcher(appname string, appversion string) string { + baselinefile := "static_baseline.py" + appfolder := "apps" + appbasefile := "onprem/app_sdk/app_base.py" + + baseline, err := ioutil.ReadFile(baselinefile) + if err != nil { + log.Printf("Readerror: %s", err) + os.Exit(1) + } + + sourceappfile := fmt.Sprintf("%s/%s/%s/src/app.py", appfolder, appname, appversion) + appfile, err := ioutil.ReadFile(sourceappfile) + if err != nil { + log.Printf("App readerror: %s", err) + os.Exit(1) + } + + classname, appfile := formatAppfile(appfile) + if len(classname) == 0 { + log.Println("Failed finding classname in file.") + os.Exit(3) + } + + runner := getRunner(classname) + appBase := getAppbase(appbasefile) + + foldername := fmt.Sprintf("generated_apps/%s_%s", appname, appversion) + err = os.Mkdir(foldername, os.ModePerm) + if err != nil { + log.Println("Failed making temporary app folder. Probably already exists. Remaking") + os.RemoveAll(foldername) + os.MkdirAll(foldername, os.ModePerm) + } + + stitched := []byte(string(baseline) + strings.Join(appBase, "\n") + string(appfile) + string(runner)) + err = ioutil.WriteFile(fmt.Sprintf("%s/main.py", foldername), stitched, os.ModePerm) + if err != nil { + log.Println("Failed writing to stitched: %s", err) + os.Exit(3) + } + + err = Copy(fmt.Sprintf("%s/%s/%s/requirements.txt", appfolder, appname, appversion), fmt.Sprintf("%s/requirements.txt", foldername)) + if err != nil { + log.Println("Failed writing to requirement: %s", err) + os.Exit(3) + } + + log.Printf("Successfully stitched files in %s/main.py", foldername) + // Zip the folder + files := []string{ + fmt.Sprintf("%s/main.py", foldername), + fmt.Sprintf("%s/requirements.txt", foldername), + } + outputfile := fmt.Sprintf("%s.zip", foldername) + + err = ZipFiles(outputfile, files) + if err != nil { + log.Fatal(err) + } + + ctx := context.Background() + + // Creates a client. + client, err := storage.NewClient(ctx) + if err != nil { + log.Printf("Failed to create client: %v", err) + os.Exit(3) + } + + // Create bucket handle + bucket := client.Bucket(bucketName) + + remotePath := fmt.Sprintf("apps/%s_%s.zip", appname, appversion) + err = createFileFromFile(bucket, remotePath, outputfile) + if err != nil { + log.Printf("Failed to upload to bucket: %v", err) + os.Exit(3) + } + + os.Remove(outputfile) + return fmt.Sprintf("gs://%s/apps/%s_%s.zip", bucketName, appname, appversion) +} + +func createFileFromFile(bucket *storage.BucketHandle, remotePath, localPath string) error { + ctx := context.Background() + // [START upload_file] + f, err := os.Open(localPath) + if err != nil { + return err + } + defer f.Close() + + wc := bucket.Object(remotePath).NewWriter(ctx) + if _, err = io.Copy(wc, f); err != nil { + return err + } + if err := wc.Close(); err != nil { + return err + } + // [END upload_file] + return nil +} + +// Deploy to google cloud function :) +func deployFunction(appname, localization, applocation string, environmentVariables map[string]string) error { + ctx := context.Background() + service, err := cloudfunctions.NewService(ctx) + if err != nil { + return err + } + + // ProjectsLocationsListCall + projectsLocationsFunctionsService := cloudfunctions.NewProjectsLocationsFunctionsService(service) + location := fmt.Sprintf("projects/%s/locations/%s", gceProject, localization) + functionName := fmt.Sprintf("%s/functions/%s", location, appname) + + cloudFunction := &cloudfunctions.CloudFunction{ + AvailableMemoryMb: 128, + EntryPoint: "authorization", + EnvironmentVariables: environmentVariables, + HttpsTrigger: &cloudfunctions.HttpsTrigger{}, + MaxInstances: 0, + Name: functionName, + Runtime: "python37", + SourceArchiveUrl: applocation, + } + + //getCall := projectsLocationsFunctionsService.Get(fmt.Sprintf("%s/functions/function-5", location)) + //resp, err := getCall.Do() + + createCall := projectsLocationsFunctionsService.Create(location, cloudFunction) + _, err = createCall.Do() + if err != nil { + log.Println("Failed creating new function. Attempting patch, as it might exist already") + + createCall := projectsLocationsFunctionsService.Patch(fmt.Sprintf("%s/functions/%s", location, appname), cloudFunction) + _, err = createCall.Do() + if err != nil { + log.Println("Failed patching function") + return err + } + + log.Printf("Successfully patched %s to %s", appname, localization) + } else { + log.Printf("Successfully deployed %s to %s", appname, localization) + } + + // FIXME - use response to define the HTTPS entrypoint. It's default to an easy one tho + + return nil +} + +func deployAppCloudFunc(appname string, appversion string) { + _ = os.Mkdir("generated_apps", os.ModePerm) + + apikey := "eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ" + fullAppname := fmt.Sprintf("%s-%s", strings.Replace(appname, "_", "-", -1), strings.Replace(appversion, ".", "-", -1)) + locations := []string{"europe-west2"} + + // Deploys the app to all locations + bucketname := stitcher(appname, appversion) + environmentVariables := map[string]string{ + "FUNCTION_APIKEY": apikey, + } + + for _, location := range locations { + err := deployFunction(fullAppname, location, bucketname, environmentVariables) + if err != nil { + log.Printf("Failed to deploy: %s", err) + os.Exit(3) + } + } +} + +func loadYaml(fileLocation string) (WorkflowApp, error) { + action := WorkflowApp{} + + yamlFile, err := ioutil.ReadFile(fileLocation) + if err != nil { + log.Printf("yamlFile.Get err: %s", err) + return WorkflowApp{}, err + } + + //log.Printf(string(yamlFile)) + err = yaml.Unmarshal([]byte(yamlFile), &action) + if err != nil { + return WorkflowApp{}, err + } + + return action, nil +} + +// FIXME - deploy to backend (YAML config) +func deployConfigToBackend(appname string, appversion string) error { + // FIXME - no static path pls + action, err := loadYaml(fmt.Sprintf("apps/%s/%s/api.yaml", appname, appversion)) + if err != nil { + log.Println(err) + return err + } + + action.Sharing = true + + data, err := json.Marshal(action) + if err != nil { + return err + } + + url := "http://localhost:5001/api/v1/workflows/apps" + client := &http.Client{} + req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data)) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ") + + ret, err := client.Do(req) + if err != nil { + return err + } + + log.Printf("Status: %s", ret.Status) + body, err := ioutil.ReadAll(ret.Body) + if err != nil { + return err + } + + if ret.StatusCode != 200 { + return errors.New(fmt.Sprintf("Status %s. App probably already exists. Raw:\n%s", ret.Status, string(body))) + } + + log.Println(string(body)) + return nil +} + +func tarDirectory(filecontext string) (io.Reader, error) { + + // Create a filereader + //dockerFileReader, err := os.Open(dockerfile) + //if err != nil { + // return err + //} + + //// Read the actual Dockerfile + //readDockerFile, err := ioutil.ReadAll(dockerFileReader) + //if err != nil { + // return err + //} + + // Make a TAR header for the file + tarHeader := &tar.Header{ + Name: filecontext, + Typeflag: tar.TypeDir, + } + + // Writes the header described for the TAR file + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + defer tw.Close() + err := tw.WriteHeader(tarHeader) + if err != nil { + return nil, err + } + + dockerFileTarReader := bytes.NewReader(buf.Bytes()) + return dockerFileTarReader, nil +} + +func tarDir(source string, target string) (*bytes.Reader, error) { + filename := filepath.Base(source) + target = filepath.Join(target, fmt.Sprintf("%s.tar", filename)) + tarfile, err := os.Create(target) + if err != nil { + return nil, err + } + + defer tarfile.Close() + + buf := new(bytes.Buffer) + _ = buf + tarball := tar.NewWriter(tarfile) + defer tarball.Close() + + info, err := os.Stat(source) + if err != nil { + return nil, err + } + + var baseDir string + if info.IsDir() { + baseDir = filepath.Base(source) + } + + _ = filepath.Walk(source, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + header, err := tar.FileInfoHeader(info, info.Name()) + if err != nil { + return err + } + + if baseDir != "" { + header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source)) + } + + if err := tarball.WriteHeader(header); err != nil { + return err + } + + if info.IsDir() { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(tarball, file) + return nil + }) + + dockerFileTarReader := bytes.NewReader(buf.Bytes()) + return dockerFileTarReader, nil +} + +func buildImage(client *client.Client, tags []string, dockerBuildCtxDir string) error { + dockerBuildContext, err := tarDir(dockerBuildCtxDir, ".") + if err != nil { + log.Printf("Error in taring the docker root folder - %s", err.Error()) + return err + } + + imageBuildResponse, err := client.ImageBuild( + context.Background(), + dockerBuildContext, + types.ImageBuildOptions{ + Dockerfile: "Dockerfile", + PullParent: true, + Remove: true, + Tags: tags, + }, + ) + + if err != nil { + return err + } + + // Read the STDOUT from the build process + defer imageBuildResponse.Body.Close() + _, err = io.Copy(os.Stdout, imageBuildResponse.Body) + if err != nil { + return err + } + + return nil +} + +// FIXME - deploy to dockerhub +func deployWorker(appname, appversion string) error { + // Get dockerfile from ./apps/appname/appversion/Dockerfile + client, err := client.NewEnvClient() + if err != nil { + return err + } + + tags := []string{fmt.Sprintf("%s-%s", appname, appversion)} + err = buildImage(client, tags, fmt.Sprintf("./apps/%s/%s", appname, appversion)) + if err != nil { + log.Printf("Build error: %s", err) + return err + } + + return nil +} + +// Deploys all cloud functions. Onprem thooo :( +func deployAll() { + allapps := []string{ + "hoxhunt", + "secureworks", + "servicenow", + "lastline", + "netcraft", + "misp", + "email", + "testing", + "http", + "recordedfuture", + "passivetotal", + "carbon_black", + "thehive", + "cortex", + "splunk", + } + + for _, appname := range allapps { + appversion := "1.0.0" + + err := deployConfigToBackend(appname, appversion) + if err != nil { + log.Printf("Failed uploading config: %s", err) + continue + } + + deployAppCloudFunc(appname, appversion) + } +} + +func main() { + deployAll() + return + + appname := "testing" + appversion := "1.0.0" + + err := deployConfigToBackend(appname, appversion) + if err != nil { + log.Printf("Failed uploading config: %s", err) + os.Exit(1) + } + + deployAppCloudFunc(appname, appversion) + + // FIXME - build and deploy to dockerhub as well :) + // Not able to work in remote directory propely... Even tried making an actual tar and checking it rofl + //err := deployWorker(appname, appversion) + //if err != nil { + // log.Printf("Failed to deploy docker worker: %s", err) + //} +} diff --git a/functions/triggers/msteams/.gcloudignore b/functions/triggers/msteams/.gcloudignore new file mode 100644 index 00000000..6ad2be26 --- /dev/null +++ b/functions/triggers/msteams/.gcloudignore @@ -0,0 +1,3 @@ +main.go +*.swo +*.swp diff --git a/functions/triggers/msteams/README.md b/functions/triggers/msteams/README.md new file mode 100644 index 00000000..abdeb374 --- /dev/null +++ b/functions/triggers/msteams/README.md @@ -0,0 +1,17 @@ +# Local testing +1. Change hook.go package to main +```bash +mv ../main.go . +go run main.go hook.go +``` + +# Deploy local +```bash +gcloud functions deploy webhook --runtime go111 --entry-point Authorization --trigger-http --project shuffle-241517 --memory=128 --set-env-vars=FUNCTION_APIKEY=asdasd,CALLBACKURL=shuffler.io,HOOKID=test123 +``` + +# Build and deploy from gui +1. rm webhook.zip +2. zip webhook.zip hook.go +3. Upload to bucket https://console.cloud.google.com/storage/browser/shuffle-241517.appspot.com?project=shuffle-241517 +4. Restart hook(s) (https://shuffler.io/webhooks) diff --git a/functions/triggers/msteams/hook.go b/functions/triggers/msteams/hook.go new file mode 100644 index 00000000..fae0e594 --- /dev/null +++ b/functions/triggers/msteams/hook.go @@ -0,0 +1,415 @@ +package main + +// APPS: +// apps.dev.microsoft.com + +// REMOVE ACCESS: +// https://portal.office.com/account/# + +// Developer: +// https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference + +// Bots: +// https://dev.botframework.com/bots + +// Connectors +// https://outlook.office.com/connectors/home/login/#/new +// https://go.microsoft.com/fwlink/?linkid=857599 + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + "time" +) + +type Info struct { + Url string `json:"url" datastore:"url"` + Name string `json:"name" datastore:"name"` + Description string `json:"description" datastore:"description"` +} + +// Actions to be done by webhooks etc +// Field is the actual field to use from json +type HookAction struct { + Type string `json:"type" datastore:"type"` + Name string `json:"name" datastore:"name"` + Id string `json:"id" datastore:"id"` + Field string `json:"field" datastore:"field"` +} + +type Hook struct { + Id string `json:"id" datastore:"id"` + Info Info `json:"info" datastore:"info"` + Actions []HookAction `json:"actions" datastore:"actions"` + Type string `json:"type" datastore:"type"` + Status string `json:"status" datastore:"status"` + Running bool `json:"running" datastore:"running"` +} + +type TeamsHook struct { + MembersAdded []struct { + ID string `json:"id"` + } `json:"membersAdded"` + Type string `json:"type"` + Timestamp time.Time `json:"timestamp"` + LocalTimestamp string `json:"localTimestamp"` + ID string `json:"id"` + ChannelID string `json:"channelId"` + ServiceURL string `json:"serviceUrl"` + From struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"from"` + Conversation struct { + IsGroup bool `json:"isGroup"` + ConversationType string `json:"conversationType"` + ID string `json:"id"` + TenantID string `json:"tenantId"` + } `json:"conversation"` + Recipient struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"recipient"` + ChannelData struct { + Team struct { + ID string `json:"id"` + } `json:"team"` + EventType string `json:"eventType"` + Tenant struct { + ID string `json:"id"` + } `json:"tenant"` + } `json:"channelData"` +} + +var hook Hook +var baseUrl = "https://shuffler.io" + +type OauthToken struct { + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + ExtExpiresIn int `json:"ext_expires_in"` + AccessToken string `json:"access_token"` +} + +type TeamsResponse struct { + Conversation struct { + ID string `json:"id"` + } `json:"conversation"` + From struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"from"` + Recipient struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"recipient"` + ReplyToId string `json:"replyToId"` + Type string `json:"type"` + Text string `json:"text"` +} + +// This should be in a token thingy, to be controlled in workflow +func sendRequest(token OauthToken, message TeamsHook) error { + //POST https://smba.trafficmanager.net/apis/v3/conversations/12345/activities + //Authorization: Bearer eyJhbGciOiJIUzI1Ni... + // + //(JSON-serialized Activity message goes here) + + tmpData := TeamsResponse{} + tmpData.Conversation.ID = message.Conversation.ID + tmpData.From = message.Recipient + tmpData.Recipient = message.From + tmpData.ReplyToId = message.ID + tmpData.Type = "message" + tmpData.Text = "HELO" + + data, err := json.Marshal(tmpData) + if err != nil { + return err + } + + // /v3/conversations/{conversationId}/activities/{activityId} + fullurl := fmt.Sprintf("%sv3/conversations/%s/activities", message.ServiceURL, message.Conversation.ID) + log.Println(fullurl) + log.Println(string(data)) + req, err := http.NewRequest( + http.MethodPost, + fullurl, + bytes.NewBuffer([]byte(data)), + ) + + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken)) + req.Header.Add("Content-Type", "application/json") + if err != nil { + return err + } + + client := http.Client{} + res, err := client.Do(req) + if err != nil { + return err + } + + log.Printf("Status: %d", res.StatusCode) + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + + log.Println(string(body)) + + return nil +} + +func get_accesstoken() (OauthToken, error) { + client_id := "9a2a2a63-c63c-4487-baf0-4ff3f4873a7f" + client_secret := ":3]D6oFimiXbuV20xH?Dzu@LR*6IFVbq" + fullurl := fmt.Sprintf("https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token") + data := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s&scope=https://api.botframework.com/.default", client_id, client_secret) + + log.Println(data) + + req, err := http.NewRequest( + http.MethodPost, + fullurl, + bytes.NewBuffer([]byte(data)), + ) + + if err != nil { + return OauthToken{}, err + } + + client := http.Client{} + res, err := client.Do(req) + if err != nil { + return OauthToken{}, err + } + + log.Printf("Status: %d", res.StatusCode) + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return OauthToken{}, err + } + + token := OauthToken{} + err = json.Unmarshal(body, &token) + if err != nil { + return OauthToken{}, err + } + + return token, nil +} + +//func CheckTenantId(message TeamsHook) { +// fullurl := fmt.Sprintf("%s/api/v1/functions/tenants/%s", baseUrl, message.Conversation.TenantID) +// req, err := http.NewRequest( +// http.MethodPost, +// fullurl, +// bytes.NewBuffer([]byte(data)), +// ) +// +// req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, baseApikey)) +// req.Header.Add("Content-Type", "application/json") +// if err != nil { +// return []string{}, err +// } +// +// client := http.Client{} +// res, err := client.Do(req) +// if err != nil { +// return []string{}, err +// } +// +// log.Printf("Status: %d", res.StatusCode) +// body, err := ioutil.ReadAll(res.Body) +// if err != nil { +// return []string{}, err +// } +//} + +func Authorization(resp http.ResponseWriter, request *http.Request) { + // FIXME - don't have this here, but before loops etc + // How to keep it refreshed? + token, err := get_accesstoken() + if err != nil { + log.Printf("Failed: %s", err) + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return + } + + log.Println("Data") + log.Println(string(body)) + + hook := TeamsHook{} + err = json.Unmarshal(body, &hook) + if err != nil { + resp.WriteHeader(200) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Only handle messages currently + if hook.Type != "message" { + resp.WriteHeader(200) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Find the ORG based on the above info. How? + // MSTeams hook should have it attached somehow? + + log.Printf(string(body)) + //log.Printf(hook.ServiceURL) + //log.Printf(hook.ChannelID) + //log.Printf(hook.ID) + //log.Printf("%#v", hook.Conversation) + + err = sendRequest(token, hook) + if err != nil { + log.Printf("Failed: %s", err) + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +func loadConfiguration(fullUrl string, apikey string) (Hook, error) { + client := &http.Client{} + + req, err := http.NewRequest( + "GET", + fullUrl, + nil, + ) + + if err != nil { + log.Printf("Error making http request: %s", req) + return Hook{}, err + } + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey)) + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + log.Printf("Error in http request: %s", req) + return Hook{}, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("Error reading response: %s", req) + return Hook{}, err + } + + err = json.Unmarshal(body, &hook) + if err != nil { + log.Printf("Failed unmarshaling hook API", req) + return Hook{}, err + } + + return hook, nil +} + +// GetUserDetails - Get one user's details from randomuser.me API +func ForwardRequest(resp http.ResponseWriter, request *http.Request) error { + callbackUrl := os.Getenv("CALLBACKURL") + hookId := os.Getenv("HOOKID") + apikey := os.Getenv("FUNCTION_APIKEY") + + hook, err := loadConfiguration( + fmt.Sprintf("%s/api/v1/hooks/%s", callbackUrl, hookId), + apikey, + ) + + log.Println("Done loading!") + + if err != nil { + return err + } + + log.Printf("%#v", hook) + + // Find all things to execute + workflowUrls := []string{} + for _, item := range hook.Actions { + if item.Type == "" { + log.Printf("CONTINUE AAS EMPTY ITEM: %#v", item) + continue + } + + if item.Type == "workflow" { + workflowUrls = append(workflowUrls, item.Id) + } + } + + if len(workflowUrls) == 0 { + return errors.New("No actions to do yet") + } + + log.Printf("Should send data to the following: %s", strings.Join(workflowUrls, ", ")) + + randomUserClient := http.Client{ + Timeout: time.Second * 3, + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return err + } + + // Prepare data + type arg struct { + ExecutionArgument string `json:"execution_argument"` + } + data := arg{ + ExecutionArgument: string(body), + } + + newjson, err := json.Marshal(data) + if err != nil { + return err + } + + // Loop all executions to run + for _, item := range workflowUrls { + fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", callbackUrl, item) + log.Printf("Sending data to %s", fullUrl) + req, err := http.NewRequest( + http.MethodPost, + fullUrl, + bytes.NewBuffer(newjson), + ) + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey)) + req.Header.Add("Content-Type", "application/json") + if err != nil { + return err + } + + res, err := randomUserClient.Do(req) + if err != nil { + return err + } + + log.Printf("Status: %d", res.StatusCode) + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + + log.Printf(string(body)) + } + + //log.Println(string(newbody)) + return nil +} diff --git a/functions/triggers/msteams/main.go b/functions/triggers/msteams/main.go new file mode 100644 index 00000000..c41f030d --- /dev/null +++ b/functions/triggers/msteams/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "log" + "net/http" + "os" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" +) + +func webhook() { + // FIXME - remove static + port := ":8080" + baseFilePath := "/" + + mux := mux.NewRouter() + mux.SkipClean(true) + + // FIXME - Add path for updating the hook? Can be a specific POST requeuest from backend + mux.HandleFunc(baseFilePath, Authorization).Methods("POST") + mux.HandleFunc("/test", Authorization).Methods("POST") + + handlers.LoggingHandler(os.Stdout, mux) + loggedRouter := handlers.LoggingHandler(os.Stdout, mux) + + log.Printf("Starting on http://localhost%s", port) + err := http.ListenAndServe( + port, + loggedRouter, + ) + + if err != nil { + log.Fatal("ListenAndServer: ", err) + } + +} + +func main() { + webhook() +} diff --git a/functions/triggers/msteams/manifest.json b/functions/triggers/msteams/manifest.json new file mode 100644 index 00000000..bd23e55b --- /dev/null +++ b/functions/triggers/msteams/manifest.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", + "manifestVersion": "1.5", + "version": "1.0.0", + "id": "9a2a2a63-c63c-4487-baf0-4ff3f4873a7f", + "packageName": "com.example.myapp", + "devicePermissions" : [], + "developer": { + "name": "@frikkylikeme", + "websiteUrl": "https://shuffler.io/", + "privacyUrl": "https://shuffler.io/privacy", + "termsOfUseUrl": "https://shuffler.io/tos" + }, + "localizationInfo": { + "defaultLanguageTag": "en-us" + }, + "name": { + "short": "Shuffle", + "full": "Shuffle" + }, + "description": { + "short": "Shuffle is a workflow automation platform", + "full": "Shuffle is a workflow automation platform. Find more info at https://shuffler.io" + }, + "icons": { + "outline": "outline.png", + "color": "color.png" + }, + "accentColor": "#15202b", + "bots": [ + { + "botId": "9a2a2a63-c63c-4487-baf0-4ff3f4873a7f", + "needsChannelSelector": false, + "isNotificationOnly": false, + "scopes": [ "team", "personal", "groupchat" ], + "supportsFiles": false, + "commandLists": [ + { + "scopes": [ "team", "groupchat", "personal" ], + "commands": [ + { + "title": "test", + "description": "THIS IS FOR TESTING" + } + ] + } + ] + } + ] +} diff --git a/functions/triggers/msteams/test.sh b/functions/triggers/msteams/test.sh new file mode 100644 index 00000000..9cd847e6 --- /dev/null +++ b/functions/triggers/msteams/test.sh @@ -0,0 +1,35 @@ +curl -XPOST http://localhost:8080 -d '{ + "membersAdded": [ + { + "id": "28:f5d48856-5b42-41a0-8c3a-c5f944b679b0" + } + ], + "type": "conversationUpdate", + "timestamp": "2017-02-23T19:38:35.312Z", + "localTimestamp": "2017-02-23T12:38:35.312-07:00", + "id": "f:5f85c2ad", + "channelId": "msteams", + "serviceUrl": "https://smba.trafficmanager.net/amer-client-ss.msg/", + "from": { + "id": "29:1I9Is_Sx0OIy2rQ7Xz1lcaPKlO9eqmBRTBuW6XzkFtcjqxTjPaCMij8BVMdBcL9L_RwWNJyAHFQb0TRzXgyQvA" + }, + "conversation": { + "isGroup": true, + "conversationType": "channel", + "id": "19:efa9296d959346209fea44151c742e73@thread.skype" + }, + "recipient": { + "id": "28:f5d48856-5b42-41a0-8c3a-c5f944b679b0", + "name": "SongsuggesterBot" + }, + "channelData": { + "team": { + "id": "19:efa9296d959346209fea44151c742e73@thread.skype" + }, + "eventType": "teamMemberAdded", + "tenant": { + "id": "72f988bf-86f1-41af-91ab-2d7cd011db47" + } + } +}' +#{"type":"message","id":"4oN7bHB4dit7scwHygF1pf-h|0000000","timestamp":"2019-09-06T15:21:21.9035613Z","serviceUrl":"https://webchat.botframework.com/","channelId":"webchat","from":{"id":"4ccfb6b9-5755-426e-914d-641dd74f5e0f"},"conversation":{"id":"4oN7bHB4dit7scwHygF1pf-h"},"recipient":{"id":"Shuffle@qKw6tMx9fE8","name":"Shuffler"},"textFormat":"plain","locale":"en-US","text":"hi","entities":[{"type":"ClientCapabilities","requiresBotState":true,"supportsListening":true,"supportsTts":true}],"channelData":{"clientActivityID":"15677832808420.ishosdmdfbd"}} diff --git a/functions/triggers/outlook/README.md b/functions/triggers/outlook/README.md new file mode 100644 index 00000000..c1c5db5d --- /dev/null +++ b/functions/triggers/outlook/README.md @@ -0,0 +1,57 @@ +# Outlook trigger +Makes it possible to trigger a workflow based on an email + +## Local testing - Same as ../webhook +```bash +mv ../main.go +go run main.go hook.go +``` + +# Deploy gcloud +gcloud functions deploy outlooktrigger --runtime go111 --entry-point Authorization --trigger-http --project shuffler --memory=128 --set-env-vars=FUNCTION_APIKEY=asdasd,CALLBACKURL=shuffler.io,TRIGGERID=test123,WORKFLOW_ID=YOUR_WORKFLOW_ID + +# Build and deploy +1. Set hook.go line 1 from "package main" to "package function" +2. zip outlooktrigger.tar hook.go +3. Upload to bucket https://console.cloud.google.com/storage/browser/shuffler.appspot.com?project=shuffler +4. Go to the functions https://console.cloud.google.com/functions/list?project=shuffler + + +## How it works (from frontend to backend) +### Choose mailfolders +1. Use microsoft graph api to get the folders the user wants to listen to +* Have the user write their primary email (default) or another one +* Have it show the folders for the email with chooseable buttons somehow +|inbox +|-subinbox +|--subsubinbox <-- choose e.g. this one +|otherfolder + +API: +// requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/me/mailfolders") + +### Add callback subscription +2. Make an APIcall to ("https://outlook.office.com/api/v2.0/me/mailfolders('inbox')/messages") with callback url defined as "https://shuffler.io/api/v1/workflows/{key}/email/authorize" +* Should this be set up whenever the user clicked start or when the workflow is created? +* Start click -> +1. Add another cloud function for the item +2. When it's ready, deploy it to authorize +3. Show it as ready + +### Remove a subscription +* Since everything is already generated above, one would need to +* https://docs.microsoft.com/en-us/graph/api/subscription-delete?view=graph-rest-1.0&tabs=http +* DELETE https://graph.microsoft.com/v1.0/subscriptions/{id} + + +## CREATE - Fixme: LIST all current subscriptions, and stop them if they're towards the same endpoint +* POST /api/v1/workflows/{key}/outlook +* createOutlookSub(resp, request) +* getOutlookSubscriptions(client) // Used to remove all existing for same endpoint +* makeOutlookSubscription(client, folderIds, notificationUrl) +* Add data from ^ to triggerAuth + +## DELETE +* DELETE /api/v1/workflows/{key}/outlook/{triggerId} +* handleDeleteOutlookSub(resp, request) +* handleOutlookSubRemoval(workflowId, triggerId) diff --git a/functions/triggers/outlook/hook.go b/functions/triggers/outlook/hook.go new file mode 100644 index 00000000..9762c2c2 --- /dev/null +++ b/functions/triggers/outlook/hook.go @@ -0,0 +1,222 @@ +package function + +// Shuffle: +// https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Authentication/appId/e080cbf4-5dba-44b4-8643-a7c982189c16/isMSAApp//defaultBlade/Overview/servicePrincipalCreated/true + +// Oauth playground: +// https://oauthplay.azurewebsites.net/ + +// APPS: +// https://apps.dev.microsoft.com + +// REMOVE ACCESS: +// https://portal.office.com/account/# + +// Developer: +// https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference + +// Bots: +// https://dev.botframework.com/bots + +// Connectors +// https://outlook.office.com/connectors/home/login/#/new +// https://go.microsoft.com/fwlink/?linkid=857599 + +import ( + //"encoding/json" + "bytes" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "time" +) + +type Info struct { + Url string `json:"url" datastore:"url"` + Name string `json:"name" datastore:"name"` + Description string `json:"description" datastore:"description"` +} + +// Actions to be done by webhooks etc +// Field is the actual field to use from json +type HookAction struct { + Type string `json:"type" datastore:"type"` + Name string `json:"name" datastore:"name"` + Id string `json:"id" datastore:"id"` + Field string `json:"field" datastore:"field"` +} + +type Hook struct { + Id string `json:"id" datastore:"id"` + Info Info `json:"info" datastore:"info"` + Actions []HookAction `json:"actions" datastore:"actions"` + Type string `json:"type" datastore:"type"` + Status string `json:"status" datastore:"status"` + Running bool `json:"running" datastore:"running"` +} + +type TeamsHook struct { + MembersAdded []struct { + ID string `json:"id"` + } `json:"membersAdded"` + Type string `json:"type"` + Timestamp time.Time `json:"timestamp"` + LocalTimestamp string `json:"localTimestamp"` + ID string `json:"id"` + ChannelID string `json:"channelId"` + ServiceURL string `json:"serviceUrl"` + From struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"from"` + Conversation struct { + IsGroup bool `json:"isGroup"` + ConversationType string `json:"conversationType"` + ID string `json:"id"` + TenantID string `json:"tenantId"` + } `json:"conversation"` + Recipient struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"recipient"` + ChannelData struct { + Team struct { + ID string `json:"id"` + } `json:"team"` + EventType string `json:"eventType"` + Tenant struct { + ID string `json:"id"` + } `json:"tenant"` + } `json:"channelData"` +} + +var hook Hook +var baseUrl = "https://shuffler.io" + +type OauthToken struct { + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + ExtExpiresIn int `json:"ext_expires_in"` + AccessToken string `json:"access_token"` +} + +type TeamsResponse struct { + Conversation struct { + ID string `json:"id"` + } `json:"conversation"` + From struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"from"` + Recipient struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"recipient"` + ReplyToId string `json:"replyToId"` + Type string `json:"type"` + Text string `json:"text"` +} + +type O365hook struct { + OdataContext string `json:"@odata.context"` + Value []struct { + OdataType string `json:"@odata.type"` + ID interface{} `json:"Id"` + SubscriptionID string `json:"SubscriptionId"` + SubscriptionExpirationDateTime time.Time `json:"SubscriptionExpirationDateTime"` + SequenceNumber int `json:"SequenceNumber"` + ChangeType string `json:"ChangeType"` + Resource string `json:"Resource"` + ResourceData struct { + OdataType string `json:"@odata.type"` + OdataID string `json:"@odata.id"` + OdataEtag string `json:"@odata.etag"` + ID string `json:"Id"` + } `json:"ResourceData"` + } `json:"value"` +} + +func Authorization(resp http.ResponseWriter, request *http.Request) { + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Body: %s", err) + resp.WriteHeader(403) + return + } + + if len(body) > 0 { + // In here - get the email data + // Check who it belongs to and run those workflows + // This should be set from run.go with the callback + // userId: {subscriptionID: {workflowID}} + // E.g. workflow: {auth: {userId + // workflow: {trigger: { + + err = forwardRequest(body) + if err != nil { + log.Printf("Failed unmarshal: %s", err) + resp.WriteHeader(403) + return + } + + resp.WriteHeader(200) + resp.Write([]byte("OK")) + return + } + + token := request.URL.Query().Get("validationToken") + if len(token) == 0 { + log.Println("Validation token is missing") + resp.WriteHeader(403) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(string(token))) +} + +// GetUserDetails - Get one user's details from randomuser.me API +func forwardRequest(body []byte) error { + callbackUrl := os.Getenv("CALLBACKURL") + workflowId := os.Getenv("WORKFLOW_ID") + apikey := os.Getenv("FUNCTION_APIKEY") + + fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", callbackUrl, workflowId) + //log.Printf("Sending data to %s", fullUrl) + + data := fmt.Sprintf(`{"execution_argument": "%s"}`, string(body)) + + req, err := http.NewRequest( + http.MethodPost, + fullUrl, + bytes.NewBuffer([]byte(data)), + ) + + if err != nil { + return err + } + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey)) + req.Header.Add("Content-Type", "application/json") + randomUserClient := http.Client{ + Timeout: time.Second * 5, + } + + res, err := randomUserClient.Do(req) + if err != nil { + return err + } + + log.Printf("Status: %d", res.StatusCode) + returnbody, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + + log.Printf("New body: %s", string(returnbody)) + + //log.Println(string(newbody)) + return nil +} diff --git a/functions/triggers/outlook/integrations/config.json b/functions/triggers/outlook/integrations/config.json new file mode 100644 index 00000000..8d98dba3 --- /dev/null +++ b/functions/triggers/outlook/integrations/config.json @@ -0,0 +1,7 @@ +{ + "clientID": "70e37005-c954-4290-b573-d4b94e484336", + "clientSecret": ".eNw/A[kQFB5zL.agvRputdEJENeJ392", + "RedirectURL": "https://44d84ee7.ngrok.io/functions/outlook/register", + "AuthURL": "https://login.microsoftonline.com/common/oauth2/authorize", + "TokenURL": "https://login.microsoftonline.com/common/oauth2/token" +} diff --git a/functions/triggers/outlook/integrations/server.crt b/functions/triggers/outlook/integrations/server.crt new file mode 100644 index 00000000..92244399 --- /dev/null +++ b/functions/triggers/outlook/integrations/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYDCCAkigAwIBAgIJAOvgxcclM1eyMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAk5PMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgwMzA1MTE1MTIwWhcNMjgwMzAyMTE1MTIwWjBF +MQswCQYDVQQGEwJOTzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAowNqrvocJCLLcytYBfZhsqG3CkPsJ4PTicNyjuUGmlILPHlN1DE7jbDo +KuOKImfV4AfQANnttaPksZyuKJL8XGpC7YmF5mmInUWG48SmZjvRbBi1LnCrjJKS +ywh2lJqw4w30w2ItcpogDrYhh6+T3VyabcYngZjKSFgON3wo4I0c6aT19VVXGqnK +y1WEejZmiChV4iwEu4vMPIzt16QpIBr8NPSkBLLRDAGWMjFnIuYDEwgjVn6XYhM9 ++NxhvY9es+qeqLQsZj2a1wcDGaw7G4iNZdltlPmlTCreRDBYBTsYCds/rJmPZ2xi +6jgiyNZj3xHG5Knw2YIw0OwHyT9mWQIDAQABo1MwUTAdBgNVHQ4EFgQUckr5tCzg +F1eBID7mtWTfeqyX4g0wHwYDVR0jBBgwFoAUckr5tCzgF1eBID7mtWTfeqyX4g0w +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAAdH1aPcWBjJHF6n/ +NgIiRSEU4mi5I6RUlPuR7dN7dcmF1Ho7quurNFzknXwks+a62oKSnkkFxxwrv/d6 +dIH5kNibVs7oRxEpA3gUmXXKUW4RPrxAp2zN37t7zs5xbpTATxfJiIMP8Rjo0sOf +SilS22Sn0e0HxBi78t3DJEZvOQ9KSRuD1g9gOAY4lj/fni6rVJo8YCR2MyjmQoXB +luHDF4jTqi/TkXECfqQZu0pctx3maISpB1fAuaELwPDvqbLgoC97gl6bIEiKLkJ0 +JkbGnb997K80ztFvFAKGyUtsvEDCupe/fdBPFqCruAQWI/BqVJFTdRD43dWEQANF +S8ApvA== +-----END CERTIFICATE----- diff --git a/functions/triggers/outlook/integrations/server.key b/functions/triggers/outlook/integrations/server.key new file mode 100644 index 00000000..87c60e08 --- /dev/null +++ b/functions/triggers/outlook/integrations/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAowNqrvocJCLLcytYBfZhsqG3CkPsJ4PTicNyjuUGmlILPHlN +1DE7jbDoKuOKImfV4AfQANnttaPksZyuKJL8XGpC7YmF5mmInUWG48SmZjvRbBi1 +LnCrjJKSywh2lJqw4w30w2ItcpogDrYhh6+T3VyabcYngZjKSFgON3wo4I0c6aT1 +9VVXGqnKy1WEejZmiChV4iwEu4vMPIzt16QpIBr8NPSkBLLRDAGWMjFnIuYDEwgj +Vn6XYhM9+NxhvY9es+qeqLQsZj2a1wcDGaw7G4iNZdltlPmlTCreRDBYBTsYCds/ +rJmPZ2xi6jgiyNZj3xHG5Knw2YIw0OwHyT9mWQIDAQABAoIBABJ+9L/dyQuglwz+ +QgKLLhKinq4fftAM+ReMgZcNDW69GGFIMjh9TZCKHg2fu7Cjr3S37jXqhDoz2mL8 +sBYSd2fU9rsU+4hlOQb/OIrnaSn4Z46oTwZx6kUM7HL1Bt9dnexlTPxOS3HRYwnI +SI2oslJPi4YhEaJ2v5ztwM8y20B/E/zSW2onWz5gB8/bdxSmuJaWfHioIEoac8Gf +BE7jiYMnx9kKeVfgkKPMBKXhAyE2lbAz7N5nDS/4HkbUMh389RBvakc4gkv/QkSW +LNuXpbcSqJiG0FcVutjYS87a/ul3IdhAYmZDTuvRUhNsWBiY3LSY4C9NlHhiJihg +RU1kknECgYEA1dPT6n4hy/OK2mT3ThGQQFMJWsnmcHSsvuK31+UYZOI9kNtBVxc6 +HSHkh0G53o6o2wZLr2gMXy35O5ZxSbectA1Q4MJDlYBs1MfHdOVyE4z6mmA9TLN1 +c+9pYOC0qx+6NwPdO7j2xdUMEUfzocWOeay0AzJg20BQYmKuy3Kc+tcCgYEAwyn6 +n+XS0vodfJdHhvbW/jocQlHQOBK5HklZfq2PMgpRRDaBOvHP/f07egLc2inec2sC +yPSCEfRMMhFcU5NoBt4Unzz2Y8pbpL1L5kbM6B4IqK/5vYcbvkmBkhL3AFT6Fg/3 +3XCdygPW9Vf1nRKr2KhT9dDvB+XO2B75JmKwMk8CgYApKzGf8kz7gZZ4WfwrccI+ +QD6K1lihyjUAQ5J15Mv/kHeeDjjUVcqAlWf0irkImpr0IJAt43COWsGjsWF6efmX +yQCLZZuxixppFVXXsd122ivd0S28OMkiWzQEzP67+83Ujc/okcIhcNVz9lB4ExtN +Xe0CuI5haE6RwsI4tYZ33QKBgFq6ckPRcOAZ3IlmPp9Us3/+fdKq/BSFR7/3s347 +q11FBKCkghFoBxx5lCPVntxhKIQZlHLdkHZOTvnbrkNAPNUsewPIMHcVxOLiCZ3k +/i9OfxIEtSJR5CjjPTQuUtu5pYWKKN2uE/ytKkpmeM1rt64CGv4lAmp2gGFijMs2 +h9jrAoGBANPQO6cKqtnxvst3lnljVBoftlJgeHamUac+xeYKA5Hocv5VwLXMTzzu +09tAhQFvFwCWWrfdgtvIM6k5Sl9F5MdiO9VNflI0IVudIcm9FKorWogH02mtwxsw +hvk5VUk3awiZ/Nu9t38ukeqCetjQEf6yupy/14ZPLndN5naSeEwo +-----END RSA PRIVATE KEY----- diff --git a/functions/triggers/outlook/main.go b/functions/triggers/outlook/main.go new file mode 100644 index 00000000..12bc84b3 --- /dev/null +++ b/functions/triggers/outlook/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "log" + "net/http" + "os" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" +) + +func webhook() { + // FIXME - remove static + port := ":8080" + baseFilePath := "/" + + mux := mux.NewRouter() + mux.SkipClean(true) + + // FIXME - Add path for updating the hook? Can be a specific POST requeuest from backend + mux.HandleFunc(baseFilePath, Authorization).Methods("POST") + mux.HandleFunc("/authorize", Authorization).Methods("POST") + + handlers.LoggingHandler(os.Stdout, mux) + loggedRouter := handlers.LoggingHandler(os.Stdout, mux) + + log.Printf("Starting on http://localhost%s", port) + err := http.ListenAndServe( + port, + loggedRouter, + ) + + if err != nil { + log.Fatal("ListenAndServer: ", err) + } + +} + +func main() { + webhook() +} diff --git a/functions/triggers/outlook/run.go b/functions/triggers/outlook/run.go new file mode 100644 index 00000000..55d3d420 --- /dev/null +++ b/functions/triggers/outlook/run.go @@ -0,0 +1,304 @@ +package main + +// This entire script should be part of the API backend + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "time" + + "golang.org/x/oauth2" +) + +type Subscription struct { + ChangeType string `json:"changeType"` + NotificationURL string `json:"notificationUrl"` + Resource string `json:"resource"` + ExpirationDateTime string `json:"expirationDateTime"` + ClientState string `json:"clientState"` +} + +// ClientState string `json:"ClientState,omitempty"` +// OdataType string `json:"@odata.type"` + +//odata.type - Include "@odata.type":"#Microsoft.OutlookServices.PushSubscription". The PushSubscription entity defines NotificationURL. +//ChangeType - Specifies the types of events to monitor for that resource. See ChangeType for the supported types. +//ClientState - Optional property that indicates that each notification should be sent with a header by the same ClientState value. This lets the listener check the legitimacy of each notification. +//NotificationURL - Specifies where notifications should be sent to. This URL represents a web service typically implemented by the client. +//Resource - Specifies the resource to monitor and receive notifications on. You can use the optional query parameter $filter to refine the conditions for a notification, or use $select to include specific properties in a rich notification. + +//https://outlook.office.com/mail.read + +type Config struct { + ClientID string + ClientSecret string + RedirectUrl string + AuthUrl string + TokenUrl string +} + +func getOfficeAppInfo() (Config, error) { + configpath := "integrations/config.json" + + data, err := ioutil.ReadFile(configpath) + if err != nil { + //log.Fatal(err) + log.Printf("Error getting hive: %s\n", err) + } + + config := Config{} + err = json.Unmarshal(data, &config) + if err != nil { + return Config{}, err + } + + return config, nil +} + +// This should be a popup for the user +func get_accesstoken() (*http.Client, OauthToken, error) { + ctx := context.Background() + config, err := getOfficeAppInfo() + if err != nil { + return nil, OauthToken{}, err + } + + conf := &oauth2.Config{ + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + Scopes: []string{ + "Mail.Read", + "User.Read", + "https://outlook.office.com/mail.read", + }, + RedirectURL: "https://localhost:8000", + Endpoint: oauth2.Endpoint{ + AuthURL: config.AuthUrl, + TokenURL: config.TokenUrl, + }, + } + //"Mail.Read.Shared", + + //url := conf.AuthCodeURL("state", oauth2.SetAuthURLParam("resource", "https://outlook.office.com")) + // ADD DATA TO STATE HERE :O + url := conf.AuthCodeURL("workflow_id%3Dc2e0b50a-2957-427e-a97b-b989dc5a5408%26trigger_id%3D9e845679-5843-4959-a76c-a6d664e9df35%26username%3Drheyix.yt@gmail.com", oauth2.SetAuthURLParam("resource", "https://graph.microsoft.com")) + + fmt.Printf("Visit the URL for the auth dialog: \n%v\n\n", url) + codechannel := make(chan string) + + // Handles the server callback, listening on port 8000 + go func() { + port := ":8000" + + http.HandleFunc("/", func(response http.ResponseWriter, request *http.Request) { + tmpcode := request.URL.Query().Get("code") + if len(tmpcode) < 100 { + return + } else { + codechannel <- tmpcode + } + }) + + // FIX - might cause errors not being printed + err := http.ListenAndServeTLS(port, "integrations/server.crt", "integrations/server.key", nil) + if err != nil { + log.Printf("%s\n", err) + } + }() + + code := <-codechannel + close(codechannel) + + // https://stackoverflow.com/questions/52787420/multiple-resources-in-a-single-authorization-request + // Multi resource ^ + access_token, err := conf.Exchange(ctx, code) + //log.Printf("%#v", access_token) + if err != nil { + return nil, OauthToken{}, err + } + + //log.Printf("%#v", access_token) + outlookClient := conf.Client(ctx, access_token) + + oauthToken := OauthToken{ + AccessToken: access_token.AccessToken, + TokenType: access_token.TokenType, + RefreshToken: access_token.RefreshToken, + Expiry: access_token.Expiry, + } + + return outlookClient, oauthToken, nil +} + +type OauthToken struct { + AccessToken string `json:"AccessToken" datastore:"AccessToken,noindex"` + TokenType string `json:"TokenType" datastore:"TokenType,noindex"` + RefreshToken string `json:"RefreshToken" datastore:"RefreshToken,noindex"` + Expiry time.Time `json:"Expiry" datastore:"Expiry,noindex"` +} + +type Mailfolders struct { + OdataContext string `json:"@odata.context"` + OdataNextLink string `json:"@odata.nextLink"` + Value []struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + ParentFolderID string `json:"parentFolderId"` + ChildFolderCount int `json:"childFolderCount"` + UnreadItemCount int `json:"unreadItemCount"` + TotalItemCount int `json:"totalItemCount"` + } `json:"value"` +} + +func getFolders(client *http.Client) (Mailfolders, error) { + requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/frikky@shuffletest.onmicrosoft.com/mailfolders") + + ret, err := client.Get(requestUrl) + if err != nil { + log.Printf("FolderErr: %s", err) + return Mailfolders{}, err + } + + log.Printf("Status folders: %d", ret.StatusCode) + body, err := ioutil.ReadAll(ret.Body) + if err != nil { + log.Printf("Body: %s", err) + return Mailfolders{}, err + } + + //log.Printf("Body: %s", string(body)) + + mailfolders := Mailfolders{} + err = json.Unmarshal(body, &mailfolders) + if err != nil { + log.Printf("Unmarshal: %s", err) + return Mailfolders{}, err + } + + //fmt.Printf("%#v", mailfolders) + // FIXME - recursion for subfolders + // Recursive struct + // folderEndpoint := fmt.Sprintf("%s/%s/childfolders?$top=40", requestUrl, parentId) + for _, folder := range mailfolders.Value { + log.Println(folder.DisplayName) + } + + return mailfolders, nil +} + +// Subscribes to a mailbox based on some thingies +func makeSubscription(client *http.Client, folderIds []string) { + // FIXME - show the users folders from oauth and let them choose + + fullUrl := "https://graph.microsoft.com/v1.0/subscriptions" + //resource := fmt.Sprintf("https://outlook.office.com/api/v2.0/me/mailfolders('inbox')/messages") + resource := fmt.Sprintf("me/mailfolders('inbox')/messages") + sub := Subscription{ + ChangeType: "created", + NotificationURL: "https://de4fc12b.ngrok.io", + ExpirationDateTime: "2019-09-22T18:23:45.9356913Z", + ClientState: "This is a test", + Resource: resource, + } + + data, err := json.Marshal(sub) + if err != nil { + log.Printf("Marshal: %s", err) + return + } + + log.Printf(string(data)) + + req, err := http.NewRequest( + "POST", + fullUrl, + bytes.NewBuffer(data), + ) + req.Header.Add("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + log.Printf("Client: %s", err) + return + } + + log.Printf("Status: %d", res.StatusCode) + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Printf("Body: %s", err) + return + } + + fmt.Println(string(body)) + log.Println("Shoooould be set up :)") + +} + +func getOutlookClient(code string, accessToken OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) { + ctx := context.Background() + + conf := &oauth2.Config{ + ClientID: "70e37005-c954-4290-b573-d4b94e484336", + ClientSecret: ".eNw/A[kQFB5zL.agvRputdEJENeJ392", + Scopes: []string{ + "Mail.Read", + "User.Read", + "https://outlook.office.com/mail.read", + }, + RedirectURL: redirectUri, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://login.microsoftonline.com/common/oauth2/authorize", + TokenURL: "https://login.microsoftonline.com/common/oauth2/token", + }, + } + + if len(code) > 0 { + access_token, err := conf.Exchange(ctx, code) + if err != nil { + log.Printf("Access_token issue: %s", err) + return &http.Client{}, access_token, err + } + + client := conf.Client(ctx, access_token) + return client, access_token, nil + } else { + // Manually recreate the oauthtoken + access_token := &oauth2.Token{ + AccessToken: accessToken.AccessToken, + TokenType: accessToken.TokenType, + RefreshToken: accessToken.RefreshToken, + Expiry: accessToken.Expiry, + } + + client := conf.Client(ctx, access_token) + return client, access_token, nil + } +} + +func main() { + graphclient, oauthToken, err := get_accesstoken() + if err != nil { + log.Printf("Oauth setup: %s", err) + return + } + + // FIXME - make this possible for alternative users (shared) + folders, err := getFolders(graphclient) + if err != nil { + log.Printf("Folder get error: %s", err) + return + } + _ = folders + + folderIds := []string{"inbox"} + //log.Println(folders) + //log.Printf("%#v", oauthToken) + // Use oauthToken to generate data for outlook + outlookclient, _, err := getOutlookClient("", oauthToken, "https://localhost:8000") + makeSubscription(outlookclient, folderIds) +} diff --git a/functions/triggers/webhook/.gcloudignore b/functions/triggers/webhook/.gcloudignore new file mode 100644 index 00000000..6ad2be26 --- /dev/null +++ b/functions/triggers/webhook/.gcloudignore @@ -0,0 +1,3 @@ +main.go +*.swo +*.swp diff --git a/functions/triggers/webhook/README.md b/functions/triggers/webhook/README.md new file mode 100644 index 00000000..44e83cd6 --- /dev/null +++ b/functions/triggers/webhook/README.md @@ -0,0 +1,17 @@ +# Local testing +1. Change hook.go package to main +```bash +mv ../main.go . +go run main.go hook.go +``` + +# Deploy local +```bash +gcloud functions deploy webhook --runtime go111 --entry-point Authorization --trigger-http --project shuffler --memory=128 --set-env-vars=FUNCTION_APIKEY=asdasd,CALLBACKURL=shuffler.io,HOOKID=test123 +``` + +# Build and deploy from gui +1. rm webhook.zip +2. zip webhook.zip hook.go +3. Upload to bucket https://console.cloud.google.com/storage/browser/shuffler.appspot.com?project=shuffler +4. Restart hook(s) (https://shuffler.io/webhooks) diff --git a/functions/triggers/webhook/hook.go b/functions/triggers/webhook/hook.go new file mode 100644 index 00000000..9395fbd7 --- /dev/null +++ b/functions/triggers/webhook/hook.go @@ -0,0 +1,249 @@ +package function + +// BOTS +// https://dev.botframework.com/bots/channels?id=Shuffle + +// APPS: +// apps.dev.microsoft.com + +// REMOVE ACCESS: +// https://portal.office.com/account/# + +// Developer: +// https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + "time" +) + +type Info struct { + Url string `json:"url" datastore:"url"` + Name string `json:"name" datastore:"name"` + Description string `json:"description" datastore:"description"` +} + +// Actions to be done by webhooks etc +// Field is the actual field to use from json +type HookAction struct { + Type string `json:"type" datastore:"type"` + Name string `json:"name" datastore:"name"` + Id string `json:"id" datastore:"id"` + Field string `json:"field" datastore:"field"` +} + +type Hook struct { + Id string `json:"id" datastore:"id"` + Info Info `json:"info" datastore:"info"` + Actions []HookAction `json:"actions" datastore:"actions"` + Type string `json:"type" datastore:"type"` + Status string `json:"status" datastore:"status"` + Running bool `json:"running" datastore:"running"` +} + +var hook Hook + +func Authorization(resp http.ResponseWriter, request *http.Request) { + apikey := os.Getenv("FUNCTION_APIKEY") + callbackUrl := os.Getenv("CALLBACKURL") + hookId := os.Getenv("HOOKID") + if len(apikey) == 0 { + log.Println("Env FUNCTION_APIKEY not set") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Internal error"}`))) + return + } + + if len(callbackUrl) == 0 { + log.Println("Env CALLBACKURL not set") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Internal error"}`))) + return + } + + if len(hookId) == 0 { + log.Println("Env HOOKID not set") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Internal error"}`))) + return + } + + authorization := request.Header.Get("Authorization") + if len(authorization) == 0 { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Authorization header required"}`))) + return + } + + if !strings.HasPrefix(authorization, "Bearer") { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Authorization header must start with Bearer"}`))) + return + } + + apikeyCheck := strings.Split(authorization, " ") + if len(apikeyCheck) != 2 { + log.Println("Length is not 2 for apikey: %s vs %s", apikeyCheck[1], apikey) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid Apikey"}`))) + return + } + + if apikeyCheck[1] != apikey { + log.Printf("Apikeys are not equal. Failed authentication.") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid Apikey"}`))) + return + } + + err := ForwardRequest(resp, request) + if err != nil { + log.Printf("Error: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + log.Println("Success?") + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +func loadConfiguration(fullUrl string, apikey string) (Hook, error) { + client := &http.Client{} + + req, err := http.NewRequest( + "GET", + fullUrl, + nil, + ) + + if err != nil { + log.Printf("Error making http request: %s", req) + return Hook{}, err + } + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey)) + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + log.Printf("Error in http request: %s", req) + return Hook{}, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("Error reading response: %s", req) + return Hook{}, err + } + + err = json.Unmarshal(body, &hook) + if err != nil { + log.Printf("Failed unmarshaling hook API", req) + return Hook{}, err + } + + return hook, nil +} + +// GetUserDetails - Get one user's details from randomuser.me API +func ForwardRequest(resp http.ResponseWriter, request *http.Request) error { + callbackUrl := os.Getenv("CALLBACKURL") + hookId := os.Getenv("HOOKID") + apikey := os.Getenv("FUNCTION_APIKEY") + + hook, err := loadConfiguration( + fmt.Sprintf("%s/api/v1/hooks/%s", callbackUrl, hookId), + apikey, + ) + + log.Println("Done loading!") + + if err != nil { + return err + } + + log.Printf("%#v", hook) + + // Find all things to execute + workflowUrls := []string{} + for _, item := range hook.Actions { + if item.Type == "" { + log.Printf("CONTINUE AAS EMPTY ITEM: %#v", item) + continue + } + + if item.Type == "workflow" { + workflowUrls = append(workflowUrls, item.Id) + } + } + + if len(workflowUrls) == 0 { + return errors.New("No actions to do yet") + } + + log.Printf("Should send data to the following: %s", strings.Join(workflowUrls, ", ")) + + randomUserClient := http.Client{ + Timeout: time.Second * 3, + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return err + } + + // Prepare data + type arg struct { + ExecutionArgument string `json:"execution_argument"` + } + data := arg{ + ExecutionArgument: string(body), + } + + newjson, err := json.Marshal(data) + if err != nil { + return err + } + + // Loop all executions to run + for _, item := range workflowUrls { + fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", callbackUrl, item) + log.Printf("Sending data to %s", fullUrl) + req, err := http.NewRequest( + http.MethodPost, + fullUrl, + bytes.NewBuffer(newjson), + ) + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey)) + req.Header.Add("Content-Type", "application/json") + if err != nil { + return err + } + + res, err := randomUserClient.Do(req) + if err != nil { + return err + } + + log.Printf("Status: %d", res.StatusCode) + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + + log.Printf(string(body)) + } + + //log.Println(string(newbody)) + return nil +} diff --git a/functions/triggers/webhook/main.go b/functions/triggers/webhook/main.go new file mode 100644 index 00000000..5dc914af --- /dev/null +++ b/functions/triggers/webhook/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "log" + "net/http" + "os" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" +) + +func webhook() { + // FIXME - remove static + port := ":8080" + baseFilePath := "/" + + mux := mux.NewRouter() + mux.SkipClean(true) + + // FIXME - Add path for updating the hook? Can be a specific POST requeuest from backend + mux.HandleFunc(baseFilePath, Authorization).Methods("POST") + + handlers.LoggingHandler(os.Stdout, mux) + loggedRouter := handlers.LoggingHandler(os.Stdout, mux) + + err := http.ListenAndServe( + port, + loggedRouter, + ) + + if err != nil { + log.Fatal("ListenAndServer: ", err) + } + +} + +func main() { + webhook() +} diff --git a/install-guide.md b/install-guide.md new file mode 100644 index 00000000..05601f11 --- /dev/null +++ b/install-guide.md @@ -0,0 +1,27 @@ +# Installation guide +Installation of Shuffle is currently only available in docker. + +There are four parts to the infrastructure: +* Frontend - GUI, React +* Backend - Go +* Database - Google Datastore +* Orborus - Go, controls the workers to deploy. Can be used to connect to others' setup +* Worker - Controls each workflow execution +* App_sdk - Used + +## Docker +The Docker setup is done with docker-compose and is a single command to get set up. + +1. Make sure you have Docker and [docker-compose](https://docs.docker.com/compose/install/) installed. + +2. Run docker-compose. +``` +git clone https://github.com/frikky/shuffle +cd shuffle +docker-compose up -d +``` + +3. Useful info: +* The server is available on http://localhost:3001 (or your servername) +* Further configuration in docker-compose.yml and .env. +* Default database location is /etc/shuffle diff --git a/setup.sh b/setup.sh new file mode 100644 index 00000000..42e1d3ed --- /dev/null +++ b/setup.sh @@ -0,0 +1,31 @@ +# Build script to make it work as an open source platform + +# 1. Grab builtin functions - Done in backend as a button click +# 2. Upload to database & build docker images +# 3. Run docker-compose: frontend, backend, database & orborus +# 4. Set up docker swarm for apps? + +# Basic overview of how it works: +# +# Backend: o +# | +# Orborus: o +# / \ +# Workers: o o +# / / \ +# Apps: o o o + +# 1. Grab builtin functions +# Where should I have these? Maybe OpenAPI github repo and just preload? + +echo "Building frontend" +cd frontend +npm run build +rm -rf ../backend/go-app/build +cp -r build/ ../backend/go-app/build + +echo "Setting up backend" +cd ../backend/go-app +go build +go test +#gcloud app deploy $GOPATH/src/github.com/frikky/shuffle/app.yaml