2018-02-06 08:25:46 -05:00
|
|
|
require 'resolv'
|
|
|
|
|
|
|
|
class VerifyPagesDomainService < BaseService
|
|
|
|
# The maximum number of seconds to be spent on each DNS lookup
|
|
|
|
RESOLVER_TIMEOUT_SECONDS = 15
|
|
|
|
|
|
|
|
# How long verification lasts for
|
|
|
|
VERIFICATION_PERIOD = 7.days
|
|
|
|
|
|
|
|
attr_reader :domain
|
|
|
|
|
|
|
|
def initialize(domain)
|
|
|
|
@domain = domain
|
|
|
|
end
|
|
|
|
|
|
|
|
def execute
|
|
|
|
return error("No verification code set for #{domain.domain}") unless domain.verification_code.present?
|
|
|
|
|
|
|
|
if !verification_enabled? || dns_record_present?
|
|
|
|
verify_domain!
|
|
|
|
elsif expired?
|
|
|
|
disable_domain!
|
|
|
|
else
|
|
|
|
unverify_domain!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def verify_domain!
|
|
|
|
was_disabled = !domain.enabled?
|
|
|
|
was_unverified = domain.unverified?
|
|
|
|
|
|
|
|
# Prevent any pre-existing grace period from being truncated
|
|
|
|
reverify = [domain.enabled_until, VERIFICATION_PERIOD.from_now].compact.max
|
|
|
|
|
2018-03-26 09:54:59 -04:00
|
|
|
domain.assign_attributes(verified_at: Time.now, enabled_until: reverify)
|
|
|
|
domain.save!(validate: false)
|
2018-02-06 08:25:46 -05:00
|
|
|
|
|
|
|
if was_disabled
|
|
|
|
notify(:enabled)
|
|
|
|
elsif was_unverified
|
|
|
|
notify(:verification_succeeded)
|
|
|
|
end
|
|
|
|
|
|
|
|
success
|
|
|
|
end
|
|
|
|
|
|
|
|
def unverify_domain!
|
|
|
|
if domain.verified?
|
2018-03-26 09:54:59 -04:00
|
|
|
domain.assign_attributes(verified_at: nil)
|
|
|
|
domain.save!(validate: false)
|
|
|
|
|
2018-02-06 08:25:46 -05:00
|
|
|
notify(:verification_failed)
|
|
|
|
end
|
|
|
|
|
|
|
|
error("Couldn't verify #{domain.domain}")
|
|
|
|
end
|
|
|
|
|
|
|
|
def disable_domain!
|
2018-03-26 09:54:59 -04:00
|
|
|
domain.assign_attributes(verified_at: nil, enabled_until: nil)
|
|
|
|
domain.save!(validate: false)
|
2018-02-06 08:25:46 -05:00
|
|
|
|
|
|
|
notify(:disabled)
|
|
|
|
|
|
|
|
error("Couldn't verify #{domain.domain}. It is now disabled.")
|
|
|
|
end
|
|
|
|
|
|
|
|
# A domain is only expired until `disable!` has been called
|
|
|
|
def expired?
|
|
|
|
domain.enabled_until && domain.enabled_until < Time.now
|
|
|
|
end
|
|
|
|
|
|
|
|
def dns_record_present?
|
|
|
|
Resolv::DNS.open do |resolver|
|
|
|
|
resolver.timeouts = RESOLVER_TIMEOUT_SECONDS
|
|
|
|
|
|
|
|
check(domain.domain, resolver) || check(domain.verification_domain, resolver)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check(domain_name, resolver)
|
|
|
|
records = parse(txt_records(domain_name, resolver))
|
|
|
|
|
|
|
|
records.any? do |record|
|
|
|
|
record == domain.keyed_verification_code || record == domain.verification_code
|
|
|
|
end
|
|
|
|
rescue => err
|
|
|
|
log_error("Failed to check TXT records on #{domain_name} for #{domain.domain}: #{err}")
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
def txt_records(domain_name, resolver)
|
|
|
|
resolver.getresources(domain_name, Resolv::DNS::Resource::IN::TXT)
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse(records)
|
|
|
|
records.flat_map(&:strings).flat_map(&:split)
|
|
|
|
end
|
|
|
|
|
|
|
|
def verification_enabled?
|
|
|
|
Gitlab::CurrentSettings.pages_domain_verification_enabled?
|
|
|
|
end
|
|
|
|
|
|
|
|
def notify(type)
|
|
|
|
return unless verification_enabled?
|
|
|
|
|
2018-02-19 12:18:41 -05:00
|
|
|
Gitlab::AppLogger.info("Pages domain '#{domain.domain}' changed state to '#{type}'")
|
2018-02-06 08:25:46 -05:00
|
|
|
notification_service.public_send("pages_domain_#{type}", domain) # rubocop:disable GitlabSecurity/PublicSend
|
|
|
|
end
|
|
|
|
end
|