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