Use exceptions extensively.

master
Philippe Pittoli 2024-03-14 02:03:26 +01:00
parent 8824835975
commit 8534dcb246
3 changed files with 93 additions and 172 deletions

View File

@ -11,10 +11,24 @@ require "./config"
module DNSManager module DNSManager
class Exception < ::Exception class Exception < ::Exception
end end
class DomainNotFoundException < ::Exception
end
class UnknownUserException < ::Exception
end
class RRReadOnlyException < ::Exception
property domain : String
property rr : DNSManager::Storage::Zone::ResourceRecord
def initialize(@domain, @rr)
end
end
class AuthorizationException < ::Exception class AuthorizationException < ::Exception
end end
class NoOwnershipException < ::Exception
end
class NotLoggedException < ::Exception class NotLoggedException < ::Exception
end end
class RRNotFoundException < ::Exception
end
class AdminAuthorizationException < ::Exception class AdminAuthorizationException < ::Exception
end end
end end
@ -85,12 +99,27 @@ class DNSManager::Service < IPC
rescue e : AuthorizationException rescue e : AuthorizationException
Baguette::Log.error "#{reqname} authorization error" Baguette::Log.error "#{reqname} authorization error"
Response::Error.new "authorization error" Response::Error.new "authorization error"
rescue e : DomainNotFoundException
Baguette::Log.error "#{reqname} domain not found"
Response::DomainNotFound.new
rescue e : UnknownUserException
Baguette::Log.error "#{reqname} unknown user"
Response::UnknownUser.new
rescue e : NoOwnershipException
Baguette::Log.error "#{reqname} no ownership error"
Response::NoOwnership.new
rescue e : AdminAuthorizationException rescue e : AdminAuthorizationException
Baguette::Log.error "#{reqname} no admin authorization" Baguette::Log.error "#{reqname} no admin authorization"
Response::Error.new "admin authorization error" Response::Error.new "admin authorization error"
rescue e : NotLoggedException rescue e : NotLoggedException
Baguette::Log.error "#{reqname} user not logged" Baguette::Log.error "#{reqname} user not logged"
Response::Error.new "user not logged" Response::Error.new "user not logged"
rescue e : RRNotFoundException
Baguette::Log.error "#{reqname} RR not found"
Response::RRNotFound.new
rescue e : RRReadOnlyException
Baguette::Log.error "#{reqname} RR is read only"
Response::RRReadOnly.new e.domain, e.rr
rescue e # Generic case rescue e # Generic case
Baguette::Log.error "#{reqname} generic error #{e}" Baguette::Log.error "#{reqname} generic error #{e}"
DNSManager::Response::Error.new "generic error" DNSManager::Response::Error.new "generic error"

View File

@ -97,20 +97,10 @@ class DNSManager::Storage
end end
def get_generated_zonefile(user_id : Int32, domain : String) : IPC::JSON def get_generated_zonefile(user_id : Int32, domain : String) : IPC::JSON
zone = zones_by_domain.get? domain
return Response::DomainNotFound.new unless zone
# User must own the zone. user_data = user_must_exist! user_id
user_data = user_data_by_uid.get? user_id.to_s zone = zone_must_exist! domain
unless user_data user_should_own! user_data, zone.domain
Baguette::Log.warning "unknown user #{user_id} asked a zonefile for domain #{domain}"
return Response::UnknownUser.new
end
unless user_data.domains.includes?(zone.domain) || user_data.admin
Baguette::Log.warning "user #{user_id} doesn't own domain #{zone.domain}"
return Response::NoOwnership.new
end
io = IO::Memory.new io = IO::Memory.new
zone.to_bind9 io zone.to_bind9 io
@ -122,12 +112,7 @@ class DNSManager::Storage
user_id : Int32, user_id : Int32,
domain : String) : IPC::JSON domain : String) : IPC::JSON
# User must exist. user_data = user_must_exist! user_id
user_data = user_data_by_uid.get? user_id.to_s
unless user_data
Baguette::Log.warning "unknown user #{user_id} tries to add domain #{domain}"
return Response::UnknownUser.new
end
return Response::DomainAlreadyExists.new if zones_by_domain.get? domain return Response::DomainAlreadyExists.new if zones_by_domain.get? domain
@ -167,26 +152,17 @@ class DNSManager::Storage
end end
def add_or_update_zone(user_id : Int32, zone : Zone) : IPC::JSON def add_or_update_zone(user_id : Int32, zone : Zone) : IPC::JSON
user_data = user_must_exist! user_id
# Test zone validity. # Test zone validity.
if errors = zone.get_errors? if errors = zone.get_errors?
Baguette::Log.warning "zone #{zone.domain} update with errors: #{errors}" Baguette::Log.warning "zone #{zone.domain} update with errors: #{errors}"
return Response::InvalidZone.new errors return Response::InvalidZone.new errors
end end
# User must exist.
user_data = user_data_by_uid.get? user_id.to_s
unless user_data
Baguette::Log.warning "unknown user #{user_id} tries to add -or update- zone #{zone.domain}"
return Response::UnknownUser.new
end
# Does the zone already exist? # Does the zone already exist?
if z = zones_by_domain.get? zone.domain if z = zones_by_domain.get? zone.domain
# User must own the zone. user_should_own! user_data, z.domain
unless user_data.domains.includes?(zone.domain) || user_data.admin
Baguette::Log.warning "user #{user_id} doesn't own domain #{zone.domain}"
return Response::NoOwnership.new
end
else else
# Add the domain to the user's domain. # Add the domain to the user's domain.
user_data.domains << zone.domain user_data.domains << zone.domain
@ -199,28 +175,12 @@ class DNSManager::Storage
zones_by_domain.update_or_create zone.domain, zone zones_by_domain.update_or_create zone.domain, zone
Response::Success.new Response::Success.new
rescue e
Baguette::Log.error "trying to add -or update- zone #{zone.domain}: #{e}"
Response::Error.new "error while adding or updating the domain #{zone.domain}"
end end
def add_rr(user_id : Int32, domain : String, rr : Zone::ResourceRecord) : IPC::JSON def add_rr(user_id : Int32, domain : String, rr : Zone::ResourceRecord) : IPC::JSON
# User must exist. user_data = user_must_exist! user_id
user_data = user_data_by_uid.get? user_id.to_s zone = zone_must_exist! domain
unless user_data user_should_own! user_data, domain
Baguette::Log.warning "unknown user #{user_id} tries to add a RR in domain #{domain}"
return Response::UnknownUser.new
end
# Zone must exist.
zone = zones_by_domain.get? domain
return Response::DomainNotFound.new unless zone
# User must own the zone.
unless user_data.domains.includes?(domain) || user_data.admin
Baguette::Log.warning "user #{user_id} doesn't own domain #{domain}"
return Response::NoOwnership.new
end
# Test RR validity. # Test RR validity.
rr.get_errors.tap do |errors| rr.get_errors.tap do |errors|
@ -235,9 +195,6 @@ class DNSManager::Storage
update_zone zone update_zone zone
Response::RRAdded.new zone.domain, rr Response::RRAdded.new zone.domain, rr
rescue e
Baguette::Log.error "trying to add a resource record in domain #{domain}: #{e}"
Response::Error.new "error while adding a resource record in domain #{domain}"
end end
# Any modification of the zone must be performed here. # Any modification of the zone must be performed here.
@ -248,29 +205,11 @@ class DNSManager::Storage
end end
def update_rr(user_id : Int32, domain : String, rr : Zone::ResourceRecord) : IPC::JSON def update_rr(user_id : Int32, domain : String, rr : Zone::ResourceRecord) : IPC::JSON
# User must exist. user_data = user_must_exist! user_id
user_data = user_data_by_uid.get? user_id.to_s zone = zone_must_exist! domain
unless user_data user_should_own! user_data, domain
Baguette::Log.warning "unknown user #{user_id} tries to update a RR in domain #{domain}"
return Response::UnknownUser.new
end
# Zone must exist. zone.rr_not_readonly! rr.rrid
zone = zones_by_domain.get? domain
return Response::DomainNotFound.new unless zone
# User must own the zone.
unless user_data.domains.includes?(domain) || user_data.admin
Baguette::Log.warning "user #{user_id} doesn't own domain #{domain}"
return Response::NoOwnership.new
end
# Verify the RR exists.
stored_rr = zone.get_rr rr.rrid
return Response::RRNotFound.new unless stored_rr
# Verify the RR isn't read only.
return Response::RRReadOnly.new domain, stored_rr if stored_rr.readonly
# Test RR validity. # Test RR validity.
rr.get_errors.tap do |errors| rr.get_errors.tap do |errors|
@ -282,68 +221,33 @@ class DNSManager::Storage
zone.update_rr rr zone.update_rr rr
zone.resources = zone.resources.map { |x| x.rrid == rr.rrid ? rr : x }
update_zone zone update_zone zone
Response::RRUpdated.new domain, rr Response::RRUpdated.new domain, rr
rescue e
Baguette::Log.error "trying to replace a resource record in domain #{domain}: #{e}"
Response::Error.new "error while replacing a resource record in domain #{domain}"
end end
def delete_rr(user_id : Int32, domain : String, rrid : UInt32) : IPC::JSON def delete_rr(user_id : Int32, domain : String, rrid : UInt32) : IPC::JSON
# User must exist. user_data = user_must_exist! user_id
user_data = user_data_by_uid.get? user_id.to_s zone = zone_must_exist! domain
unless user_data user_should_own! user_data, domain
Baguette::Log.warning "unknown user #{user_id} tries to delete a RR in domain #{domain}"
return Response::UnknownUser.new
end
# Zone must exist. rr = zone.rr_not_readonly! rrid
zone = zones_by_domain.get? domain
return Response::DomainNotFound.new unless zone
# User must own the zone.
unless user_data.domains.includes?(domain) || user_data.admin
Baguette::Log.warning "user #{user_id} doesn't own domain #{domain}"
return Response::NoOwnership.new
end
# Verify the RR exists.
rr = zone.get_rr rrid
return Response::RRNotFound.new unless rr
# Verify the RR isn't read only.
return Response::RRReadOnly.new domain, rr if rr.readonly
# Remove token from the db. # Remove token from the db.
if token_uuid = rr.token if token_uuid = rr.token
tokens_by_uuid.delete token_uuid tokens_by_uuid.delete token_uuid
end end
zone.resources.select! { |x| x.rrid != rrid }
zone.delete_rr rrid
update_zone zone update_zone zone
Response::RRDeleted.new rrid Response::RRDeleted.new rrid
rescue e
Baguette::Log.error "trying to remove a resource record in domain #{domain}: #{e}"
Response::Error.new "error while removing a resource record in domain #{domain}"
end end
def delete_domain(user_id : Int32, domain : String) : IPC::JSON def delete_domain(user_id : Int32, domain : String) : IPC::JSON
# User must exist. user_data = user_must_exist! user_id
user_data = user_data_by_uid.get? user_id.to_s zone_must_exist! domain
unless user_data user_should_own! user_data, domain
Baguette::Log.warning "unknown user #{user_id} tries to delete domain #{domain}"
return Response::UnknownUser.new
end
# User must own the domain.
unless user_data.domains.includes?(domain) || user_data.admin
Baguette::Log.warning "user #{user_id} tries to delete domain #{domain} but doesn't own it"
return Response::NoOwnership.new
end
# Remove this domain from the list of user's domains. # Remove this domain from the list of user's domains.
user_data.domains.delete domain user_data.domains.delete domain
@ -356,73 +260,45 @@ class DNSManager::Storage
tokens_by_domain.delete domain tokens_by_domain.delete domain
Response::DomainDeleted.new domain Response::DomainDeleted.new domain
rescue e
Baguette::Log.error "trying to delete a domain #{domain}: #{e}"
Response::Error.new "error while deleting the domain #{domain}"
end end
def get_zone(user_id : Int32, domain : String) : IPC::JSON def get_zone(user_id : Int32, domain : String) : IPC::JSON
# User must exist. user_data = user_must_exist! user_id
user_data = user_data_by_uid.get? user_id.to_s zone = zone_must_exist! domain
unless user_data user_should_own! user_data, domain
Baguette::Log.warning "unknown user #{user_id} tries to get zone #{domain}"
return Response::UnknownUser.new
end
# User must own the domain.
unless user_data.domains.includes?(domain) || user_data.admin
Baguette::Log.warning "user #{user_id} tries to get zone #{domain} but doesn't own it"
return Response::NoOwnership.new
end
zone = zones_by_domain.get? domain
unless zone
return Response::UnknownZone.new
end
Response::Zone.new zone Response::Zone.new zone
rescue e
Baguette::Log.error "trying to get a zone #{domain}: #{e}"
Response::Error.new "error while accessing a zone #{domain}"
end end
def user_domains(user_id : Int32) : IPC::JSON def user_domains(user_id : Int32) : IPC::JSON
# User must exist. user_data = user_must_exist! user_id
user_data = user_data_by_uid.get? user_id.to_s Response::DomainList.new user_data.domains
unless user_data
Baguette::Log.warning "unknown user #{user_id} tries to list its domains"
return Response::UnknownUser.new
end end
Response::DomainList.new user_data.domains def user_must_exist!(user_id : Int32) : UserData
rescue e user_data = user_data_by_uid.get? user_id.to_s
Baguette::Log.error "trying to list all user #{user_id} domains: #{e}" raise UnknownUserException.new unless user_data
Response::Error.new "error while listing domains" user_data
end
def zone_must_exist!(domain : String) : Zone
zone = zones_by_domain.get? domain
raise DomainNotFoundException.new unless zone
zone
end
def user_should_own!(user_data : UserData, domain : String)
unless user_data.domains.includes?(domain) || user_data.admin
raise NoOwnershipException.new
end
end end
def new_token(user_id : Int32, domain : String, rrid : UInt32) : IPC::JSON def new_token(user_id : Int32, domain : String, rrid : UInt32) : IPC::JSON
# 0. verifications: must exist: user, zone, RR. Zone must be owned by user. user_data = user_must_exist! user_id
zone = zone_must_exist! domain
user_should_own! user_data, domain
# User must exist. rr = zone.rr_must_exist! rrid
user_data = user_data_by_uid.get? user_id.to_s
unless user_data
Baguette::Log.warning "unknown user #{user_id} tries get a new token for a RR in domain #{domain}"
return Response::UnknownUser.new
end
# Zone must exist.
zone = zones_by_domain.get? domain
return Response::DomainNotFound.new unless zone
# User should own it.
unless user_data.domains.includes?(domain) || user_data.admin
Baguette::Log.warning "user #{user_id} doesn't own domain #{domain}"
return Response::NoOwnership.new
end
# RR must exist.
rr = zone.get_rr rrid
return Response::RRNotFound.new unless rr
old_token = rr.token old_token = rr.token

View File

@ -630,10 +630,26 @@ class DNSManager::Storage::Zone
end end
end end
def rr_must_exist!(rrid : UInt32) : ResourceRecord
rr = get_rr rrid
raise RRNotFoundException.new unless rr
rr
end
def rr_not_readonly!(rrid : UInt32) : ResourceRecord
rr = rr_must_exist! rrid
raise RRReadOnlyException.new @domain, rr if rr.readonly
rr
end
def update_rr(rr : ResourceRecord) def update_rr(rr : ResourceRecord)
@resources.map! { |x| x.rrid != rr.rrid ? x : rr } @resources.map! { |x| x.rrid != rr.rrid ? x : rr }
end end
def delete_rr(rrid : UInt32)
@resources.select! { |x| x.rrid != rrid }
end
def to_s(io : IO) def to_s(io : IO)
io << "DOMAIN #{@domain}.\n" io << "DOMAIN #{@domain}.\n"
@resources.each do |rr| @resources.each do |rr|