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 +4 -4
- data/pg_engine/app/controllers/pg_engine/base_controller.rb +3 -0
- data/pg_engine/app/controllers/public/webhooks_controller.rb +70 -3
- data/pg_engine/lib/pg_engine/mailgun/log_sync.rb +0 -2
- data/pg_engine/lib/pg_engine/utils/check_invalid_records.rb +39 -0
- data/pg_engine/lib/pg_engine/utils/pg_logger.rb +12 -1
- data/pg_engine/spec/controllers/pg_engine/base_controller_spec.rb +11 -0
- data/pg_engine/spec/controllers/public/webhooks_controller_spec.rb +122 -22
- data/pg_engine/spec/lib/pg_engine/mailgun/log_sync_spec.rb +1 -1
- data/pg_layout/app/views/layouts/pg_layout/base.html.slim +11 -0
- data/pg_rails/lib/pg_rails/rspec_logger_matchers.rb +62 -0
- data/pg_rails/lib/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c61900d2d0b3a827718b84a3cab67f4851b2eb3087e7fbc1d313f0acf163bf3
|
4
|
+
data.tar.gz: bfca92cbb58ba292877ccb61b15b08049a0d800fdc13408f82647e9ea7c66ee9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2282deaa98881e20bd0b754c6496fc9da679214760ea45fd5f8c2f0b64a0903e1557cffb2f8d4c13b156d46e62b237113bf8f24bdba676436a404eb1e57e37c5
|
7
|
+
data.tar.gz: d9ece2e6b39c53169548c253afdb18fb9707d76bc762b0b86b7cc2656e563a4efea6d303f51814aac22926effd37ef1ec1d6c6a6d00bded153476864586f6b14
|
@@ -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
|
-
|
5
|
-
|
6
|
-
|
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
|
@@ -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.
|
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
|
-
|
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
|
-
|
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
|
@@ -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:
|
data/pg_rails/lib/version.rb
CHANGED
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.
|
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-
|
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
|