Ownership: full overhaul in progress.
parent
9b6457f575
commit
61bace491c
|
@ -53,7 +53,7 @@ class DNSManager::Service < IPC
|
|||
|
||||
def initialize(@configuration)
|
||||
super()
|
||||
@storage = DNSManager::Storage.new @configuration.storage_directory, @configuration.recreate_indexes
|
||||
@storage = DNSManager::Storage.new self, @configuration.storage_directory, @configuration.recreate_indexes
|
||||
|
||||
@logged_users = Hash(Int32, AuthD::User::Public).new
|
||||
|
||||
|
|
|
@ -77,9 +77,10 @@ class DNSManager::Request
|
|||
user = dnsmanagerd.get_logged_user event
|
||||
return Response::ErrorUserNotLogged.new unless user
|
||||
|
||||
udata = dnsmanagerd.storage.get_user_data user
|
||||
return Response::InsufficientRights.new unless udata.admin
|
||||
dnsmanagerd.storage.generate_zonefile @domain
|
||||
# TODO: verify user must be admin.
|
||||
#udata = dnsmanagerd.storage.get_user_data user
|
||||
#return Response::InsufficientRights.new unless udata.admin
|
||||
#dnsmanagerd.storage.generate_zonefile @domain
|
||||
end
|
||||
end
|
||||
DNSManager.requests << GenerateZoneFile
|
||||
|
|
142
src/storage.cr
142
src/storage.cr
|
@ -5,9 +5,14 @@ require "baguette-crystal-base"
|
|||
|
||||
require "dodb"
|
||||
|
||||
alias UserDataID = UInt32
|
||||
|
||||
class DNSManager::Storage
|
||||
getter user_data : DODB::CachedDataBase(UserData)
|
||||
getter user_data_by_uid : DODB::Index(UserData)
|
||||
getter domains : DODB::CachedDataBase(Domain)
|
||||
getter domains_by_name : DODB::Index(Domain)
|
||||
getter domains_by_share_key : DODB::Index(Domain)
|
||||
getter domains_by_transfer_key : DODB::Index(Domain)
|
||||
getter domains_by_owners : DODB::Tags(Domain)
|
||||
|
||||
getter zones : DODB::CachedDataBase(Zone)
|
||||
getter zones_by_domain : DODB::Index(Zone)
|
||||
|
@ -19,9 +24,26 @@ class DNSManager::Storage
|
|||
getter root : String
|
||||
getter zonefiledir : String
|
||||
|
||||
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
|
||||
getter dnsmanagerd : DNSManager::Service
|
||||
|
||||
def initialize(@dnsmanagerd, @root : String, reindex : Bool = false)
|
||||
@domains = DODB::CachedDataBase(Domain).new "#{@root}/domains"
|
||||
@domains_by_name = @domains.new_index "name", &.name
|
||||
@domains_by_share_key = @domains.new_nilable_index "share-key", do |d|
|
||||
if k = d.share_key
|
||||
k
|
||||
else
|
||||
DODB.no_index
|
||||
end
|
||||
end
|
||||
@domains_by_transfer_key = @domains.new_nilable_index "transfer-key", do |d|
|
||||
if k = d.transfer_key
|
||||
k
|
||||
else
|
||||
DODB.no_index
|
||||
end
|
||||
end
|
||||
@domains_by_owners = @domains.new_tags "owners", &.owners.map &.to_s
|
||||
@zones = DODB::CachedDataBase(Zone).new "#{@root}/zones"
|
||||
@zones_by_domain = @zones.new_index "domain", &.domain
|
||||
@tokens = DODB::CachedDataBase(Token).new "#{@root}/tokens"
|
||||
|
@ -34,8 +56,8 @@ class DNSManager::Storage
|
|||
Baguette::Log.info "storage initialized"
|
||||
|
||||
if reindex
|
||||
Baguette::Log.debug "Reindexing user data..."
|
||||
@user_data.reindex_everything!
|
||||
Baguette::Log.debug "Reindexing domains..."
|
||||
@domains.reindex_everything!
|
||||
Baguette::Log.debug "Reindexing zones..."
|
||||
@zones.reindex_everything!
|
||||
Baguette::Log.debug "Reindexing tokens..."
|
||||
|
@ -44,32 +66,6 @@ class DNSManager::Storage
|
|||
end
|
||||
end
|
||||
|
||||
def get_user_data(uid : UserDataID)
|
||||
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
|
||||
|
||||
def ensure_user_data(user_id : UserDataID)
|
||||
user_data = user_data_by_uid.get? user_id.to_s
|
||||
unless user_data
|
||||
Baguette::Log.info "New user #{user_id}"
|
||||
user_data = UserData.new user_id
|
||||
@user_data << user_data
|
||||
end
|
||||
|
||||
user_data
|
||||
end
|
||||
|
||||
# Only an admin can access this function.
|
||||
def generate_all_zonefiles() : IPC::JSON
|
||||
Baguette::Log.info "writing all zone files in #{@zonefiledir}/"
|
||||
|
@ -96,9 +92,9 @@ class DNSManager::Storage
|
|||
|
||||
def get_generated_zonefile(user_id : UserDataID, domain : String) : IPC::JSON
|
||||
|
||||
user_data = user_must_exist! user_id
|
||||
user_must_exist! user_id
|
||||
zone = zone_must_exist! domain
|
||||
user_should_own! user_data, zone.domain
|
||||
user_should_own! user_id, zone.domain
|
||||
|
||||
io = IO::Memory.new
|
||||
zone.to_bind9 io
|
||||
|
@ -110,7 +106,7 @@ class DNSManager::Storage
|
|||
user_id : UserDataID,
|
||||
domain : String) : IPC::JSON
|
||||
|
||||
user_data = user_must_exist! user_id
|
||||
user_must_exist! user_id
|
||||
|
||||
# Prevent future very confusing errors.
|
||||
domain = domain.downcase
|
||||
|
@ -142,9 +138,8 @@ class DNSManager::Storage
|
|||
# Actually write data on-disk.
|
||||
#
|
||||
|
||||
# Add the domain to the user's domain.
|
||||
user_data.domains << domain
|
||||
update_user_data user_data
|
||||
# Add the new domain.
|
||||
@domains << DomainInfo.new domain, owners: [user_id]
|
||||
|
||||
# Add the new zone in the database.
|
||||
zones_by_domain.update_or_create domain, default_zone
|
||||
|
@ -153,7 +148,7 @@ class DNSManager::Storage
|
|||
end
|
||||
|
||||
def add_or_update_zone(user_id : UserDataID, zone : Zone) : IPC::JSON
|
||||
user_data = user_must_exist! user_id
|
||||
user_must_exist! user_id
|
||||
|
||||
# Test zone validity.
|
||||
if errors = zone.get_errors?
|
||||
|
@ -163,13 +158,10 @@ class DNSManager::Storage
|
|||
|
||||
# Does the zone already exist?
|
||||
if z = zones_by_domain.get? zone.domain
|
||||
user_should_own! user_data, z.domain
|
||||
user_should_own! user_id, z.domain
|
||||
else
|
||||
# Add the domain to the user's domain.
|
||||
user_data.domains << zone.domain
|
||||
|
||||
# Actually write data on-disk.
|
||||
update_user_data user_data
|
||||
domains << DomainInfo.new zone.domain
|
||||
end
|
||||
|
||||
# Add -or replace- the zone.
|
||||
|
@ -179,9 +171,9 @@ class DNSManager::Storage
|
|||
end
|
||||
|
||||
def add_rr(user_id : UserDataID, domain : String, rr : Zone::ResourceRecord) : IPC::JSON
|
||||
user_data = user_must_exist! user_id
|
||||
user_must_exist! user_id
|
||||
zone = zone_must_exist! domain
|
||||
user_should_own! user_data, domain
|
||||
user_should_own! user_id, domain
|
||||
|
||||
# Test RR validity.
|
||||
rr.get_errors.tap do |errors|
|
||||
|
@ -206,9 +198,9 @@ class DNSManager::Storage
|
|||
end
|
||||
|
||||
def update_rr(user_id : UserDataID, domain : String, rr : Zone::ResourceRecord) : IPC::JSON
|
||||
user_data = user_must_exist! user_id
|
||||
user_must_exist! user_id
|
||||
zone = zone_must_exist! domain
|
||||
user_should_own! user_data, domain
|
||||
user_should_own! user_id, domain
|
||||
|
||||
zone.rr_not_readonly! rr.rrid
|
||||
|
||||
|
@ -228,9 +220,9 @@ class DNSManager::Storage
|
|||
end
|
||||
|
||||
def delete_rr(user_id : UserDataID, domain : String, rrid : UInt32) : IPC::JSON
|
||||
user_data = user_must_exist! user_id
|
||||
user_must_exist! user_id
|
||||
zone = zone_must_exist! domain
|
||||
user_should_own! user_data, domain
|
||||
user_should_own! user_id, domain
|
||||
|
||||
rr = zone.rr_not_readonly! rrid
|
||||
|
||||
|
@ -246,15 +238,12 @@ class DNSManager::Storage
|
|||
end
|
||||
|
||||
def delete_domain(user_id : UserDataID, domain : String) : IPC::JSON
|
||||
user_data = user_must_exist! user_id
|
||||
user_must_exist! user_id
|
||||
zone_must_exist! domain
|
||||
user_should_own! user_data, domain
|
||||
user_should_own! user_id, domain
|
||||
|
||||
# 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
|
||||
domains_by_name.delete domain
|
||||
|
||||
# Remove the related zone and their registered tokens.
|
||||
zones_by_domain.delete domain
|
||||
|
@ -264,14 +253,14 @@ class DNSManager::Storage
|
|||
end
|
||||
|
||||
# Get all removed users from `authd`, list all their domains and remove their data from `dnsmanagerd`.
|
||||
def get_orphan_domains(dnsmanagerd : DNSManager::Service, user_id : UserDataID) : IPC::JSON
|
||||
user_must_be_admin! dnsmanagerd, user_id
|
||||
def get_orphan_domains(user_id : UserDataID) : IPC::JSON
|
||||
user_must_be_admin! user_id
|
||||
|
||||
Baguette::Log.debug "list all orphan domains (long computation)"
|
||||
orphans = [] of String
|
||||
user_data.each do |user|
|
||||
begin
|
||||
dnsmanagerd.authd.get_user? user.uid
|
||||
@dnsmanagerd.authd.get_user? user.uid
|
||||
rescue e
|
||||
Baguette::Log.warning "no authd info on user #{user.uid}: #{e} (removing this user)"
|
||||
Baguette::Log.debug "user #{user.uid} had #{user.domains.size} domains"
|
||||
|
@ -289,12 +278,14 @@ class DNSManager::Storage
|
|||
def get_zone(user_id : UserDataID, domain : String) : IPC::JSON
|
||||
user_data = user_must_exist! user_id
|
||||
zone = zone_must_exist! domain
|
||||
user_should_own! user_data, domain
|
||||
user_should_own! user_id, domain
|
||||
|
||||
Response::Zone.new zone
|
||||
end
|
||||
|
||||
def wipe_user_data(user_data : UserData)
|
||||
domains_by_owners.get(user_id.to_s).each do |d|
|
||||
end
|
||||
# Remove the user's domains.
|
||||
user_data.domains.each do |domain|
|
||||
tokens_by_domain.delete domain
|
||||
|
@ -302,15 +293,12 @@ class DNSManager::Storage
|
|||
rescue e
|
||||
Baguette::Log.error "while removing a domain: #{e}"
|
||||
end
|
||||
|
||||
# Remove the user.
|
||||
user_data_by_uid.delete user_data.uid.to_s
|
||||
end
|
||||
|
||||
def delete_user_data(dnsmanagerd : DNSManager::Service, user_id : UserDataID, user_to_delete : UserDataID?) : IPC::JSON
|
||||
def delete_user_data(user_id : UserDataID, user_to_delete : UserDataID?) : IPC::JSON
|
||||
user_data = user_must_exist! user_id
|
||||
user_data_to_delete = if u = user_to_delete
|
||||
user_must_be_admin! dnsmanagerd, user_id
|
||||
user_must_be_admin! user_id
|
||||
Baguette::Log.info "Admin #{user_id} removes data of user #{u}."
|
||||
user_must_exist! u
|
||||
else
|
||||
|
@ -328,16 +316,13 @@ class DNSManager::Storage
|
|||
Response::DomainList.new user_data.domains
|
||||
end
|
||||
|
||||
def user_must_exist!(user_id : UserDataID) : UserData
|
||||
user_data = user_data_by_uid.get? user_id.to_s
|
||||
raise UnknownUserException.new unless user_data
|
||||
user_data
|
||||
# TODO: is the user known from authd?
|
||||
def user_must_exist!(user_id : UserDataID)
|
||||
@dnsmanagerd.authd.get_user? user.uid
|
||||
end
|
||||
|
||||
def user_must_be_admin!(dnsmanagerd : DNSManager::Service, user_id : UserDataID) : UserData
|
||||
user_data = user_must_exist! user_id
|
||||
dnsmanagerd.assert_permissions! user_id, "*", AuthD::User::PermissionLevel::Admin
|
||||
user_data
|
||||
def user_must_be_admin!(user_id : UserDataID) : Nil
|
||||
@dnsmanagerd.assert_permissions! user_id, "*", AuthD::User::PermissionLevel::Admin
|
||||
end
|
||||
|
||||
def zone_must_exist!(domain : String) : Zone
|
||||
|
@ -346,8 +331,11 @@ class DNSManager::Storage
|
|||
zone
|
||||
end
|
||||
|
||||
def user_should_own!(user_data : UserData, domain : String)
|
||||
unless user_data.domains.includes?(domain) || user_data.admin
|
||||
# Owning a domain means to be in the owners' list of the domain.
|
||||
# TODO: accept admin users to override this test.
|
||||
def user_should_own!(user_id : UserDataID, domain : String)
|
||||
d = domains_by_name.get domain
|
||||
unless d.includes? user_id
|
||||
raise NoOwnershipException.new
|
||||
end
|
||||
end
|
||||
|
@ -355,7 +343,7 @@ class DNSManager::Storage
|
|||
def new_token(user_id : UserDataID, domain : String, rrid : UInt32) : IPC::JSON
|
||||
user_data = user_must_exist! user_id
|
||||
zone = zone_must_exist! domain
|
||||
user_should_own! user_data, domain
|
||||
user_should_own! user_id, domain
|
||||
|
||||
rr = zone.rr_must_exist! rrid
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
require "json"
|
||||
require "uuid"
|
||||
require "uuid/json"
|
||||
|
||||
class DNSManager::Storage::Domain
|
||||
include JSON::Serializable
|
||||
|
||||
property name : String
|
||||
property share_key : String? = nil
|
||||
property transfer_key : String? = nil
|
||||
|
||||
# Users may have many domains, and a domain may have many owners.
|
||||
property owners = [] of UserDataID
|
||||
|
||||
def initialize(@name, share_key = nil, transfer_key = nil, owners = [] of UserDataID)
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
require "json"
|
||||
require "uuid"
|
||||
require "uuid/json"
|
||||
|
||||
alias UserDataID = UInt32
|
||||
|
||||
class DNSManager::Storage::UserData
|
||||
include JSON::Serializable
|
||||
|
||||
property uid : UserDataID
|
||||
|
||||
# Users may have many domains, and a domain can have many owners.
|
||||
property domains = [] of String
|
||||
|
||||
property admin : Bool = false
|
||||
|
||||
def initialize(@uid)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue