From e6d61501a3a4e5ae1ba102dace11ca267993de3d Mon Sep 17 00:00:00 2001 From: BIOPZ-Kanitz Alexander Date: Fri, 28 Sep 2018 23:17:02 +0200 Subject: [PATCH 1/4] Authentication can optionally be enabled, adding a button to the Swagger UI. When active, the auth token is parsed, validated and decoded, and a Unauthorized message returned if applicable, according to the API specs. Decoded token data is passed into the controllers as an (optional) kwarg 'token_data'. --- .gitignore | 1 + requirements.txt | 7 + setup.py | 2 +- ...a3.workflow_execution_service.swagger.yaml | 0 wes_elixir/api/ga4gh.wes.0_3_0.openapi.yaml | 585 ------------------ wes_elixir/api/register_openapi.py | 55 +- wes_elixir/app.py | 5 +- wes_elixir/config/app_config.yaml | 48 +- wes_elixir/database/register_mongodb.py | 2 +- wes_elixir/errors/errors.py | 4 +- wes_elixir/factories/celery_app.py | 4 +- wes_elixir/ga4gh/wes/server.py | 9 +- wes_elixir/ga4gh/wes/utils_runs.py | 4 +- wes_elixir/ga4gh/wes/utils_service_info.py | 2 +- wes_elixir/security/decorators.py | 79 +++ 15 files changed, 188 insertions(+), 619 deletions(-) rename wes_elixir/{ga4gh/wes => api}/20180830.74fd916da3.workflow_execution_service.swagger.yaml (100%) delete mode 100644 wes_elixir/api/ga4gh.wes.0_3_0.openapi.yaml create mode 100644 wes_elixir/security/decorators.py diff --git a/.gitignore b/.gitignore index 45a0f85..dc61522 100644 --- a/.gitignore +++ b/.gitignore @@ -224,3 +224,4 @@ cwl-tes/ .vscode/ tests/tmp/* tests/output/* +*.modified.yaml diff --git a/requirements.txt b/requirements.txt index f1ab5cf..4d41512 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,17 @@ amqp==2.3.2 +asn1crypto==0.24.0 astroid==2.0.4 billiard==3.5.0.4 celery==4.2.1 +cffi==1.11.5 click==6.7 clickclick==1.2.2 connexion==1.5.2 +cryptography==2.3.1 Flask==1.0.2 Flask-Cors==3.0.6 Flask-PyMongo==2.1.0 +idna==2.7 inflection==0.3.1 isort==4.3.4 itsdangerous==0.24 @@ -17,10 +21,13 @@ kombu==4.2.1 lazy-object-proxy==1.3.1 MarkupSafe==1.0 mccabe==0.6.1 +pycparser==2.19 +PyJWT==1.6.4 pylint==2.1.1 pymongo==3.7.1 pytz==2018.5 PyYAML==3.13 +six==1.11.0 swagger-spec-validator==2.3.1 typed-ast==1.1.0 vine==1.1.4 diff --git a/setup.py b/setup.py index f99399c..76c1961 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import (setup, find_packages) with open("README.md", "r") as fh: long_description = fh.read() diff --git a/wes_elixir/ga4gh/wes/20180830.74fd916da3.workflow_execution_service.swagger.yaml b/wes_elixir/api/20180830.74fd916da3.workflow_execution_service.swagger.yaml similarity index 100% rename from wes_elixir/ga4gh/wes/20180830.74fd916da3.workflow_execution_service.swagger.yaml rename to wes_elixir/api/20180830.74fd916da3.workflow_execution_service.swagger.yaml diff --git a/wes_elixir/api/ga4gh.wes.0_3_0.openapi.yaml b/wes_elixir/api/ga4gh.wes.0_3_0.openapi.yaml deleted file mode 100644 index 7923101..0000000 --- a/wes_elixir/api/ga4gh.wes.0_3_0.openapi.yaml +++ /dev/null @@ -1,585 +0,0 @@ -basePath: '/ga4gh/wes/v1' -swagger: '2.0' -info: - title: Workflow Execution Service - version: 0.3.0 -schemes: - - http - - https -consumes: - - application/json -produces: - - application/json -paths: - /service-info: - get: - summary: |- - Get information about Workflow Execution Service. May include information related (but not limited to) the workflow descriptor formats, versions supported, the WES API versions supported, and information about general the service availability. - x-swagger-router-controller: wes_elixir.ga4gh.wes.server - operationId: GetServiceInfo - responses: - '200': - description: '' - schema: - $ref: '#/definitions/ServiceInfo' - '400': - description: The request is malformed. - schema: - $ref: '#/definitions/ErrorResponse' - '401': - description: The request is unauthorized. - schema: - $ref: '#/definitions/ErrorResponse' - '403': - description: The requester is not authorized to perform this action. - schema: - $ref: '#/definitions/ErrorResponse' - '500': - description: An unexpected error occurred. - schema: - $ref: '#/definitions/ErrorResponse' - tags: - - WorkflowExecutionService - /runs: - get: - summary: |- - List the workflow runs. This should be provided in a stable - ordering, however the ordering of this list is implementation - dependent. When paging through the list, the client should - not make assumptions about live updates, but should assume the - contents of the list reflect the workflow list at the moment - that the first page is requested. To monitor a specific - workflow run, use GetRunStatus or GetRunLog. - x-swagger-router-controller: wes_elixir.ga4gh.wes.server - operationId: ListRuns - responses: - '200': - description: '' - schema: - $ref: '#/definitions/RunListResponse' - '400': - description: The request is malformed. - schema: - $ref: '#/definitions/ErrorResponse' - '401': - description: The request is unauthorized. - schema: - $ref: '#/definitions/ErrorResponse' - '403': - description: The requester is not authorized to perform this action. - schema: - $ref: '#/definitions/ErrorResponse' - '500': - description: An unexpected error occurred. - schema: - $ref: '#/definitions/ErrorResponse' - parameters: - - name: page_size - description: |- - OPTIONAL - The preferred number of workflow runs to return in a page. - If not provided, the implementation should use a default page size. - The implementation must not return more items - than "page_size", but it may return fewer. Clients should - not assume that if fewer than "page_size" items is - returned that all items have been returned. The - availability of additional pages is indicated by the value - of "next_page_token" in the response. - in: query - required: false - type: integer - format: int64 - - name: page_token - description: |- - OPTIONAL - Token to use to indicate where to start getting results. If unspecified, return the first - page of results. - in: query - required: false - type: string - - name: tag_search - description: |- - OPTIONAL - For each key, if the key's value is empty string then match runs that are tagged with - this key regardless of value. - in: query - required: false - type: string - tags: - - WorkflowExecutionService - post: - summary: |- - Run a workflow. This endpoint creates a new workflow run and - returns the workflow ID to monitor its progress. - The request may upload files that are required to execute the - workflow identified as `workflow_attachment`. The parts - supplied in `workflow_attachment` may include the primary - workflow, tools imported by the workflow, other files - referenced by the workflow, or files which are part of the - input. The implementation should stage these files to a - temporary directory and execute the workflow from there. - These parts must have a Content-Disposition header with a - "filename" provided for each part. Filenames may include - subdirectories, but must not include references to parent - directories with '..', implementations should guard against - maliciously constructed filenames. - The `workflow_url` is either an absolute URL to a workflow - file that is accessible by the WES endpoint, or a relative URL - corresponding to one of the files attached using - `workflow_attachment`. - The `workflow_params` JSON object specifies input parameters, - such as input files. The exact format of the JSON object - depends on the conventions of the workflow language being - used. Input files should either be absolute URLs, or relative - URLs corresponding to files uploaded using - `workflow_attachment`. The WES endpoint must understand and - be able to access URLs supplied in the input. This is - implementation specific. - See documentation for WorkflowRequest for detail about other - fields. - x-swagger-router-controller: wes_elixir.ga4gh.wes.server - operationId: RunWorkflow - responses: - '200': - description: '' - schema: - $ref: '#/definitions/RunId' - '400': - description: The request is malformed. - schema: - $ref: '#/definitions/ErrorResponse' - '401': - description: The request is unauthorized. - schema: - $ref: '#/definitions/ErrorResponse' - '403': - description: The requester is not authorized to perform this action. - schema: - $ref: '#/definitions/ErrorResponse' - '500': - description: An unexpected error occurred. - schema: - $ref: '#/definitions/ErrorResponse' - consumes: - - multipart/form-data - parameters: - - in: formData - name: workflow_params - type: string - format: application/json - - - in: formData - name: workflow_type - type: string - - - in: formData - name: workflow_type_version - type: string - - - in: formData - name: tags - type: string - format: application/json - - - in: formData - name: workflow_engine_parameters - type: string - format: application/json - - - in: formData - name: workflow_url - type: string - - - in: formData - name: workflow_attachment - type: array - items: - type: string - format: binary - tags: - - WorkflowExecutionService - /runs/{run_id}: - get: - summary: Get detailed info about a workflow run. - x-swagger-router-controller: wes_elixir.ga4gh.wes.server - operationId: GetRunLog - responses: - '200': - description: '' - schema: - $ref: '#/definitions/RunLog' - '401': - description: The request is unauthorized. - schema: - $ref: '#/definitions/ErrorResponse' - '404': - description: The requested workflow run not found. - schema: - $ref: '#/definitions/ErrorResponse' - '403': - description: The requester is not authorized to perform this action. - schema: - $ref: '#/definitions/ErrorResponse' - '500': - description: An unexpected error occurred. - schema: - $ref: '#/definitions/ErrorResponse' - parameters: - - name: run_id - in: path - required: true - type: string - tags: - - WorkflowExecutionService - delete: - summary: Cancel a running workflow. - x-swagger-router-controller: wes_elixir.ga4gh.wes.server - operationId: CancelRun - responses: - '200': - description: '' - schema: - $ref: '#/definitions/RunId' - '401': - description: The request is unauthorized. - schema: - $ref: '#/definitions/ErrorResponse' - '404': - description: The requested workflow run wasn't found. - schema: - $ref: '#/definitions/ErrorResponse' - '403': - description: The requester is not authorized to perform this action. - schema: - $ref: '#/definitions/ErrorResponse' - '500': - description: An unexpected error occurred. - schema: - $ref: '#/definitions/ErrorResponse' - parameters: - - name: run_id - in: path - required: true - type: string - tags: - - WorkflowExecutionService - /runs/{run_id}/status: - get: - summary: Get quick status info about a workflow run. - x-swagger-router-controller: wes_elixir.ga4gh.wes.server - operationId: GetRunStatus - responses: - '200': - description: '' - schema: - $ref: '#/definitions/RunStatus' - '401': - description: The request is unauthorized. - schema: - $ref: '#/definitions/ErrorResponse' - '404': - description: The requested workflow run wasn't found. - schema: - $ref: '#/definitions/ErrorResponse' - '403': - description: The requester is not authorized to perform this action. - schema: - $ref: '#/definitions/ErrorResponse' - '500': - description: An unexpected error occurred. - schema: - $ref: '#/definitions/ErrorResponse' - parameters: - - name: run_id - in: path - required: true - type: string - tags: - - WorkflowExecutionService -definitions: - DefaultWorkflowEngineParameter: - type: object - properties: - type: - type: string - description: 'Describes the type of the parameter, e.g. float.' - default_value: - type: string - description: The stringified version of the default parameter. e.g. "2.45". - description: |- - A message that allows one to describe default parameters for a workflow - engine. - Log: - type: object - properties: - name: - type: string - title: The task or workflow name - cmd: - type: array - items: - type: string - title: The command line that was executed - start_time: - type: string - title: When the command started executing, in ISO 8601 format "%Y-%m-%dT%H:%M:%SZ" - end_time: - type: string - title: When the command stopped executing (completed, failed, or cancelled), in ISO 8601 format "%Y-%m-%dT%H:%M:%SZ" - stdout: - type: string - title: |- - A URL to retrieve standard output logs of the workflow run or - task. This URL may change between status requests, or may - not be available until the task or workflow has finished - execution. Should be available using the same credentials - used to access the WES endpoint. - stderr: - type: string - title: |- - A URL to retrieve standard error logs of the workflow run or - task. This URL may change between status requests, or may - not be available until the task or workflow has finished - execution. Should be available using the same credentials - used to access the WES endpoint. - exit_code: - type: integer - format: int32 - title: Exit code of the program - title: Log and other info - ServiceInfo: - type: object - properties: - workflow_type_versions: - type: object - additionalProperties: - $ref: '#/definitions/WorkflowTypeVersion' - title: |- - A map with keys as the workflow format type name (currently only CWL and WDL are used - although a service may support others) and value is a workflow_type_version object which - simply contains an array of one or more version strings - supported_wes_versions: - type: array - items: - type: string - title: The version(s) of the WES schema supported by this service - supported_filesystem_protocols: - type: array - items: - type: string - description: |- - The filesystem protocols supported by this service, currently these may include common - protocols such as 'http', 'https', 'sftp', 's3', 'gs', 'file', 'synapse', or others as - supported by this service. - workflow_engine_versions: - type: object - additionalProperties: - type: string - title: 'The engine(s) used by this WES service, key is engine name e.g. Cromwell and value is version' - default_workflow_engine_parameters: - type: array - items: - $ref: '#/definitions/DefaultWorkflowEngineParameter' - description: |- - Each workflow engine can present additional parameters that can be sent to the - workflow engine. This message will list the default values, and their types for each - workflow engine. - system_state_counts: - type: object - additionalProperties: - type: integer - format: int64 - description: |- - The system statistics, key is the statistic, value is the count of runs in that state. - See the State enum for the possible keys. - auth_instructions_url: - type: string - description: |- - A web page URL with information about how to get an - authorization token necessary to use a specific endpoint. - contact_info: - type: string - description: |- - An email address or web page URL with contact information - for the operator of a specific WES endpoint. Users of the - endpoint should use this to report problems or security - vulnerabilities. - tags: - type: object - additionalProperties: - type: string - title: |- - A key-value map of arbitrary, extended metadata outside the scope of the above but useful - to report back - description: |- - A message containing useful information about the running service, including supported versions and - default settings. - State: - type: string - enum: - - UNKNOWN - - QUEUED - - INITIALIZING - - RUNNING - - PAUSED - - COMPLETE - - EXECUTOR_ERROR - - SYSTEM_ERROR - - CANCELED - default: UNKNOWN - description: |- - - UNKNOWN: The state of the task is unknown. - This provides a safe default for messages where this field is missing, - for example, so that a missing field does not accidentally imply that - the state is QUEUED. - - QUEUED: The task is queued. - - INITIALIZING: The task has been assigned to a worker and is currently preparing to run. - For example, the worker may be turning on, downloading input files, etc. - - RUNNING: The task is running. Input files are downloaded and the first Executor - has been started. - - PAUSED: The task is paused. - An implementation may have the ability to pause a task, but this is not required. - - COMPLETE: The task has completed running. Executors have exited without error - and output files have been successfully uploaded. - - EXECUTOR_ERROR: The task encountered an error in one of the Executor processes. Generally, - this means that an Executor exited with a non-zero exit code. - - SYSTEM_ERROR: The task was stopped due to a system error, but not from an Executor, - for example an upload failed due to network issues, the worker's ran out - of disk space, etc. - - CANCELED: The task was canceled by the user. - title: Enumeration of states for a given run request - RunDescription: - type: object - properties: - run_id: - type: string - title: REQUIRED - state: - $ref: '#/definitions/State' - title: REQUIRED - title: 'Small description of a workflow run, returned by server during listing' - RunListResponse: - type: object - properties: - runs: - type: array - items: - $ref: '#/definitions/RunDescription' - description: A list of workflow runs that the service has executed or is executing. - next_page_token: - type: string - description: |- - A token which may be supplied as "page_token" in workflow run list request to get the next page - of results. An empty string indicates there are no more items to return. - description: The service will return a RunListResponse when receiving a successful RunListRequest. - RunLog: - type: object - properties: - run_id: - type: string - title: workflow run ID - request: - $ref: '#/definitions/RunRequest' - description: The original request message used to initiate this execution. - state: - $ref: '#/definitions/State' - title: state - run_log: - $ref: '#/definitions/Log' - title: 'the logs, and other key info like timing and exit code, for the overall run of this workflow' - task_logs: - type: array - items: - $ref: '#/definitions/Log' - title: 'the logs, and other key info like timing and exit code, for each step in the workflow run' - outputs: - $ref: '#/definitions/WesObject' - title: the outputs - RunRequest: - type: object - properties: - workflow_params: - $ref: '#/definitions/WesObject' - description: |- - REQUIRED - The workflow run parameterization document (typically a JSON file), includes all parameterizations for the run - including input and output file locations. - workflow_type: - type: string - title: |- - REQUIRED - The workflow descriptor type, must be "CWL" or "WDL" currently (or another alternative supported by this WES instance) - workflow_type_version: - type: string - title: |- - REQUIRED - The workflow descriptor type version, must be one supported by this WES instance - tags: - type: object - additionalProperties: - type: string - title: |- - OPTIONAL - A key-value map of arbitrary metadata outside the scope of the run_params but useful to track with this run request - workflow_engine_parameters: - type: object - additionalProperties: - type: string - description: |- - OPTIONAL - Additional parameters can be sent to the workflow engine using this field. Default values - for these parameters are provided at the ServiceInfo endpoint. - workflow_url: - type: string - description: |- - REQUIRED - The workflow CWL or WDL document. - When workflow attachments files are provided, the `workflow_url` may be a relative path - corresponding to one of the attachments. - description: |- - To execute a workflow, send a run request including all the details needed to begin downloading - and executing a given workflow. - RunId: - type: object - properties: - run_id: - type: string - title: workflow run ID - RunStatus: - type: object - properties: - run_id: - type: string - title: workflow run ID - state: - $ref: '#/definitions/State' - title: state - WorkflowTypeVersion: - type: object - properties: - workflow_type_version: - type: array - items: - type: string - description: |- - an array of one or more acceptable types for the Workflow Type. For - example, to send a base64 encoded WDL gzip, one could would offer - "base64_wdl1.0_gzip". By setting this value, and the path of the main WDL - to be executed in the workflow_url to "main.wdl" in the RunRequest. - description: Available workflow types supported by a given instance of the service. - WesObject: - type: object - additionalProperties: true - description: |- - An arbitrary structured object. - ErrorResponse: - description: |- - An object that can optionally include information about the error. - type: object - properties: - msg: - type: string - description: A detailed error message. - status_code: - type: integer - description: The integer representing the HTTP status code (e.g. 200, 404). diff --git a/wes_elixir/api/register_openapi.py b/wes_elixir/api/register_openapi.py index c724fac..25cb220 100644 --- a/wes_elixir/api/register_openapi.py +++ b/wes_elixir/api/register_openapi.py @@ -1,5 +1,8 @@ import logging import os +from shutil import copyfile + +from wes_elixir.config.config_parser import get_conf # Get logger instance @@ -8,25 +11,30 @@ def register_openapi( app=None, - specs=[] + specs=[], + add_security_definitions=True ): '''Register OpenAPI specs with Connexion app''' - # Iterate over list of APIspecs + # Iterate over list of API specs for spec in specs: - # Extract path - path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), spec['path'])) + # Get _this_ directory + path = os.path.join(os.path.abspath(os.path.dirname(os.path.realpath(__file__))), get_conf(spec, 'path')) + + # Add security definitions to copy of specs + if add_security_definitions: + path = __add_security_definitions(in_file=path) # Generate API endpoints from OpenAPI specs try: app.add_api( path, - strict_validation=spec['strict_validation'], - validate_responses=spec['validate_responses'], - swagger_ui=spec['swagger_ui'], - swagger_json=spec['swagger_json'], + strict_validation=get_conf(spec, 'strict_validation'), + validate_responses=get_conf(spec, 'validate_responses'), + swagger_ui=get_conf(spec, 'swagger_ui'), + swagger_json=get_conf(spec, 'swagger_json'), ) # Log info message @@ -41,4 +49,33 @@ def register_openapi( raise SystemExit(1) # Return Connexion app - return(app) \ No newline at end of file + return(app) + + +def __add_security_definitions( + in_file, + ext='modified.yaml' +): + + '''Add 'securityDefinitions' section to OpenAPI YAML specs''' + + # Set security definitions + amend = ''' + +# Amended by WES-ELIXIR +securityDefinitions: + jwt: + type: apiKey + name: Authorization + in: header''' + + # Create copy for modification + out_file = '.'.join([os.path.splitext(in_file)[0], ext]) + copyfile(in_file, out_file) + + # Append security definitions + with open(out_file, 'a') as mod: + mod.write(amend) + + # Return modified file path + return out_file \ No newline at end of file diff --git a/wes_elixir/app.py b/wes_elixir/app.py index 5af9311..f7b291a 100644 --- a/wes_elixir/app.py +++ b/wes_elixir/app.py @@ -1,6 +1,6 @@ from wes_elixir.api.register_openapi import register_openapi from wes_elixir.config.app_config import parse_app_config -from wes_elixir.config.config_parser import get_conf, get_conf_type +from wes_elixir.config.config_parser import (get_conf, get_conf_type) from wes_elixir.config.log_config import configure_logging from wes_elixir.database.register_mongodb import register_mongodb from wes_elixir.errors.errors import register_error_handlers @@ -32,7 +32,8 @@ def main(): # Register OpenAPI specs connexion_app = register_openapi( app=connexion_app, - specs=get_conf_type(config, 'api', 'specs', types=(list)) + specs=get_conf_type(config, 'api', 'specs', types=(list)), + add_security_definitions=get_conf(config, 'security', 'enable_authentication') ) # Enable cross-origin resource sharing diff --git a/wes_elixir/config/app_config.yaml b/wes_elixir/config/app_config.yaml index b7c9c67..a2dc94e 100644 --- a/wes_elixir/config/app_config.yaml +++ b/wes_elixir/config/app_config.yaml @@ -7,6 +7,28 @@ server: testing: False use_reloader: True +# Security settings +security: + enable_authentication: True + jwt: + algorithm: RS256 + public_key: |- + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA33TqqLR3eeUmDtHS89qF + 3p4MP7Wfqt2Zjj3lZjLjjCGDvwr9cJNlNDiuKboODgUiT4ZdPWbOiMAfDcDzlOxA + 04DDnEFGAf+kDQiNSe2ZtqC7bnIc8+KSG/qOGQIVaay4Ucr6ovDkykO5Hxn7OU7s + Jp9TP9H0JH8zMQA6YzijYH9LsupTerrY3U6zyihVEDXXOv08vBHk50BMFJbE9iwF + wnxCsU5+UZUZYw87Uu0n4LPFS9BT8tUIvAfnRXIEWCha3KbFWmdZQZlyrFw0buUE + f0YN3/Q0auBkdbDR/ES2PbgKTJdkjc/rEeM0TxvOUf7HuUNOhrtAVEN1D5uuxE1W + SwIDAQAB + -----END PUBLIC KEY----- + header_name: Authorization + token_prefix: Bearer + identity_claim: sub + user_claims: + type: access + fresh: non-fresh + # Database settings database: host: 'localhost' @@ -18,14 +40,14 @@ database: # Storage storage: - permanent_dir: "tests/output" - tmp_dir: "tests/tmp" - remote_storage_url: ftp://ftp-private.ebi.ac.uk/upload/foivos + permanent_dir: 'tests/output' + tmp_dir: 'tests/tmp' + remote_storage_url: 'ftp://ftp-private.ebi.ac.uk/upload/foivos' # Celery task queuer celery: - broker_url: pyamqp://localhost:5672// - result_backend: rpc:// + broker_url: 'pyamqp://localhost:5672//' + result_backend: 'rpc://' include: - wes_elixir.ga4gh.wes.utils_bg_tasks monitor: @@ -34,7 +56,7 @@ celery: # OpenAPI specs api: specs: - - path: "ga4gh.wes.0_3_0.openapi.yaml" + - path: '20180830.74fd916da3.workflow_execution_service.swagger.yaml' strict_validation: True validate_responses: True swagger_ui: True @@ -42,8 +64,8 @@ api: # WES service info settings service_info: - contact_info: https://github.com/elixir-europe/WES-ELIXIR - auth_instructions_url: https://www.elixir-europe.org/services/compute/aai + contact_info: 'https://github.com/elixir-europe/WES-ELIXIR' + auth_instructions_url: 'https://www.elixir-europe.org/services/compute/aai' supported_file_system_protocols: - http supported_wes_versions: @@ -56,12 +78,12 @@ service_info: cwl-tes: 0.2.0 default_workflow_engine_parameters: - type: string - default_value: "some_string" + default_value: some_string - type: int - default_value: "5" + default_value: 5 tags: - known_tes_endpoints: "https://tes-dev.tsi.ebi.ac.uk/" - wes_elixir_version: 0.5.0 + known_tes_endpoints: 'https://tes-dev.tsi.ebi.ac.uk/' + wes_elixir_version: 0.7.0 # Endpoint parameters api_endpoints: @@ -69,4 +91,4 @@ api_endpoints: # TES server tes: - url: "https://tes-dev.tsi.ebi.ac.uk/" + url: 'https://tes-dev.tsi.ebi.ac.uk/' \ No newline at end of file diff --git a/wes_elixir/database/register_mongodb.py b/wes_elixir/database/register_mongodb.py index 1222d0c..a48bc0f 100644 --- a/wes_elixir/database/register_mongodb.py +++ b/wes_elixir/database/register_mongodb.py @@ -53,5 +53,5 @@ def register_mongodb(app): logger.debug("Initializing service info...") get_service_info(app.app.config, silent=True) - # Return database and collections + # Return app return app \ No newline at end of file diff --git a/wes_elixir/errors/errors.py b/wes_elixir/errors/errors.py index 97d4ed0..c5bbe4e 100644 --- a/wes_elixir/errors/errors.py +++ b/wes_elixir/errors/errors.py @@ -1,10 +1,10 @@ import logging from connexion import ProblemException -from connexion.exceptions import ExtraParameterProblem, Forbidden, Unauthorized +from connexion.exceptions import (ExtraParameterProblem, Forbidden, Unauthorized) from flask import Response from json import dumps -from werkzeug.exceptions import BadRequest, InternalServerError, NotFound +from werkzeug.exceptions import (BadRequest, InternalServerError, NotFound) # Get logger instance diff --git a/wes_elixir/factories/celery_app.py b/wes_elixir/factories/celery_app.py index 02d47fb..d6185fb 100644 --- a/wes_elixir/factories/celery_app.py +++ b/wes_elixir/factories/celery_app.py @@ -3,7 +3,7 @@ from celery import Celery -from wes_elixir.config.config_parser import get_conf, get_conf_type +from wes_elixir.config.config_parser import (get_conf, get_conf_type) # Get logger instance @@ -45,4 +45,4 @@ def __call__(self, *args, **kwargs): celery.Task = ContextTask logger.debug("App context added to celery.Task class.") - return celery + return celery \ No newline at end of file diff --git a/wes_elixir/ga4gh/wes/server.py b/wes_elixir/ga4gh/wes/server.py index db2097a..26e2f90 100644 --- a/wes_elixir/ga4gh/wes/server.py +++ b/wes_elixir/ga4gh/wes/server.py @@ -6,6 +6,7 @@ import wes_elixir.ga4gh.wes.utils_runs as runs import wes_elixir.ga4gh.wes.utils_service_info as service_info +from wes_elixir.security.decorators import auth_token_optional # Get logger instance @@ -17,6 +18,7 @@ #################################### ### DELETE /runs/ ### +@auth_token_optional def CancelRun(run_id, *args, **kwargs): '''Cancel unfinished workflow run''' response = runs.cancel_run( @@ -37,6 +39,7 @@ def CancelRun(run_id, *args, **kwargs): ### GET /runs/ ### +@auth_token_optional def GetRunLog(run_id, *args, **kwargs): '''Return detailed run info''' response = runs.get_run_log( @@ -56,6 +59,7 @@ def GetRunLog(run_id, *args, **kwargs): ### GET /runs//status ### +@auth_token_optional def GetRunStatus(run_id, *args, **kwargs): '''Return run status''' response = runs.get_run_status( @@ -75,6 +79,7 @@ def GetRunStatus(run_id, *args, **kwargs): ### GET /service-info ### +@auth_token_optional def GetServiceInfo(*args, **kwargs): '''Return service info''' response = service_info.get_service_info( @@ -93,6 +98,7 @@ def GetServiceInfo(*args, **kwargs): ### GET /runs ### +@auth_token_optional def ListRuns(*args, **kwargs): '''List ids and status of all workflow runs''' response = runs.list_runs( @@ -111,6 +117,7 @@ def ListRuns(*args, **kwargs): ### POST /runs ### +@auth_token_optional def RunWorkflow(*args, **kwargs): '''Execute workflow''' response = runs.run_workflow( @@ -126,4 +133,4 @@ def RunWorkflow(*args, **kwargs): remote_addr=request.environ['REMOTE_ADDR'], response=response, )) - return response \ No newline at end of file + return response diff --git a/wes_elixir/ga4gh/wes/utils_runs.py b/wes_elixir/ga4gh/wes/utils_runs.py index 8f64a99..cfbba8a 100644 --- a/wes_elixir/ga4gh/wes/utils_runs.py +++ b/wes_elixir/ga4gh/wes/utils_runs.py @@ -5,7 +5,7 @@ import subprocess from celery import uuid -from json import decoder, loads +from json import (decoder, loads) from pymongo.errors import DuplicateKeyError from random import choice from yaml import dump @@ -13,7 +13,7 @@ import wes_elixir.database.db_utils as db_utils from wes_elixir.config.config_parser import get_conf -from wes_elixir.errors.errors import BadRequest, WorkflowNotFound +from wes_elixir.errors.errors import (BadRequest, WorkflowNotFound) from wes_elixir.ga4gh.wes.utils_bg_tasks import add_command_to_task_queue diff --git a/wes_elixir/ga4gh/wes/utils_service_info.py b/wes_elixir/ga4gh/wes/utils_service_info.py index 8ae3a65..69a9c73 100644 --- a/wes_elixir/ga4gh/wes/utils_service_info.py +++ b/wes_elixir/ga4gh/wes/utils_service_info.py @@ -1,5 +1,5 @@ from copy import deepcopy -from datetime import datetime, timezone +from datetime import datetime import logging import wes_elixir.database.db_utils as db_utils diff --git a/wes_elixir/security/decorators.py b/wes_elixir/security/decorators.py new file mode 100644 index 0000000..5abd2ee --- /dev/null +++ b/wes_elixir/security/decorators.py @@ -0,0 +1,79 @@ +from connexion.exceptions import Unauthorized +from connexion import request +from flask import current_app +from functools import wraps +import logging + +from jwt import decode + +from wes_elixir.config.config_parser import get_conf + + +# Get logger instance +logger = logging.getLogger(__name__) + + +def auth_token_optional(fn): + + ''' + If protect is True, the decorator will ensure that the requester has a + valid access token before allowing the endpoint to be called. + ''' + + @wraps(fn) + def wrapper(*args, **kwargs): + if get_conf(current_app.config, 'security', 'enable_authentication'): + token = _parse_jwt_from_header( + header_name=get_conf(current_app.config, 'security', 'jwt', 'header_name'), + expected_prefix=get_conf(current_app.config, 'security', 'jwt', 'token_prefix'), + ) + try: + token_data = decode( + jwt=token, + key=get_conf(current_app.config, 'security', 'jwt', 'public_key'), + algorithms=get_conf(current_app.config, 'security', 'jwt', 'algorithm'), + verify=True, + ) + return fn(token_data=token_data, *args, **kwargs) + except Exception as e: + logger.warning("Authentication token could not be decoded. Original error message: {type}: {msg}".format( + type=type(e).__name__, + msg=e, + )) + raise Unauthorized + else: + return fn(*args, **kwargs) + return wrapper + + +def _parse_jwt_from_header(header_name='Authorization', expected_prefix='Bearer'): + # TODO: Add custom errors + + '''Parse authentication token from HTTP header''' + + # Ensure that authorization header is present + jwt_header = request.headers.get(header_name, None) + if not jwt_header: + logger.warning("No HTTP header with name '{header_name}' found.".format( + header_name=header_name, + )) + raise Unauthorized + + # Ensure that authorization header is formatted correctly + try: + (prefix, token) = jwt_header.split() + except ValueError as e: + logger.warning("Authentication header is malformed. Original error message: {type}: {msg}".format( + type=type(e).__name__, + msg=e, + )) + raise Unauthorized + if prefix != expected_prefix: + logger.warning("Expected token prefix in authentication header is '{expected_prefix}', but '{prefix}' was found.".format( + expected_prefix=expected_prefix, + prefix=prefix, + )) + raise Unauthorized + + # Return token + return token \ No newline at end of file From 6194a51df8cf81a1e22fd6a690357ec2f3094396 Mon Sep 17 00:00:00 2001 From: BIOPZ-Kanitz Alexander Date: Sat, 29 Sep 2018 02:19:18 +0200 Subject: [PATCH 2/4] User IDs extracted from auth tokens are stored together with each run in the database. When authorization is enabled, only workflow runs owned by current user are returned for GET/DELETE requests. Exception: GET /service-info reports total state counts for all workflow runs; this may be desirable. When authorization is not enabled, all workflow runs can be accessed and null object is used as user ID in database. --- wes_elixir/app.py | 2 +- wes_elixir/config/app_config.yaml | 4 +- wes_elixir/database/db_utils.py | 40 ------- wes_elixir/ga4gh/wes/utils_runs.py | 128 ++++++++++++++------- wes_elixir/ga4gh/wes/utils_service_info.py | 8 +- wes_elixir/security/decorators.py | 62 ++++++++-- 6 files changed, 149 insertions(+), 95 deletions(-) diff --git a/wes_elixir/app.py b/wes_elixir/app.py index f7b291a..d2eaa45 100644 --- a/wes_elixir/app.py +++ b/wes_elixir/app.py @@ -33,7 +33,7 @@ def main(): connexion_app = register_openapi( app=connexion_app, specs=get_conf_type(config, 'api', 'specs', types=(list)), - add_security_definitions=get_conf(config, 'security', 'enable_authentication') + add_security_definitions=get_conf(config, 'security', 'authorization_required') ) # Enable cross-origin resource sharing diff --git a/wes_elixir/config/app_config.yaml b/wes_elixir/config/app_config.yaml index a2dc94e..a4b3a40 100644 --- a/wes_elixir/config/app_config.yaml +++ b/wes_elixir/config/app_config.yaml @@ -9,7 +9,7 @@ server: # Security settings security: - enable_authentication: True + authorization_required: True jwt: algorithm: RS256 public_key: |- @@ -80,7 +80,7 @@ service_info: - type: string default_value: some_string - type: int - default_value: 5 + default_value: '5' tags: known_tes_endpoints: 'https://tes-dev.tsi.ebi.ac.uk/' wes_elixir_version: 0.7.0 diff --git a/wes_elixir/database/db_utils.py b/wes_elixir/database/db_utils.py index 9feb96b..c40c802 100644 --- a/wes_elixir/database/db_utils.py +++ b/wes_elixir/database/db_utils.py @@ -2,46 +2,6 @@ from pymongo.collection import ReturnDocument -def find_one_by_id( - collection, - object_id -): - '''Returns single object by object id, stripped of object id, or None if object not found''' - return collection.find_one({'_id': ObjectId(object_id)}, {'_id': False}) - - -def find_one_by_index( - collection, - index, - value -): - '''Returns single object by index field value, stripped of object id, or None if object not found''' - return collection.find_one({index: value}, {'_id': False}) - - -def find_one_field_by_index( - collection, - index, - value, - select -): - '''Returns single field from single object by index field value, stripped of object id, or None if object not found''' - result = collection.find_one({index: value}, {select: True, '_id': False}) - if result is not None and select in result: - result = result[select] - return result - - -def find_fields( - collection, - projection -): - '''Returns selected fields from all objects, stripped of object id, or None if no objects found''' - projection_dict = {key:True for key in projection} - projection_dict['_id'] = False - return collection.find({}, projection_dict) - - def find_one_latest(collection): '''Returns newest/latest object, stripped of the object id, or None if no object exists''' try: diff --git a/wes_elixir/ga4gh/wes/utils_runs.py b/wes_elixir/ga4gh/wes/utils_runs.py index cfbba8a..ef03382 100644 --- a/wes_elixir/ga4gh/wes/utils_runs.py +++ b/wes_elixir/ga4gh/wes/utils_runs.py @@ -1,3 +1,4 @@ +from connexion.exceptions import Forbidden import logging import os import shutil @@ -10,9 +11,7 @@ from random import choice from yaml import dump -import wes_elixir.database.db_utils as db_utils - -from wes_elixir.config.config_parser import get_conf +from wes_elixir.config.config_parser import (get_conf, get_conf_type) from wes_elixir.errors.errors import (BadRequest, WorkflowNotFound) from wes_elixir.ga4gh.wes.utils_bg_tasks import add_command_to_task_queue @@ -31,13 +30,30 @@ def cancel_run(config, celery_app, run_id, *args, **kwargs): # Re-assign config values collection_runs = get_conf(config, 'database', 'collections', 'runs') - # Get task ID from database - task_id = db_utils.find_one_field_by_index(collection_runs, 'run_id', run_id, 'task_id') - - # Raise error if workflow run was not found - if task_id is None: + # Get document from database + document = collection_runs.find_one( + filter={'run_id': run_id}, + projection={ + 'user_id': True, + 'task_id': True, + '_id': False, + } + ) + + # Raise error if workflow run was not found or has no task ID + if document is None: logger.error("Run '{run_id}' not found.".format(run_id=run_id)) raise WorkflowNotFound + else: + task_id = document['task_id'] + + # Raise error trying to access workflow run that is not owned by user (if authorization enabled) + if 'user_id' in kwargs and document['user_id'] != kwargs['user_id']: + logger.error("User '{user_id}' is not allowed to access workflow run {run_id}.".format( + user_id=kwargs['user_id'], + run_id=run_id, + )) + raise Forbidden # Cancel workflow run try: @@ -73,15 +89,32 @@ def get_run_log(config, run_id, *args, **kwargs): collection_runs = get_conf(config, 'database', 'collections', 'runs') # Get document from database - document = db_utils.find_one_field_by_index(collection_runs, 'run_id', run_id, 'api') + document = collection_runs.find_one( + filter={'run_id': run_id}, + projection={ + 'user_id': True, + 'api': True, + '_id': False, + } + ) - # Raise error if workflow run was not found + # Raise error if workflow run was not found or has no task ID if document is None: logger.error("Run '{run_id}' not found.".format(run_id=run_id)) raise WorkflowNotFound + else: + run_log = document['api'] + + # Raise error trying to access workflow run that is not owned by user (if authorization enabled) + if 'user_id' in kwargs and document['user_id'] != kwargs['user_id']: + logger.error("User '{user_id}' is not allowed to access workflow run {run_id}.".format( + user_id=kwargs['user_id'], + run_id=run_id, + )) + raise Forbidden # Return response - return document + return run_log ################################# @@ -95,16 +128,29 @@ def get_run_status(config, run_id, *args, **kwargs): collection_runs = get_conf(config, 'database', 'collections', 'runs') # Get document from database - document = db_utils.find_one_field_by_index(collection_runs, 'run_id', run_id, 'api') - - # Raise error if workflow run was not found + document = collection_runs.find_one( + filter={'run_id': run_id}, + projection={ + 'user_id': True, + 'api.state': True, + '_id': False, + } + ) + + # Raise error if workflow run was not found or has no task ID if document is None: logger.error("Run '{run_id}' not found.".format(run_id=run_id)) raise WorkflowNotFound - - # Extract workflow run state else: - state = document['state'] + state = document['api']['state'] + + # Raise error trying to access workflow run that is not owned by user (if authorization enabled) + if 'user_id' in kwargs and document['user_id'] != kwargs['user_id']: + logger.error("User '{user_id}' is not allowed to access workflow run {run_id}.".format( + user_id=kwargs['user_id'], + run_id=run_id, + )) + raise Forbidden # Build formatted response object response = { @@ -134,7 +180,18 @@ def list_runs(config, *args, **kwargs): #page_size = kwargs['page_size'] if 'page_size' in kwargs else cnx_app.app.config['api_endpoints']['default_page_size'] # Query database for workflow runs - cursor = db_utils.find_fields(collection_runs, ['run_id', 'state']) + if 'user_id' in kwargs: + filter_dict = {'user_id': kwargs['user_id']} + else: + filter_dict = {} + cursor = collection_runs.find( + filter=filter_dict, + projection={ + 'run_id': True, + 'state': True, + '_id': False, + } + ) # Iterate through list runs_list = list() @@ -171,11 +228,9 @@ def run_workflow(config, form_data, *args, **kwargs): document = __init_run_document(data=form_data) # Create run environment - document = __create_run_environment(config=config, document=document) + document = __create_run_environment(config=config, document=document, **kwargs) # Start workflow run in background - # tmp_dir and out_dir need to go in document - # tes_url c __run_workflow(config=config, document=document) # Build formatted response object @@ -282,7 +337,7 @@ def __init_run_document(data): return document -def __create_run_environment(config, document): +def __create_run_environment(config, document, **kwargs): '''Create unique run identifier and permanent and temporary storage directories for current run''' # Re-assign config values @@ -296,42 +351,35 @@ def __create_run_environment(config, document): # TODO: If no more possible IDs => inf loop; fix (raise customerror; 500 to user) while True: - # Create unique run id + # Create unique run and task ids run_id = __create_run_id( charset=run_id_charset, length=run_id_length, ) - - # Create unique Celery task id task_id = uuid() + # Set temporary and output directories + current_tmp_dir = os.path.abspath(os.path.join(tmp_dir, run_id)) + current_out_dir = os.path.abspath(os.path.join(out_dir, run_id)) + # Try to create workflow run directory (temporary) try: # TODO: Think about permissions - # TODO: Add this to document # TODO: Add working dir (currently one has to run the app from the outermost dir) - current_tmp_dir = os.path.abspath(os.path.join(tmp_dir, run_id)) os.mkdir(current_tmp_dir) - - # Try new run id if directory already exists - except FileExistsError: - continue - - # Try to create output directory (permanent) - try: - # TODO: Think about permissions - # TODO: Add this to document - # TODO: Add working dir (currently one has to run the app from the outermost dir) - current_out_dir = os.path.abspath(os.path.join(out_dir, run_id)) os.mkdir(current_out_dir) # Try new run id if directory already exists except FileExistsError: continue - # Add run/task identifier, temp/output directories to document + # Add run/task/user identifier, temp/output directories to document document['run_id'] = run_id document['task_id'] = task_id + if 'user_id' in kwargs: + document['user_id'] = kwargs['user_id'] + else: + document['user_id'] = None document['internal']['tmp_dir'] = current_tmp_dir document['internal']['out_dir'] = current_out_dir @@ -477,4 +525,4 @@ def __run_workflow(config, document): ) # Nothing to return - return None \ No newline at end of file + return None diff --git a/wes_elixir/ga4gh/wes/utils_service_info.py b/wes_elixir/ga4gh/wes/utils_service_info.py index 69a9c73..be2a017 100644 --- a/wes_elixir/ga4gh/wes/utils_service_info.py +++ b/wes_elixir/ga4gh/wes/utils_service_info.py @@ -50,7 +50,13 @@ def __get_system_state_counts(collection_runs): current_counts = __init_system_state_counts() # Query database for workflow run states - cursor = db_utils.find_fields(collection_runs, ['api.state']) + cursor = collection_runs.find( + filter={}, + projection={ + 'api.state': True, + '_id': False, + } + ) # Iterate over states for record in cursor: diff --git a/wes_elixir/security/decorators.py b/wes_elixir/security/decorators.py index 5abd2ee..2b4f89b 100644 --- a/wes_elixir/security/decorators.py +++ b/wes_elixir/security/decorators.py @@ -22,11 +22,17 @@ def auth_token_optional(fn): @wraps(fn) def wrapper(*args, **kwargs): - if get_conf(current_app.config, 'security', 'enable_authentication'): + + # Check if authentication is enabled + if get_conf(current_app.config, 'security', 'authorization_required'): + + # Parse token from HTTP header token = _parse_jwt_from_header( header_name=get_conf(current_app.config, 'security', 'jwt', 'header_name'), expected_prefix=get_conf(current_app.config, 'security', 'jwt', 'token_prefix'), ) + + # Decode token try: token_data = decode( jwt=token, @@ -34,46 +40,80 @@ def wrapper(*args, **kwargs): algorithms=get_conf(current_app.config, 'security', 'jwt', 'algorithm'), verify=True, ) - return fn(token_data=token_data, *args, **kwargs) except Exception as e: - logger.warning("Authentication token could not be decoded. Original error message: {type}: {msg}".format( + logger.error("Authentication token could not be decoded. Original error message: {type}: {msg}".format( type=type(e).__name__, msg=e, )) raise Unauthorized + + # Validate claims + identity_claim = get_conf(current_app.config, 'security', 'jwt', 'identity_claim') + _validate_claims( + token_data=token_data, + required_claims=[identity_claim], + ) + + # Extract user ID + user_id = token_data[identity_claim] + + # Return wrapped function with token data + return fn(token=token, token_data=token_data, user_id=user_id, *args, **kwargs) + + # Return wrapped function without token data else: return fn(*args, **kwargs) + return wrapper -def _parse_jwt_from_header(header_name='Authorization', expected_prefix='Bearer'): +def _parse_jwt_from_header( + header_name='Authorization', + expected_prefix='Bearer' +): # TODO: Add custom errors '''Parse authentication token from HTTP header''' # Ensure that authorization header is present - jwt_header = request.headers.get(header_name, None) - if not jwt_header: - logger.warning("No HTTP header with name '{header_name}' found.".format( + auth_header = request.headers.get(header_name, None) + if not auth_header: + logger.error("No HTTP header with name '{header_name}' found.".format( header_name=header_name, )) raise Unauthorized # Ensure that authorization header is formatted correctly try: - (prefix, token) = jwt_header.split() + (prefix, token) = auth_header.split() except ValueError as e: - logger.warning("Authentication header is malformed. Original error message: {type}: {msg}".format( + logger.error("Authentication header is malformed. Original error message: {type}: {msg}".format( type=type(e).__name__, msg=e, )) raise Unauthorized if prefix != expected_prefix: - logger.warning("Expected token prefix in authentication header is '{expected_prefix}', but '{prefix}' was found.".format( + logger.error("Expected token prefix in authentication header is '{expected_prefix}', but '{prefix}' was found.".format( expected_prefix=expected_prefix, prefix=prefix, )) raise Unauthorized # Return token - return token \ No newline at end of file + return token + + +def _validate_claims( + token_data, + required_claims=[] +): + + '''Validate token data''' + + # Check for existence of required claims + for claim in required_claims: + if not claim in token_data: + logger.error("Required claim '{claim}' not found in token.".format( + claim=claim, + )) + raise Unauthorized \ No newline at end of file From 782dc96cb81233fad201af1beb7432da134d5609 Mon Sep 17 00:00:00 2001 From: BIOPZ-Kanitz Alexander Date: Sat, 29 Sep 2018 04:34:38 +0200 Subject: [PATCH 3/4] Token and public key are passed to cwl-tes; with a change in cwl-tes code to handle escaped newlines, back to back 'secured' WES->cwl-tes->TES was successful --- wes_elixir/ga4gh/wes/utils_runs.py | 11 +++++++++-- wes_elixir/tasks/celery_task_monitor.py | 7 ++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/wes_elixir/ga4gh/wes/utils_runs.py b/wes_elixir/ga4gh/wes/utils_runs.py index ef03382..c08e155 100644 --- a/wes_elixir/ga4gh/wes/utils_runs.py +++ b/wes_elixir/ga4gh/wes/utils_runs.py @@ -231,7 +231,7 @@ def run_workflow(config, form_data, *args, **kwargs): document = __create_run_environment(config=config, document=document, **kwargs) # Start workflow run in background - __run_workflow(config=config, document=document) + __run_workflow(config=config, document=document, **kwargs) # Build formatted response object response = {"run_id": document['run_id']} @@ -468,7 +468,7 @@ def __process_workflow_attachments(data): return data -def __run_workflow(config, document): +def __run_workflow(config, document, **kwargs): '''Helper function for `run_workflow()`''' # Re-assign config values @@ -483,6 +483,12 @@ def __run_workflow(config, document): yaml_path = document['internal']['yaml_path'] # Build command + auth_params = [] + if 'token' in kwargs: + auth_params = [ + "--token-public-key", get_conf(config, 'security', 'jwt', 'public_key').encode('unicode_escape').decode('utf-8'), + "--token", kwargs['token'], + ] command_list = [ "cwl-tes", "--leave-outputs", @@ -491,6 +497,7 @@ def __run_workflow(config, document): cwl_path, yaml_path ] + command_list[2:2] = auth_params ## TEST CASE FOR SYSTEM ERROR #command_list = [ diff --git a/wes_elixir/tasks/celery_task_monitor.py b/wes_elixir/tasks/celery_task_monitor.py index dbef6af..4f679c2 100644 --- a/wes_elixir/tasks/celery_task_monitor.py +++ b/wes_elixir/tasks/celery_task_monitor.py @@ -159,8 +159,9 @@ def on_task_received(self, event): '''Event handler for received Celery tasks''' # Parse subprocess inputs - kwargs = literal_eval(event['kwargs']) - command = ' '.join([quote(item) for item in kwargs['command_list']]) + # TODO: string too long when passing key and token; command not added + #kwargs = literal_eval(event['kwargs']) + #command = ' '.join([quote(item) for item in kwargs['command_list']]) # Create dictionary for internal parameters internal = dict() @@ -174,7 +175,7 @@ def on_task_received(self, event): state='QUEUED', internal=internal, task_received=datetime.utcfromtimestamp(event['timestamp']).strftime("%Y-%m-%d %H:%M:%S.%f"), - command=command, + # command=command, # TODO: see above utc_offset=event['utcoffset'], max_retries=event['retries'], expires=event['expires'], From ff4229a4ff4891a5a9f5736125528df602b622ef Mon Sep 17 00:00:00 2001 From: BIOPZ-Kanitz Alexander Date: Sat, 29 Sep 2018 05:00:36 +0200 Subject: [PATCH 4/4] Minor update to app config --- wes_elixir/config/app_config.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/wes_elixir/config/app_config.yaml b/wes_elixir/config/app_config.yaml index a4b3a40..f4cd206 100644 --- a/wes_elixir/config/app_config.yaml +++ b/wes_elixir/config/app_config.yaml @@ -25,9 +25,6 @@ security: header_name: Authorization token_prefix: Bearer identity_claim: sub - user_claims: - type: access - fresh: non-fresh # Database settings database: