pact_broker 2.19.2 → 2.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/example/README.md +7 -1
  4. data/example/config.ru +2 -3
  5. data/lib/pact_broker/api/contracts/webhook_contract.rb +73 -6
  6. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +9 -13
  7. data/lib/pact_broker/api/resources/base_resource.rb +3 -3
  8. data/lib/pact_broker/api/resources/pact_webhooks.rb +1 -1
  9. data/lib/pact_broker/api/resources/webhook_execution.rb +5 -2
  10. data/lib/pact_broker/configuration.rb +9 -1
  11. data/lib/pact_broker/doc/views/webhooks.markdown +23 -0
  12. data/lib/pact_broker/domain/webhook_request.rb +49 -37
  13. data/lib/pact_broker/locale/en.yml +2 -0
  14. data/lib/pact_broker/version.rb +1 -1
  15. data/lib/pact_broker/webhooks/check_host_whitelist.rb +22 -0
  16. data/lib/pact_broker/webhooks/render.rb +23 -0
  17. data/lib/pact_broker/webhooks/service.rb +2 -1
  18. data/lib/pact_broker/webhooks/webhook.rb +0 -5
  19. data/spec/features/create_webhook_spec.rb +2 -3
  20. data/spec/features/edit_webhook_spec.rb +2 -2
  21. data/spec/features/execute_webhook_spec.rb +32 -1
  22. data/spec/fixtures/webhook_valid.json +1 -1
  23. data/spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb +64 -1
  24. data/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb +17 -9
  25. data/spec/lib/pact_broker/api/resources/pact_spec.rb +1 -1
  26. data/spec/lib/pact_broker/api/resources/pact_webhooks_spec.rb +3 -3
  27. data/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb +3 -1
  28. data/spec/lib/pact_broker/domain/webhook_request_spec.rb +73 -68
  29. data/spec/lib/pact_broker/webhooks/check_host_whitelist_spec.rb +47 -0
  30. data/spec/lib/pact_broker/webhooks/render_spec.rb +36 -0
  31. data/spec/lib/pact_broker/webhooks/service_spec.rb +7 -0
  32. data/spec/support/shared_examples_for_responses.rb +2 -2
  33. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6596910c63bcfb8363e958de4bb4bb698df0613e
4
- data.tar.gz: 929b0ac8d06300c3a6226f4afb3af099f97c7476
3
+ metadata.gz: e14b4974d382ad35da338f8b493f4ea721105a7c
4
+ data.tar.gz: 3d27068e75bea109b5bc3bac0262061ce973f574
5
5
  SHA512:
6
- metadata.gz: aaf62c676cffcc46679e3cba55afd4234d74208577869803d76e670805b314d7e8a742525de8d3599aafa5f5769d50b62b76ee8464796766c42c92dc73807b36
7
- data.tar.gz: 3cb076e57dcb8f23450ddd50e1b8f92f4161f79fd4541799296a5fdc0ed4671e13d1ba409eb0dfeff60c41cc86cc3472a304fb6e1068b6219a2359b3365799e7
6
+ metadata.gz: bf62aadde7feb83cb578fccb4a853530e1d2c3967f9abfc85d649ff9640c58f410bcfaa5b6a0d347444cf1aab47f7bf5e68d00f67c71d34c9d4fc8ebb3fc8e25
7
+ data.tar.gz: fb663dbb7503fb6f1d43f53c820257844a2573f9cf1913995e22d04f8cc3e15c53acf59e56fe5bb9e049ef5d9d915e660eeec99f58defcc46b5d22d71df392d4
@@ -1,3 +1,20 @@
1
+ <a name="v2.20.0"></a>
2
+ ### v2.20.0 (2018-06-03)
3
+
4
+
5
+ #### Features
6
+
7
+ * only log webhook response details when a webhook host whitelist has been configured ([3e1c562](/../../commit/3e1c562))
8
+ * validate webhook host against configurable list on creation ([077e37f](/../../commit/077e37f))
9
+ * validate webhook scheme and http method against configurable lists on creation ([d7a2b0a](/../../commit/d7a2b0a))
10
+ * add ${pactbroker.consumerVersionNumber} to webhook templates ([d525527](/../../commit/d525527))
11
+
12
+
13
+ #### Bug Fixes
14
+
15
+ * correct all content types that were application/json to application/hal+json ([690e39b](/../../commit/690e39b))
16
+
17
+
1
18
  <a name="v2.19.2"></a>
2
19
  ### v2.19.2 (2018-05-29)
3
20
 
@@ -1,5 +1,7 @@
1
1
  # Run Pact Broker example
2
2
 
3
+ The configuration for this example should not be used in production. Either use the [Docker Pact Broker image][docker-pact-broker], or copy the [pact_broker directory][pact-broker-dir] from the Docker project as your starting point. Ensure you configure a web server/reverse proxy (such as Passenger/Nginx) in front of it (you can also copy the configuration for these from the Docker image.)
4
+
3
5
  Clone project
4
6
 
5
7
  ```bash
@@ -40,7 +42,7 @@ Set up postgres database
40
42
 
41
43
  ```bash
42
44
  psql postgres -c "CREATE DATABASE pact_broker;"
43
- psql postgres -c "CREATE ROLE pact_broker WITH LOGIN PASSWORD 'pact_broker';"
45
+ psql postgres -c "CREATE ROLE pact_broker WITH LOGIN PASSWORD 'CHANGE_ME';"
44
46
  psql postgres -c "GRANT ALL PRIVILEGES ON DATABASE pact_broker TO pact_broker;"
45
47
  ```
46
48
 
@@ -63,3 +65,7 @@ psql pact_broker < example_data.sql
63
65
  ```
64
66
 
65
67
  Now Pact Broker can be access locally at [http://localhost:9292](http://localhost:9292).
68
+
69
+ [docker-pact-broker]: https://github.com/DiUS/pact_broker-docker
70
+ [pact-broker-dir]: https://github.com/DiUS/pact_broker-docker/tree/master/pact_broker
71
+
@@ -11,10 +11,10 @@ DATABASE_CREDENTIALS = {adapter: "sqlite", database: "pact_broker_database.sqlit
11
11
  # For postgres:
12
12
  #
13
13
  # $ psql postgres -c "CREATE DATABASE pact_broker;"
14
- # $ psql postgres -c "CREATE ROLE pact_broker WITH LOGIN PASSWORD 'pact_broker';"
14
+ # $ psql postgres -c "CREATE ROLE pact_broker WITH LOGIN PASSWORD 'CHANGE_ME';"
15
15
  # $ psql postgres -c "GRANT ALL PRIVILEGES ON DATABASE pact_broker TO pact_broker;"
16
16
  #
17
- # DATABASE_CREDENTIALS = {adapter: "postgres", database: "pact_broker", username: 'pact_broker', password: 'pact_broker', :encoding => 'utf8'}
17
+ # DATABASE_CREDENTIALS = {adapter: "postgres", database: "pact_broker", username: 'pact_broker', password: 'CHANGE_ME', :encoding => 'utf8'}
18
18
 
19
19
  # Have a look at the Sequel documentation to make decisions about things like connection pooling
20
20
  # and connection validation.
@@ -25,7 +25,6 @@ app = PactBroker::App.new do | config |
25
25
  # change these from their default values if desired
26
26
  # config.log_dir = "./log"
27
27
  # config.auto_migrate_db = true
28
- # config.use_hal_browser = true
29
28
  config.database_connection = Sequel.connect(DATABASE_CREDENTIALS.merge(:logger => config.logger))
30
29
  end
31
30
 
@@ -1,11 +1,25 @@
1
1
  require 'reform'
2
2
  require 'reform/form'
3
+ require 'pact_broker/webhooks/check_host_whitelist'
3
4
 
4
5
  module PactBroker
5
6
  module Api
6
7
  module Contracts
7
8
  class WebhookContract < Reform::Form
8
9
 
10
+ def validate(*)
11
+ result = super
12
+ # I just cannot seem to get the validation to stop on the first error.
13
+ # If one rule fails, they all come back failed, and it's driving me nuts.
14
+ # Why on earth would I want that behaviour?
15
+ new_errors = Reform::Contract::Errors.new
16
+ errors.messages.each do | key, value |
17
+ new_errors.add(key, value.first)
18
+ end
19
+ @errors = new_errors
20
+ result
21
+ end
22
+
9
23
  validation do
10
24
  configure do
11
25
  config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
@@ -23,20 +37,73 @@ module PactBroker
23
37
  configure do
24
38
  config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
25
39
 
26
- def valid_method?(value)
27
- Net::HTTP.const_defined?(value.capitalize)
40
+ def self.messages
41
+ super.merge(
42
+ en: {
43
+ errors: {
44
+ allowed_webhook_method?: http_method_error_message,
45
+ allowed_webhook_scheme?: scheme_error_message,
46
+ allowed_webhook_host?: host_error_message
47
+ }
48
+ }
49
+ )
50
+ end
51
+
52
+ def self.http_method_error_message
53
+ if PactBroker.configuration.webhook_http_method_whitelist.size == 1
54
+ "must be #{PactBroker.configuration.webhook_http_method_whitelist.first}. See /doc/webhooks#whitelist for more information."
55
+ else
56
+ "must be one of #{PactBroker.configuration.webhook_http_method_whitelist.join(", ")}. See /doc/webhooks#whitelist for more information."
57
+ end
28
58
  end
29
59
 
30
- def valid_url?(value)
31
- uri = URI(value)
60
+ def self.scheme_error_message
61
+ "scheme must be #{PactBroker.configuration.webhook_scheme_whitelist.join(", ")}. See /doc/webhooks#whitelist for more information."
62
+ end
63
+
64
+ def self.host_error_message
65
+ "host must be in the whitelist #{PactBroker.configuration.webhook_host_whitelist.join(",")}. See /doc/webhooks#whitelist for more information."
66
+ end
67
+
68
+ def valid_method?(http_method)
69
+ Net::HTTP.const_defined?(http_method.capitalize)
70
+ end
71
+
72
+ def valid_url?(url)
73
+ uri = URI(url)
32
74
  uri.scheme && uri.host
33
75
  rescue URI::InvalidURIError
34
76
  false
35
77
  end
78
+
79
+ def allowed_webhook_method?(http_method)
80
+ PactBroker.configuration.webhook_http_method_whitelist.any? do | allowed_method |
81
+ http_method.downcase == allowed_method.downcase
82
+ end
83
+ end
84
+
85
+ def allowed_webhook_scheme?(url)
86
+ scheme = URI(url).scheme
87
+ PactBroker.configuration.webhook_scheme_whitelist.any? do | allowed_scheme |
88
+ scheme.downcase == allowed_scheme.downcase
89
+ end
90
+ end
91
+
92
+ def allowed_webhook_host?(url)
93
+ if host_whitelist.any?
94
+ PactBroker::Webhooks::CheckHostWhitelist.call(URI(url).host, host_whitelist).any?
95
+ else
96
+ true
97
+ end
98
+ end
99
+
100
+ def host_whitelist
101
+ PactBroker.configuration.webhook_host_whitelist
102
+ end
36
103
  end
37
104
 
38
- required(:http_method).filled(:valid_method?)
39
- required(:url).filled(:valid_url?)
105
+ required(:http_method).filled(:valid_method?, :allowed_webhook_method?)
106
+ required(:url).filled(:valid_url?, :allowed_webhook_scheme?, :allowed_webhook_host?)
40
107
  end
41
108
  end
42
109
 
@@ -1,21 +1,17 @@
1
1
  require_relative 'base_decorator'
2
2
  require 'json'
3
+ require 'pact_broker/messages'
3
4
 
4
5
  module PactBroker
5
6
  module Api
6
7
  module Decorators
7
-
8
8
  class WebhookExecutionResultDecorator < BaseDecorator
9
-
10
9
  class ErrorDecorator < BaseDecorator
11
-
12
10
  property :message
13
11
  property :backtrace
14
-
15
12
  end
16
13
 
17
14
  class HTTPResponseDecorator < BaseDecorator
18
-
19
15
  property :status, :getter => lambda { |_| code.to_i }
20
16
  property :headers, exec_context: :decorator
21
17
  property :body, exec_context: :decorator
@@ -36,13 +32,9 @@ module PactBroker
36
32
  end
37
33
  end
38
34
 
39
- property :message, exec_context: :decorator
40
- # property :error, :extend => ErrorDecorator
41
- # property :response, :extend => HTTPResponseDecorator
42
-
43
- def message
44
- "Webhook response has been redacted temporarily for security purposes. Please see the Pact Broker application logs for the response body."
45
- end
35
+ property :error, :extend => ErrorDecorator, if: lambda { |context| context[:options][:user_options][:show_response] }
36
+ property :response, :extend => HTTPResponseDecorator, if: lambda { |context| context[:options][:user_options][:show_response] }
37
+ property :response_hidden_message, as: :message, exec_context: :decorator, if: lambda { |context| !context[:options][:user_options][:show_response] }
46
38
 
47
39
  link :webhook do | options |
48
40
  {
@@ -56,7 +48,11 @@ module PactBroker
56
48
  href: webhook_execution_url(options.fetch(:webhook), options.fetch(:base_url))
57
49
  }
58
50
  end
51
+
52
+ def response_hidden_message
53
+ PactBroker::Messages.message('messages.response_body_hidden')
54
+ end
59
55
  end
60
56
  end
61
57
  end
62
- end
58
+ end
@@ -96,12 +96,12 @@ module PactBroker
96
96
  end
97
97
 
98
98
  def set_json_error_message message
99
- response.headers['Content-Type'] = 'application/json;charset=utf-8'
99
+ response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
100
100
  response.body = {error: message}.to_json
101
101
  end
102
102
 
103
103
  def set_json_validation_error_messages errors
104
- response.headers['Content-Type'] = 'application/json;charset=utf-8'
104
+ response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
105
105
  response.body = {errors: errors}.to_json
106
106
  end
107
107
 
@@ -128,7 +128,7 @@ module PactBroker
128
128
  rescue StandardError => e
129
129
  logger.error "Error parsing JSON #{e} - #{request_body}"
130
130
  set_json_error_message "Error parsing JSON - #{e.message}"
131
- response.headers['Content-Type'] = 'application/json;charset=utf-8'
131
+ response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
132
132
  true
133
133
  end
134
134
  end
@@ -38,7 +38,7 @@ module PactBroker
38
38
  errors = webhook_service.errors(webhook)
39
39
 
40
40
  unless errors.empty?
41
- response.headers['Content-Type'] = 'application/json;charset=utf-8'
41
+ response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
42
42
  response.body = { errors: errors.messages }.to_json
43
43
  end
44
44
 
@@ -32,7 +32,7 @@ module PactBroker
32
32
  private
33
33
 
34
34
  def post_response_body webhook_execution_result
35
- Decorators::WebhookExecutionResultDecorator.new(webhook_execution_result).to_json(user_options: { base_url: base_url, webhook: webhook })
35
+ Decorators::WebhookExecutionResultDecorator.new(webhook_execution_result).to_json(user_options: user_options)
36
36
  end
37
37
 
38
38
  def webhook
@@ -46,8 +46,11 @@ module PactBroker
46
46
  def uuid
47
47
  identifier_from_path[:uuid]
48
48
  end
49
+
50
+ def user_options
51
+ { base_url: base_url, webhook: webhook, show_response: PactBroker.configuration.show_webhook_response? }
52
+ end
49
53
  end
50
54
  end
51
55
  end
52
-
53
56
  end
@@ -30,9 +30,10 @@ module PactBroker
30
30
  attr_accessor :validate_database_connection_config, :enable_diagnostic_endpoints, :version_parser, :sha_generator
31
31
  attr_accessor :use_case_sensitive_resource_names, :order_versions_by_date
32
32
  attr_accessor :check_for_potential_duplicate_pacticipant_names
33
+ attr_accessor :webhook_http_method_whitelist, :webhook_scheme_whitelist, :webhook_host_whitelist
34
+ attr_accessor :webhook_retry_schedule
33
35
  attr_accessor :semver_formats
34
36
  attr_accessor :enable_public_badge_access, :shields_io_base_url
35
- attr_accessor :webhook_retry_schedule
36
37
  attr_accessor :disable_ssl_verification
37
38
  attr_accessor :base_equality_only_on_content_that_affects_verification_results
38
39
  attr_reader :api_error_reporters
@@ -74,6 +75,9 @@ module PactBroker
74
75
  config.webhook_retry_schedule = [10, 60, 120, 300, 600, 1200] #10 sec, 1 min, 2 min, 5 min, 10 min, 20 min => 38 minutes
75
76
  config.check_for_potential_duplicate_pacticipant_names = true
76
77
  config.disable_ssl_verification = false
78
+ config.webhook_http_method_whitelist = ['POST']
79
+ config.webhook_scheme_whitelist = ['https']
80
+ config.webhook_host_whitelist = []
77
81
  config
78
82
  end
79
83
 
@@ -142,6 +146,10 @@ module PactBroker
142
146
  end
143
147
  end
144
148
 
149
+ def show_webhook_response?
150
+ webhook_host_whitelist.any?
151
+ end
152
+
145
153
  def enable_badge_resources= enable_badge_resources
146
154
  puts "Pact Broker configuration property `enable_badge_resources` is deprecated. Please use `enable_public_badge_access`"
147
155
  self.enable_public_badge_access = enable_badge_resources
@@ -54,6 +54,28 @@ A request body can be specified as well.
54
54
 
55
55
  **BEWARE** The password can be reverse engineered from the database, so make a separate account for the Pact Broker to use, don't use your personal account!
56
56
 
57
+ <a name="whitelist"></a>
58
+ #### Webhook Whitelist
59
+
60
+ To ensure that webhooks cannot be used maliciously to expose either data about your contracts or your internal network, the following validation rules are applied to webhooks via the Pact Broker configuration settings.
61
+
62
+ * **Scheme**: Must be included in the `webhook_scheme_whitelist`, which by default only includes `https`. You can change this to include `http` if absolutely necessary, however, keep in mind that the body of any http traffic is visible to the network. You can load a self signed certificate into the Pact Broker to be used for https connections using [script/insert-self-signed-certificate-from-url.rb](https://github.com/pact-foundation/pact_broker/blob/master/script/insert-self-signed-certificate-from-url.rb) in the
63
+ Pact Broker Github repository.
64
+
65
+ * **HTTP method**: Must be included in the `webhook_http_method_whitelist`, which by default only includes `POST`. It is highly recommended that only `POST` requests are allowed to ensure that webhooks cannot be used to retrieve sensitive information from hosts within the same network.
66
+
67
+ * **Host**: If the `webhook_host_whitelist` contains any entries, the host must match one or more of the entries. By default, it is empty. For security purposes, if the host whitelist is empty, the response details will not be logged to the UI (though they can be seen in the application logs at debug level).
68
+
69
+ The host whitelist may contain hostnames (eg `"github.com"`), IPs (eg `"192.0.345.4"`), network ranges (eg `"10.0.0.0/8"`) or regular expressions (eg `/.*\.foo\.com$/`). Note that IPs are not resolved, so if you specify an IP range, you need to use the IP in the webhook URL. If you wish to allow webhooks to any host (not recommended!), you can set `webhook_host_whitelist` to `[/.*/]`. Beware of any sensitive endpoints that may be exposed within the same network.
70
+
71
+ The recommended set of values to start with are:
72
+
73
+ * your CI server's hostname (for triggering builds)
74
+ * your company chat (eg. Slack, for publishing notifications)
75
+ * your code repository (eg. Github, for sending commit statuses)
76
+
77
+ Alternatively, you could use a regular expression to limit requests to your company's domain. eg `/.*\.foo\.com$/` (don't forget the end of string anchor). You can test Ruby regular expressions at [rubular.com](http://rubular.com).
78
+
57
79
  #### Event types
58
80
 
59
81
  `contract_content_changed:` triggered when the content of the contract has changed since the previous publication. Uses plain string equality, so changes to the ordering of hash keys, or whitespace changes will trigger this webhook.
@@ -65,6 +87,7 @@ A request body can be specified as well.
65
87
  The following variables may be used in the request parameters or body, and will be replaced with their appropriate values at runtime.
66
88
 
67
89
  `${pactbroker.pactUrl}`: the "permalink" URL to the newly published pact (the URL specifying the consumer version URL, rather than the "/latest" format.)
90
+ `${pactbroker.consumerVersionNumber}`: the version number of the most recent consumer version associated with the pact content.
68
91
 
69
92
  Example usage:
70
93
 
@@ -5,8 +5,10 @@ require 'pact_broker/logging'
5
5
  require 'pact_broker/messages'
6
6
  require 'net/http'
7
7
  require 'pact_broker/webhooks/redact_logs'
8
+ require 'pact_broker/webhooks/render'
8
9
  require 'pact_broker/api/pact_broker_urls'
9
10
  require 'pact_broker/build_http_options'
11
+ require 'cgi'
10
12
 
11
13
  module PactBroker
12
14
 
@@ -66,15 +68,14 @@ module PactBroker
66
68
  uri = build_uri(pact)
67
69
  req = build_request(uri, pact, execution_logger)
68
70
  response = do_request(uri, req)
69
- log_response(response, execution_logger)
71
+ log_response(response, execution_logger, options)
70
72
  result = WebhookExecutionResult.new(response, logs.string)
71
73
  log_completion_message(options, execution_logger, result.success?)
72
74
  result
73
75
  end
74
76
 
75
77
  def handle_error_and_build_result e, options, logs, execution_logger
76
- logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message} #{e.backtrace.join("\n")}"
77
- execution_logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
78
+ log_error(e, execution_logger, options)
78
79
  log_completion_message(options, execution_logger, false)
79
80
  WebhookExecutionResult.new(nil, logs.string, e)
80
81
  end
@@ -91,14 +92,10 @@ module PactBroker
91
92
  req.basic_auth(username, password) if username
92
93
 
93
94
  unless body.nil?
94
- if String === body
95
- req.body = gsub_body(pact, body)
96
- else
97
- req.body = gsub_body(pact, body.to_json)
98
- end
95
+ req.body = PactBroker::Webhooks::Render.call(String === body ? body : body.to_json, pact)
99
96
  end
100
97
 
101
- execution_logger.info req.body
98
+ execution_logger.info(req.body) if req.body
102
99
  req
103
100
  end
104
101
 
@@ -110,23 +107,40 @@ module PactBroker
110
107
  end
111
108
  end
112
109
 
113
- def log_response response, execution_logger
114
- execution_logger.info(" ")
110
+ def log_response response, execution_logger, options
111
+ log_response_to_application_logger(response)
112
+ if options[:show_response]
113
+ log_response_to_execution_logger(response, execution_logger)
114
+ else
115
+ execution_logger.info response_body_hidden_message
116
+ end
117
+ end
118
+
119
+ def response_body_hidden_message
120
+ PactBroker::Messages.message('messages.response_body_hidden')
121
+ end
122
+
123
+ def log_response_to_application_logger response
115
124
  logger.info "Received response for webhook #{uuid} status=#{response.code}"
116
- execution_logger.info "HTTP/#{response.http_version} #{response.code} #{response.message}"
117
- #response.each_header do | header |
118
- # execution_logger.info "#{header.split("-").collect(&:capitalize).join('-')}: #{response[header]}"
119
- #end
125
+ logger.debug "headers=#{response.to_hash}"
120
126
  logger.debug "body=#{response.body}"
121
- # safe_body = nil
122
- # if response.body
123
- # safe_body = response.body.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
124
- # if response.body != safe_body
125
- # execution_logger.debug "Note that invalid UTF-8 byte sequences were removed from response body before saving the logs"
126
- # end
127
- # end
128
- #execution_logger.info safe_body
129
- execution_logger.info "Webhook response has been redacted temporarily for security purposes. Please see the Pact Broker application logs for the response body."
127
+ end
128
+
129
+ def log_response_to_execution_logger response, execution_logger
130
+ execution_logger.info "HTTP/#{response.http_version} #{response.code} #{response.message}"
131
+ response.each_header do | header |
132
+ execution_logger.info "#{header.split("-").collect(&:capitalize).join('-')}: #{response[header]}"
133
+ end
134
+
135
+ safe_body = nil
136
+
137
+ if response.body
138
+ safe_body = response.body.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
139
+ if response.body != safe_body
140
+ execution_logger.debug "Note that invalid UTF-8 byte sequences were removed from response body before saving the logs"
141
+ end
142
+ end
143
+ execution_logger.info safe_body
130
144
  end
131
145
 
132
146
  def log_completion_message options, execution_logger, success
@@ -141,6 +155,16 @@ module PactBroker
141
155
  end
142
156
  end
143
157
 
158
+ def log_error e, execution_logger, options
159
+ logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message} #{e.backtrace.join("\n")}"
160
+
161
+ if options[:show_response]
162
+ execution_logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
163
+ else
164
+ execution_logger.error "Error executing webhook #{uuid}. #{response_body_hidden_message}"
165
+ end
166
+ end
167
+
144
168
  def to_s
145
169
  "#{method.upcase} #{url}, username=#{username}, password=#{display_password}, headers=#{headers}, body=#{body}"
146
170
  end
@@ -150,7 +174,7 @@ module PactBroker
150
174
  end
151
175
 
152
176
  def build_uri pact
153
- URI(gsub_url(pact, url))
177
+ URI(PactBroker::Webhooks::Render.call(url, pact){ | value | CGI::escape(value)} )
154
178
  end
155
179
 
156
180
  def url_with_credentials pact
@@ -158,18 +182,6 @@ module PactBroker
158
182
  u.userinfo = "#{CGI::escape username}:#{display_password}" if username
159
183
  u
160
184
  end
161
-
162
- def gsub_body pact, body
163
- base_url = PactBroker.configuration.base_url
164
- body.gsub('${pactbroker.pactUrl}', PactBroker::Api::PactBrokerUrls.pact_url(base_url, pact))
165
- end
166
-
167
- def gsub_url pact, url
168
- base_url = PactBroker.configuration.base_url
169
- pact_url = PactBroker::Api::PactBrokerUrls.pact_url(base_url, pact)
170
- escaped_pact_url = CGI::escape(pact_url)
171
- url.gsub('${pactbroker.pactUrl}', escaped_pact_url)
172
- end
173
185
  end
174
186
  end
175
187
  end