diff --git a/src/main.cr b/src/main.cr index 9cb4566..33b6896 100644 --- a/src/main.cr +++ b/src/main.cr @@ -11,10 +11,24 @@ require "./config" module DNSManager class Exception < ::Exception 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 end + class NoOwnershipException < ::Exception + end class NotLoggedException < ::Exception end + class RRNotFoundException < ::Exception + end class AdminAuthorizationException < ::Exception end end @@ -85,12 +99,27 @@ class DNSManager::Service < IPC rescue e : AuthorizationException Baguette::Log.error "#{reqname} 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 Baguette::Log.error "#{reqname} no admin authorization" Response::Error.new "admin authorization error" rescue e : NotLoggedException Baguette::Log.error "#{reqname} 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 Baguette::Log.error "#{reqname} generic error #{e}" DNSManager::Response::Error.new "generic error" diff --git a/src/storage.cr b/src/storage.cr index 3f8e7c3..775d581 100644 --- a/src/storage.cr +++ b/src/storage.cr @@ -97,20 +97,10 @@ class DNSManager::Storage end 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_data_by_uid.get? user_id.to_s - unless user_data - 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 + user_data = user_must_exist! user_id + zone = zone_must_exist! domain + user_should_own! user_data, zone.domain io = IO::Memory.new zone.to_bind9 io @@ -122,12 +112,7 @@ class DNSManager::Storage user_id : Int32, domain : String) : IPC::JSON - # 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 domain #{domain}" - return Response::UnknownUser.new - end + user_data = user_must_exist! user_id return Response::DomainAlreadyExists.new if zones_by_domain.get? domain @@ -167,26 +152,17 @@ class DNSManager::Storage end def add_or_update_zone(user_id : Int32, zone : Zone) : IPC::JSON + user_data = user_must_exist! user_id + # Test zone validity. if errors = zone.get_errors? Baguette::Log.warning "zone #{zone.domain} update with errors: #{errors}" return Response::InvalidZone.new errors 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? if z = zones_by_domain.get? zone.domain - # User must own the zone. - 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 + user_should_own! user_data, z.domain else # Add the domain to the user's domain. user_data.domains << zone.domain @@ -199,28 +175,12 @@ class DNSManager::Storage zones_by_domain.update_or_create zone.domain, zone 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 def add_rr(user_id : Int32, domain : String, rr : Zone::ResourceRecord) : IPC::JSON - # 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 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 + user_data = user_must_exist! user_id + zone = zone_must_exist! domain + user_should_own! user_data, domain # Test RR validity. rr.get_errors.tap do |errors| @@ -235,9 +195,6 @@ class DNSManager::Storage update_zone zone 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 # Any modification of the zone must be performed here. @@ -248,29 +205,11 @@ class DNSManager::Storage end def update_rr(user_id : Int32, domain : String, rr : Zone::ResourceRecord) : IPC::JSON - # 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 update a RR in domain #{domain}" - return Response::UnknownUser.new - end + user_data = user_must_exist! user_id + zone = zone_must_exist! domain + user_should_own! user_data, domain - # 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 - - # 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 + zone.rr_not_readonly! rr.rrid # Test RR validity. rr.get_errors.tap do |errors| @@ -282,68 +221,33 @@ class DNSManager::Storage zone.update_rr rr - zone.resources = zone.resources.map { |x| x.rrid == rr.rrid ? rr : x } - update_zone zone 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 def delete_rr(user_id : Int32, domain : String, rrid : UInt32) : IPC::JSON - # 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 delete a RR in domain #{domain}" - return Response::UnknownUser.new - end + user_data = user_must_exist! user_id + zone = zone_must_exist! domain + user_should_own! user_data, domain - # 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 - - # 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 + rr = zone.rr_not_readonly! rrid # Remove token from the db. if token_uuid = rr.token tokens_by_uuid.delete token_uuid end - zone.resources.select! { |x| x.rrid != rrid } + zone.delete_rr rrid update_zone zone 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 def delete_domain(user_id : Int32, domain : String) : IPC::JSON - # 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 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 + user_data = user_must_exist! user_id + zone_must_exist! domain + user_should_own! user_data, domain # Remove this domain from the list of user's domains. user_data.domains.delete domain @@ -356,73 +260,45 @@ class DNSManager::Storage tokens_by_domain.delete 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 def get_zone(user_id : Int32, domain : String) : IPC::JSON - # 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 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 + user_data = user_must_exist! user_id + zone = zone_must_exist! domain + user_should_own! user_data, domain 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 def user_domains(user_id : Int32) : IPC::JSON - # 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 list its domains" - return Response::UnknownUser.new - end - + user_data = user_must_exist! user_id Response::DomainList.new user_data.domains - rescue e - Baguette::Log.error "trying to list all user #{user_id} domains: #{e}" - Response::Error.new "error while listing domains" + end + + def user_must_exist!(user_id : Int32) : UserData + user_data = user_data_by_uid.get? user_id.to_s + raise UnknownUserException.new unless user_data + 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 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. - 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 + rr = zone.rr_must_exist! rrid old_token = rr.token diff --git a/src/storage/zone.cr b/src/storage/zone.cr index 3681583..bcc7532 100644 --- a/src/storage/zone.cr +++ b/src/storage/zone.cr @@ -630,10 +630,26 @@ class DNSManager::Storage::Zone 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) @resources.map! { |x| x.rrid != rr.rrid ? x : rr } end + def delete_rr(rrid : UInt32) + @resources.select! { |x| x.rrid != rrid } + end + def to_s(io : IO) io << "DOMAIN #{@domain}.\n" @resources.each do |rr|