Use exceptions extensively.
This commit is contained in:
		
							parent
							
								
									8824835975
								
							
						
					
					
						commit
						8534dcb246
					
				
					 3 changed files with 93 additions and 172 deletions
				
			
		
							
								
								
									
										29
									
								
								src/main.cr
									
										
									
									
									
								
							
							
						
						
									
										29
									
								
								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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										220
									
								
								src/storage.cr
									
										
									
									
									
								
							
							
						
						
									
										220
									
								
								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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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|
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue