From 17dfdf4aa1020c7478987a18b46cea6b09ccebc6 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 19 Nov 2020 09:42:01 -0500 Subject: [PATCH 1/3] Syntax Parser --- app/lib/exceptions.rb | 12 +++ app/lib/exceptions/jwt_expired_token.rb | 6 -- app/lib/json_web_token.rb | 2 +- app/lib/syntax_parser.rb | 135 ++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 app/lib/exceptions.rb delete mode 100644 app/lib/exceptions/jwt_expired_token.rb create mode 100644 app/lib/syntax_parser.rb diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb new file mode 100644 index 0000000..c1ba73e --- /dev/null +++ b/app/lib/exceptions.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class InvalidPayloadKey < StandardError +end + +class InvalidEnvironmentVariable < StandardError +end + +module JWT + class ExpiredWebToken < StandardError + end +end diff --git a/app/lib/exceptions/jwt_expired_token.rb b/app/lib/exceptions/jwt_expired_token.rb deleted file mode 100644 index b80506e..0000000 --- a/app/lib/exceptions/jwt_expired_token.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -module JWT - class ExpiredWebToken < StandardError - end -end diff --git a/app/lib/json_web_token.rb b/app/lib/json_web_token.rb index bc6da71..1e9ffb8 100644 --- a/app/lib/json_web_token.rb +++ b/app/lib/json_web_token.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative './exceptions/jwt_expired_token' +require_relative './exceptions' class JsonWebToken SECRET_KEY = Rails.application.secrets.secret_key_base.to_s diff --git a/app/lib/syntax_parser.rb b/app/lib/syntax_parser.rb new file mode 100644 index 0000000..b631250 --- /dev/null +++ b/app/lib/syntax_parser.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require_relative './exceptions' + +# This class parses user defined headers & payloads into the values we expect +# to send to the outbound service. +# +# Example: +# +# ```ruby +# custom_user_payload = { +# 'hello' => '$payload.top_level_key', +# 'environment_variable' => '$env.API_KEY', +# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3' +# } +# +# incoming_request_from_service_a = { +# 'top_level_key' => 'world', +# 'nested_key_1' => { +# 'nested_key_2' => { +# 'nested_key_3' => 'make it!' +# } +# } +# } +# +# bridge = Bridge.where(user_id: @current_user.id) +# +# syntax_parser = SyntaxParser.new(bridge) +# +# syntax_parser.parse_payload( +# incoming_request_from_service_a, +# custom_user_payload +# ) # => Hash(String, String) where $payload & $env are replaced with their respective values +# +# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is +# replaced with decrypted environment variable value +# ``` +class SyntaxParser + # Set bridge headers & env's. + # + # @param [Bridge] bridge + def initialize(bridge) + @headers = bridge.headers + @environment_variables = bridge.environment_variables + end + + # Parses the user's custom payload replacing any values containing `$env` + # or `$payload` with the respective value + # + # @param [Hash(String, String)] incoming_request + # @param [Hash(String, String)] user_data + # + # @return [Hash(String, String)] + def parse_payload(incoming_request, user_data) + @incoming_request = incoming_request + @user_data = user_data + + parse_payload! + + outbound_request + end + + # Parses the user's headers replacing any values containing `$env` + # with the decrypted environment variable value + # + # @return [Array(Hash(String, String))] + def parse_headers + parse_headers! + + outbound_headers + end + + private + + attr_reader :user_data, # User defined payload (bridge.data['payload']) + :outbound_request, # Parsed request returned from `parse_payload` + :incoming_request, # Inbound request from service A + :outbound_headers, # Parsed headers returned from `parse_headers` + :environment_variables, + :headers + + # Iterates through user defined payload and parse values containing `$env` or `$payload` + # Reinitializes & mutates `outbound_request` + def parse_payload! + @outbound_request = {} # Reset + + user_data.each do |key, val| + outbound_request[key] = if val.include?('$env') + fetch_environment_variable(val) + elsif val.include?('$payload') + fetch_payload_data(val) + else + val + end + end + end + + # Iterates through user defined payload and parse values containing `$env` + # Reinitializes & mutates `outbound_headers` + def parse_headers! + @outbound_headers = [] # Reset + headers.each do |header| + parsed_header = {} + parsed_header[header.key] = if header.value.include?('$env') + fetch_environment_variable(header.value) + else + header.value + end + + outbound_headers << parsed_header + end + end + + def fetch_environment_variable(value) + key = value.split('.').last + environment_variable = environment_variables.where(key: key) + raise InvalidEnvironmentVariable unless environment_variable + + environment_variable.decrypt + end + + def fetch_payload_data(value) + values = value.split('.') + data = incoming_request # Set data to the incoming request + + values.each_with_index do |val, idx| + next if idx.zero? # skip the $payload + + data = data[val] # dig deeper into the request on each iteration + raise InvalidPayloadKey if data.nil? + end + + data + end +end From 81cf04b9e9c388e81ef34f530d9c222d133bfe3f Mon Sep 17 00:00:00 2001 From: = Date: Thu, 19 Nov 2020 10:01:00 -0500 Subject: [PATCH 2/3] Skeleton classes --- app/lib/request_deconstructor.rb | 11 +++++++++++ app/lib/request_handler.rb | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 app/lib/request_deconstructor.rb create mode 100644 app/lib/request_handler.rb diff --git a/app/lib/request_deconstructor.rb b/app/lib/request_deconstructor.rb new file mode 100644 index 0000000..1dc3304 --- /dev/null +++ b/app/lib/request_deconstructor.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative './exceptions' + +# TODO: Build +# +# Deconstructs the request & response from us & service B. Replaces +# any headers that contain environment variables with placeholder values +# to prevent data leakage. +class RequestDeconstructor +end diff --git a/app/lib/request_handler.rb b/app/lib/request_handler.rb new file mode 100644 index 0000000..6a1bed5 --- /dev/null +++ b/app/lib/request_handler.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative './exceptions' + +# TODO: Build or delete +# +# Thinking some EventWorker functionality will be moved into here. +# SyntaxParser & RequestDeconstructor would be attributes and aid +# in setting up & properly saving requests/responses. +class RequestHandler +end From ae590efe3fe30fba1172a60f78da80aea790606a Mon Sep 17 00:00:00 2001 From: = Date: Thu, 19 Nov 2020 10:04:26 -0500 Subject: [PATCH 3/3] Fix Zeitwerk::NameError --- app/lib/exceptions.rb | 12 ------------ app/lib/exceptions/invalid_environment_variable.rb | 4 ++++ app/lib/exceptions/invalid_payload_key.rb | 4 ++++ app/lib/exceptions/jwt/expired_web_token.rb | 6 ++++++ app/lib/json_web_token.rb | 2 +- app/lib/syntax_parser.rb | 3 ++- 6 files changed, 17 insertions(+), 14 deletions(-) delete mode 100644 app/lib/exceptions.rb create mode 100644 app/lib/exceptions/invalid_environment_variable.rb create mode 100644 app/lib/exceptions/invalid_payload_key.rb create mode 100644 app/lib/exceptions/jwt/expired_web_token.rb diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb deleted file mode 100644 index c1ba73e..0000000 --- a/app/lib/exceptions.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -class InvalidPayloadKey < StandardError -end - -class InvalidEnvironmentVariable < StandardError -end - -module JWT - class ExpiredWebToken < StandardError - end -end diff --git a/app/lib/exceptions/invalid_environment_variable.rb b/app/lib/exceptions/invalid_environment_variable.rb new file mode 100644 index 0000000..750487e --- /dev/null +++ b/app/lib/exceptions/invalid_environment_variable.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class InvalidEnvironmentVariable < StandardError +end diff --git a/app/lib/exceptions/invalid_payload_key.rb b/app/lib/exceptions/invalid_payload_key.rb new file mode 100644 index 0000000..af828fe --- /dev/null +++ b/app/lib/exceptions/invalid_payload_key.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class InvalidPayloadKey < StandardError +end \ No newline at end of file diff --git a/app/lib/exceptions/jwt/expired_web_token.rb b/app/lib/exceptions/jwt/expired_web_token.rb new file mode 100644 index 0000000..b80506e --- /dev/null +++ b/app/lib/exceptions/jwt/expired_web_token.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module JWT + class ExpiredWebToken < StandardError + end +end diff --git a/app/lib/json_web_token.rb b/app/lib/json_web_token.rb index 1e9ffb8..b8e3ded 100644 --- a/app/lib/json_web_token.rb +++ b/app/lib/json_web_token.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative './exceptions' +require_relative './exceptions/jwt/expired_web_token' class JsonWebToken SECRET_KEY = Rails.application.secrets.secret_key_base.to_s diff --git a/app/lib/syntax_parser.rb b/app/lib/syntax_parser.rb index b631250..db2563a 100644 --- a/app/lib/syntax_parser.rb +++ b/app/lib/syntax_parser.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -require_relative './exceptions' +require_relative './exceptions/invalid_environment_variable' +require_relative './exceptions/invalid_payload_key' # This class parses user defined headers & payloads into the values we expect # to send to the outbound service.