package classes import ( "context" "fmt" "strings" "git.golem15.com/golem15/summercms/modules/compass" "gorm.io/gorm" ) // PermissionManagePrivilegedGroups is the backend permission an administrator // needs, on top of golem15.users.access_users, to change the email or the // password of a member of a privileged group, or to delete such a user for // good. const PermissionManagePrivilegedGroups = "golem15.users.manage_privileged_groups" // privilegedGroupsKey is the config list of privileged group codes. const privilegedGroupsKey = "golem15.user.privileged_groups" // defaultPrivilegedGroup is the only privileged code when the config names // none: the site-admin group. const defaultPrivilegedGroup = "admin" // PrivilegedGroupCodes returns the group codes whose members are protected, // read from golem15.user.privileged_groups on every call so a config change // applies without a restart. A nil config or a missing key gives the single // code admin. Blank entries are dropped; a key that is present and empty // means that no group is privileged. func PrivilegedGroupCodes(cfg *compass.Config) []string { if cfg == nil { return []string{defaultPrivilegedGroup} } raw, ok := cfg.Lookup(privilegedGroupsKey) if !ok { return []string{defaultPrivilegedGroup} } var entries []string switch v := raw.(type) { case []string: entries = v case []any: for _, item := range v { if s, ok := item.(string); ok { entries = append(entries, s) } } case string: // An environment override arrives as one comma-separated string. entries = strings.Split(v, ",") } codes := make([]string, 0, len(entries)) for _, entry := range entries { if code := strings.TrimSpace(entry); code != "" { codes = append(codes, code) } } return codes } // IsPrivilegedCode reports whether a group's code is in codes. Codes compare // case-sensitively, as HasGroupCode does, and a group without a code is // never privileged. func IsPrivilegedCode(codes []string, code *string) bool { if code == nil || *code == "" { return false } for _, privileged := range codes { if privileged == *code { return true } } return false } // PrivilegedGroupIDs returns the ids of the user_groups rows whose code is in // codes, ordered by id. func PrivilegedGroupIDs(ctx context.Context, db *gorm.DB, codes []string) ([]uint, error) { if db == nil { return nil, fmt.Errorf("golem15.user: gorm handle is missing") } ids := []uint{} if len(codes) == 0 { return ids, nil } err := db.Session(&gorm.Session{NewDB: true}).WithContext(ctx). Table("user_groups"). Where("code IN ?", codes). Order("id"). Pluck("id", &ids).Error if err != nil { return nil, err } return ids, nil } // IsPrivilegedMember reports whether the user belongs to at least one group // whose code is in codes. A user in no group, and a group without a code, // give false. A deactivated user's membership counts: it survives the soft // delete. func IsPrivilegedMember(ctx context.Context, db *gorm.DB, codes []string, userID uint) (bool, error) { if db == nil { return false, fmt.Errorf("golem15.user: gorm handle is missing") } if len(codes) == 0 || userID == 0 { return false, nil } held, err := UserGroupCodes(ctx, db.Session(&gorm.Session{NewDB: true}), userID) if err != nil { return false, err } for i := range held { if IsPrivilegedCode(codes, &held[i]) { return true, nil } } return false, nil }