parent
089a3b98be
commit
4d9ce2f89f
@ -1,36 +1,67 @@ |
||||
class Analysis < ApplicationRecord |
||||
enum service: %i[https smtp xmpp tls ssh].collect { |e| [e, e.to_s] }.to_h |
||||
validates :service, presence: true |
||||
validates :host, presence: true |
||||
|
||||
def self.[](service, host, args) |
||||
key = self.key service, host, args |
||||
self.find_by key |
||||
end |
||||
|
||||
def self.pending!(service, host, args) |
||||
key = self.key service, host, args |
||||
analysis = self.find_or_create_by! key |
||||
analysis.pending! |
||||
end |
||||
|
||||
def pending! |
||||
self.update! pending: true |
||||
self |
||||
end |
||||
|
||||
def self.post!(service, host, args, result) |
||||
analysis = self[service, host, args] |
||||
analysis.post! result |
||||
end |
||||
|
||||
def post!(result) |
||||
self.update! pending: false, result: result |
||||
end |
||||
|
||||
private |
||||
|
||||
def self.key(service, host, args) |
||||
{ service: service, host: host, args: args } |
||||
end |
||||
enum service: %i[https smtp xmpp tls ssh].collect { |e| [e, e.to_s] }.to_h |
||||
validates :service, presence: true |
||||
validates :host, presence: true |
||||
|
||||
def self.[](service, host, args) |
||||
key = self.key service, host, args |
||||
self.find_by key |
||||
end |
||||
|
||||
def self.pending!(service, host, args) |
||||
key = self.key service, host, args |
||||
analysis = self.find_or_create_by! key |
||||
analysis.pending! |
||||
end |
||||
|
||||
def pending! |
||||
self.update! pending: true |
||||
self |
||||
end |
||||
|
||||
def self.post!(service, host, args, result) |
||||
analysis = self[service, host, args] |
||||
analysis.post! result |
||||
end |
||||
|
||||
RESOLVER = ::Resolv::DNS.new |
||||
DNS_TXT_FORMAT = /^cryptcheck=(.*)/ |
||||
DEBUG = 'debug' |
||||
DEFAULT_REFRESH_DELAY = Rails.configuration.refresh_delay |
||||
|
||||
def find_refresh_delay |
||||
host = self.host |
||||
loop do |
||||
break unless host && PublicSuffix.valid?(host) |
||||
RESOLVER.getresources(host, ::Resolv::DNS::Resource::IN::TXT).each do |txt| |
||||
txt.strings.each do |value| |
||||
if match = DNS_TXT_FORMAT.match(value) |
||||
match = match[1] |
||||
return if match == DEBUG |
||||
begin |
||||
delay = ::ActiveSupport::Duration.parse match |
||||
delay = DEFAULT_REFRESH_DELAY if delay > DEFAULT_REFRESH_DELAY |
||||
return delay |
||||
rescue ActiveSupport::Duration::ISO8601Parser::ParsingError |
||||
end |
||||
end |
||||
end |
||||
end |
||||
_, host = host.split '.', 2 |
||||
end |
||||
DEFAULT_REFRESH_DELAY |
||||
rescue |
||||
DEFAULT_REFRESH_DELAY |
||||
end |
||||
|
||||
def post!(result) |
||||
refresh_at = self.find_refresh_delay&.since |
||||
self.update! pending: false, refresh_at: refresh_at, result: result |
||||
end |
||||
|
||||
private |
||||
|
||||
def self.key(service, host, args) |
||||
{ service: service, host: host, args: args } |
||||
end |
||||
end |
||||
|
@ -0,0 +1,16 @@ |
||||
class AddRefreshToAnalysis < ActiveRecord::Migration[7.0] |
||||
def up |
||||
add_column :analyses, :refresh_at, :timestamp |
||||
|
||||
delay = Rails.configuration.refresh_delay |
||||
progress = ProgressBar.create total: Analysis.count, format: '%t (%c/%C) %W %e' |
||||
Analysis.all.each do |analysis| |
||||
analysis.update_column :refresh_at, analysis.updated_at + delay |
||||
progress.increment |
||||
end |
||||
end |
||||
|
||||
def down |
||||
remove_column :analyses, :refresh_at |
||||
end |
||||
end |
@ -0,0 +1,55 @@ |
||||
require 'rails_helper' |
||||
|
||||
describe Analysis do |
||||
describe '::find_refresh_delay' do |
||||
it 'must return host value' do |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=PT1M')]) |
||||
allow(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_raise(Exception, :must_not_be_called) |
||||
host = Analysis.new host: 'www.example.org' |
||||
expect(host.find_refresh_delay).to eq 1.minute |
||||
end |
||||
|
||||
it 'must recurse domain value' do |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([]) |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=PT1M')]) |
||||
allow(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_raise(Exception, :must_not_be_called) |
||||
host = Analysis.new host: 'www.example.org' |
||||
expect(host.find_refresh_delay).to eq 1.minute |
||||
end |
||||
|
||||
it 'must not test TLD' do |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([]) |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([]) |
||||
allow(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_raise(Exception, :must_not_be_called) |
||||
host = Analysis.new host: 'www.example.org' |
||||
expect(host.find_refresh_delay).to eq 1.hour |
||||
end |
||||
|
||||
it 'must support debug mode' do |
||||
expect(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('www.example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_return([::Resolv::DNS::Resource::IN::TXT.new('cryptcheck=debug')]) |
||||
allow(Analysis::RESOLVER).to receive(:getresources) |
||||
.with('example.org', ::Resolv::DNS::Resource::IN::TXT) |
||||
.and_raise(Exception, :must_not_be_called) |
||||
host = Analysis.new host: 'www.example.org' |
||||
expect(host.find_refresh_delay).to be_nil |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue