pg_rails 7.0.8.pre.alpha.83 → 7.0.8.pre.alpha.84

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f13f3f371227ad2016ab3996077254555a2d258d2132b6c61e1dbb616cdabf48
4
- data.tar.gz: 11206668b12f2acfc9f7878ab7049b27476f7984013a5a107a269ab8273aa8bd
3
+ metadata.gz: 9c61900d2d0b3a827718b84a3cab67f4851b2eb3087e7fbc1d313f0acf163bf3
4
+ data.tar.gz: bfca92cbb58ba292877ccb61b15b08049a0d800fdc13408f82647e9ea7c66ee9
5
5
  SHA512:
6
- metadata.gz: b274dd6247c0d49ced832c8ad44c7bfb4c2916876b87921399c88468ee4297c0008ca2fd9a1a806db8477a7d9fada576b8b02faa19c48fe69298bb8a33bd32fe
7
- data.tar.gz: 6a4dbbff767631ed6f1ee0cf9b1eea4a8e8ef7f61c015b900b3f6d4656481f9b74aecd77704bd65931884665230194bbec53d3710c64c27c8b32cde6d77309fb
6
+ metadata.gz: 2282deaa98881e20bd0b754c6496fc9da679214760ea45fd5f8c2f0b64a0903e1557cffb2f8d4c13b156d46e62b237113bf8f24bdba676436a404eb1e57e37c5
7
+ data.tar.gz: d9ece2e6b39c53169548c253afdb18fb9707d76bc762b0b86b7cc2656e563a4efea6d303f51814aac22926effd37ef1ec1d6c6a6d00bded153476864586f6b14
@@ -40,6 +40,9 @@ module PgEngine
40
40
  render turbo_stream: (turbo_stream.remove_all('.modal') + render_turbo_stream_flash_messages),
41
41
  status: :internal_server_error
42
42
  end
43
+ format.any do
44
+ head :internal_server_error
45
+ end
43
46
  end
44
47
  end
45
48
 
@@ -1,10 +1,77 @@
1
1
  module Public
2
2
  class WebhooksController < PublicController
3
+ skip_before_action :verify_authenticity_token
4
+
5
+ before_action :verify_signature, only: :mailgun
6
+
7
+ rescue_from StandardError do
8
+ pg_err 'webhook internal server error', request.body.read
9
+ head :internal_server_error
10
+ end
11
+
12
+ rescue_from ActionDispatch::Http::Parameters::ParseError do
13
+ pg_warn 'webhook parser error', request.body.read
14
+ head :bad_request
15
+ end
16
+
3
17
  def mailgun
4
- # FIXME: verify signature
5
- # params['signature']['timestamp']
6
- PgEngine::Mailgun::LogSync.digest(params['event-data'])
18
+ if PgEngine::Mailgun::LogSync.digest(params['event-data'])
19
+ head :ok
20
+ else
21
+ # Si no se guardó el log es porque ya existía un log con ese ID
22
+ # Mando :not_acceptable (406) para que Mailgun no vuelva a intentar
23
+ # https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages
24
+ pg_warn 'ya existía un log con ese id, raaaaro', params
25
+ head :not_acceptable
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def used_tokens
32
+ Kredis.unique_list 'mailgun_webhook_used_tokens'
33
+ end
34
+
35
+ def not_used_token(token)
36
+ if used_tokens.elements.include?(token)
37
+ pg_warn 'Mailgun Webhook: refusing used token'
38
+ head :ok
39
+
40
+ false
41
+ else
42
+ used_tokens << token
43
+ true
44
+ end
45
+ end
46
+
47
+ def timestamp_not_too_far(timestamp)
48
+ if (Time.zone.at(timestamp.to_i) - Time.zone.now).abs > 1.hour
49
+ pg_warn 'Mailgun Webhook: refusing due to timestamp too far'
50
+ head :ok
51
+
52
+ false
53
+ else
54
+ true
55
+ end
56
+ end
57
+
58
+ def verify_signature
59
+ timestamp = params['signature']['timestamp']
60
+ token = params['signature']['token']
61
+ signature = params['signature']['signature']
62
+ hexdigest = encode(timestamp + token)
63
+
64
+ return unless not_used_token(token)
65
+ return unless timestamp_not_too_far(timestamp)
66
+ return unless hexdigest != signature
67
+
68
+ pg_warn 'Mailgun Webhook: refusing invalid signature'
7
69
  head :ok
8
70
  end
71
+
72
+ def encode(data)
73
+ webhook_secret_key = Rails.application.credentials.dig(:mailgun, :webhook_secret_key)
74
+ OpenSSL::HMAC.hexdigest('sha256', webhook_secret_key, data)
75
+ end
9
76
  end
10
77
  end
@@ -47,8 +47,6 @@ module PgEngine
47
47
  timestamp: item['timestamp'],
48
48
  message_id:
49
49
  )
50
- rescue StandardError => e
51
- pg_err e, item
52
50
  end
53
51
 
54
52
  def self.write_log(items)
@@ -0,0 +1,39 @@
1
+ # :nocov:
2
+ module PgEngine
3
+ module Utils
4
+ class CheckInvalidRecords
5
+ def run
6
+ invalids = []
7
+ classes.each do |klass|
8
+ klass.find_each do |record|
9
+ invalids << record unless record.valid?
10
+ end
11
+ end
12
+ invalids.map do |r|
13
+ [
14
+ (r.account.to_s if r.respond_to?(:account)),
15
+ r.class.to_s,
16
+ r.id,
17
+ r.errors.full_messages
18
+ ]
19
+ end
20
+ end
21
+
22
+ def classes
23
+ ActiveRecord::Base.descendants - ignored_classes
24
+ end
25
+
26
+ def ignored_classes
27
+ [
28
+ ActiveStorage::Record,
29
+ PgEngine::BaseRecord,
30
+ ActiveAdmin::Comment,
31
+ Audited::Audit,
32
+ ActiveStorage::Blob,
33
+ ApplicationRecord
34
+ ]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ # :nocov:
@@ -37,6 +37,10 @@ end
37
37
 
38
38
  module PgEngine
39
39
  class PgLogger
40
+ def self.test_logged_messages
41
+ @test_logged_messages ||= []
42
+ end
43
+
40
44
  class << self
41
45
  def log(type, *args)
42
46
  notify_all(build_msg(*args), type)
@@ -48,6 +52,7 @@ module PgEngine
48
52
  send_to_logger(mensaje, type)
49
53
  send_to_rollbar(mensaje, type)
50
54
  send_to_stdout(mensaje, type) if ENV.fetch('LOG_TO_STDOUT', nil)
55
+ save_internal(mensaje, type) if Rails.env.test?
51
56
  nil
52
57
  end
53
58
 
@@ -68,6 +73,10 @@ module PgEngine
68
73
  Rails.logger.send(type, rainbow_wrap(mensaje, type))
69
74
  end
70
75
 
76
+ def save_internal(mensaje, type)
77
+ PgEngine::PgLogger.test_logged_messages << [type, mensaje]
78
+ end
79
+
71
80
  # Format
72
81
 
73
82
  # TODO: loguear time
@@ -117,7 +126,9 @@ module PgEngine
117
126
 
118
127
  def cleaner
119
128
  bc = ActiveSupport::BacktraceCleaner.new
120
- bc.add_filter { |line| line.gsub(%r{.*pg_rails/}, '') }
129
+ bc.remove_silencers!
130
+ pattern = %r{\A[^/]+ \([\w.]+\) }
131
+ bc.add_silencer { |line| pattern.match?(line) && !line.match?(/pg_contable|pg_rails/) }
121
132
  bc.add_silencer { |line| /pg_logger/.match?(line) }
122
133
  bc
123
134
  end
@@ -60,6 +60,17 @@ describe DummyBaseController do
60
60
  expect(response.body).to include '<turbo-stream action="remove" targets=".modal">'
61
61
  end
62
62
  end
63
+
64
+ context 'cuando acepta json' do
65
+ before do
66
+ request.headers['Accept'] = 'application/json'
67
+ end
68
+
69
+ fit do
70
+ subject
71
+ expect(response).to have_http_status(:internal_server_error)
72
+ end
73
+ end
63
74
  end
64
75
 
65
76
  describe 'not_authorized' do
@@ -1,33 +1,47 @@
1
1
  require 'rails_helper'
2
2
 
3
- describe Public::WebhooksController do
3
+ RSpec::Matchers.define_negated_matcher :not_change, :change
4
+
5
+ def build_body(log_id, signature, timestamp)
6
+ <<~JSON
7
+ {
8
+ "signature": {
9
+ "timestamp": "#{timestamp}",
10
+ "token": "6fcc81a280a2458de8c83bf22dde8c71485be1d51333d3427e",
11
+ "signature": "#{signature}"
12
+ },
13
+ "event-data": {
14
+ "timestamp": 1715014542.2477064,
15
+ "ip": "66.102.8.333",
16
+ "event": "delivered",
17
+ "id": "#{log_id}",
18
+ "severity":"temporary",
19
+ "log-level": "info",
20
+ "message": {
21
+ "headers": { "message-id": "msgid@fakeapp2024.mail" }
22
+ },
23
+ "recipient": "natprobel@fakemail.com"
24
+ }
25
+ }
26
+ JSON
27
+ end
28
+
29
+ fdescribe Public::WebhooksController do
30
+ include ActiveSupport::Testing::TimeHelpers
31
+
32
+ before { travel_to Time.zone.at(1_716_564_587) }
33
+
4
34
  describe '#mailgun' do
5
35
  subject do
6
36
  post :mailgun, body:, as: :json
7
37
  end
8
38
 
39
+ let(:signature) { 'c524037907046276117758afae8a340e77a43a6e48eb35a9521426e7a3ff675b' }
40
+ let(:log_id) { 'log_2' }
41
+ let(:timestamp) { '1716564587' }
42
+
9
43
  let(:body) do
10
- <<~JSON
11
- {
12
- "signature": {
13
- "timestamp": "1529006854",
14
- "token": "a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0",
15
- "signature": "d2271d12299f6592d9d44cd9d250f0704e4674c30d79d07c47a66f95ce71cf55"
16
- },
17
- "event-data": {
18
- "timestamp": 1715014542.2477064,
19
- "ip": "66.102.8.333",
20
- "event": "delivered",
21
- "id": "log_2",
22
- "severity":"temporary",
23
- "log-level": "info",
24
- "message": {
25
- "headers": { "message-id": "msgid@fakeapp2024.mail" }
26
- },
27
- "recipient": "natprobel@fakemail.com"
28
- }
29
- }
30
- JSON
44
+ build_body(log_id, signature, timestamp)
31
45
  end
32
46
 
33
47
  it do
@@ -38,5 +52,91 @@ describe Public::WebhooksController do
38
52
  it do
39
53
  expect { subject }.to change(EmailLog, :count).by(1)
40
54
  end
55
+
56
+ context 'cuando tira internal server error' do
57
+ let(:body) { '{ "este json": "no me sirve" }' }
58
+
59
+ it do
60
+ subject
61
+ expect(response).to have_http_status(:internal_server_error)
62
+ end
63
+
64
+ fit do
65
+ expect { subject }.to have_errored('internal server error')
66
+ .and(have_errored('no me sirve'))
67
+ end
68
+ end
69
+
70
+ context 'cuando no es un json' do
71
+ let(:body) { 'mal json' }
72
+
73
+ it do
74
+ subject
75
+ expect(response).to have_http_status(:bad_request)
76
+ end
77
+
78
+ fit do
79
+ expect { subject }.to have_warned('parser error').and(have_warned('mal json'))
80
+ end
81
+ end
82
+
83
+ context 'cuando ya existe un log con ese ID' do
84
+ before do
85
+ create(:email_log, log_id:)
86
+ end
87
+
88
+ it do
89
+ expect { subject }.to have_warned('ya existía un log con ese id')
90
+ .and(not_change(EmailLog, :count))
91
+ end
92
+ end
93
+
94
+ shared_context 'todo bien pero no guarda el log' do
95
+ it 'responds ok' do
96
+ subject
97
+ expect(response).to have_http_status(:ok)
98
+ end
99
+
100
+ it 'no guarda el log ;)' do
101
+ expect { subject }.not_to change(EmailLog, :count)
102
+ end
103
+ end
104
+
105
+ context 'si la signature no es válida' do
106
+ let(:signature) { 'no válida' }
107
+
108
+ it_behaves_like 'todo bien pero no guarda el log'
109
+
110
+ fit do
111
+ expect { subject }.to have_warned('refusing invalid signature')
112
+ end
113
+ end
114
+
115
+ context 'cuando ya se usó el token' do
116
+ subject do
117
+ post :mailgun, body: build_body('otro id', signature, timestamp), as: :json
118
+ end
119
+
120
+ before do
121
+ post :mailgun, body: build_body(log_id, signature, timestamp), as: :json
122
+ end
123
+
124
+ it_behaves_like 'todo bien pero no guarda el log'
125
+
126
+ fit do
127
+ expect { subject }.to have_warned('refusing used token')
128
+ end
129
+ end
130
+
131
+ context 'cuando la timestamp está muy lejos' do
132
+ let(:timestamp) { '1111111' }
133
+ let(:signature) { '869c06cea4be27321a6a7c6a8e3d0668bb9c2b5b3de5532f644dba181a157d9a' }
134
+
135
+ it_behaves_like 'todo bien pero no guarda el log'
136
+
137
+ fit do
138
+ expect { subject }.to have_warned('refusing due to timestamp')
139
+ end
140
+ end
41
141
  end
42
142
  end
@@ -92,7 +92,7 @@ describe PgEngine::Mailgun::LogSync, vcr: { cassette_name: 'mailgun/log_sync_dow
92
92
  end
93
93
 
94
94
  it do
95
- expect { subject }.not_to change(EmailLog, :count)
95
+ expect { subject }.to raise_error(NoMethodError)
96
96
  end
97
97
  end
98
98
  end
@@ -33,6 +33,17 @@ html
33
33
  - if @rollbar_token.present?
34
34
  meta name="rollbar-token" content="#{@rollbar_token}"
35
35
  meta name="rollbar-env" content="#{Rails.env}"
36
+
37
+ link rel="preconnect" href="https://fonts.googleapis.com"
38
+ link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin"
39
+ link href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap" rel="stylesheet"
40
+ css:
41
+ body {
42
+ font-family: "Ubuntu", sans-serif;
43
+ font-weight: 400;
44
+ font-style: normal;
45
+ }
46
+
36
47
  body
37
48
  = render partial: 'pg_layout/sidebar_mobile'
38
49
 
@@ -0,0 +1,62 @@
1
+ # :nocov:
2
+ module PgEngine
3
+ module Matchers
4
+ module PgLogger
5
+ class Base < RSpec::Rails::Matchers::BaseMatcher
6
+ def initialize(text, level)
7
+ super()
8
+ @text = text
9
+ @level = level
10
+ end
11
+ end
12
+
13
+ class PgHaveLogged < Base
14
+ def matches?(proc)
15
+ msg = 'have_logged only support block expectations'
16
+ raise ArgumentError, msg unless proc.is_a?(Proc)
17
+
18
+ original_messages = Set.new(PgEngine::PgLogger.test_logged_messages)
19
+ proc.call
20
+ logged_messages = Set.new(PgEngine::PgLogger.test_logged_messages)
21
+
22
+ @new_messages = logged_messages - original_messages
23
+ @new_messages.any? do |level, message|
24
+ if @text.present? && @level.present?
25
+ level == @level && message.include?(@text)
26
+ elsif @text.present?
27
+ message.include? @text
28
+ elsif @level.present?
29
+ level == @level
30
+ else
31
+ true
32
+ end
33
+ end
34
+ end
35
+
36
+ def failure_message
37
+ msg = "expected to #{@level || log}"
38
+ msg << "with text: #{@text}" if @text.present?
39
+ return unless @new_messages.any?
40
+
41
+ msg << "\nLogged messages:"
42
+ @new_messages.each do |level, message|
43
+ msg << "\n #{level}: #{message[0..200]}"
44
+ end
45
+ end
46
+
47
+ def supports_block_expectations?
48
+ true
49
+ end
50
+ end
51
+ end
52
+
53
+ def have_errored(text = nil) # rubocop:disable Naming/PredicateName
54
+ PgLogger::PgHaveLogged.new(text, :error)
55
+ end
56
+
57
+ def have_warned(text = nil) # rubocop:disable Naming/PredicateName
58
+ PgLogger::PgHaveLogged.new(text, :warn)
59
+ end
60
+ end
61
+ end
62
+ # :nocov:
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgRails
4
- VERSION = '7.0.8-alpha.83'
4
+ VERSION = '7.0.8-alpha.84'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.8.pre.alpha.83
4
+ version: 7.0.8.pre.alpha.84
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martín Rosso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-23 00:00:00.000000000 Z
11
+ date: 2024-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -1066,6 +1066,7 @@ files:
1066
1066
  - pg_engine/lib/pg_engine/error.rb
1067
1067
  - pg_engine/lib/pg_engine/mailgun/log_sync.rb
1068
1068
  - pg_engine/lib/pg_engine/route_helpers.rb
1069
+ - pg_engine/lib/pg_engine/utils/check_invalid_records.rb
1069
1070
  - pg_engine/lib/pg_engine/utils/pdf_preview_generator.rb
1070
1071
  - pg_engine/lib/pg_engine/utils/pg_logger.rb
1071
1072
  - pg_engine/lib/tasks/auto_anotar_modelos.rake
@@ -1172,6 +1173,7 @@ files:
1172
1173
  - pg_rails/lib/pg_rails/current_attributes_support.rb
1173
1174
  - pg_rails/lib/pg_rails/dotenv_support.rb
1174
1175
  - pg_rails/lib/pg_rails/redis_support.rb
1176
+ - pg_rails/lib/pg_rails/rspec_logger_matchers.rb
1175
1177
  - pg_rails/lib/pg_rails/vcr_support.rb
1176
1178
  - pg_rails/lib/version.rb
1177
1179
  - pg_rails/scss/bootstrap_overrides.scss