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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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