dnsmanager/src/storage.cr

302 lines
8.6 KiB
Crystal
Raw Normal View History

2020-12-03 17:13:40 +01:00
require "json"
require "uuid"
require "uuid/json"
require "baguette-crystal-base"
2020-12-03 17:13:40 +01:00
require "dodb"
class DNSManager::Storage
getter user_data : DODB::CachedDataBase(UserData)
getter user_data_by_uid : DODB::Index(UserData)
getter zones : DODB::CachedDataBase(Zone)
getter zones_by_domain : DODB::Index(Zone)
def initialize(@root : String, reindex : Bool = false)
@user_data = DODB::CachedDataBase(UserData).new "#{@root}/user-data"
@user_data_by_uid = @user_data.new_index "uid", &.uid.to_s
@zones = DODB::CachedDataBase(Zone).new "#{@root}/zones"
@zones_by_domain = @zones.new_index "domain", &.domain
Baguette::Log.info "storage initialized"
if reindex
Baguette::Log.debug "Reindexing user data..."
@user_data.reindex_everything!
Baguette::Log.debug "Reindexing zones..."
@zones.reindex_everything!
Baguette::Log.debug "Reindexed!"
end
end
def get_user_data(uid : Int32)
user_data_by_uid.get uid.to_s
rescue e : DODB::MissingEntry
entry = UserData.new uid
entry
end
def get_user_data(user : ::AuthD::User::Public)
get_user_data user.uid
end
def update_user_data(user_data : UserData)
user_data_by_uid.update_or_create user_data.uid.to_s, user_data
end
2023-05-07 18:32:32 +02:00
def ensure_user_data(user_id : Int32)
user_data = user_data_by_uid.get? user_id.to_s
unless user_data
Baguette::Log.info "New user #{user_id}"
2023-05-08 17:34:50 +02:00
@user_data << UserData.new user_id
2023-05-07 18:32:32 +02:00
end
Response::Success.new
end
2023-06-17 12:57:51 +02:00
def new_domain(user_id : Int32, domain : String) : IPC::JSON
2023-06-16 18:41:20 +02:00
# User must exist.
user_data = user_data_by_uid.get? user_id.to_s
unless user_data
2023-06-17 12:57:51 +02:00
Baguette::Log.warning "unknown user #{user_id} tries to add domain #{domain}"
2023-06-16 18:41:20 +02:00
return Response::UnknownUser.new
end
if zones_by_domain.get? domain
Response::DomainAlreadyExists.new
else
# Add the domain to the user's domain.
2023-06-17 12:57:51 +02:00
user_data.domains << domain
2023-06-16 18:41:20 +02:00
# Actually write data on-disk.
update_user_data user_data
# TODO: Fill a template zone.
## # 2 NS
## zone << rr
## # Update the zone.
## zones_by_domain.update_or_create zone.domain, zone
Response::Success.new
end
end
2023-05-07 18:32:32 +02:00
def add_or_update_zone(user_id : Int32, zone : Zone) : IPC::JSON
# Test zone validity.
if errors = zone.get_errors?
Baguette::Log.warning "zone #{zone.domain} update with errors: #{errors}"
2023-05-08 17:34:50 +02:00
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.
2023-05-08 21:38:39 +02:00
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
# Add the domain to the user's domain.
user_data.domains << zone.domain
# Actually write data on-disk.
update_user_data user_data
end
# Add -or replace- the zone.
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}"
2023-05-08 19:34:57 +02:00
Response::Error.new "error while adding or updating the domain #{zone.domain}"
2020-12-03 17:13:40 +01:00
end
2023-05-08 17:34:50 +02:00
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
unless zone
return Response::InvalidZone.new ["Domain not found."]
end
# User must own the zone.
2023-05-08 21:38:39 +02:00
unless user_data.domains.includes?(domain) || user_data.admin
2023-05-08 17:34:50 +02:00
Baguette::Log.warning "user #{user_id} doesn't own domain #{domain}"
return Response::NoOwnership.new
end
2023-05-08 19:07:20 +02:00
# Test RR validity.
rr.get_errors.tap do |errors|
unless errors.empty?
2023-05-08 19:34:57 +02:00
Baguette::Log.warning "add RR with errors: #{errors}"
2023-05-08 19:07:20 +02:00
return Response::InvalidZone.new errors
end
end
2023-05-08 17:34:50 +02:00
zone << rr
# Update the zone.
zones_by_domain.update_or_create zone.domain, zone
Response::Success.new
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
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
2023-05-08 19:34:57 +02:00
Baguette::Log.warning "unknown user #{user_id} tries to update a RR in domain #{domain}"
2023-05-08 17:34:50 +02:00
return Response::UnknownUser.new
end
# Zone must exist.
zone = zones_by_domain.get? domain
unless zone
return Response::InvalidZone.new ["Domain not found."]
end
# User must own the zone.
2023-05-08 21:38:39 +02:00
unless user_data.domains.includes?(domain) || user_data.admin
2023-05-08 17:34:50 +02:00
Baguette::Log.warning "user #{user_id} doesn't own domain #{domain}"
return Response::NoOwnership.new
end
2023-05-08 19:07:20 +02:00
# Test RR validity.
rr.get_errors.tap do |errors|
unless errors.empty?
Baguette::Log.warning "update RR with errors: #{errors}"
return Response::InvalidZone.new errors
end
end
zone.resources = zone.resources.map { |x| x.rrid == rr.rrid ? rr : x }
2023-05-08 17:34:50 +02:00
# Update the zone.
zones_by_domain.update_or_create zone.domain, zone
Response::Success.new
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
2023-05-08 19:23:36 +02:00
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
2023-05-08 19:34:57 +02:00
Baguette::Log.warning "unknown user #{user_id} tries to delete a RR in domain #{domain}"
2023-05-08 19:23:36 +02:00
return Response::UnknownUser.new
end
# Zone must exist.
zone = zones_by_domain.get? domain
unless zone
return Response::InvalidZone.new ["Domain not found."]
end
# User must own the zone.
2023-05-08 21:38:39 +02:00
unless user_data.domains.includes?(domain) || user_data.admin
2023-05-08 19:23:36 +02:00
Baguette::Log.warning "user #{user_id} doesn't own domain #{domain}"
return Response::NoOwnership.new
end
zone.resources.select! { |x| x.rrid != rrid }
# Update the zone.
zones_by_domain.update_or_create zone.domain, zone
Response::Success.new
rescue e
2023-05-08 19:34:57 +02:00
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}"
2023-05-08 19:23:36 +02:00
end
2023-05-07 18:32:32 +02:00
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.
2023-05-08 21:38:39 +02:00
unless user_data.domains.includes?(domain) || user_data.admin
2023-05-07 20:23:34 +02:00
Baguette::Log.warning "user #{user_id} tries to delete domain #{domain} but doesn't own it"
return Response::NoOwnership.new
end
2023-05-07 20:23:34 +02:00
# Remove this domain from the list of user's domains.
user_data.domains.delete domain
# Update on-disk user data.
update_user_data user_data
# Remove the related zone.
zones_by_domain.delete domain
2023-05-07 17:28:11 +02:00
Response::Success.new
rescue e
Baguette::Log.error "trying to delete a domain #{domain}: #{e}"
Response::Error.new "error while deleting the domain #{domain}"
end
2023-05-07 21:05:53 +02:00
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.
2023-05-08 21:38:39 +02:00
unless user_data.domains.includes?(domain) || user_data.admin
2023-05-07 21:05:53 +02:00
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
rescue e
Baguette::Log.error "trying to get a zone #{domain}: #{e}"
Response::Error.new "error while accessing a zone #{domain}"
end
2023-05-07 20:50:56 +02:00
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
Response::DomainList.new user_data.domains
rescue e
2023-05-08 19:34:57 +02:00
Baguette::Log.error "trying to list all user #{user_id} domains: #{e}"
2023-05-07 20:50:56 +02:00
Response::Error.new "error while listing domains"
end
2020-12-03 17:13:40 +01:00
end
require "./storage/*"