330 lines
13 KiB
Lua
330 lines
13 KiB
Lua
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
-- ic-web – Verwaltung
|
|||
|
|
--
|
|||
|
|
-- Zwei Ebenen, beide ueber die Anmeldung:
|
|||
|
|
--
|
|||
|
|
-- superadmin legt Domaenen an, setzt je Domaene einen Admin ein, sperrt
|
|||
|
|
-- und loescht. Sieht alles.
|
|||
|
|
-- admin verwaltet die Zugaenge seiner Domaene und pflegt ihre Seiten.
|
|||
|
|
--
|
|||
|
|
-- Ein Zugang ist zugleich ein Postfach: wer hier einen Zugang anlegt, legt
|
|||
|
|
-- damit die Mailadresse mit an.
|
|||
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
ICWeb = ICWeb or {}
|
|||
|
|
ICWeb.Manage = {}
|
|||
|
|
|
|||
|
|
local M = ICWeb.Manage
|
|||
|
|
local DB = ICWeb.DB
|
|||
|
|
local FW = ICWeb.FW
|
|||
|
|
local D = ICWeb.Domains
|
|||
|
|
local A = ICWeb.Auth
|
|||
|
|
|
|||
|
|
-- Was der Client ueber einen Zugang erfahren darf.
|
|||
|
|
local function publicAccount(row)
|
|||
|
|
return {
|
|||
|
|
id = row.id,
|
|||
|
|
username = row.username,
|
|||
|
|
domain = row.domain,
|
|||
|
|
role = row.role,
|
|||
|
|
display_name = row.display_name,
|
|||
|
|
active = ICWeb.Bool(row.active),
|
|||
|
|
must_change = ICWeb.Bool(row.must_change),
|
|||
|
|
last_login = row.last_login,
|
|||
|
|
created_at = row.created_at,
|
|||
|
|
}
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Zugaenge einer Domaene ──────────────────────────────────────────────────
|
|||
|
|
function M.ListAccounts(src, domain)
|
|||
|
|
domain = tostring(domain or ''):lower()
|
|||
|
|
if not A.CanManageAccounts(src, domain) then return nil, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
local out = {}
|
|||
|
|
for _, row in ipairs(DB.GetAccountsForDomain(domain)) do
|
|||
|
|
out[#out + 1] = publicAccount(row)
|
|||
|
|
end
|
|||
|
|
return out, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Gibt (zugang, nil) oder (nil, fehlertext) zurueck.
|
|||
|
|
--
|
|||
|
|
-- Das Passwort wird vom Anleger vergeben und danach weitergegeben. Es kommt
|
|||
|
|
-- deshalb einmal im Klartext zurueck an den Bildschirm des Anlegers, aber nie
|
|||
|
|
-- wieder aus der Datenbank heraus.
|
|||
|
|
function M.CreateAccount(src, domain, username, password, role, displayName)
|
|||
|
|
domain = tostring(domain or ''):lower()
|
|||
|
|
if not A.CanManageAccounts(src, domain) then return nil, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
local site = DB.GetSiteByDomain(domain)
|
|||
|
|
if not site then return nil, 'Domäne nicht gefunden.' end
|
|||
|
|
|
|||
|
|
-- Nur der Anbieter darf weitere Anbieterzugaenge oder Domaenenadmins
|
|||
|
|
-- ausserhalb der eigenen Domaene anlegen.
|
|||
|
|
if role == 'superadmin' and not A.IsSuperadmin(src) then
|
|||
|
|
return nil, 'Keine Berechtigung für diese Rolle.'
|
|||
|
|
end
|
|||
|
|
if role ~= 'admin' and role ~= 'editor' and role ~= 'superadmin' then
|
|||
|
|
return nil, 'Unbekannte Rolle.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
if DB.CountAccountsForDomain(domain) >= (Config.MaxAccountsPerDomain or 15) then
|
|||
|
|
return nil, ('Höchstens %d Zugänge je Domäne.'):format(Config.MaxAccountsPerDomain or 15)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
local id, err = A.CreateAccount({
|
|||
|
|
username = username,
|
|||
|
|
domain = domain,
|
|||
|
|
role = role,
|
|||
|
|
display_name = displayName,
|
|||
|
|
password = password,
|
|||
|
|
must_change = true,
|
|||
|
|
created_by = session and session.username or 'system',
|
|||
|
|
})
|
|||
|
|
if not id then return nil, err end
|
|||
|
|
|
|||
|
|
ICWeb.Log.Write(src, 'account.created',
|
|||
|
|
{ domain = domain, username = tostring(username):lower(), role = role })
|
|||
|
|
|
|||
|
|
return publicAccount(DB.GetAccountById(id)), nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function M.SetAccountPassword(src, accountId, password)
|
|||
|
|
local account = DB.GetAccountById(tonumber(accountId) or -1)
|
|||
|
|
if not account then return false, 'Zugang nicht gefunden.' end
|
|||
|
|
|
|||
|
|
-- Den eigenen Zugang darf jeder aendern, fremde nur die Verwaltung.
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
local own = session ~= nil and session.username == account.username
|
|||
|
|
if not own and not A.CanManageAccounts(src, account.domain) then
|
|||
|
|
return false, 'Keine Berechtigung.'
|
|||
|
|
end
|
|||
|
|
-- Der Anbieterzugang laesst sich nur von ihm selbst oder einem anderen
|
|||
|
|
-- Anbieterzugang aendern.
|
|||
|
|
if account.role == 'superadmin' and not own and not A.IsSuperadmin(src) then
|
|||
|
|
return false, 'Keine Berechtigung.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local ok, err = A.SetPassword(account.id, password)
|
|||
|
|
if not ok then return false, err end
|
|||
|
|
|
|||
|
|
if own then A.ClearMustChange(src) end
|
|||
|
|
|
|||
|
|
ICWeb.Log.Write(src, 'account.password.changed', { username = account.username })
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function M.SetAccountActive(src, accountId, active)
|
|||
|
|
local account = DB.GetAccountById(tonumber(accountId) or -1)
|
|||
|
|
if not account then return false, 'Zugang nicht gefunden.' end
|
|||
|
|
if not A.CanManageAccounts(src, account.domain) then return false, 'Keine Berechtigung.' end
|
|||
|
|
if account.role == 'superadmin' and not A.IsSuperadmin(src) then
|
|||
|
|
return false, 'Keine Berechtigung.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
if session and session.username == account.username then
|
|||
|
|
return false, 'Den eigenen Zugang kannst du nicht sperren.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
DB.SetAccountActive(account.id, active == true)
|
|||
|
|
ICWeb.Log.Write(src, active and 'account.enabled' or 'account.disabled',
|
|||
|
|
{ username = account.username })
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function M.SetAccountRole(src, accountId, role)
|
|||
|
|
local account = DB.GetAccountById(tonumber(accountId) or -1)
|
|||
|
|
if not account then return false, 'Zugang nicht gefunden.' end
|
|||
|
|
if not A.CanManageAccounts(src, account.domain) then return false, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
if role ~= 'admin' and role ~= 'editor' then return false, 'Unbekannte Rolle.' end
|
|||
|
|
if account.role == 'superadmin' then return false, 'Der Anbieterzugang bleibt, wie er ist.' end
|
|||
|
|
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
if session and session.username == account.username then
|
|||
|
|
return false, 'Die eigene Rolle kannst du nicht ändern.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
DB.SetAccountRole(account.id, role)
|
|||
|
|
ICWeb.Log.Write(src, 'account.role.changed', { username = account.username, role = role })
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Loeschen entfernt den Zugang, nicht das Postfach: dort liegen Nachrichten.
|
|||
|
|
function M.DeleteAccount(src, accountId)
|
|||
|
|
local account = DB.GetAccountById(tonumber(accountId) or -1)
|
|||
|
|
if not account then return false, 'Zugang nicht gefunden.' end
|
|||
|
|
if not A.CanManageAccounts(src, account.domain) then return false, 'Keine Berechtigung.' end
|
|||
|
|
if account.role == 'superadmin' then return false, 'Der Anbieterzugang lässt sich nicht löschen.' end
|
|||
|
|
|
|||
|
|
local session = A.Get(src)
|
|||
|
|
if session and session.username == account.username then
|
|||
|
|
return false, 'Den eigenen Zugang kannst du nicht löschen.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
DB.DeleteAccount(account.id)
|
|||
|
|
ICWeb.Log.Write(src, 'account.deleted', { username = account.username }, 'warn')
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- ── Anbieterebene ───────────────────────────────────────────────────────────
|
|||
|
|
function M.AdminListSites(src)
|
|||
|
|
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
local out = {}
|
|||
|
|
for _, s in ipairs(DB.GetAllSites()) do
|
|||
|
|
out[#out + 1] = {
|
|||
|
|
domain = s.domain,
|
|||
|
|
title = s.title,
|
|||
|
|
owner_type = s.owner_type,
|
|||
|
|
owner_id = s.owner_id,
|
|||
|
|
published = ICWeb.Bool(s.published),
|
|||
|
|
blocked = ICWeb.Bool(s.blocked),
|
|||
|
|
pages = DB.CountPages(s.id),
|
|||
|
|
accounts = DB.CountAccountsForDomain(s.domain),
|
|||
|
|
created_at = s.created_at,
|
|||
|
|
}
|
|||
|
|
end
|
|||
|
|
return out, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Domaene anlegen und gleich den ersten Zugang dafuer einrichten. Ohne diesen
|
|||
|
|
-- Zugang koennte niemand die neue Seite pflegen.
|
|||
|
|
--
|
|||
|
|
-- Gibt (site, nil, zugangsdaten) oder (nil, fehlertext) zurueck.
|
|||
|
|
function M.AdminRegister(src, domain, title, ownerId, adminUser, adminPassword)
|
|||
|
|
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
-- Der Anbieter darf auch Behoerdennamen vergeben – dafuer ist er da.
|
|||
|
|
local name, err = D.CheckName(domain, true)
|
|||
|
|
if not name then return nil, err end
|
|||
|
|
|
|||
|
|
ownerId = tostring(ownerId or ''):lower()
|
|||
|
|
if ownerId == '' then return nil, 'Kein Besitzer (Job) angegeben.' end
|
|||
|
|
|
|||
|
|
if DB.GetSiteByDomain(name) then return nil, 'Diese Domäne ist bereits vergeben.' end
|
|||
|
|
|
|||
|
|
-- Zugangsdaten zuerst pruefen: eine Domaene ohne Zugang waere unbrauchbar,
|
|||
|
|
-- und ein Rueckbau nach dem Anlegen ist fehleranfaelliger als eine
|
|||
|
|
-- Pruefung davor.
|
|||
|
|
local user = tostring(adminUser or 'admin'):lower():gsub('[^a-z0-9%.%-_]', '')
|
|||
|
|
if user == '' then user = 'admin' end
|
|||
|
|
local username = user .. '@' .. name
|
|||
|
|
|
|||
|
|
local password = adminPassword
|
|||
|
|
local generated = false
|
|||
|
|
if type(password) ~= 'string' or password == '' then
|
|||
|
|
password = D.RandomPassword()
|
|||
|
|
generated = true
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local pwErr = A.CheckPassword(password)
|
|||
|
|
if pwErr then return nil, pwErr end
|
|||
|
|
|
|||
|
|
local realmOk, realmErr = D.EnsureRealm(name, title, ownerId)
|
|||
|
|
if not realmOk then return nil, realmErr end
|
|||
|
|
|
|||
|
|
local site = D.CreateSiteWithHome(name, title, 'job', ownerId,
|
|||
|
|
(A.Get(src) or {}).username or 'system')
|
|||
|
|
if not site then return nil, 'Speichern fehlgeschlagen.' end
|
|||
|
|
|
|||
|
|
local accountId, accErr = A.CreateAccount({
|
|||
|
|
username = username,
|
|||
|
|
domain = name,
|
|||
|
|
role = 'admin',
|
|||
|
|
display_name = title or name,
|
|||
|
|
password = password,
|
|||
|
|
must_change = true,
|
|||
|
|
created_by = (A.Get(src) or {}).username or 'system',
|
|||
|
|
})
|
|||
|
|
if not accountId then
|
|||
|
|
-- Ohne Zugang ist die Domaene nicht pflegbar; lieber nichts stehen lassen.
|
|||
|
|
DB.DeleteSite(site.id)
|
|||
|
|
return nil, accErr
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
ICWeb.Log.Write(src, 'domain.registered', { domain = name, owner = ownerId }, 'warn')
|
|||
|
|
|
|||
|
|
return site, nil, { username = username, password = password, generated = generated }
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function M.AdminSetBlocked(src, domain, blocked)
|
|||
|
|
if not A.IsSuperadmin(src) then return false, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return false, 'Domäne nicht gefunden.' end
|
|||
|
|
|
|||
|
|
DB.SetBlocked(site.id, blocked == true)
|
|||
|
|
ICWeb.Log.Write(src, blocked and 'site.blocked' or 'site.unblocked',
|
|||
|
|
{ domain = site.domain }, 'warn')
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Besitzer wechseln, etwa wenn eine Firma verkauft wird.
|
|||
|
|
function M.AdminSetOwner(src, domain, ownerId)
|
|||
|
|
if not A.IsSuperadmin(src) then return false, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return false, 'Domäne nicht gefunden.' end
|
|||
|
|
|
|||
|
|
ownerId = tostring(ownerId or ''):lower()
|
|||
|
|
if ownerId == '' then return false, 'Kein Besitzer angegeben.' end
|
|||
|
|
|
|||
|
|
DB.SetOwner(site.id, 'job', ownerId)
|
|||
|
|
ICWeb.Log.Write(src, 'site.owner.changed',
|
|||
|
|
{ domain = site.domain, from = site.owner_id, to = ownerId }, 'warn')
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Loeschen entfernt Seiten und Meldungen ueber ON DELETE CASCADE sowie die
|
|||
|
|
-- Zugaenge dieser Domaene. Die Mail-Domaene und die Postfaecher bleiben
|
|||
|
|
-- bestehen: dort liegen Nachrichten, die eine geloeschte Webseite nichts angehen.
|
|||
|
|
function M.AdminDeleteSite(src, domain)
|
|||
|
|
if not A.IsSuperadmin(src) then return false, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
local site = DB.GetSiteByDomain(tostring(domain or ''):lower())
|
|||
|
|
if not site then return false, 'Domäne nicht gefunden.' end
|
|||
|
|
|
|||
|
|
for _, acc in ipairs(DB.GetAccountsForDomain(site.domain)) do
|
|||
|
|
DB.DeleteAccount(acc.id)
|
|||
|
|
end
|
|||
|
|
DB.DeleteSite(site.id)
|
|||
|
|
|
|||
|
|
ICWeb.Log.Write(src, 'site.deleted', { domain = site.domain }, 'warn')
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function M.AdminListReports(src)
|
|||
|
|
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
|||
|
|
return DB.GetOpenReports(), nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function M.AdminSetReportStatus(src, id, status)
|
|||
|
|
if not A.IsSuperadmin(src) then return false, 'Keine Berechtigung.' end
|
|||
|
|
if status ~= 'reviewed' and status ~= 'dismissed' and status ~= 'open' then
|
|||
|
|
return false, 'Unbekannter Status.'
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
DB.SetReportStatus(tonumber(id) or -1, status)
|
|||
|
|
return true, nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Jobliste fuer das Besitzerfeld in der Verwaltung.
|
|||
|
|
function M.AdminListJobs(src)
|
|||
|
|
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
|||
|
|
return FW.GetJobs(), nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Alle Zugaenge quer ueber alle Domaenen.
|
|||
|
|
function M.AdminListAccounts(src)
|
|||
|
|
if not A.IsSuperadmin(src) then return nil, 'Keine Berechtigung.' end
|
|||
|
|
|
|||
|
|
local out = {}
|
|||
|
|
for _, row in ipairs(DB.GetAllAccounts()) do
|
|||
|
|
out[#out + 1] = publicAccount(row)
|
|||
|
|
end
|
|||
|
|
return out, nil
|
|||
|
|
end
|