You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
573 lines
13 KiB
573 lines
13 KiB
|
4 weeks ago
|
package store
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/sha256"
|
||
|
|
"database/sql"
|
||
|
|
"encoding/hex"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"sort"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
_ "modernc.org/sqlite"
|
||
|
|
)
|
||
|
|
|
||
|
|
var ErrDependencyExists = errors.New("group is assigned to one or more users")
|
||
|
|
|
||
|
|
type SQLiteStore struct {
|
||
|
|
db *sql.DB
|
||
|
|
}
|
||
|
|
|
||
|
|
type ActivityRecord struct {
|
||
|
|
ID int64
|
||
|
|
Kind string
|
||
|
|
Path string
|
||
|
|
Title string
|
||
|
|
Action string
|
||
|
|
CreatedAt time.Time
|
||
|
|
}
|
||
|
|
|
||
|
|
type UserRecord struct {
|
||
|
|
ID int64
|
||
|
|
Username string
|
||
|
|
GroupName string
|
||
|
|
Enabled bool
|
||
|
|
PasswordHash string
|
||
|
|
CreatedAt time.Time
|
||
|
|
UpdatedAt time.Time
|
||
|
|
}
|
||
|
|
|
||
|
|
type CreateUserParams struct {
|
||
|
|
Username string
|
||
|
|
Password string
|
||
|
|
GroupName string
|
||
|
|
Enabled bool
|
||
|
|
}
|
||
|
|
|
||
|
|
type UpdateUserParams struct {
|
||
|
|
ID int64
|
||
|
|
Username string
|
||
|
|
Password string
|
||
|
|
GroupName string
|
||
|
|
Enabled bool
|
||
|
|
}
|
||
|
|
|
||
|
|
type GroupRecord struct {
|
||
|
|
ID int64
|
||
|
|
Name string
|
||
|
|
Description string
|
||
|
|
Permissions []string
|
||
|
|
CreatedAt time.Time
|
||
|
|
UpdatedAt time.Time
|
||
|
|
}
|
||
|
|
|
||
|
|
type CreateGroupParams struct {
|
||
|
|
Name string
|
||
|
|
Description string
|
||
|
|
Permissions []string
|
||
|
|
}
|
||
|
|
|
||
|
|
type UpdateGroupParams struct {
|
||
|
|
ID int64
|
||
|
|
Name string
|
||
|
|
Description string
|
||
|
|
Permissions []string
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewSQLiteStore(dbPath string) (*SQLiteStore, error) {
|
||
|
|
if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
db, err := sql.Open("sqlite", dbPath)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
store := &SQLiteStore{db: db}
|
||
|
|
if err := store.init(); err != nil {
|
||
|
|
_ = db.Close()
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return store, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) Close() error {
|
||
|
|
if s == nil || s.db == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return s.db.Close()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) RecordPageVisit(path string, title string) error {
|
||
|
|
_, err := s.db.Exec(`
|
||
|
|
INSERT INTO activity_logs (kind, path, title, action)
|
||
|
|
VALUES ('page_visit', ?, ?, '')
|
||
|
|
`, path, title)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) RecordMockAction(action string, path string) error {
|
||
|
|
_, err := s.db.Exec(`
|
||
|
|
INSERT INTO activity_logs (kind, path, title, action)
|
||
|
|
VALUES ('mock_action', ?, '', ?)
|
||
|
|
`, path, action)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) RecentActivity(limit int) ([]ActivityRecord, error) {
|
||
|
|
if limit <= 0 {
|
||
|
|
limit = 20
|
||
|
|
}
|
||
|
|
|
||
|
|
rows, err := s.db.Query(`
|
||
|
|
SELECT id, kind, path, title, action, created_at
|
||
|
|
FROM activity_logs
|
||
|
|
ORDER BY id DESC
|
||
|
|
LIMIT ?
|
||
|
|
`, limit)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
records := make([]ActivityRecord, 0, limit)
|
||
|
|
for rows.Next() {
|
||
|
|
var record ActivityRecord
|
||
|
|
if err := rows.Scan(&record.ID, &record.Kind, &record.Path, &record.Title, &record.Action, &record.CreatedAt); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
records = append(records, record)
|
||
|
|
}
|
||
|
|
|
||
|
|
return records, rows.Err()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) ListUsers() ([]UserRecord, error) {
|
||
|
|
rows, err := s.db.Query(`
|
||
|
|
SELECT id, username, group_name, enabled, created_at, updated_at
|
||
|
|
FROM users
|
||
|
|
ORDER BY username COLLATE NOCASE ASC
|
||
|
|
`)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
users := make([]UserRecord, 0)
|
||
|
|
for rows.Next() {
|
||
|
|
var user UserRecord
|
||
|
|
if err := rows.Scan(&user.ID, &user.Username, &user.GroupName, &user.Enabled, &user.CreatedAt, &user.UpdatedAt); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
users = append(users, user)
|
||
|
|
}
|
||
|
|
|
||
|
|
return users, rows.Err()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) GetUser(id int64) (UserRecord, error) {
|
||
|
|
var user UserRecord
|
||
|
|
err := s.db.QueryRow(`
|
||
|
|
SELECT id, username, group_name, enabled, created_at, updated_at
|
||
|
|
FROM users
|
||
|
|
WHERE id = ?
|
||
|
|
`, id).Scan(&user.ID, &user.Username, &user.GroupName, &user.Enabled, &user.CreatedAt, &user.UpdatedAt)
|
||
|
|
return user, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) GetUserByUsername(username string) (UserRecord, error) {
|
||
|
|
var user UserRecord
|
||
|
|
err := s.db.QueryRow(`
|
||
|
|
SELECT id, username, group_name, enabled, password_hash, created_at, updated_at
|
||
|
|
FROM users
|
||
|
|
WHERE username = ?
|
||
|
|
`, strings.TrimSpace(username)).Scan(
|
||
|
|
&user.ID,
|
||
|
|
&user.Username,
|
||
|
|
&user.GroupName,
|
||
|
|
&user.Enabled,
|
||
|
|
&user.PasswordHash,
|
||
|
|
&user.CreatedAt,
|
||
|
|
&user.UpdatedAt,
|
||
|
|
)
|
||
|
|
return user, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) ListGroups() ([]GroupRecord, error) {
|
||
|
|
rows, err := s.db.Query(`
|
||
|
|
SELECT id, name, description, permissions, created_at, updated_at
|
||
|
|
FROM groups
|
||
|
|
ORDER BY name COLLATE NOCASE ASC
|
||
|
|
`)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
groups := make([]GroupRecord, 0)
|
||
|
|
for rows.Next() {
|
||
|
|
var group GroupRecord
|
||
|
|
var permissions string
|
||
|
|
if err := rows.Scan(&group.ID, &group.Name, &group.Description, &permissions, &group.CreatedAt, &group.UpdatedAt); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
group.Permissions = decodePermissions(permissions)
|
||
|
|
groups = append(groups, group)
|
||
|
|
}
|
||
|
|
|
||
|
|
return groups, rows.Err()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) GetGroup(id int64) (GroupRecord, error) {
|
||
|
|
var group GroupRecord
|
||
|
|
var permissions string
|
||
|
|
err := s.db.QueryRow(`
|
||
|
|
SELECT id, name, description, permissions, created_at, updated_at
|
||
|
|
FROM groups
|
||
|
|
WHERE id = ?
|
||
|
|
`, id).Scan(&group.ID, &group.Name, &group.Description, &permissions, &group.CreatedAt, &group.UpdatedAt)
|
||
|
|
if err != nil {
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
group.Permissions = decodePermissions(permissions)
|
||
|
|
return group, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) GetGroupByName(name string) (GroupRecord, error) {
|
||
|
|
var group GroupRecord
|
||
|
|
var permissions string
|
||
|
|
err := s.db.QueryRow(`
|
||
|
|
SELECT id, name, description, permissions, created_at, updated_at
|
||
|
|
FROM groups
|
||
|
|
WHERE name = ?
|
||
|
|
`, strings.TrimSpace(name)).Scan(&group.ID, &group.Name, &group.Description, &permissions, &group.CreatedAt, &group.UpdatedAt)
|
||
|
|
if err != nil {
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
group.Permissions = decodePermissions(permissions)
|
||
|
|
return group, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) CreateGroup(params CreateGroupParams) (GroupRecord, error) {
|
||
|
|
result, err := s.db.Exec(`
|
||
|
|
INSERT INTO groups (name, description, permissions)
|
||
|
|
VALUES (?, ?, ?)
|
||
|
|
`, strings.TrimSpace(params.Name), strings.TrimSpace(params.Description), encodePermissions(params.Permissions))
|
||
|
|
if err != nil {
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
id, err := result.LastInsertId()
|
||
|
|
if err != nil {
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return s.GetGroup(id)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) UpdateGroup(params UpdateGroupParams) (GroupRecord, error) {
|
||
|
|
current, err := s.GetGroup(params.ID)
|
||
|
|
if err != nil {
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
tx, err := s.db.Begin()
|
||
|
|
if err != nil {
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := tx.Exec(`
|
||
|
|
UPDATE groups
|
||
|
|
SET name = ?, description = ?, permissions = ?, updated_at = CURRENT_TIMESTAMP
|
||
|
|
WHERE id = ?
|
||
|
|
`, strings.TrimSpace(params.Name), strings.TrimSpace(params.Description), encodePermissions(params.Permissions), params.ID); err != nil {
|
||
|
|
_ = tx.Rollback()
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
if current.Name != strings.TrimSpace(params.Name) {
|
||
|
|
if _, err := tx.Exec(`
|
||
|
|
UPDATE users
|
||
|
|
SET group_name = ?, updated_at = CURRENT_TIMESTAMP
|
||
|
|
WHERE group_name = ?
|
||
|
|
`, strings.TrimSpace(params.Name), current.Name); err != nil {
|
||
|
|
_ = tx.Rollback()
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := tx.Commit(); err != nil {
|
||
|
|
return GroupRecord{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return s.GetGroup(params.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) DeleteGroup(id int64) error {
|
||
|
|
group, err := s.GetGroup(id)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
var userCount int
|
||
|
|
if err := s.db.QueryRow(`
|
||
|
|
SELECT COUNT(*)
|
||
|
|
FROM users
|
||
|
|
WHERE group_name = ?
|
||
|
|
`, group.Name).Scan(&userCount); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if userCount > 0 {
|
||
|
|
return ErrDependencyExists
|
||
|
|
}
|
||
|
|
|
||
|
|
result, err := s.db.Exec(`DELETE FROM groups WHERE id = ?`, id)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
rows, err := result.RowsAffected()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if rows == 0 {
|
||
|
|
return sql.ErrNoRows
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) CreateUser(params CreateUserParams) (UserRecord, error) {
|
||
|
|
result, err := s.db.Exec(`
|
||
|
|
INSERT INTO users (username, password_hash, group_name, enabled)
|
||
|
|
VALUES (?, ?, ?, ?)
|
||
|
|
`, strings.TrimSpace(params.Username), hashPassword(params.Password), strings.TrimSpace(params.GroupName), params.Enabled)
|
||
|
|
if err != nil {
|
||
|
|
return UserRecord{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
id, err := result.LastInsertId()
|
||
|
|
if err != nil {
|
||
|
|
return UserRecord{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return s.GetUser(id)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) UpdateUser(params UpdateUserParams) (UserRecord, error) {
|
||
|
|
if strings.TrimSpace(params.Password) == "" {
|
||
|
|
result, err := s.db.Exec(`
|
||
|
|
UPDATE users
|
||
|
|
SET username = ?, group_name = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP
|
||
|
|
WHERE id = ?
|
||
|
|
`, strings.TrimSpace(params.Username), strings.TrimSpace(params.GroupName), params.Enabled, params.ID)
|
||
|
|
if err != nil {
|
||
|
|
return UserRecord{}, err
|
||
|
|
}
|
||
|
|
rows, err := result.RowsAffected()
|
||
|
|
if err != nil {
|
||
|
|
return UserRecord{}, err
|
||
|
|
}
|
||
|
|
if rows == 0 {
|
||
|
|
return UserRecord{}, sql.ErrNoRows
|
||
|
|
}
|
||
|
|
return s.GetUser(params.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
result, err := s.db.Exec(`
|
||
|
|
UPDATE users
|
||
|
|
SET username = ?, password_hash = ?, group_name = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP
|
||
|
|
WHERE id = ?
|
||
|
|
`, strings.TrimSpace(params.Username), hashPassword(params.Password), strings.TrimSpace(params.GroupName), params.Enabled, params.ID)
|
||
|
|
if err != nil {
|
||
|
|
return UserRecord{}, err
|
||
|
|
}
|
||
|
|
rows, err := result.RowsAffected()
|
||
|
|
if err != nil {
|
||
|
|
return UserRecord{}, err
|
||
|
|
}
|
||
|
|
if rows == 0 {
|
||
|
|
return UserRecord{}, sql.ErrNoRows
|
||
|
|
}
|
||
|
|
return s.GetUser(params.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) DeleteUser(id int64) error {
|
||
|
|
result, err := s.db.Exec(`DELETE FROM users WHERE id = ?`, id)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
rows, err := result.RowsAffected()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if rows == 0 {
|
||
|
|
return sql.ErrNoRows
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) init() error {
|
||
|
|
_, err := s.db.Exec(`
|
||
|
|
PRAGMA journal_mode = WAL;
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS activity_logs (
|
||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
|
kind TEXT NOT NULL,
|
||
|
|
path TEXT NOT NULL DEFAULT '',
|
||
|
|
title TEXT NOT NULL DEFAULT '',
|
||
|
|
action TEXT NOT NULL DEFAULT '',
|
||
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS users (
|
||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
|
username TEXT NOT NULL UNIQUE,
|
||
|
|
password_hash TEXT NOT NULL,
|
||
|
|
group_name TEXT NOT NULL,
|
||
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS groups (
|
||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
|
name TEXT NOT NULL UNIQUE,
|
||
|
|
description TEXT NOT NULL DEFAULT '',
|
||
|
|
permissions TEXT NOT NULL DEFAULT '[]',
|
||
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
`)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := s.ensureDefaultGroups(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return s.ensureDefaultUsers()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) ensureDefaultGroups() error {
|
||
|
|
defaultGroups := []CreateGroupParams{
|
||
|
|
{Name: "administrator", Description: "전체 접근 가능", Permissions: defaultPermissionKeys()},
|
||
|
|
{Name: "operator", Description: "운영 담당 권한 그룹", Permissions: []string{"network", "ports", "admin", "common", "status", "password"}},
|
||
|
|
{Name: "guest", Description: "조회 전용", Permissions: []string{"status"}},
|
||
|
|
}
|
||
|
|
|
||
|
|
tx, err := s.db.Begin()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, group := range defaultGroups {
|
||
|
|
if _, err := tx.Exec(`
|
||
|
|
INSERT OR IGNORE INTO groups (name, description, permissions)
|
||
|
|
VALUES (?, ?, ?)
|
||
|
|
`, group.Name, group.Description, encodePermissions(group.Permissions)); err != nil {
|
||
|
|
_ = tx.Rollback()
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return tx.Commit()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *SQLiteStore) ensureDefaultUsers() error {
|
||
|
|
defaultUsers := []CreateUserParams{
|
||
|
|
{Username: "terauser", Password: "terauser", GroupName: "administrator", Enabled: true},
|
||
|
|
{Username: "guest", Password: "guest", GroupName: "guest", Enabled: true},
|
||
|
|
{Username: "admin", Password: "admin", GroupName: "administrator", Enabled: true},
|
||
|
|
}
|
||
|
|
|
||
|
|
tx, err := s.db.Begin()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, user := range defaultUsers {
|
||
|
|
if _, err := tx.Exec(`
|
||
|
|
INSERT OR IGNORE INTO users (username, password_hash, group_name, enabled)
|
||
|
|
VALUES (?, ?, ?, ?)
|
||
|
|
`, user.Username, hashPassword(user.Password), user.GroupName, user.Enabled); err != nil {
|
||
|
|
_ = tx.Rollback()
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return tx.Commit()
|
||
|
|
}
|
||
|
|
|
||
|
|
func hashPassword(password string) string {
|
||
|
|
sum := sha256.Sum256([]byte(password))
|
||
|
|
return hex.EncodeToString(sum[:])
|
||
|
|
}
|
||
|
|
|
||
|
|
func VerifyPassword(password string, encoded string) bool {
|
||
|
|
return hashPassword(password) == encoded
|
||
|
|
}
|
||
|
|
|
||
|
|
func IsUniqueConstraintError(err error) bool {
|
||
|
|
return err != nil && strings.Contains(strings.ToLower(err.Error()), "unique")
|
||
|
|
}
|
||
|
|
|
||
|
|
func IsNotFoundError(err error) bool {
|
||
|
|
return errors.Is(err, sql.ErrNoRows)
|
||
|
|
}
|
||
|
|
|
||
|
|
func IsDependencyError(err error) bool {
|
||
|
|
return errors.Is(err, ErrDependencyExists)
|
||
|
|
}
|
||
|
|
|
||
|
|
func encodePermissions(permissions []string) string {
|
||
|
|
if len(permissions) == 0 {
|
||
|
|
return "[]"
|
||
|
|
}
|
||
|
|
|
||
|
|
normalized := normalizePermissions(permissions)
|
||
|
|
payload, err := json.Marshal(normalized)
|
||
|
|
if err != nil {
|
||
|
|
return "[]"
|
||
|
|
}
|
||
|
|
return string(payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
func decodePermissions(raw string) []string {
|
||
|
|
if strings.TrimSpace(raw) == "" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
var permissions []string
|
||
|
|
if err := json.Unmarshal([]byte(raw), &permissions); err != nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return normalizePermissions(permissions)
|
||
|
|
}
|
||
|
|
|
||
|
|
func normalizePermissions(permissions []string) []string {
|
||
|
|
seen := make(map[string]struct{})
|
||
|
|
result := make([]string, 0, len(permissions))
|
||
|
|
for _, permission := range permissions {
|
||
|
|
permission = strings.TrimSpace(permission)
|
||
|
|
if permission == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if _, ok := seen[permission]; ok {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
seen[permission] = struct{}{}
|
||
|
|
result = append(result, permission)
|
||
|
|
}
|
||
|
|
sort.Strings(result)
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
func defaultPermissionKeys() []string {
|
||
|
|
return []string{"admin", "common", "login", "network", "operate", "password", "ports", "status", "tools"}
|
||
|
|
}
|