diff --git a/backend/app_gen/openapi-parsers/generated/carbon_black_response.yaml b/backend/app_gen/openapi-parsers/generated/carbon_black_response.yaml deleted file mode 100644 index 124f2968..00000000 --- a/backend/app_gen/openapi-parsers/generated/carbon_black_response.yaml +++ /dev/null @@ -1,235 +0,0 @@ -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/backend/app_gen/openapi-parsers/generated/cyberreason.yaml b/backend/app_gen/openapi-parsers/generated/cyberreason.yaml deleted file mode 100644 index 70e10109..00000000 --- a/backend/app_gen/openapi-parsers/generated/cyberreason.yaml +++ /dev/null @@ -1,559 +0,0 @@ -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/backend/app_gen/openapi-parsers/generated/recorded_future.yaml b/backend/app_gen/openapi-parsers/generated/recorded_future.yaml deleted file mode 100644 index 2c3bc20e..00000000 --- a/backend/app_gen/openapi-parsers/generated/recorded_future.yaml +++ /dev/null @@ -1,1383 +0,0 @@ -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/backend/app_gen/openapi-parsers/generated/shodan.yaml b/backend/app_gen/openapi-parsers/generated/shodan.yaml deleted file mode 100644 index 8004d442..00000000 --- a/backend/app_gen/openapi-parsers/generated/shodan.yaml +++ /dev/null @@ -1,166 +0,0 @@ -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/backend/app_gen/openapi-parsers/generated/tenable_tenable.io.yaml b/backend/app_gen/openapi-parsers/generated/tenable_tenable.io.yaml deleted file mode 100644 index 3bac77a9..00000000 --- a/backend/app_gen/openapi-parsers/generated/tenable_tenable.io.yaml +++ /dev/null @@ -1,149 +0,0 @@ -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/backend/app_gen/openapi-parsers/swimlane.py b/backend/app_gen/openapi-parsers/swimlane.py index 4926c690..d65b601a 100644 --- a/backend/app_gen/openapi-parsers/swimlane.py +++ b/backend/app_gen/openapi-parsers/swimlane.py @@ -1,5 +1,25 @@ import requests import yaml +import json +import os +import io +import base64 +from PIL import Image +#import tkinter +#import _tkinter +#tkinter._test() + + +#sudo apt-get install python-imaging-tk +#sudo apt-get install python3-tk + +# USAGE: +# 1. Find the item here: +# https://apphub.swimlane.com/swimbundles/swimlane/sw_alienvault_threatcrowd +# 2. +# https://jsonlint.com/ + +# META: data["meta"]. Stuff like count. May be useful :) def parse_data(data): openapi = { @@ -21,14 +41,61 @@ def parse_data(data): } } - 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 = data["swimbundle"] + filename = "%s.yaml" % data["product"].replace(" ", "_").lower() + openapi["info"]["title"] = "%s %s" % (data["vendor"], data["product"]) + openapi["info"]["description"] = "Automated generation of %s" % (openapi["info"]["title"]) # data["description"] + # https://swagger.io/docs/specification/authentication/ + try: + asset = data["asset"] + inputparams = asset["inputParameters"] + + try: + openapi["servers"] = [inputparams["api_url"]["example"]] + except KeyError as e: + #print(inputparams) + #print("Field error: %s" % e) + pass + + authset = False + try: + tmpauth = inputparams["api_user"] + tmpauth = inputparams["api_key"] + + openapi["components"]["securitySchemes"] = { + "BasicAuth": { + "type": "http", + "scheme": "basic" + } + } + authset = True + except KeyError as e: + pass + + try: + tmpauth = inputparams["username"] + tmpauth = inputparams["password"] + + openapi["components"]["securitySchemes"] = { + "BasicAuth": { + "type": "http", + "scheme": "basic" + } + } + authset = True + except KeyError as e: + pass + + #if not authset: + # print("AUTH NOT SET: %s" % inputparams) + + except KeyError as e: + print("KeyError asset: %s" % e) + cnt = 0 + paramnames = [] for task in data["tasks"]: method = "post" @@ -45,51 +112,58 @@ def parse_data(data): }, } - #taskname = task["name"] - #taskdescription = task["description"] taskcategory = task["family"] + taskname = task["name"] + paramnames.append(taskname) - # This doesn't really do much except build the return value structures - for parameter in task["input_parameters"]: - example = parameter["example"] - + for key, value in task["inputParameters"].items(): + schema = "string" inVar = "query" - if parameter["type"] == 6: + if value["type"] == 6: inVar = "body" schema = "string" schemaset = False - if parameter["type"] != 1: - if (parameter["type"] == 7): + if value["type"] != 1: + if (value["type"] == 7): schema = "boolean" schemaset = True if schema == "string" and schemaset: - print("Should change type: %d" % parameter["type"]) + print("Should change type: %d" % value["type"]) print(task["name"]) - print(parameter["name"]) - print() + print(value["name"]) + example = "" + try: + example = value["example"] + except KeyError: + pass - if len(example) == 1: - print("Change to number?") - if example.startswith("{"): - print("Change to json object?") - if example.startswith("["): - print("Change to array object?") + description = "" + try: + description = value["description"] + except KeyError: + pass - # Not sure how to tackle this + required = False + try: + required = value["required"] + except KeyError: + pass + openapi["paths"]["tmp%d" % cnt][method]["parameters"].append({ - "in": inVar, - "name": parameter["name"], - "required": parameter["required"], - "description": parameter["description"], - "schema": {"type": schema} + "name": value["name"], + "required": required, + "example": example, + "description": description, + "schema": {"type": schema}, + "in": inVar }) - if len(task["available_output_variables"]) > 0: - openapi["paths"]["tmp%d" % cnt][method]["responses"]["200"]["content"]: { + if len(task["availableOutputVariables"]) > 0: + openapi["paths"]["tmp%d" % cnt][method]["responses"]["200"]["content"] = { "application/json": { "schema": { "$ref": "#/components/schemas/tmp%d" % cnt @@ -97,35 +171,141 @@ def parse_data(data): } } + #responses: + # '200': + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/tmp1' + #description: Successful request + 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"} + for key, value in task["availableOutputVariables"].items(): + if key == "response_code": + continue + openapi["components"]["schemas"]["tmp%d" % cnt]["properties"][key] = { + "type": "string" + } cnt += 1 - #print(openapi) - #print(filename) + print("%s: %d" % (openapi["info"]["title"], len(paramnames))) return filename, openapi -def dump_data(filename, openapi): - generatedfile = "generated/%s" % filename - with open(generatedfile, "w+") as tmp: - tmp.write(yaml.dump(openapi)) +def dump_data(filename, openapi, category): + generatedfile = "generated/%s/%s" % (category, filename) + try: + with open(generatedfile, "w+") as tmp: + tmp.write(yaml.dump(openapi)) + except FileNotFoundError: + try: + os.mkdir("generated/%s" % category) + with open(generatedfile, "w+") as tmp: + tmp.write(yaml.dump(openapi)) - print("Generated %s" % generatedfile) + except FileExistsError: + pass 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) + #https://apphub.swimlane.com/ + categories = [ + "Investigation", + "Endpoint Security & Management", + "Network Security & Management", + "Communication", + "SIEM & Log Management", + "Governance & Risk Management", + "Vulnerability & Patch Management", + "Ticket Management", + "DevOps & Application Security", + "Identity & Access Management", + "Infrastructure", + "Miscellaneous", + ] + + search_category = categories[2] + total = 0 + for search_category in categories: + number = 1 + innertotal = 0 + + while(True): + url = "https://apphub.swimlane.io/api/search/swimbundles?page=%d" % number + + json = {"fields": {"family": search_category}} + ret = requests.post( + url, + json=json, + ) + + if ret.status_code != 201: + print("RET NOT 201: %d" % ret.status_code) + break + + parsed = ret.json() + try: + category = parsed["data"][0]["swimbundleMeta"]["family"][0] + except KeyError: + category = "" + except IndexError: + category = "" + + if category == "": + break + + for data in parsed["data"]: + try: + filename, openapi = parse_data(data) + except: + try: + print("Skipping %s %s because of an error" % (data["vendor"], data["product"])) + except KeyError: + pass + + continue + + openapi["tags"] = [ + { + "name": category, + } + ] + + appid = data["swimbundleMeta"]["logo"]["id"] + logoUrl = "https://apphub.swimlane.io/api/logos/%s" % appid + logodata = requests.get(logoUrl) + if logodata.status_code == 200: + logojson = logodata.json() + try: + logobase64 = logojson["data"]["base64"] + #.split(",")[1] + + openapi["info"]["x-logo"] = logobase64 + #print(logobase64) + #msg = base64.b64decode(logobase64) + #with io.BytesIO(msg) as buf: + # with Image.open(buf) as tempImg: + # newWidth = 174 / tempImg.width # change this to what ever width you need. + # newHeight = 174 / tempImg.height # change this to what ever height you need. + # newSize = (int(newWidth * tempImg.width), int(newHeight * tempImg.height)) + # newImg1 = tempImg.resize(newSize) + # lbl1.IMG = ImageTk.PhotoImage(image=newImg1) + # lbl1.configure(image=lbl1.IMG) + except KeyError: + print("Failed logo parsing for %s" % appid) + pass + + dump_data(filename, openapi, category) + innertotal += 1 + total += 1 + + number += 1 + + print("Created %d openapi specs from Swimlane with category %s" % (innertotal, search_category)) + + print("\nCreated %d TOTAL openapi specs from Swimlane" % (total)) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index e896f67c..1492305a 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -308,7 +308,18 @@ class AppBase: print("DATA: %s\n" % data) return parse_wrapper(data) + # Looks for parantheses to grab special cases within a string, e.g: + # int(1) lower(HELLO) or length(what's the length) + # FIXME: + # There is an issue in here where it returns data wrong. Example: + # Authorization=Bearer authkey + # = + # Authorization=Bearer authkey + # ^ Double space. def parse_wrapper_start(data): + if "(" not in data or ")" not in data: + return data + newdata = [] newstring = "" record = True @@ -337,7 +348,7 @@ class AppBase: if len(newstring) > 0: newdata.append(newstring) - #print(newdata) + print("Newdata: ", newdata) parsedlist = [] non_string = False for item in newdata: @@ -348,17 +359,20 @@ class AppBase: parsedlist.append(ret) if len(parsedlist) > 0 and not non_string: + print("Returning parsed list: ", parsedlist) return " ".join(parsedlist) elif len(parsedlist) == 1 and non_string: return parsedlist[0] else: - #print("Casting back to string because multi: ", parsedlist) + print("Casting back to string because multi: ", parsedlist) newlist = [] for item in parsedlist: try: newlist.append(str(item)) except ValueError: newlist.append("parsing_error") + + # Does this create the issue? return " ".join(newlist) # Parses JSON loops and such down to the item you're looking for @@ -400,7 +414,7 @@ class AppBase: # Means it's a single item -> continue if seconditem == "": - print("In first - handling %s", seconditem) + print("In first - handling %s" % seconditem) tmpitem = basejson[int(firstitem)] try: newvalue, is_loop = recurse_json(tmpitem, parsersplit[outercnt+1:]) @@ -460,23 +474,44 @@ class AppBase: # Returns a string if the result is single, or a list if it's a list def get_json_value(execution_data, input_data): parsersplit = input_data.split(".") - actionname = parsersplit[0][1:].replace(" ", "_", -1) + actionname_lower = parsersplit[0][1:].lower() + #Actionname: Start_node print(f"Actionname: {actionname}") - + # 1. Find the action baseresult = "" - actionname_lower = actionname.lower() + + appendresult = "" + print("Parsersplit length: %d" % len(parsersplit)) + if (actionname_lower.startswith("exec ") or actionname_lower.startswith("webhook ") or actionname_lower.startswith("schedule ") or actionname_lower.startswith("userinput ") or actionname_lower.startswith("email_trigger ") or actionname_lower.startswith("trigger ")) and len(parsersplit) == 1: + record = False + for char in actionname_lower: + if char == " ": + record = True + + if record: + appendresult += char + + actionname_lower = "exec" + + actionname_lower = actionname_lower.replace(" ", "_", -1) + try: if actionname_lower == "exec" or actionname_lower == "webhook" or actionname_lower == "schedule" or actionname_lower == "userinput" or actionname_lower == "email_trigger" or actionname_lower == "trigger": baseresult = execution_data["execution_argument"] else: - for result in execution_data["results"]: - resultlabel = result["action"]["label"].replace(" ", "_", -1).lower() - if resultlabel.lower() == actionname_lower: - baseresult = result["result"] - break + #print("Within execution data check. Execution data: %s", execution_data["results"]) + if execution_data["results"] != None: + for result in execution_data["results"]: + resultlabel = result["action"]["label"].replace(" ", "_", -1).lower() + if resultlabel.lower() == actionname_lower: + baseresult = result["result"] + break + else: + print("No results to get values from.") + baseresult = "$" + parsersplit[0][1:] print("BEFORE VARIABLES!") if len(baseresult) == 0: @@ -518,17 +553,17 @@ class AppBase: # 2. Find the JSON data if len(baseresult) == 0: - return "", False + return ""+appendresult, False if len(parsersplit) == 1: - return baseresult, False + return baseresult+appendresult, False baseresult = baseresult.replace("\'", "\"") basejson = {} try: basejson = json.loads(baseresult) except json.decoder.JSONDecodeError as e: - return baseresult, False + return baseresult+appendresult, False data, is_loop = recurse_json(basejson, parsersplit[1:]) parseditem = data @@ -542,7 +577,7 @@ class AppBase: print("SET DATA WRAPPER TO %s!" % parsersplit[-1]) parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data)) - return parseditem, is_loop + return parseditem+appendresult, is_loop # Parses parameters sent to it and returns whether it did it successfully with the values found def parse_params(action, fullexecution, parameter): @@ -863,7 +898,7 @@ class AppBase: # Custom format for ${name[0,1,2,...]}$ #submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})" - submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})" + submatch = "([${]{2}#?([0-9a-zA-Z_-]+)#?(\[.*\])[}$]{2})" actualitem = re.findall(submatch, value, re.MULTILINE) try: if action["skip_multicheck"]: @@ -875,12 +910,14 @@ class AppBase: print("Return value: %s" % value) actionname = action["name"] #print("Multicheck ", actualitem) - print("Actual item: %s" % actualitem) + print("ITEM LENGTH: %d, Actual item: %s" % (len(actualitem), actualitem)) if len(actualitem) > 0: multiexecution = True # Loop WITHOUT JSON variables go here. # Loop WITH variables go in else. + print("Before first part in multiexec!") + handled = False if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER": print("Pre replacement: %s" % actualitem[0][2]) tmpitem = value @@ -910,13 +947,13 @@ class AppBase: #print("RESULTARRAY: %s" % resultarray) print("MULTI finished: %s" % replacement) else: - # This is here to handle for loops within variables.. kindof # 1. Find the length of the longest array # 2. Build an array with the base values based on parameter["value"] # 3. Get the n'th value of the generated list from values # 4. Execute all n answers replacements = {} + curminlength = 0 for replace in actualitem: try: to_be_replaced = replace[0] @@ -928,6 +965,9 @@ class AppBase: itemlist = json.loads(actualitem) if len(itemlist) > minlength: minlength = len(itemlist) + + if len(itemlist) > curminlength: + curminlength = len(itemlist) except json.decoder.JSONDecodeError as e: print("JSON Error: %s in %s" % (e, actualitem)) @@ -935,8 +975,9 @@ class AppBase: # This is a result array for JUST this value.. # What if there are more? + print("LENGTH: %d. In second part of else: %s" % (len(itemlist), replacements)) resultarray = [] - for i in range(0, minlength): + for i in range(0, curminlength): tmpitem = json.loads(json.dumps(parameter["value"])) for key, value in replacements.items(): replacement = json.dumps(json.loads(value)[i]) @@ -959,9 +1000,10 @@ class AppBase: multi_parameters[parameter["name"]] = resultarray else: # Parses things like int(value) - self.logger.info("Parsing wrapper data for %s" % value) + print("Normal parsing (not looping) with data %s" % value) value = parse_wrapper_start(value) + print("POST data value: %s" % value) params[parameter["name"]] = value multi_parameters[parameter["name"]] = value @@ -992,8 +1034,9 @@ class AppBase: #for i in range(calltimes): if not multiexecution: print("APP_SDK DONE: Starting NORMAL execution of function") + print("Running with params %s" % params) newres = await func(**params) - #print("NEWRES: ", newres) + print("Return from execution: %s" % newres) if isinstance(newres, str): result += newres else: @@ -1074,6 +1117,7 @@ class AppBase: print("Running with params %s" % baseparams) ret = await func(**baseparams) + print("Return from execution: %s" % ret) if isinstance(ret, dict) or isinstance(ret, list): results.append(ret) json_object = True diff --git a/backend/app_sdk/build.sh b/backend/app_sdk/build.sh index b1ac5113..bfd63f22 100644 --- a/backend/app_sdk/build.sh +++ b/backend/app_sdk/build.sh @@ -1,6 +1,6 @@ #!/bin/bash NAME=app_sdk -VERSION=0.7.3 +VERSION=0.7.6 docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION diff --git a/backend/go-app/codegen.go b/backend/go-app/codegen.go index 89576345..609acfc9 100644 --- a/backend/go-app/codegen.go +++ b/backend/go-app/codegen.go @@ -396,10 +396,11 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet bodyAddin, verifyAddin, ) - //if strings.Contains(functionname, "get_returns_the_vuln") { - // log.Println(data) - // log.Printf("Queries: %s", queryString) - //} + + if strings.Contains(functionname, "api_dumps_delete") { + log.Println(data) + log.Printf("Queries: %s", queryString) + } //log.Printf(data) return functionname, data @@ -414,7 +415,13 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, } if len(swagger.Servers) == 0 { - return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'") + //return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'") + //return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'") + swagger.Servers = openapi3.Servers{ + &openapi3.Server{ + URL: "https://hostname.com", + }, + } } api.Name = swagger.Info.Title @@ -1734,7 +1741,7 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W action.Parameters = append(action.Parameters, optionalParam) } - functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "delete", parameters, optionalQueries, headersFound) + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "patch", parameters, optionalQueries, headersFound) if len(functionname) > 0 { action.Name = functionname @@ -1868,7 +1875,7 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor action.Parameters = append(action.Parameters, optionalParam) } - functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "delete", parameters, optionalQueries, headersFound) + functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "put", parameters, optionalQueries, headersFound) if len(functionname) > 0 { action.Name = functionname diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 4a78d089..e88e8a9e 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -72,6 +72,7 @@ var gceProject = "shuffle" var bucketName = "shuffler.appspot.com" var baseAppPath = "/home/frikky/git/shaffuru/tmp/apps" var baseDockerName = "frikky/shuffle" +var syncUrl = "http://192.168.3.6:5002" var dbclient *datastore.Client @@ -139,6 +140,14 @@ type UserLimits struct { MaxWorkflows int64 `json:"max_workflows" datastore:"max_workflows"` } +type retStruct struct { + Success bool `json:"success"` + SyncFeatures SyncFeatures `json:"sync_features"` + SessionKey string `json:"session_key"` + IntervalSeconds int64 `json:"interval_seconds"` + Reason string `json:"reason"` +} + // Saves some data, not sure what to have here lol type UserAuth struct { Description string `json:"description" datastore:"description,noindex" yaml:"description"` @@ -161,6 +170,7 @@ type Environment struct { Default bool `datastore:"default" json:"default"` Archived bool `datastore:"archived" json:"archived"` Id string `datastore:"id" json:"id"` + OrgId string `datastore:"org_id" json:"org_id"` } type User struct { @@ -380,6 +390,7 @@ type Hook struct { Status string `json:"status" datastore:"status"` Workflows []string `json:"workflows" datastore:"workflows"` Running bool `json:"running" datastore:"running"` + OrgId string `json:"org_id" datastore:"org_id"` } func createFileFromFile(ctx context.Context, bucket *storage.BucketHandle, remotePath, localPath string) error { @@ -679,47 +690,14 @@ func handleApiAuthentication(resp http.ResponseWriter, request *http.Request) (U 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) + log.Printf("Session %s doesn't exist (session auth): %s", sessionToken, err) return User{}, err } @@ -855,7 +833,7 @@ func deleteUser(resp http.ResponseWriter, request *http.Request) { return } - user, userErr := handleApiAuthentication(resp, request) + userInfo, userErr := handleApiAuthentication(resp, request) if userErr != nil { log.Printf("Api authentication failed in edit workflow: %s", userErr) resp.WriteHeader(401) @@ -863,8 +841,8 @@ func deleteUser(resp http.ResponseWriter, request *http.Request) { return } - if user.Role != "admin" { - log.Printf("Wrong user (%s) when deleting - must be admin", user.Username) + if userInfo.Role != "admin" { + log.Printf("Wrong user (%s) when deleting - must be admin", userInfo.Username) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Must be admin"}`)) return @@ -882,46 +860,57 @@ func deleteUser(resp http.ResponseWriter, request *http.Request) { userId = location[4] } - if userId == user.Id { + if userId == userInfo.Id { resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Can't deactivate yourself"}`)) return } ctx := context.Background() - q := datastore.NewQuery("Users").Filter("id =", userId) - var users []User - _, err := dbclient.GetAll(ctx, q, &users) + foundUser, err := getUser(ctx, userId) if err != nil { - log.Printf("Error getting users apikey (deleteuser): %s", err) + log.Printf("Can't find user %s (delete user): %s", userId, err) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Failed getting users for verification"}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) return } - if len(users) != 1 { - log.Printf("Found too many users!") - resp.WriteHeader(500) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Backend error: too many or too few users with id %s: %d"}`, userId, len(users)))) + orgFound := false + if userInfo.ActiveOrg.Id == foundUser.ActiveOrg.Id { + orgFound = true + } else { + log.Printf("FoundUser: %#v", foundUser.Orgs) + for _, item := range foundUser.Orgs { + if item == userInfo.ActiveOrg.Id { + orgFound = true + break + } + } + } + + if !orgFound { + log.Printf("User %s is admin, but can't delete users outside their own org.", userInfo.Id) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't change users outside your org."}`))) return } // Invert. No user deletion. - if users[0].Active { - users[0].Active = false + if foundUser.Active { + foundUser.Active = false } else { - users[0].Active = true + foundUser.Active = true } - err = setUser(ctx, &users[0]) + err = setUser(ctx, foundUser) if err != nil { - log.Printf("Failed swapping active for user %s (%s)", users[0].Username, users[0].Id) + log.Printf("Failed swapping active for user %s (%s)", foundUser, foundUser.Username, foundUser.Id) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": true"}`))) + resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) return } - log.Printf("Successfully inverted %s", users[0].Username) + log.Printf("Successfully inverted %s", foundUser.Username) resp.WriteHeader(200) resp.Write([]byte(`{"success": true}`)) @@ -1026,7 +1015,7 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) { ctx := context.Background() var environments []Environment - q := datastore.NewQuery("Environments") + q := datastore.NewQuery("Environments").Filter("org_id =", user.ActiveOrg.Id) _, err = dbclient.GetAll(ctx, q, &environments) if err != nil { resp.WriteHeader(401) @@ -1081,6 +1070,10 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) { } for _, item := range newEnvironments { + if item.OrgId != user.ActiveOrg.Id { + item.OrgId = user.ActiveOrg.Id + } + err = setEnvironment(ctx, &item) if err != nil { resp.WriteHeader(401) @@ -1185,6 +1178,19 @@ func createNewUser(username, password, role, apikey string, org Org) error { log.Printf("Error adding User %s: %s", username, err) return err } + + neworg, err := getOrg(ctx, org.Id) + if err == nil { + log.Printf("Updating org %s with user %s", org.Name, newUser.Username) + neworg.Users = append(org.Users, *newUser) + err = setOrg(ctx, *neworg, neworg.Id) + if err != nil { + log.Printf("Failed updating org with user %s", newUser.Username) + } else { + log.Printf("Successfully updated org with user %s!", newUser.Username) + } + } + // url := fmt.Sprintf("https://shuffler.io/register/%s", verifyToken.String()) // const verifyMessage = ` //Registration URL :) @@ -1253,7 +1259,22 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { role = "admin" } - err = createNewUser(data.Username, data.Password, role, "", user.ActiveOrg) + ctx := context.Background() + currentOrg := user.ActiveOrg + if user.ActiveOrg.Id == "" { + log.Printf("There's no active org for the user. Checking if there's a single one to assing it to.") + + var orgs []Org + q := datastore.NewQuery("Organizations") + _, err = dbclient.GetAll(ctx, q, &orgs) + if err == nil && len(orgs) == 1 { + log.Printf("No org exists in auth. Setting to default (first one)") + currentOrg = orgs[0] + } + + } + + err = createNewUser(data.Username, data.Password, role, "", currentOrg) if err != nil { log.Printf("Failed registering user: %s", err) resp.WriteHeader(401) @@ -1285,51 +1306,53 @@ func handleLogout(resp http.ResponseWriter, request *http.Request) { return } - // Check cookie - c, err := request.Cookie("session_token") + userInfo, err := handleApiAuthentication(resp, request) 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) + log.Printf("Api authentication failed in handleLogout: %s", err) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) + resp.Write([]byte(`{"success": false}`)) return } + ctx := context.Background() + session, err := getSession(ctx, userInfo.Session) + if err != nil { + log.Printf("Session %#v doesn't exist: %s", session, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "No session"}`)) + 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 to %s!", c.Value) + //} + + //var Userdata User + //ctx := context.Background() + //sessionToken = c.Value + //session, err := getSession(ctx, sessionToken) + //if err != nil { + // log.Printf("Session %s doesn't exist (logout): %s", sessionToken, err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "reason": "Couldn't find your session"}`)) + // return + //} + // Get session first // Should basically never happen - _, err = getUser(ctx, session.Id) - if err != nil { - log.Printf("Username %s doesn't exist (logout): %s", session.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) - return - } + //_, err = getUser(ctx, session.Id) + //if err != nil { + // log.Printf("Username %s doesn't exist (logout): %s", session.Username, err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) + // return + //} // Userdata = *tmpdata //} @@ -1337,7 +1360,7 @@ func handleLogout(resp http.ResponseWriter, request *http.Request) { // FIXME // Session might delete someone elses here? // No need to think about before possible scale..? - err = SetSession(ctx, Userdata, "") + err = SetSession(ctx, userInfo, "") if err != nil { log.Printf("Error removing session for: %s", err) resp.WriteHeader(401) @@ -1345,16 +1368,16 @@ func handleLogout(resp http.ResponseWriter, request *http.Request) { return } - err = DeleteKey(ctx, "sessions", sessionToken) + err = DeleteKey(ctx, "sessions", userInfo.Session) if err != nil { - log.Printf("Error deleting key %s for %s: %s", c.Value, Userdata.Username, err) + log.Printf("Error deleting key %s for %s: %s", userInfo.Session, userInfo.Username, err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) return } - Userdata.Session = "" - err = setUser(ctx, &Userdata) + userInfo.Session = "" + err = setUser(ctx, &userInfo) if err != nil { log.Printf("Failed updating user: %s", err) resp.WriteHeader(401) @@ -1363,10 +1386,10 @@ func handleLogout(resp http.ResponseWriter, request *http.Request) { } //memcache.Delete(request.Context(), sessionToken) + //http.SetCookie(resp, c) 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) { @@ -1429,6 +1452,8 @@ func handleUpdateUser(resp http.ResponseWriter, request *http.Request) { return } + // Should this role reflect the users' org access? + // When you change org -> change user role if userInfo.Role != "admin" { log.Printf("%s tried to update user %s", userInfo.Username, t.UserId) resp.WriteHeader(401) @@ -1444,6 +1469,21 @@ func handleUpdateUser(resp http.ResponseWriter, request *http.Request) { return } + orgFound := false + for _, item := range foundUser.Orgs { + if item == userInfo.ActiveOrg.Id { + orgFound = true + break + } + } + + if !orgFound { + log.Printf("User %s is admin, but can't edit users outside their own org.", userInfo.Id) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't change users outside your org."}`))) + return + } + if t.Role != "admin" && t.Role != "user" { log.Printf("%s tried and failed to update user %s", userInfo.Username, t.UserId) resp.WriteHeader(401) @@ -1502,7 +1542,6 @@ func handleUpdateUser(resp http.ResponseWriter, request *http.Request) { } func handleApiGeneration(resp http.ResponseWriter, request *http.Request) { - log.Printf("APIGEN!") cors := handleCors(resp, request) if cors { return @@ -1515,7 +1554,6 @@ func handleApiGeneration(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(`{"success": false}`)) return } - log.Printf("APIKEY") ctx := context.Background() if request.Method == "GET" { @@ -1617,13 +1655,13 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { } ctx := context.Background() - session, err := getSession(ctx, userInfo.Session) - if err != nil { - log.Printf("Session %#v doesn't exist: %s", session, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "No session"}`)) - return - } + //session, err := getSession(ctx, userInfo.Session) + //if err != nil { + // log.Printf("Session %#v doesn't exist: %s", session, err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "reason": "No session"}`)) + // return + //} // This is a long check to see if an inactive admin can access the site parsedAdmin := "false" @@ -1686,12 +1724,12 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { } //log.Printf("%s %s", session.Session, UserInfo.Session) - if session.Session != userInfo.Session { - log.Printf("Session %s is not the same as %s for %s. %s", userInfo.Session, session.Session, userInfo.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) - return - } + //if session.Session != userInfo.Session { + // log.Printf("Session %s is not the same as %s for %s. %s", userInfo.Session, session.Session, userInfo.Username, err) + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "reason": ""}`)) + // return + //} expiration := time.Now().Add(3600 * time.Second) http.SetCookie(resp, &http.Cookie{ @@ -1750,16 +1788,16 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { currentOrg = []byte("{}") } - returnData := fmt.Sprintf(` - { - "success": true, - "admin": %s, - "tutorials": [], - "id": "%s", - "orgs": [%s], - "active_org": %s, - "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}] - }`, parsedAdmin, userInfo.Id, currentOrg, currentOrg, userInfo.Session, expiration.Unix()) + returnData := fmt.Sprintf(`{ + "success": true, + "username": "%s", + "admin": %s, + "tutorials": [], + "id": "%s", + "orgs": [%s], + "active_org": %s, + "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}] +}`, userInfo.Username, parsedAdmin, userInfo.Id, currentOrg, currentOrg, userInfo.Session, expiration.Unix()) resp.WriteHeader(200) resp.Write([]byte(returnData)) @@ -1975,7 +2013,7 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) { return } - user, err := handleApiAuthentication(resp, request) + userInfo, err := handleApiAuthentication(resp, request) if err != nil { log.Printf("Api authentication failed in set new workflowhandler: %s", err) resp.WriteHeader(401) @@ -1984,15 +2022,15 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) { } curUserFound := false - if t.Username != user.Username && user.Role != "admin" { + if t.Username != userInfo.Username && userInfo.Role != "admin" { resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Admin required to change others' passwords"}`)) return - } else if t.Username == user.Username { + } else if t.Username == userInfo.Username { curUserFound = true } - if user.Role != "admin" { + if userInfo.Role != "admin" { if t.Newpassword != t.Newpassword2 { err := "Passwords don't match" resp.WriteHeader(401) @@ -2006,6 +2044,8 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } + } else { + // Check ORG HERE? } // Current password @@ -2018,6 +2058,7 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) { } ctx := context.Background() + foundUser := User{} if !curUserFound { log.Printf("Have to find a different user") q := datastore.NewQuery("Users").Filter("Username =", strings.ToLower(t.Username)) @@ -2037,13 +2078,32 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) { return } - user = users[0] + foundUser = users[0] + orgFound := false + if userInfo.ActiveOrg.Id == foundUser.ActiveOrg.Id { + orgFound = true + } else { + log.Printf("FoundUser: %#v", foundUser.Orgs) + for _, item := range foundUser.Orgs { + if item == userInfo.ActiveOrg.Id { + orgFound = true + break + } + } + } + + if !orgFound { + log.Printf("User %s is admin, but can't change user's passowrd outside their own org.", userInfo.Id) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't change users outside your org."}`))) + return + } } else { // Admins can re-generate others' passwords as well. - if user.Role != "admin" { - err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(t.Newpassword)) + if userInfo.Role != "admin" { + err = bcrypt.CompareHashAndPassword([]byte(userInfo.Password), []byte(t.Newpassword)) if err != nil { - log.Printf("Bad password for %s: %s", user.Username, err) + log.Printf("Bad password for %s: %s", userInfo.Username, err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) return @@ -2051,18 +2111,25 @@ func handlePasswordChange(resp http.ResponseWriter, request *http.Request) { } } + if len(foundUser.Id) == 0 { + log.Printf("Something went wrong in password reset", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false}`)) + return + } + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(t.Newpassword), 8) if err != nil { - log.Printf("New password failure for %s: %s", user.Username, err) + log.Printf("New password failure for %s: %s", userInfo.Username, err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) return } - user.Password = string(hashedPassword) - err = setUser(ctx, &user) + userInfo.Password = string(hashedPassword) + err = setUser(ctx, &foundUser) if err != nil { - log.Printf("Error fixing password for user %s: %s", user.Username, err) + log.Printf("Error fixing password for user %s: %s", userInfo.Username, err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) return @@ -2192,7 +2259,7 @@ func handleGetEnvironments(resp http.ResponseWriter, request *http.Request) { return } - _, err := handleApiAuthentication(resp, request) + user, err := handleApiAuthentication(resp, request) if err != nil { log.Printf("Api authentication failed in set new workflowhandler: %s", err) resp.WriteHeader(401) @@ -2202,7 +2269,7 @@ func handleGetEnvironments(resp http.ResponseWriter, request *http.Request) { ctx := context.Background() var environments []Environment - q := datastore.NewQuery("Environments") + q := datastore.NewQuery("Environments").Filter("org_id =", user.ActiveOrg.Id) _, err = dbclient.GetAll(ctx, q, &environments) if err != nil { resp.WriteHeader(401) @@ -2299,18 +2366,17 @@ func handleGetUsers(resp http.ResponseWriter, request *http.Request) { return } + // FIXME: Check by org. ctx := context.Background() - var users []User - q := datastore.NewQuery("Users") - _, err = dbclient.GetAll(ctx, q, &users) + org, err := getOrg(ctx, user.ActiveOrg.Id) if err != nil { resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Can't get users"}`)) + resp.Write([]byte(`{"success": false, "reason": "Failed getting org users"}`)) return } newUsers := []User{} - for _, item := range users { + for _, item := range org.Users { if len(item.Username) == 0 { continue } @@ -2417,19 +2483,10 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { return } - log.Printf("%s SUCCESSFULLY LOGGED IN with session %s", data.Username, Userdata.Session) - //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 + loginData := `{"success": true}` if len(Userdata.Session) != 0 { - //log.Println("Nonexisting session") + log.Println("User session exists - resetting") expiration := time.Now().Add(3600 * time.Second) http.SetCookie(resp, &http.Cookie{ @@ -2449,20 +2506,36 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { resp.WriteHeader(200) resp.Write([]byte(loginData)) return + } else { + log.Printf("User session is empty - create one!") + + sessionToken := uuid.NewV4().String() + expiration := time.Now().Add(3600 * time.Second) + http.SetCookie(resp, &http.Cookie{ + Name: "session_token", + Value: sessionToken, + Expires: expiration, + }) + + // ADD TO DATABASE + err = SetSession(ctx, Userdata, sessionToken) + if err != nil { + log.Printf("Error adding session to database: %s", err) + } + + Userdata.Session = sessionToken + err = setUser(ctx, &Userdata) + if err != nil { + log.Printf("Failed updating user when setting session: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false}`)) + return + } + + loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, sessionToken, expiration.Unix()) } - sessionToken := uuid.NewV4() - http.SetCookie(resp, &http.Cookie{ - Name: "session_token", - Value: sessionToken.String(), - Expires: time.Now().Add(3600 * time.Second), - }) - - // ADD TO DATABASE - err = SetSession(ctx, Userdata, sessionToken.String()) - if err != nil { - log.Printf("Error adding session to database: %s", err) - } + log.Printf("%s SUCCESSFULLY LOGGED IN with session %s", data.Username, Userdata.Session) resp.WriteHeader(200) resp.Write([]byte(loginData)) @@ -2496,6 +2569,28 @@ func getSession(ctx context.Context, thissession string) (*session, error) { return curUser, nil } +// ListBooks returns a list of books, ordered by title. +func getFile(ctx context.Context, id string) (*File, error) { + key := datastore.NameKey("Files", id, nil) + curFile := &File{} + if err := dbclient.Get(ctx, key, curFile); err != nil { + return &File{}, err + } + + return curFile, nil +} + +func setFile(ctx context.Context, file File) error { + // clear session_token and API_token for user + k := datastore.NameKey("Files", file.Id, nil) + if _, err := dbclient.Put(ctx, k, &file); err != nil { + log.Println(err) + return err + } + + return nil +} + // ListBooks returns a list of books, ordered by title. func getOrg(ctx context.Context, id string) (*Org, error) { key := datastore.NameKey("Organizations", id, nil) @@ -2622,8 +2717,61 @@ func setEnvironment(ctx context.Context, data *Environment) error { return nil } +func fixUserOrg(ctx context.Context, user *User) *User { + found := false + for _, id := range user.Orgs { + if user.ActiveOrg.Id == id { + found = true + break + } + } + + if !found { + user.Orgs = append(user.Orgs, user.ActiveOrg.Id) + } + + log.Printf("Updating %d orgs for user %s", len(user.Orgs), user.Id) + // Might be vulnerable to timing attacks. + for _, orgId := range user.Orgs { + if len(orgId) == 0 { + continue + } + + org, err := getOrg(ctx, orgId) + if err != nil { + log.Printf("Error getting org %s", orgId) + continue + } + + orgIndex := 0 + userFound := false + for index, orgUser := range org.Users { + if orgUser.Id == user.Id { + orgIndex = index + userFound = true + break + } + } + + if userFound { + org.Users[orgIndex] = *user + } else { + org.Users = append(org.Users, *user) + } + + err = setOrg(ctx, *org, orgId) + if err != nil { + log.Printf("Failed setting org %s", orgId) + } + } + + return user +} + // ListBooks returns a list of books, ordered by title. func setUser(ctx context.Context, data *User) error { + data = fixUserOrg(ctx, data) + // clear session_token and API_token for user k := datastore.NameKey("Users", data.Id, nil) if _, err := dbclient.Put(ctx, k, data); err != nil { @@ -3299,8 +3447,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)), } + // OrgId: activeOrgs[0].Id, workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest) - if err == nil { err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1) if err != nil { @@ -3418,6 +3566,7 @@ func handleNewHook(resp http.ResponseWriter, request *http.Request) { }, }, Running: false, + OrgId: user.ActiveOrg.Id, } hook.Status = "running" @@ -6454,6 +6603,14 @@ func runInit(ctx context.Context) { log.Printf("Should add %d users to organization default", len(users)) } + if len(activeOrgs) == 0 { + orgQuery := datastore.NewQuery("Organizations") + _, err = dbclient.GetAll(ctx, orgQuery, &activeOrgs) + if err != nil { + log.Printf("Failed getting orgs the second time around") + } + } + // Fix active users etc q := datastore.NewQuery("Users").Filter("active =", true) var activeusers []User @@ -6520,24 +6677,117 @@ func runInit(ctx context.Context) { } } } else { - //log.Printf("Found %d users.", len(users)) + log.Printf("Found %d users.", len(users)) + if len(activeOrgs) == 1 && len(users) > 0 { + for _, user := range users { + if user.ActiveOrg.Id == "" && len(user.Username) > 0 { + user.ActiveOrg = activeOrgs[0] + err = setUser(ctx, &user) + if err != nil { + log.Printf("Failed updating user %s with org", user.Username) + } else { + log.Printf("Updated user %s to have org", user.Username) + } + } + } + } //log.Printf(users[0].Username) } } // Gets environments and inits if it doesn't exist - log.Printf("Setting up environments") count, err := getEnvironmentCount() - if count == 0 && err == nil { + if count == 0 && err == nil && len(activeOrgs) == 1 { + log.Printf("Setting up environment with org %s", activeOrgs[0].Id) item := Environment{ - Name: "Shuffle", - Type: "onprem", + Name: "Shuffle", + Type: "onprem", + OrgId: activeOrgs[0].Id, } err = setEnvironment(ctx, &item) if err != nil { log.Printf("Failed setting up new environment") } + } else if len(activeOrgs) == 1 { + log.Printf("Setting up all environments with org %s", activeOrgs[0].Id) + var environments []Environment + q := datastore.NewQuery("Environments") + _, err = dbclient.GetAll(ctx, q, &environments) + if err == nil { + for _, item := range environments { + if item.OrgId == activeOrgs[0].Id { + continue + } + + item.OrgId = activeOrgs[0].Id + err = setEnvironment(ctx, &item) + if err != nil { + log.Printf("Failed adding environment to org %s", activeOrgs[0].Id) + } + } + } + } + + // Fixing workflows to have real activeorg IDs + if len(activeOrgs) == 1 { + q := datastore.NewQuery("workflow") + var workflows []Workflow + _, err = dbclient.GetAll(ctx, q, &workflows) + if err != nil { + log.Printf("Error getting workflows in runinit: %s", err) + } else { + updated := 0 + for _, workflow := range workflows { + if workflow.ExecutingOrg.Id == "" { + workflow.ExecutingOrg = activeOrgs[0] + + err = setWorkflow(ctx, workflow, workflow.ID) + if err != nil { + log.Printf("Failed setting workflow in init: %s", err) + } else { + log.Printf("Fixed workflow %s to have the right org.", workflow.ID) + updated += 1 + } + } + } + + if updated > 0 { + log.Printf("Set workflow orgs for %d workflows", updated) + } + } + + fileq := datastore.NewQuery("Files").Limit(1) + count, err := dbclient.Count(ctx, fileq) + + if err == nil && count == 0 { + basepath := "." + filename := "testfile.txt" + fileId := uuid.NewV4().String() + log.Printf("Creating new file reference %s because none exist!", fileId) + workflowId := "2e9d6474-402c-4dcc-bb53-45f638ca18d3" + downloadPath := fmt.Sprintf("%s/%s/%s/%s", basepath, activeOrgs[0].Id, workflowId, fileId) + + timeNow := time.Now().Unix() + newFile := File{ + Id: fileId, + CreatedAt: timeNow, + UpdatedAt: timeNow, + Description: "Created by system for testing", + Status: "active", + Filename: filename, + OrgId: activeOrgs[0].Id, + WorkflowId: workflowId, + DownloadPath: downloadPath, + } + + err = setFile(ctx, newFile) + if err != nil { + log.Printf("Failed setting file: %s", err) + } else { + log.Printf("Created file %s in init", newFile.DownloadPath) + } + } } // Gets schedules and starts them @@ -6575,7 +6825,7 @@ func runInit(ctx context.Context) { log.Printf("Getting remote workflow apps") workflowapps, err := getAllWorkflowApps(ctx) if err != nil { - log.Printf("Failed getting apps: %s", err) + log.Printf("Failed getting apps (runInit): %s", err) } else if err == nil && len(workflowapps) == 0 { log.Printf("Downloading default workflow apps") fs := memfs.New() @@ -6600,7 +6850,7 @@ func runInit(ctx context.Context) { } } branch := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_BRANCH") - if len(branch) > 0 { + if len(branch) > 0 && branch != "master" && branch != "main" { cloneOptions.ReferenceName = plumbing.ReferenceName(branch) } @@ -6609,7 +6859,7 @@ func runInit(ctx context.Context) { r, err := git.Clone(storer, fs, cloneOptions) if err != nil { - log.Printf("Failed loading repo into memory: %s", err) + log.Printf("Failed loading repo into memory (init): %s", err) } dir, err := fs.ReadDir("") @@ -6680,7 +6930,136 @@ func runInit(ctx context.Context) { log.Printf("Finished INIT") } +func handleVerifyCloudsync(orgId string) (SyncFeatures, error) { + ctx := context.Background() + org, err := getOrg(ctx, orgId) + if err != nil { + return SyncFeatures{}, err + } + + //r.HandleFunc("/api/v1/getorgs", handleGetOrgs).Methods("GET", "OPTIONS") + + syncURL := fmt.Sprintf("%s/api/v1/cloud/sync/get_access", syncUrl) + client := &http.Client{} + req, err := http.NewRequest( + "GET", + syncURL, + nil, + ) + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) + newresp, err := client.Do(req) + if err != nil { + return SyncFeatures{}, err + } + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + return SyncFeatures{}, err + } + + responseData := retStruct{} + err = json.Unmarshal(respBody, &responseData) + if err != nil { + return SyncFeatures{}, err + } + + if newresp.StatusCode != 200 { + return SyncFeatures{}, errors.New(fmt.Sprintf("Got status code %d when getting org remotely. Expected 200. Contact support.", newresp.StatusCode)) + } + + if !responseData.Success { + return SyncFeatures{}, errors.New(responseData.Reason) + } + + return responseData.SyncFeatures, nil +} + +// Actually stops syncing with cloud for an org. +// Disables potential schedules, removes environments, breaks workflows etc. +func handleStopCloudSync(syncUrl string, org Org) error { + if len(org.SyncConfig.Apikey) == 0 { + return errors.New(fmt.Sprintf("Couldn't find any sync key to disable org %s", org.Id)) + } + + log.Printf("Should run cloud sync disable for org %s with URL %s and sync key %s", org.Id, syncUrl, org.SyncConfig.Apikey) + + client := &http.Client{} + req, err := http.NewRequest( + "DELETE", + syncUrl, + nil, + ) + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) + newresp, err := client.Do(req) + if err != nil { + return err + } + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + return err + } + log.Printf("Remote disable ret: %s", string(respBody)) + + responseData := retStruct{} + err = json.Unmarshal(respBody, &responseData) + if err != nil { + return err + } + + if newresp.StatusCode != 200 { + return errors.New(fmt.Sprintf("Got status code %d when disabling org remotely. Expected 200. Contact support.", newresp.StatusCode)) + } + + if !responseData.Success { + //log.Printf("Success reason: %s", responseData.Reason) + return errors.New(responseData.Reason) + } + + log.Printf("Everything is success. Should disable org sync for %s", org.Id) + + ctx := context.Background() + org.CloudSync = false + org.SyncFeatures = SyncFeatures{} + org.SyncConfig = SyncConfig{} + + err = setOrg(ctx, org, org.Id) + if err != nil { + newerror := fmt.Sprintf("ERROR: Failed updating even though there was success: %s", err) + log.Printf(newerror) + return errors.New(newerror) + } + + var environments []Environment + q := datastore.NewQuery("Environments").Filter("org_id =", org.Id) + _, err = dbclient.GetAll(ctx, q, &environments) + if err != nil { + return err + } + + // Don't disable, this will be deleted entirely + for _, environment := range environments { + if environment.Type == "cloud" { + environment.Name = "Cloud" + environment.Archived = true + err = setEnvironment(ctx, &environment) + if err == nil { + log.Printf("Updated cloud environment %s", environment.Name) + } else { + log.Printf("Failed to update cloud environment %s", environment.Name) + } + } + } + + return nil +} + // INFO: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit +/* + This is here to both enable and disable cloud sync features for an organization +*/ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -6712,6 +7091,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { type ReturnData struct { Apikey string `datastore:"apikey"` Organization Org `datastore:"organization"` + Disable bool `datastore:"disable"` } var tmpData ReturnData @@ -6733,6 +7113,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { } // FIXME: Check if user is admin of this org + log.Printf("Checking org %s", org.Name) userFound := false admin := false for _, inneruser := range org.Users { @@ -6765,8 +7146,43 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { log.Printf("Apidata: %s", tmpData.Apikey) + // FIXME: Path client := &http.Client{} - syncPath := "http://192.168.3.6:5002/api/v1/cloud/sync" + apiPath := "/api/v1/cloud/sync/setup" + if tmpData.Disable { + if !org.CloudSync { + log.Printf("Org %s isn't syncing. Can't stop.", org.Id) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Skipped cloud sync setup. Already syncing."}`))) + return + } + + log.Printf("Should disable sync for org %s", org.Id) + apiPath := "/api/v1/cloud/sync/stop" + syncPath := fmt.Sprintf("%s%s", syncUrl, apiPath) + + err = handleStopCloudSync(syncPath, *org) + if err != nil { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + } else { + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Successfully disabled cloud sync for org."}`))) + } + + return + } + + // Everything below here is to SET UP CLOUD SYNC. + // If you want to disable cloud sync, see previous section. + if org.CloudSync { + log.Printf("Org %s is already syncing. Skip", org.Id) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Org is already syncing. Nothing to set up."}`))) + return + } + + syncPath := fmt.Sprintf("%s%s", syncUrl, apiPath) type requestStruct struct { ApiKey string `json:"api_key"` @@ -6780,7 +7196,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { if err != nil { log.Printf("Failed marshaling api key data: %s", err) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync."`, err))) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync."}`, err))) return } @@ -6793,49 +7209,254 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { newresp, err := client.Do(req) if err != nil { resp.WriteHeader(400) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync: %s"`, err))) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed cloud sync: %s. Contact support."}`, err))) //setBadMemcache(ctx, docPath) return } - if newresp.StatusCode != 200 { - resp.WriteHeader(400) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Response code %d during sync. Expecting 200."`, newresp.StatusCode))) - return - } - respBody, err := ioutil.ReadAll(newresp.Body) if err != nil { resp.WriteHeader(500) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't parse sync data"`))) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Can't parse sync data. Contact support."}`))) return } - type responseStruct struct { - Success bool `json:"success"` - Reason string `json:"reason"` - } log.Printf("Respbody: %s", string(respBody)) - - responseData := responseStruct{} + responseData := retStruct{} err = json.Unmarshal(respBody, &responseData) if err != nil { resp.WriteHeader(500) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed handling cloud data"`))) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed handling cloud data"}`))) return } - if responseData.Success { - resp.WriteHeader(200) - if len(responseData.Reason) > 0 { - resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%s"}`, responseData.Reason))) - } else { - resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) - } - } else { + if newresp.StatusCode != 200 { + resp.WriteHeader(401) + resp.Write(respBody) + return + } + + if !responseData.Success { resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, responseData.Reason))) + return } + + // FIXME: + // 1. Set cloudsync for org to be active + // 2. Add iterative sync schedule for interval seconds + // 3. Add another environment for the org's users + org.CloudSync = true + org.SyncFeatures = responseData.SyncFeatures + + org.SyncConfig = SyncConfig{ + Apikey: responseData.SessionKey, + Interval: responseData.IntervalSeconds, + } + + // FIXME: Add this for every feature + if org.SyncFeatures.Workflows.Active { + log.Printf("Should activate cloud workflows for org %s!", org.Id) + + // 1. Find environment + // 2. If cloud env found, enable it (un-archive) + // 3. If it doesn't create it + var environments []Environment + q := datastore.NewQuery("Environments").Filter("org_id =", org.Id) + _, err = dbclient.GetAll(ctx, q, &environments) + if err == nil { + + // Don't disable, this will be deleted entirely + found := false + for _, environment := range environments { + if environment.Type == "cloud" { + environment.Name = "Cloud" + environment.Archived = false + err = setEnvironment(ctx, &environment) + if err == nil { + log.Printf("Re-added cloud environment %s", environment.Name) + } else { + log.Printf("Failed to re-enable cloud environment %s", environment.Name) + } + + found = true + break + } + } + + if !found { + log.Printf("Env for cloud not found. Should add it!") + newEnv := Environment{ + Name: "Cloud", + Type: "cloud", + Archived: false, + Registered: true, + Default: false, + OrgId: org.Id, + } + + err = setEnvironment(ctx, &newEnv) + if err != nil { + log.Printf("Failed setting up NEW org environment for org %s: %s", org.Id, err) + } else { + log.Printf("Successfully added new environment for org %s", org.Id) + } + } + } else { + log.Printf("Failed setting org environment, because none were found: %s", err) + } + } + + err = setOrg(ctx, *org, org.Id) + if err != nil { + log.Printf("ERROR: Failed updating org even though there was success: %s", err) + resp.WriteHeader(400) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting up org after sync success. Contact support."}`))) + return + } + + if responseData.IntervalSeconds > 0 { + // FIXME: + log.Printf("Should set up interval for %d with session key %s for org %s", responseData.IntervalSeconds, responseData.SessionKey, org.Name) + } + + resp.WriteHeader(200) + resp.Write(respBody) +} + +type File struct { + Id string `json:"id" datastore:"id"` + Type string `json:"type" datastore:"type"` + CreatedAt int64 `json:"created_at" datastore:"created_at"` + UpdatedAt int64 `json:"updated_at" datastore:"updated_at"` + CreatedBy struct { + ID int `json:"id" datastore:"id"` + Type string `json:"type" datastore:"type"` + Login string `json:"login" datastore:"login"` + Name string `json:"name" datastore:"name"` + } `json:"created_by" datastore:"created_by"` + Description string `json:"description" datastore:"description"` + Etag int `json:"etag" datastore:"etag"` + ExpiresAt string `json:"expires_at" datastore:"expires_at"` + Folder struct { + ID int `json:"id" datastore:"id"` + Type string `json:"type" datastore:"type"` + Etag int `json:"etag" datastore:"etag"` + Name string `json:"name" datastore:"name"` + SequenceID int `json:"sequence_id" datastore:"sequence_id"` + } `json:"folder" datastore:"folder"` + Status string `json:"status" datastore:"status"` + Filename string `json:"filename" datastore:"filename"` + UpdatedBy struct { + ID int `json:"id" datastore:"id"` + Type string `json:"type" datastore:"type"` + Login string `json:"login" datastore:"login"` + Name string `json:"name" datastore:"name"` + } `json:"updated_by" datastore:"updated_by"` + URL string `json:"url" datastore:"org"` + OrgId string `json:"org_id" datastore:"org_id"` + WorkflowId string `json:"workflow_id" datastore:"workflow_id"` + Workflows []string `json:"workflows" datastore:"workflows"` + DownloadPath string `json:"download_path" datastore:"download_path"` +} + +func handleGetFileContent(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + var fileId string + location := strings.Split(request.URL.String(), "/") + if location[1] == "api" { + if len(location) <= 4 { + log.Printf("Path too short: %d", len(location)) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + //log.Printf("In file download") + user, err := handleApiAuthentication(resp, request) + if err != nil { + log.Printf("Api authentication failed in file download: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // 1. Verify if the user has access to the file: org_id and workflow + + log.Printf("Should get file %s", fileId) + ctx := context.Background() + file, err := getFile(ctx, fileId) + if err != nil { + log.Printf("File %s not found: %s", fileId, err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + found := false + if file.OrgId == user.ActiveOrg.Id { + found = true + } else { + for _, item := range user.Orgs { + if item == file.OrgId { + found = true + break + } + } + } + + if !found { + log.Printf("User %s doesn't have access to %s", user.Username, fileId) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Fixme: More auth: org and workflow! + downloadPath := file.DownloadPath + log.Printf("Downloadpath: %s", downloadPath) + Openfile, err := os.Open(downloadPath) + defer Openfile.Close() //Close after function return + if err != nil { + //File not found, send 404 + http.Error(resp, "File not found.", 404) + return + } + + //File is found, create and send the correct headers + //Get the Content-Type of the file + //Create a buffer to store the header of the file in + FileHeader := make([]byte, 512) + //Copy the headers into the FileHeader buffer + Openfile.Read(FileHeader) + //Get content type of file + FileContentType := http.DetectContentType(FileHeader) + + //Get the file size + FileStat, _ := Openfile.Stat() //Get info from file + FileSize := strconv.FormatInt(FileStat.Size(), 10) //Get file size as a string + + //Send the headers + resp.Header().Set("Content-Disposition", "attachment; filename="+fileId) + resp.Header().Set("Content-Type", FileContentType) + resp.Header().Set("Content-Length", FileSize) + + //Send the file + //We read 512 bytes from the file already, so we reset the offset back to 0 + Openfile.Seek(0, 0) + io.Copy(resp, Openfile) //'Copy' the file to the client + return + + //log.Printf("Should download file %s", downloadPath) + //resp.WriteHeader(200) + //resp.Write([]byte("OK")) } func initHandlers() { @@ -6862,9 +7483,9 @@ func initHandlers() { r.HandleFunc("/api/v1/users/logout", handleLogout).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/users/register", handleRegister).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/users/checkusers", checkAdminLogin).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/users/getusers", handleGetUsers).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/getsettings", handleSettings).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/users/getusers", handleGetUsers).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/updateuser", handleUpdateUser).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/users/{user}", deleteUser).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/users/passwordchange", handlePasswordChange).Methods("POST", "OPTIONS") @@ -6879,10 +7500,10 @@ func initHandlers() { 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", "POST", "OPTIONS") + r.HandleFunc("/api/v1/passwordchange", handlePasswordChange).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/getenvironments", handleGetEnvironments).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/setenvironments", handleSetEnvironments).Methods("PUT", "OPTIONS") - r.HandleFunc("/api/v1/passwordchange", handlePasswordChange).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/docs", getDocList).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/docs/{key}", getDocs).Methods("GET", "OPTIONS") @@ -6899,6 +7520,7 @@ func initHandlers() { r.HandleFunc("/api/v1/workflows/queue/confirm", handleGetWorkflowqueueConfirm).Methods("POST") // App specific + // From here down isnt checked for org specific r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps/download_remote", loadSpecificApps).Methods("POST", "OPTIONS") @@ -6938,7 +7560,6 @@ func initHandlers() { r.HandleFunc("/api/v1/workflows/{key}", deleteWorkflow).Methods("DELETE", "OPTIONS") // Triggers - // Webhook redirect to the correct cloud function r.HandleFunc("/api/v1/hooks/new", handleNewHook).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/hooks/{key}", handleWebhookCallback).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/hooks/{key}/delete", handleDeleteHook).Methods("DELETE", "OPTIONS") @@ -6957,7 +7578,17 @@ func initHandlers() { // NEW for 0.8.0 r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/getorgs", handleGetOrgs).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/orgs", handleGetOrgs).Methods("GET", "OPTIONS") + + // Important for email, IDS etc. Create this by: + // PS: For cloud, this has to use cloud storage. + // https://developer.box.com/reference/get-files-id-content/ + // 1. Creating the "get file" option. Make it possible to run this in the frontend. + r.HandleFunc("/api/v1/files/{fileId}/content", handleGetFileContent).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/files/create", handleGetFile).Methods("POST", "OPTIONS") + //r.HandleFunc("/api/v1/files/upload", handleGetFile).Methods("POST", "OPTIONS") + //r.HandleFunc("/api/v1/files/{fileId}", handleGetFile).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/files/{fileId}", handleGetFile).Methods("DELETE", "OPTIONS") http.Handle("/", r) } diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index e20908f9..c6c08c2f 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -28,7 +28,6 @@ import ( "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/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/storage/memory" http2 "gopkg.in/src-d/go-git.v4/plumbing/transport/http" @@ -69,16 +68,21 @@ type ExecutionRequest struct { type SyncFeatures struct { Apps SyncData `json:"apps" datastore:"apps"` - Workflows SyncData `json:"apps" datastore:"apps"` - Schedules SyncData `json:"apps" datastore:"apps"` - Autocomplete SyncData `json:"apps" datastore:"apps"` - Authentication SyncData `json:"apps" datastore:"apps"` + Workflows SyncData `json:"workflows" datastore:"workflows"` + Schedules SyncData `json:"schedules" datastore:"schedules"` + Autocomplete SyncData `json:"autocomplete" datastore:"autocomplete"` + Authentication SyncData `json:"authentication" datastore:"authentication"` } type SyncData struct { Active bool `json:"active" datastore:"active"` } +type SyncConfig struct { + Interval int64 `json:"interval" datastore:"interval"` + Apikey string `json:"api_key" datastore:"api_key"` +} + // Role is just used for feedback for a user type Org struct { Name string `json:"name" datastore:"name"` @@ -88,6 +92,7 @@ type Org struct { Role string `json:"role" datastore:"role"` Roles []string `json:"roles" datastore:"roles"` CloudSync bool `json:"cloud_sync" datastore:"CloudSync"` + SyncConfig SyncConfig `json:"sync_config" datastore:"sync_config"` SyncFeatures SyncFeatures `json:"sync_features" datastore:"sync_features"` } @@ -100,6 +105,7 @@ type AppAuthenticationStorage struct { Usage []AuthenticationUsage `json:"usage" datastore:"usage"` WorkflowCount int64 `json:"workflow_count" datastore:"workflow_count"` NodeCount int64 `json:"node_count" datastore:"node_count"` + OrgId string `json:"org_id" datastore:"org_id"` } type AuthenticationUsage struct { @@ -192,6 +198,7 @@ type WorkflowAppAction struct { } // FIXME: Generate a callback authentication ID? +// FIXME: Add org check .. type WorkflowExecution struct { Type string `json:"type" datastore:"type"` Status string `json:"status" datastore:"status"` @@ -199,6 +206,7 @@ type WorkflowExecution struct { ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"` ExecutionId string `json:"execution_id" datastore:"execution_id"` ExecutionSource string `json:"execution_source" datastore:"execution_source"` + ExecutionOrg string `json:"execution_org" datastore:"execution_org"` WorkflowId string `json:"workflow_id" datastore:"workflow_id"` LastNode string `json:"last_node" datastore:"last_node"` Authorization string `json:"authorization" datastore:"authorization"` @@ -215,6 +223,7 @@ type WorkflowExecution struct { Name string `json:"name" datastore:"name"` Value string `json:"value" datastore:"value,noindex"` } `json:"execution_variables,omitempty" datastore:"execution_variables,omitempty"` + OrgId string `json:"org_id" datastore:"org_id"` } // This is for the nodes in a workflow, NOT the app action itself. @@ -297,6 +306,7 @@ type Schedule struct { Frequency string `json:"frequency" datastore:"frequency"` ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"` Id string `json:"id" datastore:"id"` + OrgId string `json:"org_id" datastore:"org_id"` } type Workflow struct { @@ -476,7 +486,7 @@ func getWorkflowQueue(ctx context.Context, id string) (ExecutionRequestWrapper, //} // Frequency = cronjob OR minutes between execution -func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode, frequency string, body []byte) error { +func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode, frequency, orgId string, body []byte) error { var err error testSplit := strings.Split(frequency, "*") cronJob := "" @@ -520,7 +530,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)), } - _, _, err := handleExecution(workflowId, Workflow{}, request) + _, _, err := handleExecution(workflowId, Workflow{ExecutingOrg: Org{Id: orgId}}, request) if err != nil { log.Printf("Failed to execute %s: %s", workflowId, err) } @@ -548,6 +558,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode CreationTime: timeNow, LastModificationtime: timeNow, LastRuntime: timeNow, + Org: orgId, } err = setSchedule(ctx, schedule) @@ -1041,6 +1052,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { extraInputs := 0 for _, result := range workflowExecution.Results { if result.Action.Name == "User Input" && result.Action.AppName == "User Input" { + log.Printf("Found User Input node - prepare cloud?") extraInputs += 1 } } @@ -1350,6 +1362,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { workflow.ID = uuid.NewV4().String() workflow.Owner = user.Id workflow.Sharing = "private" + workflow.ExecutingOrg = user.ActiveOrg ctx := context.Background() log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name) @@ -1390,7 +1403,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { if err == nil { // FIXME: Add real env envName := "Shuffle" - environments, err := getEnvironments(ctx) + environments, err := getEnvironments(ctx, user.ActiveOrg.Id) if err == nil { for _, env := range environments { if env.Default { @@ -1552,12 +1565,6 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { log.Printf("Failed to increase total workflows: %s", err) } - // recalculate authenticators stats - err = recalculateAppAuthentications() - if err != nil { - log.Printf("Authentications recalculation failed: %s", err) - } - //memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) //memcache.Delete(ctx, memcacheName) //memcacheName = fmt.Sprintf("%s_workflows", user.Username) @@ -1680,8 +1687,6 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { return } - //Actions []Action `json:"actions" datastore:"actions,noindex"` - body, err := ioutil.ReadAll(request.Body) if err != nil { log.Printf("Failed hook unmarshaling: %s", err) @@ -1713,6 +1718,11 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { workflow.Owner = user.Id } + if len(workflow.ExecutingOrg.Id) == 0 { + log.Printf("setting executing org") + workflow.ExecutingOrg = user.ActiveOrg + } + // FIXME - this shouldn't be necessary with proper API checks newActions := []Action{} allNodes := []string{} @@ -1721,6 +1731,11 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { for _, action := range workflow.Actions { allNodes = append(allNodes, action.ID) + if len(action.Errors) > 0 { + action.IsValid = true + action.Errors = []string{} + } + if action.Environment == "" { resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "An environment for %s is required"}`, action.Label))) @@ -1895,7 +1910,7 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { } } - allAuths, err := getAllWorkflowAppAuth(ctx) + allAuths, err := getAllWorkflowAppAuth(ctx, user.ActiveOrg.Id) if userErr != nil { log.Printf("Api authentication failed in get all apps: %s", userErr) resp.WriteHeader(401) @@ -2125,17 +2140,7 @@ func abortExecution(resp http.ResponseWriter, request *http.Request) { return } - // FIXME: Check the execution if this fails. - 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 { @@ -2169,12 +2174,34 @@ func abortExecution(resp http.ResponseWriter, request *http.Request) { 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 + apikey := request.Header.Get("Authorization") + parsedKey := "" + if strings.HasPrefix(apikey, "Bearer ") { + apikeyCheck := strings.Split(apikey, " ") + if len(apikeyCheck) == 2 { + parsedKey = apikeyCheck[1] + } + } + + if workflowExecution.Authorization != parsedKey { + // FIXME: Check the execution if this fails. + 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 + } + + // 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 + } + } else { + log.Printf("API key %s is correct to abort %s", parsedKey, executionId) } if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" || workflowExecution.Status == "FINISHED" { @@ -2289,6 +2316,11 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf workflow = *tmpworkflow } + if len(workflow.ExecutingOrg.Id) == 0 { + log.Printf("Stopped execution because there is no executing org for workflow %s", workflow.ID) + return WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined") + } + if len(workflow.Actions) == 0 { workflow.Actions = []Action{} } @@ -2381,6 +2413,8 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf } } else { // Check for parameters of start and ExecutionId + // This is mostly used for user input trigger + start, startok := request.URL.Query()["start"] answer, answerok := request.URL.Query()["answer"] referenceId, referenceok := request.URL.Query()["reference_execution"] @@ -2547,7 +2581,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf // FIXME: Authentication parameters if len(action.AuthenticationId) > 0 { if len(allAuths) == 0 { - allAuths, err = getAllWorkflowAppAuth(ctx) + allAuths, err = getAllWorkflowAppAuth(ctx, workflow.ExecutingOrg.Id) if err != nil { log.Printf("Api authentication failed in get all app auth: %s", err) return WorkflowExecution{}, fmt.Sprintf("Api authentication failed in get all app auth: %s", err), err @@ -2626,31 +2660,83 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf // Verification for execution environments workflowExecution.Results = defaultResults workflowExecution.Workflow.Actions = newActions - onpremExecution := false + onpremExecution := true environments := []string{} + if len(workflowExecution.ExecutionOrg) == 0 && len(workflow.ExecutingOrg.Id) > 0 { + workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id + } + + var allEnvs []Environment + if len(workflowExecution.ExecutionOrg) > 0 { + log.Printf("Executing ORG: %s", workflowExecution.ExecutionOrg) + + allEnvironments, err := getEnvironments(ctx, workflowExecution.ExecutionOrg) + if err != nil { + log.Printf("Failed finding environments: %s", err) + return WorkflowExecution{}, fmt.Sprintf("Workflow environments not found for this org"), errors.New(fmt.Sprintf("Workflow environments not found for this org")) + } + + for _, curenv := range allEnvironments { + if curenv.Archived { + continue + } + + allEnvs = append(allEnvs, curenv) + } + } else { + log.Printf("[ERROR] No org identified for execution of %s. Returning", workflowExecution.Workflow.ID) + return WorkflowExecution{}, "No org identified for execution", errors.New("No org identified for execution") + } + + if len(allEnvs) == 0 { + log.Printf("[ERROR] No active environments found for org", workflowExecution.ExecutionOrg) + return WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No active env found for org %s", workflowExecution.ExecutionOrg)) + } + // Check if the actions are children of the startnode? imageNames := []string{} + cloudExec := false for _, action := range workflowExecution.Workflow.Actions { - if action.Environment != cloudname { - found := false - for _, env := range environments { - if env == action.Environment { - found = true - break + // Verify if the action environment exists and append + found := false + for _, env := range allEnvs { + if env.Name == action.Environment { + found = true + + if env.Type == "cloud" { + cloudExec = true + } else if env.Type == "onprem" { + onpremExecution = true + } else { + log.Printf("[ERROR] No handler for environment type %s", env.Type) + return WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No handler for environment type %s", env.Type)) } + break } + } - // Check if the app exists? - newName := action.AppName - newName = strings.ReplaceAll(newName, " ", "-") - imageNames = append(imageNames, fmt.Sprintf("%s:%s_%s", baseDockerName, newName, action.AppVersion)) + if !found { + log.Printf("[ERROR] Couldn't find environment %s. Maybe it's inactive?", action.Environment) + return WorkflowExecution{}, "Couldn't find the environment", errors.New(fmt.Sprintf("Couldn't find env %s in org %s", action.Environment, workflowExecution.ExecutionOrg)) + } - if !found { - environments = append(environments, action.Environment) + found = false + for _, env := range environments { + if env == action.Environment { + + found = true + break } + } - onpremExecution = true + // Check if the app exists? + newName := action.AppName + newName = strings.ReplaceAll(newName, " ", "-") + imageNames = append(imageNames, fmt.Sprintf("%s:%s_%s", baseDockerName, newName, action.AppVersion)) + + if !found { + environments = append(environments, action.Environment) } } @@ -2695,8 +2781,30 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf log.Printf("Failed adding to db: %s", err) } } - } else { - log.Printf("[ERROR] Cloud not implemented yet") + } + + // Verifies and runs cloud executions + if cloudExec { + featuresList, err := handleVerifyCloudsync(workflowExecution.ExecutionOrg) + if !featuresList.Workflows.Active || err != nil { + log.Printf("Error: %s", err) + log.Printf("[ERROR] Cloud not implemented yet. May need to work on app checking and such") + return WorkflowExecution{}, "Cloud not implemented yet", errors.New("Cloud not implemented yet") + } + + // What it needs to know: + // 1. Parameters + if len(workflowExecution.Workflow.Actions) == 1 { + log.Printf("Should execute directly with cloud instead of worker because only one action") + + //cloudExecuteAction(workflowExecution.ExecutionId, workflowExecution.Workflow.Actions[0], workflowExecution.ExecutionOrg, workflowExecution.Workflow.ID) + cloudExecuteAction(workflowExecution) + return WorkflowExecution{}, "Cloud not implemented yet (1)", errors.New("Cloud not implemented yet") + } else { + // If it's here, it should be controlled by Worker. + // If worker, should this backend be a proxy? I think so. + return WorkflowExecution{}, "Cloud not implemented yet (2)", errors.New("Cloud not implemented yet") + } } err = increaseStatisticsField(ctx, "workflow_executions", workflow.ID, 1) @@ -2707,6 +2815,77 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf return workflowExecution, "", nil } +// This updates stuff locally from remote executions +func cloudExecuteAction(execution WorkflowExecution) error { + ctx := context.Background() + org, err := getOrg(ctx, execution.ExecutionOrg) + if err != nil { + return err + } + + type ExecutionStruct struct { + ExecutionId string `json:"execution_id" datastore:"execution_id"` + Action Action `json:"action" datastore:"action"` + Authorization string `json:"authorization" datastore:"authorization"` + Results []ActionResult `json:"results" datastore:"results,noindex"` + ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"` + WorkflowId string `json:"workflow_id" datastore:"workflow_id"` + ExecutionSource string `json:"execution_source" datastore:"execution_source"` + } + + data := ExecutionStruct{ + ExecutionId: execution.ExecutionId, + WorkflowId: execution.Workflow.ID, + Action: execution.Workflow.Actions[0], + Authorization: execution.Authorization, + } + log.Printf("Executing action: %#v in execution ID %s", data.Action, data.ExecutionId) + + b, err := json.Marshal(data) + if err != nil { + log.Printf("Failed marshaling api key data: %s", err) + return err + } + + syncURL := fmt.Sprintf("%s/api/v1/cloud/sync/execute_node", syncUrl) + client := &http.Client{} + req, err := http.NewRequest( + "POST", + syncURL, + bytes.NewBuffer(b), + ) + + req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) + newresp, err := client.Do(req) + if err != nil { + return err + } + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + return err + } + + log.Printf("Finished request. Data: %s", string(respBody)) + log.Printf("Status code: %d", newresp.StatusCode) + + responseData := retStruct{} + err = json.Unmarshal(respBody, &responseData) + if err != nil { + return err + } + + if newresp.StatusCode != 200 { + return errors.New(fmt.Sprintf("Got status code %d when executing remotely. Expected 200. Contact support.", newresp.StatusCode)) + } + + if !responseData.Success { + return errors.New(responseData.Reason) + } + + return nil +} + func executeWorkflow(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -2760,6 +2939,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { } log.Printf("[INFO] Starting execution of %s!", fileId) + workflow.ExecutingOrg = user.ActiveOrg workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request) if err == nil { @@ -3121,6 +3301,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { schedule.Name, startNode, schedule.Frequency, + user.ActiveOrg.Id, []byte(parsedBody), ) @@ -3320,9 +3501,9 @@ func getWorkflow(ctx context.Context, id string) (*Workflow, error) { return workflow, nil } -func getEnvironments(ctx context.Context) ([]Environment, error) { +func getEnvironments(ctx context.Context, OrgId string) ([]Environment, error) { var environments []Environment - q := datastore.NewQuery("Environments") + q := datastore.NewQuery("Environments").Filter("org_id =", OrgId) _, err := dbclient.GetAll(ctx, q, &environments) if err != nil { @@ -3740,7 +3921,7 @@ func getAppAuthentication(resp http.ResponseWriter, request *http.Request) { return } - _, userErr := handleApiAuthentication(resp, request) + user, userErr := handleApiAuthentication(resp, request) if userErr != nil { log.Printf("Api authentication failed in get all apps: %s", userErr) resp.WriteHeader(401) @@ -3756,7 +3937,7 @@ func getAppAuthentication(resp http.ResponseWriter, request *http.Request) { // return //} ctx := context.Background() - allAuths, err := getAllWorkflowAppAuth(ctx) + allAuths, err := getAllWorkflowAppAuth(ctx, user.ActiveOrg.Id) if err != nil { log.Printf("Api authentication failed in get all app auth: %s", err) resp.WriteHeader(401) @@ -3981,7 +4162,7 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { workflowapps, err := getAllWorkflowApps(ctx) if err != nil { - log.Printf("Failed getting apps: %s", err) + log.Printf("Failed getting apps (getworkflowapps): %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -4391,7 +4572,7 @@ func deployWebhookFunction(ctx context.Context, name, localization, applocation func loadGithubWorkflows(url, username, password, userId, branch string) error { fs := memfs.New() - // FIXME: add more git options lol + log.Printf("Starting load of %s with branch %s", url, branch) if strings.Contains(url, "github") || strings.Contains(url, "gitlab") || strings.Contains(url, "bitbucket") { cloneOptions := &git.CloneOptions{ URL: url, @@ -4400,43 +4581,27 @@ func loadGithubWorkflows(url, username, password, userId, branch string) error { // FIXME: Better auth. if len(username) > 0 && len(password) > 0 { cloneOptions.Auth = &http2.BasicAuth{ + Username: username, Password: password, } } + // main is the new master + if len(branch) > 0 && branch != "main" && branch != "master" { + cloneOptions.ReferenceName = plumbing.ReferenceName(branch) + } + storer := memory.NewStorage() r, err := git.Clone(storer, fs, cloneOptions) if err != nil { - log.Printf("Failed loading repo into memory: %s", err) + log.Printf("Failed loading repo %s into memory (github workflows): %s", url, err) return err } - if len(branch) > 0 { - log.Printf("Checkout to branch: %s", branch) - - w, _ := r.Worktree() - - err := r.Fetch(&git.FetchOptions{ - RefSpecs: []config.RefSpec{"refs/*:refs/*", "HEAD:refs/heads/HEAD"}, - }) - if err != nil { - log.Printf("Failed fetch for git repo: %s", err) - } - - err = w.Checkout(&git.CheckoutOptions{ - Branch: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)), - Force: true, - }) - if err != nil { - log.Printf("Failed checkout for git repo: %s", err) - return errors.New(fmt.Sprintf("Failed checking out to branch %s - does it exist?", branch)) - } - } - dir, err := fs.ReadDir("/") if err != nil { - log.Printf("Failed reading folder: %s", err) + log.Printf("FAiled reading folder: %s", err) } _ = r @@ -4511,7 +4676,7 @@ func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) { if err != nil { log.Printf("Error with unmarshal tmpBody: %s", err) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "json decode error"}`)) + resp.Write([]byte(`{"success": false}`)) return } @@ -4520,82 +4685,14 @@ func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) { if err != nil { log.Printf("Failed to update workflows: %s", err) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + resp.Write([]byte(`{"success": false}`)) return } - // recalculate authenticators stats - err = recalculateAppAuthentications() - if err != nil { - log.Printf("Authentications recalculation failed: %s", err) - } - resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) } -func recalculateAppAuthentications() error { - // create context - ctx := context.Background() - - // form workflows list - workflows, err := getAllWorkflows(ctx) - if err != nil { - log.Printf("Error: Failed getting workflows: %s", err) - return err - } - - // form authenticators list - auths, err := getAllWorkflowAppAuth(ctx) - if err != nil { - log.Printf("Error: Failed getting auths: %s", err) - return err - } - - // iterate through auths - for _, auth := range auths { - // reset calculated values - auth.WorkflowCount = 0 - auth.NodeCount = 0 - auth.Usage = []AuthenticationUsage{} - - // iterate through workflows to find which uses this auth - for _, workflow := range workflows { - hasCurrentAuth := false - usageItem := AuthenticationUsage{ - WorkflowId: workflow.ID, - Nodes: []string{}, - } - - // iterate through actions - for _, action := range workflow.Actions { - if action.AuthenticationId == auth.Id { - // this workflow should be added to "usage" field - hasCurrentAuth = true - - // add this action to list - usageItem.Nodes = append(usageItem.Nodes, action.ID) - } - } - - // update current auth with found workflow - if hasCurrentAuth { - auth.WorkflowCount += 1 - auth.NodeCount += int64(len(usageItem.Nodes)) - auth.Usage = append(auth.Usage, usageItem) - } - } - - // update record in database - err := setWorkflowAppAuthDatastore(ctx, auth, auth.Id) - if err != nil { - log.Printf("Failed setting up app auth %s: %s", auth.Id, err) - } - } - - return nil -} - func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -4630,8 +4727,9 @@ func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) { log.Printf("Hotloading from %s", location) err = handleAppHotload(location, true) if err != nil { + log.Printf("Failed app hotload: %s", err) resp.WriteHeader(500) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed loading apps: %s"}`))) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed loading apps: %s"}`, err))) return } @@ -4699,7 +4797,7 @@ func loadSpecificApps(resp http.ResponseWriter, request *http.Request) { storer := memory.NewStorage() r, err := git.Clone(storer, fs, cloneOptions) if err != nil { - log.Printf("Failed loading repo into memory: %s", err) + log.Printf("Failed loading repo %s into memory (github workflows 2): %s", tmpBody.URL, err) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return @@ -4778,10 +4876,25 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, case mode.IsRegular(): // Check the file filename := file.Name() + filteredNames := []string{"FUNDING.yml"} if strings.Contains(filename, "yaml") || strings.Contains(filename, "yml") { + + contOuter := false + for _, name := range filteredNames { + if filename == name { + contOuter = true + break + } + } + + if contOuter { + log.Printf("Skipping %s", filename) + continue + } + //log.Printf("File: %s", filename) //log.Printf("Found file: %s", filename) - log.Printf("OpenAPI app: %s", filename) + //log.Printf("OpenAPI app: %s", filename) tmpExtra := fmt.Sprintf("%s%s/", extra, file.Name()) fileReader, err := fs.Open(tmpExtra) @@ -5368,7 +5481,7 @@ func getAllSchedules(ctx context.Context) ([]ScheduleOld, error) { func getAllWorkflowApps(ctx context.Context) ([]WorkflowApp, error) { var allworkflowapps []WorkflowApp - q := datastore.NewQuery("workflowapp") + q := datastore.NewQuery("workflowapp").Limit(50) _, err := dbclient.GetAll(ctx, q, &allworkflowapps) if err != nil { @@ -5378,9 +5491,9 @@ func getAllWorkflowApps(ctx context.Context) ([]WorkflowApp, error) { return allworkflowapps, nil } -func getAllWorkflowAppAuth(ctx context.Context) ([]AppAuthenticationStorage, error) { +func getAllWorkflowAppAuth(ctx context.Context, OrgId string) ([]AppAuthenticationStorage, error) { var allworkflowapps []AppAuthenticationStorage - q := datastore.NewQuery("workflowappauth") + q := datastore.NewQuery("workflowappauth").Filter("org_id = ", OrgId) _, err := dbclient.GetAll(ctx, q, &allworkflowapps) if err != nil { diff --git a/backend/tests/file_download.sh b/backend/tests/file_download.sh new file mode 100644 index 00000000..32bb0787 --- /dev/null +++ b/backend/tests/file_download.sh @@ -0,0 +1,3 @@ +# ./b199646b-16d2-456d-9fd6-b9972e929466/2e9d6474-402c-4dcc-bb53-45f638ca18d3/0d676d72-5d53-4803-a6b0-4afb464df828 +# org_id / workflow_id / file_id +curl http://192.168.3.6:5001/api/v1/files/0d676d72-5d53-4803-a6b0-4afb464df828/content -H "Authorization: Bearer 093b576f-19ea-4353-b685-362ab50f39f4" diff --git a/docker-compose.yml b/docker-compose.yml index 6d92e85a..37e61122 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: depends_on: - backend backend: - #build: ./backend + build: ./backend image: frikky/shuffle:backend container_name: shuffle-backend hostname: ${BACKEND_HOSTNAME} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico index 09c34ad4..9210cc67 100644 Binary files a/frontend/public/favicon.ico and b/frontend/public/favicon.ico differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 053f0ab5..446ab4df 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3,7 +3,7 @@ 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 { removeCookies, useCookies } from 'react-cookie'; import EditSchedule from "./views/EditSchedule"; import Schedules from "./views/Schedules"; @@ -93,23 +93,24 @@ const App = (message, props) => { 'Content-Type': 'application/json', }, }) - .then(response => response.json()) - .then(responseJson => { - if (responseJson.success === true) { - //console.log(responseJson.success) - setUserData(responseJson) - setIsLoggedIn(true) + .then(response => response.json()) + .then(responseJson => { + if (responseJson.success === true) { + //console.log(responseJson.success) + setUserData(responseJson) + setIsLoggedIn(true) + console.log("Cookies: ", cookies) - // Updating cookie every request - for (var key in responseJson["cookies"]) { - setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }) - } + // 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) - }); + } + setIsLoaded(true) + }) + .catch(error => { + setIsLoaded(true) + }); } // Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies) @@ -124,7 +125,7 @@ const App = (message, props) => { } /> :
-
+
} /> } /> } /> @@ -140,7 +141,7 @@ const App = (message, props) => { } /> } /> } /> - } /> + } /> } /> } /> { window.location.pathname = "/docs/about" }} /> diff --git a/frontend/src/components/Dropzone.js b/frontend/src/components/Dropzone.js new file mode 100644 index 00000000..621e74b5 --- /dev/null +++ b/frontend/src/components/Dropzone.js @@ -0,0 +1,84 @@ +import React, { useRef, useState } from 'react'; +import { useEffect } from 'react'; +import BackupIcon from '@material-ui/icons/Backup'; + +const dragOverStyle = { + backgroundColor: 'rgba(0,0,0,0.8)', + border: '5px dashed white', + borderRadius: '8px', + width: '100%', + height: '100%', + position: 'absolute', + overflow: 'hidden', + zIndex: 100, + display: 'flex', + alignItems: 'center', + justifyContent: 'center' +}; + +const Dropzone = ({ children, style, onDrop }) => { + const dropzoneRef = useRef(null); + const [dragging, setDragging] = useState(false); + + let dragCounter = 0; + + const handleDragOver = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const handleDragEnter = (e) => { + e.preventDefault(); + e.stopPropagation(); + dragCounter++; + if (e.dataTransfer.items && e.dataTransfer.items.length > 0) + setDragging(true); + }; + + const handleDragLeave = (e) => { + e.preventDefault(); + e.stopPropagation(); + dragCounter--; + if (dragCounter === 0) setDragging(false); + }; + + const handleDrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + setDragging(false); + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + onDrop(e); + e.dataTransfer.clearData(); + dragCounter = 0; + } + }; + + useEffect(() => { + if (!dropzoneRef.current) return; + + dropzoneRef.current.addEventListener('dragover', handleDragOver); + dropzoneRef.current.addEventListener('dragenter', handleDragEnter); + dropzoneRef.current.addEventListener('dragleave', handleDragLeave); + dropzoneRef.current.addEventListener('drop', handleDrop); + + return () => { + dropzoneRef.current.removeEventListener('dragover', handleDragOver); + dropzoneRef.current.removeEventListener('dragenter', handleDragEnter); + dropzoneRef.current.removeEventListener('dragleave', handleDragLeave); + dropzoneRef.current.removeEventListener('drop', handleDrop); + }; + }, [dropzoneRef]); + + return ( +
+ {dragging && ( +
+ +
+ )} + {children} +
+ ); +}; + +export default Dropzone; diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 6902eb8f..11dcf59c 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -19,7 +19,7 @@ const hoverColor = "#f85a3e" const hoverOutColor = "#e8eaf6" const Header = props => { - const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded, userdata } = props; + const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded, userdata, cookies } = props; const theme = useTheme(); const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); @@ -35,27 +35,31 @@ const Header = props => { // DEBUG HERE const handleClickLogout = () => { - console.log("SHOULD LOG OUT") - console.log(isLoggedIn) - + console.log("COOKIES: ", cookies, "Remover: ", removeCookie) // Don't really care about the logout - fetch(globalUrl+"/api/v1/logout", { + fetch(globalUrl+"/api/v1/logout", { credentials: "include", - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - }) - .then(() => { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(() => { // Log out anyway - console.log("Hey") + //cookies.remove("session_token") + //window.location.pathname = "/" + console.log("Should've logged out") removeCookie("session_token", {path: "/"}) - window.location.pathname = "/" - }) + removeCookie("session_token", {path: "/workflows"}) + window.location.reload() + }) .catch(error => { - console.log(error) - }); - } + console.log("Error in logout: ", error) + removeCookie("session_token", {path: "/"}) + window.location.reload() + //removeCookie("session_token", {path: "/"}) + }) + } // Rofl this is weird const handleDocsHover = () => { @@ -158,6 +162,16 @@ const Header = props => {
+ {/* + + +
+ + Pricing +
+ +
+ */} {/* @@ -183,6 +197,19 @@ const Header = props => { color="primary"> Settings + {/* + + + + + + */} {userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null : @@ -299,8 +326,8 @@ const Header = props => { // - const loadedCheck = isLoaded ? -
+ const loadedCheck = +
{loginTextBrowser} @@ -308,10 +335,6 @@ const Header = props => { {loginTextMobile}
- : -
-
- //
return (
diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index cc4cb29a..34141a0e 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -3,24 +3,36 @@ import React, { useEffect} from 'react'; import {Link} from 'react-router-dom'; import Paper from '@material-ui/core/Paper'; import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Typography from '@material-ui/core/Typography'; import Switch from '@material-ui/core/Switch'; import Select from '@material-ui/core/Select'; import MenuItem from '@material-ui/core/MenuItem'; -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 Grid from '@material-ui/core/Grid'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; +import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import IconButton from '@material-ui/core/IconButton'; +import Avatar from '@material-ui/core/Avatar'; + import { useAlert } from "react-alert"; import { Dialog, DialogTitle, DialogActions, DialogContent } from '@material-ui/core'; import { useTheme } from '@material-ui/core/styles'; +import PolymerIcon from '@material-ui/icons/Polymer'; +import CheckCircleIcon from '@material-ui/icons/CheckCircle'; +import CloseIcon from '@material-ui/icons/Close'; +import AppsIcon from '@material-ui/icons/Apps'; +import ImageIcon from '@material-ui/icons/Image'; +import DeleteIcon from '@material-ui/icons/Delete'; import CachedIcon from '@material-ui/icons/Cached'; import AccessibilityNewIcon from '@material-ui/icons/AccessibilityNew'; import LockIcon from '@material-ui/icons/Lock'; @@ -46,6 +58,8 @@ const Admin = (props) => { const [curTab, setCurTab] = React.useState(0); const [users, setUsers] = React.useState([]); const [organizations, setOrganizations] = React.useState([]); + const [orgSyncResponse, setOrgSyncResponse] = React.useState(""); + const [environments, setEnvironments] = React.useState([]); const [authentication, setAuthentication] = React.useState([]); const [schedules, setSchedules] = React.useState([]) @@ -182,10 +196,13 @@ const Admin = (props) => { }); } - const enableCloudSync = (apikey, organization) => { + const enableCloudSync = (apikey, organization, disableSync) => { + setOrgSyncResponse("") + const data = { apikey: apikey, organization: organization, + disable: disableSync, } const url = globalUrl + '/api/v1/cloud/setup'; @@ -200,21 +217,39 @@ const Admin = (props) => { 'Content-Type': 'application/json; charset=utf-8', }, }) - .then(response => - response.json().then(responseJson => { - setLoading(false) - console.log(responseJson) - if (responseJson["success"] === false) { - alert.error("Failed setting up cloud sync") - } else { - alert.success("Set up cloud sync!") - } - }), - ) - .catch(error => { - setLoading(false) - alert.error("Err: " + error.toString()) - }); + .then(response => { + setLoading(false) + if (response.status === 200) { + console.log("Cloud sync success?") + } else { + console.log("Cloud sync fail?") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success && responseJson.reason !== undefined) { + setOrgSyncResponse(responseJson.reason) + alert.error("Failed to handle sync: "+responseJson.reason) + } else if (!responseJson.success) { + alert.error("Failed to handle sync.") + } else { + getOrgs() + if (disableSync) { + alert.success("Successfully disabled sync!") + } else { + alert.success("Sync successfully set up!") + } + + selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync + setSelectedOrganization(selectedOrganization) + setCloudSyncApikey("") + } + }) + .catch(error => { + setLoading(false) + alert.error("Err: " + error.toString()) + }) } const onPasswordChange = () => { @@ -362,9 +397,15 @@ const Admin = (props) => { for (var key in environments) { if (environments[key].Name == name) { if (environments[key].default) { - alert.info("Can't delete the default environment") + alert.error("Can't delete the default environment") return } + + if (environments[key].type === "cloud") { + alert.error("Can't delete the cloud environments") + return + } + environments[key].archived = true } @@ -514,7 +555,7 @@ const Admin = (props) => { } const getOrgs = () => { - fetch(globalUrl + "/api/v1/getorgs", { + fetch(globalUrl + "/api/v1/orgs", { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -780,6 +821,49 @@ const Admin = (props) => { + const GridItem = (props) => { + const primary = props.data.primary + const secondary = props.data.secondary + const primaryIcon = props.data.icon + const secondaryIcon = props.data.active ? + + : + + + return ( + + + + + {primaryIcon} + + + + {secondaryIcon} + + + ) + } + + const itemColor = "black" + var syncList = [ + { + "primary": "Workflows", + "secondary": "", + "active": false, + "icon": , + }, + { + "primary": "Apps", + "secondary": "", + "active": false, + "icon": , + }, + ] + const cloudSyncModal = { What does cloud sync do? -
- Cloud Apikey
{ }} required fullWidth={true} + disabled={selectedOrganization.cloud_sync} autoComplete="cloud apikey" id="apikey_field" margin="normal" @@ -822,17 +905,35 @@ const Admin = (props) => { setCloudSyncApikey(event.target.value) }} /> -
+ {orgSyncResponse.length > 0 ? + + Error: {orgSyncResponse} + + : null + } + + {syncList.map((data, index) => { + return ( + + ) + })} + * New triggers (userinput, hotmail realtime)
* Execute in the cloud rather than onprem
* Apps can be built in the cloud
@@ -1035,9 +1136,9 @@ const Admin = (props) => { }} > Edit user - + - + ) })} @@ -1288,6 +1389,10 @@ const Admin = (props) => { primary="Orborus running (TBD)" style={{minWidth: 200, maxWidth: 200}} /> + { style={{minWidth: 150, maxWidth: 150}} /> - {environments === undefined ? null : environments.map((environment, index)=> { + {environments === undefined || environments === null ? null : environments.map((environment, index)=> { if (!showArchived && environment.archived) { return null } @@ -1321,6 +1426,10 @@ const Admin = (props) => { primary={"TBD"} style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}} /> + { const [workflowExecutions, setWorkflowExecutions] = React.useState([]); const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0) + const cloudSyncEnabled = props.userdata !== undefined && props.userdata.active_org !== null && props.userdata.active_org !== undefined ? props.userdata.active_org.cloud_sync === true : false + const unloadText = 'Are you sure you want to leave without saving (CTRL+S)?' useBeforeunload(() => { if (!lastSaved) { @@ -1896,7 +1898,6 @@ const AngularWorkflow = (props) => { ) } - const syncEnabled = props.userdata !== undefined && props.userdata.selected_org !== null && props.userdata.selected_org !== undefined ? props.userdata.selected_org.cloud_sync === true : false const triggers = [{ "name": "Webhook", "type": "TRIGGER", @@ -1918,7 +1919,7 @@ const AngularWorkflow = (props) => { "description": "Wait for user input", "trigger_type": "USERINPUT", "errors": null, - "is_valid": syncEnabled, + "is_valid": cloudSyncEnabled, "label": "User input", "environment": environments[defaultEnvironmentIndex] === undefined ? {} : environments[defaultEnvironmentIndex].Name, "long_description": "Take user input to continue execution", @@ -1943,7 +1944,7 @@ const AngularWorkflow = (props) => { "description": "Add your email provider", "trigger_type": "EMAIL", "errors": null, - "is_valid": syncEnabled, + "is_valid": cloudSyncEnabled, "label": "Email", "environment": environments[defaultEnvironmentIndex] === undefined ? {} : environments[defaultEnvironmentIndex].Name, "large_image": 'data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoMDwwKCw4LCQkNEQ0ODxAQERAKDBITEhATDxAQEP/AAAsIAGQAZAEBEQD/xAAeAAABAwUBAQAAAAAAAAAAAAAAAQgJAgQFBwoGA//EAEoQAAECBAMEBwMDEQgDAAAAAAECAwAEBREGBxIIITFRCRMiMkFSYRRicSNCQxUWGBk2U1dYc3WBlJWzwdLTJDNjZXKDkeEmgqH/2gAIAQEAAD8Ak8JKipSlBwuDSpSeDw8qeRguQQrUAQNAV4JH3s+sA7OnT8n1fcv9Bfzc7wWAAToIAOsJ8Uq++H0gI1XSUlYWdSkji6fMnkBAdS9SidesWUpI3OjyjkYXtXCgbEDSFW3JT5D6+sIOzp0/J9X3NX0F/NzvBYABOggA6wnxSr74fSAjVdJSVhZ1KSOLp8yeQEBJUSVKCysaVKHB0eVPIwXIIVqAIGgK8Ej72fWFS640kNtzjcukcGli6k/GENwVagAQO0EcEjmj1g33AATe1wD3SnmffgG/Tp337mv535T+EaB2k9tzInZilVyuMq+up4iUjWxh+mFLs8s23dbv0stnmsgnwBiNPOHpddozHExMSmWsrSsA0tZIaMs0JudCfV50FKf/AFQPjDYsT7S+0LjJ8zGJc68aTqiSrSqtPoQD6ISoJH6BGFkM5c3qW8Jim5qYvlXArUFNVuZSb89y43Tlv0ju15ltMtKZzUmsRSbZGuSxC0mebcHIrV8r/wALEP02c+l2ywzAmZXDGeND+saqvEIRVWXFP0x1Z3AOE9thPx1JHioQ/un1Kn1eQZqtLn2ZuSmkJdamZVwOIWlQuktKTuUg8xFybgnUACB2gjgkc0e9BvuAAm9rgHulPM+/CpDik3bal1p8FPd8/GEtp7Ojq+r36OPUe96wWv2dF79vR5/8T/qI/ukM6RMZMGcyWyUqDMzjZ5vRWKwmy26UlQ3IQOBmLHx3IFibncIeqvWKtiCqTVbrtSmahUJ11T0zNTLqnHXnFG5UpSiSSeZi0ggggh2GxRt8Y92X69K4cr83N1zLuadCZumLXrcp4Ue0/KXPZPiW+6r0O+JxsF4zwvmFhSl42wXV5eo0Sqy6ZySmWFakJbUO/wDHiCk7wQQd4jN2v2dF79vR5v8AE/6g6rrflPYfar/S69Or9EIAAE6QoAHshfFJ5r9Ibpt3bTrOzBkbPYhpb7f11V5aqZh9le8iZUm65i3i20ntcirQPGIA6nU6jWqlNVirzr05PTzy5iZmHllTjrqyVKWoneSSSSYtoIIIIIIkI6J/axmsA4+Rs84yqZ+tvFb5VQ1vL7EjVCNyN/Bt4C1vOEn5xiYndYghVr3IHeKuY9yKVJbJu63MrV4qY7h+EVA6rEKKwvclSuLp8quQiDnpWM5ZnMrafn8HS04pykZfy6KMw2D2BNEByZUPXWQj/aEM0h3uwvk5PYmoeLM4HcnqHmth/CU1KytdwrNy5M+uUdQtZmZBYIu83oN2z3wbcbWk7ym2Zej6zuwXJ49y5yXwbUqXNgpUPZlpelnh32XmyrU24k7ik7/0WMey+wI2OPxesJfqyv5oPsCNjj8XrCX6sr+aD7AjY4/F6wl+rq/mhs2b2SGzXjPGc3s9bKuzngiq4zZGjEWJ3pRTlKwkyrcVOKCrOzVr6GRex73AiIe65TvqRWqhSet632Kadl9enTq0LKb28L24R86ZUp6jVKUrFMmVy85IvtzMu8g2U24hQUlQPMEAx0h7O+abGdWR+DM0mnAF16lMuzRTxamgNDzQHIOJWI2IpxDZ0Lm3ZdQ4tti6U/CEdeDaHJhxYWAklahwdAF9KeRjmXzRxJMYxzLxXiyacUt2sVqdnlFRuflHlq/jHmIlv6D37is1T/mlM/cvQ5zNnZ7xtl/jWc2htlFUtIYrfs7iXCLy+rpeLGk7zcDczN2vpdFrnvcSTsrIPaHwNtBYcmKlhsv02t0h4ydfw7UE9XUKPOJJC2Xmzv3KBAWNyrbvEDaDjjbTa3XVpQhAKlKUbBIHEk+ENLxdnDj/AGr8T1LJ7Zgra6NgymPmSxhmU0LhB+kkaV4OPkGynu6gG4PAlwGUuT2AMjsDy2BMuqGin06XBcdWTrfm3j3333D2nHFHeVH/AOCwjmnxr92Ve/Oc1+9VGGibDogMVP1vZWmKI+4b4dxJOybS1G4S06ht7QPipxf/ADD4kurbGhE21LpHBtwXUn4xbVNpb9OnGAAFrl3EkI4JukgFHrHMFWpdyUrE/KvAhxmZdbUDxBCyDFnEuHQej/wjNU/5rTP3L0PQ2hs1cR4f+pOUWU/VTGZeOtbFK1jW3SZNO6YqkwPBtlJ7IPfcKUi++NdYh2H5LB1CoWLtnXFD2Fs1sLS6rV+ZUXG8SqWouPtVVP0yXnCo6+8gqFtwAGAbkdpzbFcTgXNLB1Qyay9pBEri1iXm9VQxPNo/vJeVdT/dyJ3XcG9YNgTvt6TFODKZsY4nlc2MsaEmRypn25em45oMi2eqpiUANsVllA8gsiYtvUiyzcpJhz8rOylSkGqjITTUzKzTKXmHmlhSHG1C6VJI3EEEEGOXnGv3ZV785zX71UYaJjehfk3mdn/GM4oHRM4sWEBfcsiUZ1Eeu+JBUhxSbttS60+Cnu+fjCAaDbR1fV9rRx6n3vW8c5+2Bl1MZV7TGYmDXmlIaZrkxNyhIsFy0wrr2lD00OJjT0SfdE5mphvJnIrOXHmJutdalatSmZSSlxqmKhNuNOpYlWU8VOOLISAOdzuBiQLZ5yrxJQTVs382Q0/mVjrQ9VAk6m6RJp3y9Llz4NtA9ojvuFSjfdG54N8fGekZOpyUxTajKtTMrNNLYfYdSFIdbULKSpJ3EEEgiG4ZXz07s0Zis7OuJpp1zAmJFvP5cVSYWSJVYut2hurPzkC62Ce83dHFFogGxr92Ve/Oc1+9VGGiezo0MupnLzY/wezPy5bm8RqmMROsqFiUvr+SWf8AaQ2besOl6rrflPYfar/S69Or9EIAAE6QoAHshfFJ5r9Ii76Y7Z4mJgUHaSw7IqWhlCKHiLQN6RcmWmP9Nypsn8mIizj3GWGdOY2T9Xka1gSuJlH6bO/VKWbflm5hlubDam0v9U4lSC4lClBKiLp1G1iY3v8AbSdtr8LTP7Ekf6UL9tJ22vwss/sOR/pQfbSdtr8LLP7Dkf6UH20nba/C0z+w5H+lHmMxOkB2qc1MOKwrjjMNmfkPaGZxrTSZRl1iYZWFtPNOobC21pULhSSDx8DDe5uamJ6aenZt1Tr8w4p11auKlqNyT8SY2ZszZH1raIzqw1ldSG1hqozSXKlMAHTKyLZCn3VHwsi4HvKSPGOjSj0im0CjyVBpMqJen06XalZZhG7Q22kJQE+4AAIulJbJu63MrV4qY7h+EVA6rEKKwvclSuLp8quQjB45wVhrMbB9YwNjGnIn6JW5VyQnWVjihYtoTysbEKHAgGOf7a72U8abKeZszhStMOzVAnVreoNXCfk5uXvuSojcHUAgLTz3jcRGi4IIIIIuqVSqnXKnK0ajSD89PzzyJeWlpdsrcecUbJQlI3kkkAAROd0eGxqjZiy7XiLF8sy5j/FjaFVEiyhJMDeiSB9D2lkbiqw4JEO58CrUQAdJV4pPkHu+sIpxDZ0Lm3ZdQ4tti6U/CFJKiVKUFle5Sk8HR5U8jBcghWoAgaArwSPIfe9Y8PnJkvl1nzgScy7zMoDVQpMyLtlXZekHfmvNucULHgR8DcEiIZtqro1s58gZucxFg6QmsbYJQVOonpFkqnJNrw9pYTcgAfSJuk8Tp4Qz9SSklKgQQbEHwgggjYeTOz9m7n/iFGHMq8Fz1YdCgJiZSjRKSiT8955XYQB6m58AYmN2LejtwJsyoYxri52XxVmC43unA3/ZpAEb0ygVvv4F09ojgEgm7wSSq5Kgsr3KUODo8qeRguQQrUAQNAV4JHkPvesKl1bY0Im2pdI4NuC6k/GENwVagAQO0EcEjmj1g33AATe1wD3SnmffgG/Tp337mv535T+EG4i4KiCbAnvE8j7kaHzf2Hdl/O1+YqONcraexU3jd6p0i8jNFfPU1ZLnxWlUNjxL0LOTs6+tzC+bWLKSkHUWpmXl5xKR4BJAbJjD0/oTcEoeH1Uz5rbzfe0sUZlolH+pTigFelo3Nlr0UuyZgSYZn6vQ6zjKaQQpr6uz3yBI462WQhNvRVxDscMYVwvgujMYfwfh+n0Wly/ZZlJCVQw2k8tCABp9YypsL6iQAe0U8Unkj3YDcE6gAQO0EcEjmj3oN9wAE3tcA90p5n34VIcUm7bUutPgp7vn4wOpS05MNtiyZdAW0PKo+MASkuIbIulbPXKHNfOEa+V9m6zf7Vq633rcIpSoqbQ6T2lvdQo80coVxRbRMLRuVLuBts+VJ4iKnEhtb6ECwl0BxseVR8YAlJcQ2RdK2euUOa+cI18r7N1m/wBq1db71uEUpUVNodJ7S3uoUeaOUK4otomFo3Kl3A22fKk8RFTiQ2t9CBYS6A42PKo+MASkuIbIulbPXKHNfOPtKSkvNy6JiYaC3F71KJO+P//Z', @@ -1963,7 +1964,7 @@ const AngularWorkflow = (props) => { return (
- {triggers.map(trigger => { + {triggers.map((trigger, index) => { var imageline = trigger.large_image.length === 0 ? : @@ -1972,6 +1973,7 @@ const AngularWorkflow = (props) => { const color = trigger.is_valid ? "green" : "orange" return( {handleTriggerDrag(e, trigger)}} onStop={(e) => {handleDragStop(e)}} dragging={false} @@ -2538,9 +2540,9 @@ const AngularWorkflow = (props) => { var jsonvalid = true try { const tmp = String(JSON.parse(foundResult.result)) - //if (!tmp.includes("{") && !tmp.includes("[")) { - // jsonvalid = false - //} + if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) { + jsonvalid = false + } } catch (e) { jsonvalid = false } @@ -2627,9 +2629,9 @@ const AngularWorkflow = (props) => { var jsonvalid = true try { const tmp = String(JSON.parse(actionItem.example)) - //if (!tmp.includes("{") && !tmp.includes("[")) { - // jsonvalid = false - //} + if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) { + jsonvalid = false + } } catch (e) { jsonvalid = false } @@ -2853,7 +2855,7 @@ const AngularWorkflow = (props) => { ))} - } else if (data.variant === "STATIC_VALUE") { + } else if (data.variant === "STATIC_VALUE") { staticcolor = "#f85a3e" } else if (data.variant === "ACTION_RESULT") { // Gets the parents of the current node @@ -3137,6 +3139,8 @@ const AngularWorkflow = (props) => { return (
+ + {data.configuration === true ? { @@ -3149,32 +3153,37 @@ const AngularWorkflow = (props) => {
{data.name}
- -
{ + + {selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null : +
+ +
{ + e.preventDefault() + changeActionParameterVariant("STATIC_VALUE", count) + }}> + +
+
+  |  + +
{ e.preventDefault() - changeActionParameterVariant("STATIC_VALUE", count) + changeActionParameterVariant("ACTION_RESULT", count) }}> - -
-
-  |  - -
{ - e.preventDefault() - changeActionParameterVariant("ACTION_RESULT", count) - }}> - -
-
-  |  - -
{ - e.preventDefault() - changeActionParameterVariant("WORKFLOW_VARIABLE", count) - }}> - -
-
+ +
+ +  |  + +
{ + e.preventDefault() + changeActionParameterVariant("WORKFLOW_VARIABLE", count) + }}> + +
+
+
+ }
{datafield} {showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length > 0 ? @@ -4889,28 +4898,29 @@ const AngularWorkflow = (props) => { { - setTriggerOptionsWrapper("email") - }} - color="primary" - value="email" - /> - } + { + setTriggerOptionsWrapper("email") + }} + color="primary" + value="email" + /> + } label={
Email
} /> { - setTriggerOptionsWrapper("sms") - }} - color="primary" - value="sms" /> - } - label={
SMS
} + { + setTriggerOptionsWrapper("sms") + }} + color="primary" + value="sms" + /> + } + label={
SMS
} />
@@ -5352,9 +5362,9 @@ const AngularWorkflow = (props) => { var jsonvalid = true try { const tmp = String(JSON.parse(showResult)) - //if (!tmp.includes("{") && !tmp.includes("[")) { - // jsonvalid = false - //} + if (!showResult.includes("{") && !showResult.includes("[")) { + jsonvalid = false + } } catch (e) { jsonvalid = false } @@ -5549,11 +5559,12 @@ const AngularWorkflow = (props) => { var jsonvalid = true try { const tmp = String(JSON.parse(showResult)) - //if (!tmp.includes("{") && !tmp.includes("[")) { - // console.log("IN HERE") - // jsonvalid = false - //} + if (!showResult.includes("{") && !showResult.includes("[")) { + console.log("IN HERE: ", tmp) + jsonvalid = false + } } catch (e) { + //console.log("Error: ", e) jsonvalid = false } diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 7dab66d4..4d5098bd 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -618,8 +618,7 @@ const AppCreator = (props) => { "id": props.match.params.appid, } - - if (basedata.info.contact !== undefined) { + if (basedata.info !== undefined && basedata.info.contact !== undefined) { data.info["contact"] = basedata.info.contact } else if (contact === "") { data.info["contact"] = { @@ -701,7 +700,7 @@ const AppCreator = (props) => { data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) //console.log(queryitem) } - } + } if (item.paths.length > 0) { for (querykey in item.paths) { @@ -720,6 +719,30 @@ const AppCreator = (props) => { newitem.description = queryitem.description } + data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) + //console.log(queryitem) + } + } else { + // Always goes here if they didn't click anything :/ + const values = getCurrentPaths(item.url) + const paths = values[0] + + for (querykey in paths) { + const queryitem = 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) } @@ -1220,7 +1243,7 @@ const AppCreator = (props) => { return errormessage } - const UrlPathParameters = () => { + const getCurrentPaths = (urlPath) => { var paths = [] var queries = [] @@ -1296,7 +1319,16 @@ const AppCreator = (props) => { } } + return [paths, queries] + } + + const UrlPathParameters = () => { + const values = getCurrentPaths(urlPath) + const paths = values[0] + const queries = values[1] + if (currentAction.paths !== paths && urlPath.length > 0) { + console.log("IN PATHS SETTER: !", paths) setActionField("paths", paths) } @@ -1563,6 +1595,8 @@ const AppCreator = (props) => {
: null - const errorText = openApiError.length > 0 ?
Error: {openApiError}
: null + const errorText = openApiError.length > 0 ?
Error: {openApiError}
: null const modalView = openApiModal ? {
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json */} - Or paste the YAML or JSON specification - 0} color="primary" onClick={() => { - setOpenApiError("") - validateOpenApi(openApiData) - }}>Validate OpenAPI - }} - onChange={e => setOpenApiData(e.target.value)} - helperText={Must point to a version 2 or 3 specification.} - placeholder="OpenAPI text" - fullWidth - /> +

Or upload a YAML or JSON specification

+ + {errorText} diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index 6ed887f3..ef746a6d 100644 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -160,10 +160,6 @@ const LoginDialog = props => { //var loginChange = register ? (

Want to register? Click here.

) : (

Go back to login? Click here.

); var formtitle = register ?
Login
:
Register
- - // {formtitle} - - console.log("THEME: ", theme.palette.surfaceColor) const basedata =
{ const getSettings = () => { fetch(globalUrl+"/api/v1/getsettings", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) + 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!") @@ -137,24 +137,24 @@ const Settings = (props) => { 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) - } + //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) + //} } } @@ -175,7 +175,7 @@ const Settings = (props) => {

APIKEY

- What is the API key used for? + What is the API key used for? { - const { globalUrl, isLoggedIn, isLoaded, } = props; + const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies} = props; document.title = "Shuffle - Workflows" const alert = useAlert() @@ -83,6 +82,31 @@ const Workflows = (props) => { } }) + // DEBUG HERE + const handleClickLogout = () => { + //console.log("Cookies: ", cookies) + //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 + // removeCookie("session_token", {path: "/"}) + // //window.location = "/login" + //}) + //.catch(error => { + // console.log(error) + // removeCookie("session_token", {path: "/"}) + //}); + } + const deleteModal = deleteModalOpen ? { const getAvailableWorkflows = () => { fetch(globalUrl+"/api/v1/workflows", { - method: 'GET', + method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, - credentials: "include", - }) + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for workflows :O!") @@ -149,7 +173,7 @@ const Workflows = (props) => { if (isLoggedIn) { alert.error("An error occurred while loading workflows") } else { - window.location = "/login" + handleClickLogout() } return @@ -337,6 +361,9 @@ const Workflows = (props) => { } } + data.execution_org = {"id": ""} + console.log(data) + let linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', exportFileDefaultName); @@ -583,6 +610,10 @@ const Workflows = (props) => { return null } + 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 diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index b18a2793..2520377e 100644 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -126,7 +126,7 @@ func deployWorker(image string, identifier string, env []string) { log.Printf("[INFO] Found container ID %s", containerId) hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) } else { - log.Printf("[INFO] Empty self container id, continue without NetworkMode") + //log.Printf("[INFO] Empty self container id, continue without NetworkMode") } config := &container.Config{ @@ -144,7 +144,7 @@ func deployWorker(image string, identifier string, env []string) { ) if err != nil { - log.Println(err) + log.Printf("[ERROR] Container create error: %s", err) return } @@ -218,9 +218,11 @@ func initializeImages() { if baseimageregistry == "" { baseimageregistry = "docker.io" + log.Printf("Setting baseimageregistry") } if baseimagename == "" { baseimagename = "frikky/shuffle" + log.Printf("Setting baseimagename") } // check whether they are the same first @@ -290,13 +292,15 @@ func main() { // 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 log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!") - go initializeImages() + + initializeImages() //workerName := "worker" //workerVersion := "0.1.0" //workerImage := fmt.Sprintf("docker.pkg.github.com/frikky/shuffle/%s:%s", workerName, workerVersion) //workerImage := fmt.Sprintf("%s/worker:%s", baseimagename, workerVersion) // workerImage := fmt.Sprintf("docker.io/%s:worker", baseimagename) + // fmt.Sprintf("%s/%s:app_sdk%s", baseimageregistry, baseimagename, baseimagetagsuffix), workerImage := fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix) log.Printf("[INFO] Finished configuring docker environment") @@ -387,7 +391,7 @@ func main() { } if hasStarted && len(executionRequests.Data) > 0 { - log.Printf("[INFO] Body: %s", string(body)) + //log.Printf("[INFO] Body: %s", string(body)) // Type string `json:"type"` } diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index 4fe03ce9..c6b3889a 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -694,15 +694,16 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W // FIXME: Force killing a worker should result in a notification somewhere if len(nextActions) == 0 { log.Printf("No next action. Finished? Result vs Actions: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) - //exit := true - //for _, item := range workflowExecution.Results { - // if item == "EXECUTING" { - // exit = false - // break - // } - //} + exit := true + for _, item := range workflowExecution.Results { + if item.Status == "EXECUTING" { + exit = false + break + } + } if exit && len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) { + log.Printf("Shutting down.") shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID) }