18f8d870e8
Docker Release / Check web and api (push) Waiting to run
Docker Release / Resolve release tag (push) Blocked by required conditions
Docker Release / Build and publish all-in-one (push) Blocked by required conditions
Docker Release / Build and publish api (push) Blocked by required conditions
Docker Release / Build and publish web (push) Blocked by required conditions
Docker Release / Build and publish dovecot (push) Blocked by required conditions
Docker Release / Build and publish postfix (push) Blocked by required conditions
Docker Release / Build and publish rspamd (push) Blocked by required conditions
Docker Release / Create GitHub release (push) Blocked by required conditions
1081 lines
38 KiB
Go
1081 lines
38 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
PermissionMailAccess = "mail.access"
|
|
|
|
PermissionMailRead = "mail.messages.read"
|
|
PermissionMailSend = "mail.messages.send"
|
|
PermissionMailDrafts = "mail.messages.drafts"
|
|
PermissionMailSchedule = "mail.messages.schedule"
|
|
PermissionMailOrganize = "mail.messages.organize"
|
|
PermissionMailLabels = "mail.labels.manage"
|
|
PermissionMailAttachments = "mail.attachments.download"
|
|
|
|
PermissionMailContacts = "mail.contacts.manage"
|
|
PermissionMailSignatures = "mail.signatures.manage"
|
|
PermissionMailRules = "mail.rules.manage"
|
|
PermissionMailBlocked = "mail.blocked_senders.manage"
|
|
PermissionMailStats = "mail.stats.view"
|
|
PermissionMailboxApply = "mail.mailboxes.apply"
|
|
|
|
PermissionAdminOverview = "admin.overview.view"
|
|
|
|
PermissionUsersView = "admin.users.view"
|
|
PermissionUsersCreate = "admin.users.create"
|
|
PermissionUsersUpdate = "admin.users.update"
|
|
PermissionUsersDelete = "admin.users.delete"
|
|
PermissionUsersResetPassword = "admin.users.reset_password"
|
|
|
|
PermissionGroupsView = "admin.permission_groups.view"
|
|
PermissionGroupsCreate = "admin.permission_groups.create"
|
|
PermissionGroupsUpdate = "admin.permission_groups.update"
|
|
PermissionGroupsDelete = "admin.permission_groups.delete"
|
|
|
|
PermissionDomainsView = "admin.domains.view"
|
|
PermissionDomainsCreate = "admin.domains.create"
|
|
PermissionDomainsUpdate = "admin.domains.update"
|
|
PermissionDomainsDelete = "admin.domains.delete"
|
|
|
|
PermissionDNSView = "admin.dns.view"
|
|
PermissionDNSCheck = "admin.dns.check"
|
|
|
|
PermissionMailboxesView = "admin.mailboxes.view"
|
|
PermissionMailboxesCreate = "admin.mailboxes.create"
|
|
PermissionMailboxesUpdate = "admin.mailboxes.update"
|
|
PermissionMailboxesDelete = "admin.mailboxes.delete"
|
|
|
|
PermissionAliasesView = "admin.aliases.view"
|
|
PermissionAliasesCreate = "admin.aliases.create"
|
|
PermissionAliasesUpdate = "admin.aliases.update"
|
|
PermissionAliasesDelete = "admin.aliases.delete"
|
|
|
|
PermissionMessagesView = "admin.messages.view"
|
|
PermissionMessagesRead = "admin.messages.read"
|
|
PermissionMessagesAttachment = "admin.messages.attachments"
|
|
|
|
PermissionSettingsView = "admin.settings.view"
|
|
PermissionSettingsUpdate = "admin.settings.update"
|
|
PermissionSettingsTestSMTP = "admin.settings.test_smtp"
|
|
|
|
PermissionTemplatesView = "admin.templates.view"
|
|
PermissionTemplatesUpdate = "admin.templates.update"
|
|
PermissionTemplatesReset = "admin.templates.reset"
|
|
|
|
PermissionUsersManage = PermissionUsersUpdate
|
|
PermissionGroupsManage = PermissionGroupsUpdate
|
|
PermissionDomainsManage = PermissionDomainsUpdate
|
|
PermissionDNSManage = PermissionDNSCheck
|
|
PermissionMailboxesManage = PermissionMailboxesUpdate
|
|
PermissionAliasesManage = PermissionAliasesUpdate
|
|
PermissionSystemSettings = PermissionSettingsUpdate
|
|
)
|
|
|
|
const regularUserPermissionMigrationKey = "permission_groups.regular_mail_permissions_v1"
|
|
|
|
const (
|
|
PermissionGroupSuperAdmin = "pg_super_admin"
|
|
PermissionGroupRegular = "pg_regular_user"
|
|
PermissionGroupUserAdmin = "pg_user_admin"
|
|
PermissionGroupPermissionAdmin = "pg_permission_group_admin"
|
|
PermissionGroupDomainAdmin = "pg_domain_admin"
|
|
PermissionGroupDNSAdmin = "pg_dns_admin"
|
|
PermissionGroupMailboxAdmin = "pg_mailbox_admin"
|
|
PermissionGroupAliasAdmin = "pg_alias_admin"
|
|
PermissionGroupMessageAudit = "pg_message_audit"
|
|
PermissionGroupSystemAdmin = "pg_system_admin"
|
|
)
|
|
|
|
var legacyDefaultPermissionGroupIDs = []string{
|
|
"pg_permission_manager",
|
|
"pg_user_manager",
|
|
"pg_system_operator",
|
|
"pg_mail_operator",
|
|
PermissionGroupUserAdmin,
|
|
PermissionGroupPermissionAdmin,
|
|
PermissionGroupDomainAdmin,
|
|
PermissionGroupDNSAdmin,
|
|
PermissionGroupMailboxAdmin,
|
|
PermissionGroupAliasAdmin,
|
|
PermissionGroupMessageAudit,
|
|
PermissionGroupSystemAdmin,
|
|
}
|
|
|
|
type PermissionInfo struct {
|
|
Key string `json:"key"`
|
|
Label string `json:"label"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"`
|
|
}
|
|
|
|
type PermissionGroupSummary struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type PermissionGroup struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Permissions []string `json:"permissions"`
|
|
Limits PermissionLimits `json:"limits"`
|
|
System bool `json:"system"`
|
|
UserCount int `json:"userCount"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
type PermissionLimits struct {
|
|
MaxAttachmentMB int `json:"maxAttachmentMb"`
|
|
MaxMailboxCount int `json:"maxMailboxCount"`
|
|
SMTPDailyLimit int `json:"smtpDailyLimit"`
|
|
SMTPMinuteLimit int `json:"smtpMinuteLimit"`
|
|
IMAPMinuteLimit int `json:"imapMinuteLimit"`
|
|
POP3MinuteLimit int `json:"pop3MinuteLimit"`
|
|
}
|
|
|
|
func defaultPermissionLimits() PermissionLimits {
|
|
return PermissionLimits{
|
|
MaxAttachmentMB: 25,
|
|
MaxMailboxCount: 9,
|
|
SMTPDailyLimit: 200,
|
|
SMTPMinuteLimit: 20,
|
|
IMAPMinuteLimit: 200,
|
|
POP3MinuteLimit: 150,
|
|
}
|
|
}
|
|
|
|
func normalizePermissionLimits(limits PermissionLimits) (PermissionLimits, error) {
|
|
if limits.MaxAttachmentMB < 0 {
|
|
return PermissionLimits{}, errors.New("maxAttachmentMb cannot be negative")
|
|
}
|
|
if limits.MaxMailboxCount < 0 {
|
|
return PermissionLimits{}, errors.New("maxMailboxCount cannot be negative")
|
|
}
|
|
if limits.SMTPDailyLimit < 0 {
|
|
return PermissionLimits{}, errors.New("smtpDailyLimit cannot be negative")
|
|
}
|
|
if limits.SMTPMinuteLimit < 0 {
|
|
return PermissionLimits{}, errors.New("smtpMinuteLimit cannot be negative")
|
|
}
|
|
if limits.IMAPMinuteLimit < 0 {
|
|
return PermissionLimits{}, errors.New("imapMinuteLimit cannot be negative")
|
|
}
|
|
if limits.POP3MinuteLimit < 0 {
|
|
return PermissionLimits{}, errors.New("pop3MinuteLimit cannot be negative")
|
|
}
|
|
return limits, nil
|
|
}
|
|
|
|
func normalizeMailboxLimitOverride(value *int) (*int, error) {
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
if *value < 0 {
|
|
return nil, errors.New("mailboxLimitOverride cannot be negative")
|
|
}
|
|
normalized := *value
|
|
return &normalized, nil
|
|
}
|
|
|
|
func decodeStoredLimits(value string) PermissionLimits {
|
|
limits := defaultPermissionLimits()
|
|
if strings.TrimSpace(value) == "" {
|
|
return limits
|
|
}
|
|
_ = json.Unmarshal([]byte(value), &limits)
|
|
normalized, err := normalizePermissionLimits(limits)
|
|
if err != nil {
|
|
return defaultPermissionLimits()
|
|
}
|
|
return normalized
|
|
}
|
|
|
|
func encodePermissionLimits(limits PermissionLimits) string {
|
|
normalized, err := normalizePermissionLimits(limits)
|
|
if err != nil {
|
|
normalized = defaultPermissionLimits()
|
|
}
|
|
data, _ := json.Marshal(normalized)
|
|
return string(data)
|
|
}
|
|
|
|
func mergePermissionLimits(left, right PermissionLimits) PermissionLimits {
|
|
return PermissionLimits{
|
|
MaxAttachmentMB: mergeLimitValue(left.MaxAttachmentMB, right.MaxAttachmentMB),
|
|
MaxMailboxCount: mergeLimitValue(left.MaxMailboxCount, right.MaxMailboxCount),
|
|
SMTPDailyLimit: mergeLimitValue(left.SMTPDailyLimit, right.SMTPDailyLimit),
|
|
SMTPMinuteLimit: mergeLimitValue(left.SMTPMinuteLimit, right.SMTPMinuteLimit),
|
|
IMAPMinuteLimit: mergeLimitValue(left.IMAPMinuteLimit, right.IMAPMinuteLimit),
|
|
POP3MinuteLimit: mergeLimitValue(left.POP3MinuteLimit, right.POP3MinuteLimit),
|
|
}
|
|
}
|
|
|
|
func mergeLimitValue(left, right int) int {
|
|
if left == 0 || right == 0 {
|
|
return 0
|
|
}
|
|
if right > left {
|
|
return right
|
|
}
|
|
return left
|
|
}
|
|
|
|
func minimalLimits() PermissionLimits {
|
|
// minimalLimits sets every field to 1 so that mergePermissionLimits
|
|
// (which takes the max of each field) produces correct aggregation
|
|
// when no group has a limit set for a given field.
|
|
return PermissionLimits{
|
|
MaxAttachmentMB: 1,
|
|
MaxMailboxCount: 1,
|
|
SMTPDailyLimit: 1,
|
|
SMTPMinuteLimit: 1,
|
|
IMAPMinuteLimit: 1,
|
|
POP3MinuteLimit: 1,
|
|
}
|
|
}
|
|
|
|
func actorCanGrantLimits(actor *User, limits PermissionLimits) bool {
|
|
if actor == nil {
|
|
return false
|
|
}
|
|
if actor.Role == "admin" {
|
|
return true
|
|
}
|
|
return canGrantLimitValue(actor.Limits.MaxAttachmentMB, limits.MaxAttachmentMB) &&
|
|
canGrantLimitValue(actor.Limits.MaxMailboxCount, limits.MaxMailboxCount) &&
|
|
canGrantLimitValue(actor.Limits.SMTPDailyLimit, limits.SMTPDailyLimit) &&
|
|
canGrantLimitValue(actor.Limits.SMTPMinuteLimit, limits.SMTPMinuteLimit) &&
|
|
canGrantLimitValue(actor.Limits.IMAPMinuteLimit, limits.IMAPMinuteLimit) &&
|
|
canGrantLimitValue(actor.Limits.POP3MinuteLimit, limits.POP3MinuteLimit)
|
|
}
|
|
|
|
func canGrantLimitValue(actorLimit, requestedLimit int) bool {
|
|
if actorLimit == 0 {
|
|
return true
|
|
}
|
|
if requestedLimit == 0 {
|
|
return false
|
|
}
|
|
return requestedLimit <= actorLimit
|
|
}
|
|
|
|
func attachmentLimitBytes(limits PermissionLimits) int64 {
|
|
if limits.MaxAttachmentMB <= 0 {
|
|
return 0
|
|
}
|
|
return int64(limits.MaxAttachmentMB) * 1024 * 1024
|
|
}
|
|
|
|
var legacyPermissionExpansions = map[string][]string{
|
|
"mail": regularUserDefaultPermissions(),
|
|
"mail.messages": {PermissionMailAccess, PermissionMailRead, PermissionMailSend, PermissionMailDrafts, PermissionMailSchedule, PermissionMailOrganize, PermissionMailAttachments},
|
|
"mail.labels": {PermissionMailAccess, PermissionMailRead, PermissionMailLabels},
|
|
"mail.contacts": {PermissionMailContacts},
|
|
"mail.signatures": {PermissionMailSignatures},
|
|
"mail.rules": {PermissionMailRules, PermissionMailBlocked},
|
|
"mail.mailboxes": {PermissionMailboxApply},
|
|
"admin.overview": {PermissionAdminOverview},
|
|
"admin.users": {PermissionUsersView, PermissionUsersCreate, PermissionUsersUpdate, PermissionUsersDelete, PermissionUsersResetPassword, PermissionGroupsView},
|
|
"admin.permission_groups": {PermissionGroupsView, PermissionGroupsCreate, PermissionGroupsUpdate, PermissionGroupsDelete},
|
|
"admin.domains": {PermissionDomainsView, PermissionDomainsCreate, PermissionDomainsUpdate, PermissionDomainsDelete},
|
|
"admin.dns": {PermissionDomainsView, PermissionDNSView, PermissionDNSCheck},
|
|
"admin.mailboxes": {PermissionUsersView, PermissionDomainsView, PermissionMailboxesView, PermissionMailboxesCreate, PermissionMailboxesUpdate, PermissionMailboxesDelete},
|
|
"admin.aliases": {PermissionDomainsView, PermissionAliasesView, PermissionAliasesCreate, PermissionAliasesUpdate, PermissionAliasesDelete},
|
|
"admin.messages": {PermissionMailboxesView, PermissionMessagesView, PermissionMessagesRead, PermissionMessagesAttachment},
|
|
"admin.settings": {PermissionDomainsView, PermissionSettingsView, PermissionSettingsUpdate, PermissionSettingsTestSMTP, PermissionTemplatesView, PermissionTemplatesUpdate, PermissionTemplatesReset},
|
|
}
|
|
|
|
var permissionCatalogItems = []PermissionInfo{
|
|
{Key: PermissionMailAccess, Label: "访问邮箱前台", Description: "进入邮箱前台并查看本人邮箱列表。", Category: "邮箱前台"},
|
|
{Key: PermissionMailRead, Label: "查看本人邮件", Description: "查看本人文件夹、邮件列表、星标邮件和邮件正文。", Category: "邮箱前台"},
|
|
{Key: PermissionMailSend, Label: "发送邮件", Description: "使用本人邮箱发送、回复和转发邮件。", Category: "邮箱前台"},
|
|
{Key: PermissionMailDrafts, Label: "管理草稿", Description: "保存、编辑和删除本人草稿。", Category: "邮箱前台"},
|
|
{Key: PermissionMailSchedule, Label: "定时发送", Description: "创建、查看和取消本人定时发送任务。", Category: "邮箱前台"},
|
|
{Key: PermissionMailOrganize, Label: "整理邮件", Description: "标记已读、星标、移动、归档、删除和清理本人邮件。", Category: "邮箱前台"},
|
|
{Key: PermissionMailLabels, Label: "管理邮件标签", Description: "创建标签,并为本人邮件添加或移除标签。", Category: "邮箱前台"},
|
|
{Key: PermissionMailAttachments, Label: "下载附件", Description: "下载本人邮件中的附件。", Category: "邮箱前台"},
|
|
{Key: PermissionMailContacts, Label: "管理联系人", Description: "查看、新增和删除本人的联系人。", Category: "个人中心"},
|
|
{Key: PermissionMailSignatures, Label: "管理签名", Description: "查看、新增、修改和删除本人的邮件签名。", Category: "个人中心"},
|
|
{Key: PermissionMailRules, Label: "管理收件规则", Description: "查看、新增和删除本人的收件规则。", Category: "个人中心"},
|
|
{Key: PermissionMailBlocked, Label: "管理拦截名单", Description: "查看、新增和删除本人的发件人拦截规则。", Category: "个人中心"},
|
|
{Key: PermissionMailStats, Label: "查看邮箱统计", Description: "查看本人邮箱统计和清理概览。", Category: "个人中心"},
|
|
{Key: PermissionMailboxApply, Label: "自助申请邮箱", Description: "在开放申请时为本人申请邮箱。", Category: "个人中心"},
|
|
|
|
{Key: PermissionAdminOverview, Label: "查看概览", Description: "查看后台统计和首次配置检查。", Category: "概览"},
|
|
|
|
{Key: PermissionUsersView, Label: "查看账号", Description: "查看账号列表、状态、邮箱数量上限和绑定邮箱。", Category: "账号管理"},
|
|
{Key: PermissionUsersCreate, Label: "创建账号", Description: "创建普通账号并分配权限配额。", Category: "账号管理"},
|
|
{Key: PermissionUsersUpdate, Label: "编辑账号", Description: "修改账号显示名称、状态、邮箱数量上限和权限配额。", Category: "账号管理"},
|
|
{Key: PermissionUsersDelete, Label: "删除账号", Description: "删除非受保护账号。", Category: "账号管理"},
|
|
{Key: PermissionUsersResetPassword, Label: "重置账号密码", Description: "为账号重置登录密码。", Category: "账号管理"},
|
|
|
|
{Key: PermissionGroupsView, Label: "查看权限配额", Description: "查看权限配额、权限目录和使用人数。", Category: "权限配额"},
|
|
{Key: PermissionGroupsCreate, Label: "创建权限配额", Description: "创建自定义权限配额。", Category: "权限配额"},
|
|
{Key: PermissionGroupsUpdate, Label: "编辑权限配额", Description: "修改自定义权限配额名称、说明、功能权限和额度。", Category: "权限配额"},
|
|
{Key: PermissionGroupsDelete, Label: "删除权限配额", Description: "删除未被账号使用的自定义权限配额。", Category: "权限配额"},
|
|
|
|
{Key: PermissionDomainsView, Label: "查看域名", Description: "查看邮件域名和 DKIM 配置。", Category: "域名"},
|
|
{Key: PermissionDomainsCreate, Label: "添加域名", Description: "添加新的邮件域名。", Category: "域名"},
|
|
{Key: PermissionDomainsUpdate, Label: "启停域名", Description: "启用或停用邮件域名。", Category: "域名"},
|
|
{Key: PermissionDomainsDelete, Label: "删除域名", Description: "删除未被邮箱使用的域名。", Category: "域名"},
|
|
|
|
{Key: PermissionDNSView, Label: "查看 DNS", Description: "查看域名需要配置的 DNS 记录。", Category: "DNS"},
|
|
{Key: PermissionDNSCheck, Label: "执行 DNS 检测", Description: "触发 MX、SPF、DKIM、DMARC 检测。", Category: "DNS"},
|
|
|
|
{Key: PermissionMailboxesView, Label: "查看邮箱", Description: "查看邮箱列表和归属账号。", Category: "邮箱管理"},
|
|
{Key: PermissionMailboxesCreate, Label: "创建邮箱", Description: "创建邮箱并准备归属账号。", Category: "邮箱管理"},
|
|
{Key: PermissionMailboxesUpdate, Label: "编辑邮箱", Description: "修改邮箱归属、显示名、配额和状态。", Category: "邮箱管理"},
|
|
{Key: PermissionMailboxesDelete, Label: "删除邮箱", Description: "删除邮箱及关联邮件文件。", Category: "邮箱管理"},
|
|
|
|
{Key: PermissionAliasesView, Label: "查看邮件转发", Description: "查看邮件转发规则。", Category: "邮件转发"},
|
|
{Key: PermissionAliasesCreate, Label: "创建邮件转发", Description: "创建新的邮件转发规则。", Category: "邮件转发"},
|
|
{Key: PermissionAliasesUpdate, Label: "编辑邮件转发", Description: "修改邮件转发来源、目标和启用状态。", Category: "邮件转发"},
|
|
{Key: PermissionAliasesDelete, Label: "删除邮件转发", Description: "删除邮件转发规则。", Category: "邮件转发"},
|
|
|
|
{Key: PermissionMessagesView, Label: "查看邮件列表", Description: "查看全局邮件列表和搜索结果。", Category: "邮件审计"},
|
|
{Key: PermissionMessagesRead, Label: "查看邮件正文", Description: "查看任意邮箱及未注册收件人的邮件正文。", Category: "邮件审计"},
|
|
{Key: PermissionMessagesAttachment, Label: "下载邮件附件", Description: "下载全局邮件中的附件。", Category: "邮件审计"},
|
|
|
|
{Key: PermissionSettingsView, Label: "查看系统设置", Description: "查看系统、SMTP、安全和邮件设置。", Category: "系统设置"},
|
|
{Key: PermissionSettingsUpdate, Label: "修改系统设置", Description: "保存系统、SMTP、安全和邮件设置。", Category: "系统设置"},
|
|
{Key: PermissionSettingsTestSMTP, Label: "测试 SMTP", Description: "发送 SMTP 测试邮件。", Category: "系统设置"},
|
|
{Key: PermissionTemplatesView, Label: "查看邮件模板", Description: "查看系统邮件模板。", Category: "邮件模板"},
|
|
{Key: PermissionTemplatesUpdate, Label: "编辑邮件模板", Description: "修改系统邮件模板内容。", Category: "邮件模板"},
|
|
{Key: PermissionTemplatesReset, Label: "恢复邮件模板", Description: "将系统邮件模板恢复默认。", Category: "邮件模板"},
|
|
}
|
|
|
|
func permissionCatalog() []PermissionInfo {
|
|
out := make([]PermissionInfo, len(permissionCatalogItems))
|
|
copy(out, permissionCatalogItems)
|
|
return out
|
|
}
|
|
|
|
func allPermissionKeys() []string {
|
|
out := make([]string, 0, len(permissionCatalogItems))
|
|
for _, item := range permissionCatalogItems {
|
|
out = append(out, item.Key)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func adminPermissionKeys() []string {
|
|
out := make([]string, 0, len(permissionCatalogItems))
|
|
for _, item := range permissionCatalogItems {
|
|
if strings.HasPrefix(item.Key, "admin.") {
|
|
out = append(out, item.Key)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func permissionSet() map[string]bool {
|
|
out := map[string]bool{}
|
|
for _, item := range permissionCatalogItems {
|
|
out[item.Key] = true
|
|
}
|
|
return out
|
|
}
|
|
|
|
func addPermissionToSet(item string, seen map[string]bool) bool {
|
|
item = strings.TrimSpace(item)
|
|
if item == "" {
|
|
return true
|
|
}
|
|
if permissionSet()[item] {
|
|
seen[item] = true
|
|
return true
|
|
}
|
|
if expanded, ok := legacyPermissionExpansions[item]; ok {
|
|
for _, permission := range expanded {
|
|
seen[permission] = true
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func normalizePermissionList(items []string) ([]string, error) {
|
|
seen := map[string]bool{}
|
|
for _, item := range items {
|
|
item = strings.TrimSpace(item)
|
|
if !addPermissionToSet(item, seen) {
|
|
return nil, fmt.Errorf("invalid permission: %s", item)
|
|
}
|
|
}
|
|
out := make([]string, 0, len(seen))
|
|
for _, item := range allPermissionKeys() {
|
|
if seen[item] {
|
|
out = append(out, item)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func decodeStoredPermissions(value string) []string {
|
|
var raw []string
|
|
if err := json.Unmarshal([]byte(value), &raw); err != nil {
|
|
return nil
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, item := range raw {
|
|
addPermissionToSet(item, seen)
|
|
}
|
|
out := make([]string, 0, len(seen))
|
|
for _, item := range allPermissionKeys() {
|
|
if seen[item] {
|
|
out = append(out, item)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func encodePermissions(items []string) string {
|
|
normalized, err := normalizePermissionList(items)
|
|
if err != nil {
|
|
normalized = nil
|
|
}
|
|
data, _ := json.Marshal(normalized)
|
|
return string(data)
|
|
}
|
|
|
|
func defaultPermissionGroups() []PermissionGroup {
|
|
return []PermissionGroup{
|
|
{
|
|
ID: PermissionGroupSuperAdmin,
|
|
Name: "管理员",
|
|
Description: "拥有全部后台权限,由账号身份决定,不通过权限配额分配。",
|
|
Permissions: allPermissionKeys(),
|
|
Limits: PermissionLimits{},
|
|
System: true,
|
|
},
|
|
{
|
|
ID: PermissionGroupRegular,
|
|
Name: "普通用户",
|
|
Description: "仅可使用自己的邮箱功能,不包含后台权限。",
|
|
Permissions: regularUserDefaultPermissions(),
|
|
Limits: defaultPermissionLimits(),
|
|
System: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func regularUserDefaultPermissions() []string {
|
|
return []string{
|
|
PermissionMailAccess,
|
|
PermissionMailRead,
|
|
PermissionMailSend,
|
|
PermissionMailDrafts,
|
|
PermissionMailSchedule,
|
|
PermissionMailOrganize,
|
|
PermissionMailLabels,
|
|
PermissionMailAttachments,
|
|
PermissionMailContacts,
|
|
PermissionMailSignatures,
|
|
PermissionMailRules,
|
|
PermissionMailBlocked,
|
|
PermissionMailStats,
|
|
PermissionMailboxApply,
|
|
}
|
|
}
|
|
|
|
func fixedPermissionGroupIDs() map[string]bool {
|
|
out := map[string]bool{}
|
|
for _, group := range defaultPermissionGroups() {
|
|
out[group.ID] = true
|
|
}
|
|
return out
|
|
}
|
|
|
|
func assignablePermissionGroupIDs() map[string]bool {
|
|
out := fixedPermissionGroupIDs()
|
|
delete(out, PermissionGroupSuperAdmin)
|
|
delete(out, PermissionGroupRegular)
|
|
return out
|
|
}
|
|
|
|
func isAssignablePermissionGroupID(groupID string) bool {
|
|
return groupID != "" && groupID != PermissionGroupSuperAdmin && groupID != PermissionGroupRegular
|
|
}
|
|
|
|
func permissionGroupOrder() map[string]int {
|
|
out := map[string]int{}
|
|
for index, group := range defaultPermissionGroups() {
|
|
out[group.ID] = index
|
|
}
|
|
return out
|
|
}
|
|
|
|
func permissionGroupNames() map[string]string {
|
|
out := map[string]string{}
|
|
for _, group := range defaultPermissionGroups() {
|
|
out[group.ID] = group.Name
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (a *App) ensureDefaultPermissionGroups(ctx context.Context) error {
|
|
now := a.now().UTC().Format(time.RFC3339Nano)
|
|
for _, item := range defaultPermissionGroups() {
|
|
if _, err := a.db.ExecContext(ctx, `UPDATE permission_groups SET name=name || ' (' || id || ')' WHERE name=? AND id<>?`, item.Name, item.ID); err != nil {
|
|
return err
|
|
}
|
|
query := `INSERT INTO permission_groups(id,name,description,permissions_json,limits_json,system,created_at,updated_at)
|
|
VALUES(?,?,?,?,?,?,?,?)
|
|
ON CONFLICT(id) DO UPDATE SET name=excluded.name, description=excluded.description, permissions_json=excluded.permissions_json, limits_json=excluded.limits_json, system=excluded.system, updated_at=excluded.updated_at`
|
|
if item.ID == PermissionGroupRegular {
|
|
query = `INSERT INTO permission_groups(id,name,description,permissions_json,limits_json,system,created_at,updated_at)
|
|
VALUES(?,?,?,?,?,?,?,?)
|
|
ON CONFLICT(id) DO UPDATE SET name=excluded.name, description=excluded.description, system=excluded.system, updated_at=excluded.updated_at`
|
|
}
|
|
if _, err := a.db.ExecContext(ctx, query, item.ID, item.Name, item.Description, encodePermissions(item.Permissions), encodePermissionLimits(item.Limits), boolInt(item.System), now, now); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := a.cleanupLegacyDefaultPermissionGroups(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := a.ensureRegularUserMailPermissions(ctx); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) ensureRegularUserMailPermissions(ctx context.Context) error {
|
|
var migrated string
|
|
err := a.db.QueryRowContext(ctx, `SELECT value FROM system_settings WHERE key=?`, regularUserPermissionMigrationKey).Scan(&migrated)
|
|
if err == nil && migrated == "1" {
|
|
return nil
|
|
}
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
return err
|
|
}
|
|
var raw string
|
|
if err := a.db.QueryRowContext(ctx, `SELECT permissions_json FROM permission_groups WHERE id=?`, PermissionGroupRegular).Scan(&raw); err != nil {
|
|
return err
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, permission := range decodeStoredPermissions(raw) {
|
|
seen[permission] = true
|
|
}
|
|
changed := false
|
|
for _, permission := range regularUserDefaultPermissions() {
|
|
if !seen[permission] {
|
|
seen[permission] = true
|
|
changed = true
|
|
}
|
|
}
|
|
now := a.now().UTC().Format(time.RFC3339Nano)
|
|
if changed {
|
|
permissions := make([]string, 0, len(seen))
|
|
for _, permission := range allPermissionKeys() {
|
|
if seen[permission] {
|
|
permissions = append(permissions, permission)
|
|
}
|
|
}
|
|
if _, err := a.db.ExecContext(ctx, `UPDATE permission_groups SET permissions_json=?, updated_at=? WHERE id=?`, encodePermissions(permissions), now, PermissionGroupRegular); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err = a.db.ExecContext(ctx, `INSERT INTO system_settings(key,value,updated_at) VALUES(?,?,?)
|
|
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at`, regularUserPermissionMigrationKey, "1", now)
|
|
return err
|
|
}
|
|
|
|
func (a *App) cleanupLegacyDefaultPermissionGroups(ctx context.Context) error {
|
|
for _, groupID := range legacyDefaultPermissionGroupIDs {
|
|
var userCount int
|
|
if err := a.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM user_permission_groups WHERE group_id=?`, groupID).Scan(&userCount); err != nil {
|
|
return err
|
|
}
|
|
if userCount == 0 {
|
|
if _, err := a.db.ExecContext(ctx, `DELETE FROM permission_groups WHERE id=? AND system=1`, groupID); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
if _, err := a.db.ExecContext(ctx, `UPDATE permission_groups SET system=0, updated_at=? WHERE id=? AND system=1`, a.now().UTC().Format(time.RFC3339Nano), groupID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) attachUserAuthorization(ctx context.Context, u *User) error {
|
|
if u == nil {
|
|
return nil
|
|
}
|
|
if u.LoginName == "" {
|
|
u.LoginName = u.Email
|
|
}
|
|
permissions, err := a.permissionsForUser(ctx, u.ID, u.Role)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
limits, err := a.limitsForUser(ctx, u.ID, u.Role)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
groupIDs, groups, err := a.permissionGroupsForUser(ctx, u.ID, u.Role)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.Permissions = permissions
|
|
u.Limits = limits
|
|
if u.Role != "admin" && u.MailboxLimitOverride != nil {
|
|
u.Limits.MaxMailboxCount = *u.MailboxLimitOverride
|
|
}
|
|
u.PermissionGroupIDs = groupIDs
|
|
u.PermissionGroups = groups
|
|
u.Protected = a.isDefaultAdminUser(u)
|
|
return nil
|
|
}
|
|
|
|
func (a *App) permissionsForUser(ctx context.Context, userID, role string) ([]string, error) {
|
|
if role == "admin" {
|
|
return allPermissionKeys(), nil
|
|
}
|
|
seen := map[string]bool{}
|
|
if err := a.addRegularGroupPermissions(ctx, nil, seen); err != nil {
|
|
return nil, err
|
|
}
|
|
rows, err := a.db.QueryContext(ctx, `SELECT pg.id,pg.permissions_json
|
|
FROM permission_groups pg
|
|
JOIN user_permission_groups upg ON upg.group_id=pg.id
|
|
WHERE upg.user_id=?`, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var groupID, raw string
|
|
if err := rows.Scan(&groupID, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
if !isAssignablePermissionGroupID(groupID) {
|
|
continue
|
|
}
|
|
for _, permission := range decodeStoredPermissions(raw) {
|
|
seen[permission] = true
|
|
}
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]string, 0, len(seen))
|
|
for _, item := range allPermissionKeys() {
|
|
if seen[item] {
|
|
out = append(out, item)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (a *App) limitsForUser(ctx context.Context, userID, role string) (PermissionLimits, error) {
|
|
if role == "admin" {
|
|
return PermissionLimits{}, nil
|
|
}
|
|
limits, ok, err := a.regularGroupLimits(ctx, nil)
|
|
if err != nil {
|
|
return PermissionLimits{}, err
|
|
}
|
|
if !ok {
|
|
limits = defaultPermissionLimits()
|
|
}
|
|
rows, err := a.db.QueryContext(ctx, `SELECT pg.id,pg.limits_json
|
|
FROM permission_groups pg
|
|
JOIN user_permission_groups upg ON upg.group_id=pg.id
|
|
WHERE upg.user_id=?`, userID)
|
|
if err != nil {
|
|
return PermissionLimits{}, err
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var groupID, raw string
|
|
if err := rows.Scan(&groupID, &raw); err != nil {
|
|
return PermissionLimits{}, err
|
|
}
|
|
if !isAssignablePermissionGroupID(groupID) {
|
|
continue
|
|
}
|
|
limits = mergePermissionLimits(limits, decodeStoredLimits(raw))
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return PermissionLimits{}, err
|
|
}
|
|
return limits, nil
|
|
}
|
|
|
|
func (a *App) regularGroupLimits(ctx context.Context, tx *sql.Tx) (PermissionLimits, bool, error) {
|
|
var raw string
|
|
query := `SELECT limits_json FROM permission_groups WHERE id=?`
|
|
var err error
|
|
if tx != nil {
|
|
err = tx.QueryRowContext(ctx, query, PermissionGroupRegular).Scan(&raw)
|
|
} else {
|
|
err = a.db.QueryRowContext(ctx, query, PermissionGroupRegular).Scan(&raw)
|
|
}
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return PermissionLimits{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return PermissionLimits{}, false, err
|
|
}
|
|
return decodeStoredLimits(raw), true, nil
|
|
}
|
|
|
|
func (a *App) addRegularGroupPermissions(ctx context.Context, tx *sql.Tx, seen map[string]bool) error {
|
|
var raw string
|
|
query := `SELECT permissions_json FROM permission_groups WHERE id=?`
|
|
var err error
|
|
if tx != nil {
|
|
err = tx.QueryRowContext(ctx, query, PermissionGroupRegular).Scan(&raw)
|
|
} else {
|
|
err = a.db.QueryRowContext(ctx, query, PermissionGroupRegular).Scan(&raw)
|
|
}
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, permission := range decodeStoredPermissions(raw) {
|
|
seen[permission] = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) effectivePermissionsForUserGroups(ctx context.Context, tx *sql.Tx, groupIDs []string) ([]string, error) {
|
|
seen := map[string]bool{}
|
|
if err := a.addRegularGroupPermissions(ctx, tx, seen); err != nil {
|
|
return nil, err
|
|
}
|
|
groupPermissions, err := a.permissionsForGroupIDs(ctx, tx, groupIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, permission := range groupPermissions {
|
|
seen[permission] = true
|
|
}
|
|
out := make([]string, 0, len(seen))
|
|
for _, permission := range allPermissionKeys() {
|
|
if seen[permission] {
|
|
out = append(out, permission)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (a *App) effectiveLimitsForUserGroups(ctx context.Context, tx *sql.Tx, groupIDs []string) (PermissionLimits, error) {
|
|
limits, ok, err := a.regularGroupLimits(ctx, tx)
|
|
if err != nil {
|
|
return PermissionLimits{}, err
|
|
}
|
|
if !ok {
|
|
limits = defaultPermissionLimits()
|
|
}
|
|
groupLimits, err := a.limitsForGroupIDs(ctx, tx, groupIDs)
|
|
if err != nil {
|
|
return PermissionLimits{}, err
|
|
}
|
|
return mergePermissionLimits(limits, groupLimits), nil
|
|
}
|
|
|
|
func (a *App) permissionGroupsForUser(ctx context.Context, userID, role string) ([]string, []PermissionGroupSummary, error) {
|
|
if role == "admin" {
|
|
group := PermissionGroupSummary{ID: PermissionGroupSuperAdmin, Name: "管理员"}
|
|
return []string{group.ID}, []PermissionGroupSummary{group}, nil
|
|
}
|
|
ids := []string{PermissionGroupRegular}
|
|
groups := []PermissionGroupSummary{{ID: PermissionGroupRegular, Name: "普通用户"}}
|
|
if err := a.db.QueryRowContext(ctx, `SELECT name FROM permission_groups WHERE id=?`, PermissionGroupRegular).Scan(&groups[0].Name); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil, err
|
|
}
|
|
rows, err := a.db.QueryContext(ctx, `SELECT pg.id,pg.name
|
|
FROM permission_groups pg
|
|
JOIN user_permission_groups upg ON upg.group_id=pg.id
|
|
WHERE upg.user_id=?`, userID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer rows.Close()
|
|
order := permissionGroupOrder()
|
|
for rows.Next() {
|
|
var group PermissionGroupSummary
|
|
if err := rows.Scan(&group.ID, &group.Name); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if !isAssignablePermissionGroupID(group.ID) {
|
|
continue
|
|
}
|
|
ids = append(ids, group.ID)
|
|
groups = append(groups, group)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
sort.SliceStable(groups, func(i, j int) bool {
|
|
left, leftOK := order[groups[i].ID]
|
|
right, rightOK := order[groups[j].ID]
|
|
if leftOK && rightOK {
|
|
return left < right
|
|
}
|
|
if leftOK != rightOK {
|
|
return leftOK
|
|
}
|
|
return strings.ToLower(groups[i].Name) < strings.ToLower(groups[j].Name)
|
|
})
|
|
sort.SliceStable(ids, func(i, j int) bool {
|
|
left, leftOK := order[ids[i]]
|
|
right, rightOK := order[ids[j]]
|
|
if leftOK && rightOK {
|
|
return left < right
|
|
}
|
|
if leftOK != rightOK {
|
|
return leftOK
|
|
}
|
|
return ids[i] < ids[j]
|
|
})
|
|
return ids, groups, nil
|
|
}
|
|
|
|
func userHasPermission(user *User, permission string) bool {
|
|
if user == nil {
|
|
return false
|
|
}
|
|
if user.Role == "admin" {
|
|
return true
|
|
}
|
|
for _, item := range user.Permissions {
|
|
if item == permission {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func userHasAnyPermission(user *User, permissions ...string) bool {
|
|
if user == nil {
|
|
return false
|
|
}
|
|
if user.Role == "admin" {
|
|
return true
|
|
}
|
|
for _, permission := range permissions {
|
|
if userHasPermission(user, permission) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func userHasAdminAccess(user *User) bool {
|
|
return userHasAnyPermission(user, adminPermissionKeys()...)
|
|
}
|
|
|
|
func (a *App) requireAdminAccess(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !userHasAdminAccess(currentUser(r)) {
|
|
respondError(w, http.StatusForbidden, "admin permission required")
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (a *App) requirePermission(permission string) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !userHasPermission(currentUser(r), permission) {
|
|
respondError(w, http.StatusForbidden, "permission required")
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (a *App) requireAnyPermission(permissions ...string) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !userHasAnyPermission(currentUser(r), permissions...) {
|
|
respondError(w, http.StatusForbidden, "permission required")
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func actorCanGrantPermissions(actor *User, permissions []string) bool {
|
|
if actor == nil {
|
|
return false
|
|
}
|
|
if actor.Role == "admin" {
|
|
return true
|
|
}
|
|
for _, permission := range permissions {
|
|
if !userHasPermission(actor, permission) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (a *App) permissionsForGroupIDs(ctx context.Context, tx *sql.Tx, groupIDs []string) ([]string, error) {
|
|
seen := map[string]bool{}
|
|
for _, groupID := range cleanIDList(groupIDs) {
|
|
if !isAssignablePermissionGroupID(groupID) {
|
|
return nil, fmt.Errorf("permission group not assignable: %s", groupID)
|
|
}
|
|
var raw string
|
|
query := `SELECT permissions_json FROM permission_groups WHERE id=?`
|
|
var err error
|
|
if tx != nil {
|
|
err = tx.QueryRowContext(ctx, query, groupID).Scan(&raw)
|
|
} else {
|
|
err = a.db.QueryRowContext(ctx, query, groupID).Scan(&raw)
|
|
}
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, fmt.Errorf("permission group not found: %s", groupID)
|
|
}
|
|
return nil, err
|
|
}
|
|
for _, permission := range decodeStoredPermissions(raw) {
|
|
seen[permission] = true
|
|
}
|
|
}
|
|
out := make([]string, 0, len(seen))
|
|
for _, permission := range allPermissionKeys() {
|
|
if seen[permission] {
|
|
out = append(out, permission)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (a *App) limitsForGroupIDs(ctx context.Context, tx *sql.Tx, groupIDs []string) (PermissionLimits, error) {
|
|
limits := minimalLimits()
|
|
for _, groupID := range cleanIDList(groupIDs) {
|
|
if !isAssignablePermissionGroupID(groupID) {
|
|
return PermissionLimits{}, fmt.Errorf("permission group not assignable: %s", groupID)
|
|
}
|
|
var raw string
|
|
query := `SELECT limits_json FROM permission_groups WHERE id=?`
|
|
var err error
|
|
if tx != nil {
|
|
err = tx.QueryRowContext(ctx, query, groupID).Scan(&raw)
|
|
} else {
|
|
err = a.db.QueryRowContext(ctx, query, groupID).Scan(&raw)
|
|
}
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return PermissionLimits{}, fmt.Errorf("permission group not found: %s", groupID)
|
|
}
|
|
return PermissionLimits{}, err
|
|
}
|
|
limits = mergePermissionLimits(limits, decodeStoredLimits(raw))
|
|
}
|
|
return limits, nil
|
|
}
|
|
|
|
func (a *App) setUserPermissionGroups(ctx context.Context, tx *sql.Tx, userID string, groupIDs []string, actor *User) error {
|
|
groupIDs = cleanIDList(groupIDs)
|
|
for _, groupID := range groupIDs {
|
|
if !isAssignablePermissionGroupID(groupID) {
|
|
return fmt.Errorf("permission group not assignable: %s", groupID)
|
|
}
|
|
}
|
|
groupPermissions, err := a.effectivePermissionsForUserGroups(ctx, tx, groupIDs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !actorCanGrantPermissions(actor, groupPermissions) {
|
|
return errors.New("cannot assign permissions you do not hold")
|
|
}
|
|
groupLimits, err := a.effectiveLimitsForUserGroups(ctx, tx, groupIDs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !actorCanGrantLimits(actor, groupLimits) {
|
|
return errors.New("cannot assign limits above your own")
|
|
}
|
|
exec := func(query string, args ...any) error {
|
|
var err error
|
|
if tx != nil {
|
|
_, err = tx.ExecContext(ctx, query, args...)
|
|
} else {
|
|
_, err = a.db.ExecContext(ctx, query, args...)
|
|
}
|
|
return err
|
|
}
|
|
if err := exec(`DELETE FROM user_permission_groups WHERE user_id=?`, userID); err != nil {
|
|
return err
|
|
}
|
|
now := a.now().UTC().Format(time.RFC3339Nano)
|
|
for _, groupID := range groupIDs {
|
|
if err := exec(`INSERT INTO user_permission_groups(user_id,group_id,created_at) VALUES(?,?,?)`, userID, groupID, now); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *App) permissionGroupByID(ctx context.Context, id string) (*PermissionGroup, error) {
|
|
row := a.db.QueryRowContext(ctx, `SELECT pg.id,pg.name,pg.description,pg.permissions_json,pg.limits_json,pg.system,pg.created_at,pg.updated_at,COUNT(upg.user_id)
|
|
FROM permission_groups pg
|
|
LEFT JOIN user_permission_groups upg ON upg.group_id=pg.id
|
|
WHERE pg.id=?
|
|
GROUP BY pg.id,pg.name,pg.description,pg.permissions_json,pg.limits_json,pg.system,pg.created_at,pg.updated_at`, id)
|
|
var group PermissionGroup
|
|
var rawPermissions, rawLimits, created, updated string
|
|
var system int
|
|
if err := row.Scan(&group.ID, &group.Name, &group.Description, &rawPermissions, &rawLimits, &system, &created, &updated, &group.UserCount); err != nil {
|
|
return nil, err
|
|
}
|
|
group.Permissions = decodeStoredPermissions(rawPermissions)
|
|
group.Limits = decodeStoredLimits(rawLimits)
|
|
group.System = intBool(system)
|
|
group.CreatedAt = parseTime(created)
|
|
group.UpdatedAt = parseTime(updated)
|
|
return &group, nil
|
|
}
|
|
|
|
func (a *App) isDefaultAdminUser(u *User) bool {
|
|
if u == nil {
|
|
return false
|
|
}
|
|
if adminUsername := normalizeLoginName(a.cfg.AdminUsername); adminUsername != "" && !strings.Contains(adminUsername, "@") {
|
|
return strings.EqualFold(normalizeLoginName(u.LoginName), adminUsername)
|
|
}
|
|
adminEmail := normalizeEmail(a.cfg.AdminEmail)
|
|
return adminEmail != "" && strings.EqualFold(normalizeEmail(u.Email), adminEmail)
|
|
}
|
|
|
|
func sortPermissionGroups(items []PermissionGroup) {
|
|
order := permissionGroupOrder()
|
|
sort.SliceStable(items, func(i, j int) bool {
|
|
left, leftOK := order[items[i].ID]
|
|
right, rightOK := order[items[j].ID]
|
|
if leftOK && rightOK {
|
|
return left < right
|
|
}
|
|
if leftOK != rightOK {
|
|
return leftOK
|
|
}
|
|
return strings.ToLower(items[i].Name) < strings.ToLower(items[j].Name)
|
|
})
|
|
}
|