602 lines
10 KiB
Go
602 lines
10 KiB
Go
package permissions
|
||
|
||
import (
|
||
"database/sql"
|
||
"log"
|
||
"strings"
|
||
|
||
"github.com/lib/pq"
|
||
)
|
||
|
||
type PermissionRepository struct {
|
||
DB *sql.DB
|
||
}
|
||
|
||
func NewPermissionRepository(db *sql.DB) *PermissionRepository {
|
||
return &PermissionRepository{DB: db}
|
||
}
|
||
|
||
/* =====================================================
|
||
MATRIX READ (V2) - ROLE BASED
|
||
===================================================== */
|
||
|
||
func (r *PermissionRepository) GetPermissionMatrixForRoles(
|
||
roleIDs []int,
|
||
) ([]PermissionMatrixRow, error) {
|
||
|
||
if len(roleIDs) == 0 {
|
||
return []PermissionMatrixRow{}, nil
|
||
}
|
||
|
||
query := `
|
||
SELECT
|
||
rp.role_id,
|
||
rol.code,
|
||
rp.module_code,
|
||
rp.action,
|
||
rp.allowed
|
||
FROM mk_sys_role_permissions rp
|
||
JOIN dfrole rol ON rol.id = rp.role_id
|
||
WHERE rp.role_id = ANY($1)
|
||
ORDER BY rol.id, rp.module_code, rp.action
|
||
`
|
||
|
||
rows, err := r.DB.Query(query, pq.Array(roleIDs))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]PermissionMatrixRow, 0)
|
||
|
||
for rows.Next() {
|
||
var row PermissionMatrixRow
|
||
if err := rows.Scan(
|
||
&row.RoleID,
|
||
&row.RoleCode,
|
||
&row.Module,
|
||
&row.Action,
|
||
&row.Allowed,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
row.Source = "role"
|
||
list = append(list, row)
|
||
}
|
||
|
||
return list, nil
|
||
}
|
||
|
||
/* =====================================================
|
||
MATRIX UPDATE (V2) - ROLE BASED
|
||
===================================================== */
|
||
|
||
func (r *PermissionRepository) UpdatePermissions(
|
||
list []PermissionUpdateRequest,
|
||
) error {
|
||
|
||
tx, err := r.DB.Begin()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
stmt, err := tx.Prepare(`
|
||
INSERT INTO mk_sys_role_permissions
|
||
(role_id, module_code, action, allowed)
|
||
VALUES ($1,$2,$3,$4)
|
||
ON CONFLICT (role_id, module_code, action)
|
||
DO UPDATE SET allowed = EXCLUDED.allowed
|
||
`)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer stmt.Close()
|
||
|
||
for _, p := range list {
|
||
if _, err := stmt.Exec(
|
||
p.RoleID,
|
||
p.Module,
|
||
p.Action,
|
||
p.Allowed,
|
||
); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return tx.Commit()
|
||
}
|
||
|
||
/* =====================================================
|
||
USER OVERRIDES - READ
|
||
GET /api/users/{id}/permissions
|
||
===================================================== */
|
||
|
||
// Tek tip: PermissionMatrixRow döndürüyoruz (source=user)
|
||
func (r *PermissionRepository) GetUserOverridesByUserID(
|
||
userID int64,
|
||
) ([]PermissionMatrixRow, error) {
|
||
|
||
rows, err := r.DB.Query(`
|
||
SELECT
|
||
user_id,
|
||
module_code,
|
||
action,
|
||
allowed
|
||
FROM mk_sys_user_permissions
|
||
WHERE user_id = $1
|
||
ORDER BY module_code, action
|
||
`, userID)
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]PermissionMatrixRow, 0)
|
||
|
||
for rows.Next() {
|
||
var row PermissionMatrixRow
|
||
if err := rows.Scan(
|
||
&row.UserID,
|
||
&row.Module,
|
||
&row.Action,
|
||
&row.Allowed,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
row.Source = "user"
|
||
list = append(list, row)
|
||
}
|
||
|
||
return list, nil
|
||
}
|
||
|
||
/* =====================================================
|
||
USER OVERRIDES - UPSERT SAVE (typed)
|
||
POST /api/users/{id}/permissions
|
||
===================================================== */
|
||
|
||
func (r *PermissionRepository) SaveUserOverrides(
|
||
userID int64,
|
||
list []UserPermissionRequest,
|
||
) error {
|
||
|
||
log.Println("➡️ REPO SaveUserOverrides START")
|
||
log.Println("USER:", userID)
|
||
log.Println("ROWS:", len(list))
|
||
|
||
tx, err := r.DB.Begin()
|
||
if err != nil {
|
||
log.Println("❌ TX BEGIN ERROR:", err)
|
||
return err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
// önce sil
|
||
_, err = tx.Exec(`
|
||
DELETE FROM mk_sys_user_permissions
|
||
WHERE user_id = $1
|
||
`, userID)
|
||
|
||
if err != nil {
|
||
log.Println("❌ DELETE ERROR:", err)
|
||
return err
|
||
}
|
||
|
||
stmt, err := tx.Prepare(`
|
||
INSERT INTO mk_sys_user_permissions
|
||
(user_id, module_code, action, allowed)
|
||
VALUES ($1,$2,$3,$4)
|
||
`)
|
||
|
||
if err != nil {
|
||
log.Println("❌ PREPARE ERROR:", err)
|
||
return err
|
||
}
|
||
defer stmt.Close()
|
||
|
||
for _, p := range list {
|
||
if strings.TrimSpace(p.Module) == "" {
|
||
log.Printf("⚠️ SKIP EMPTY MODULE user=%d action=%s",
|
||
userID,
|
||
p.Action,
|
||
)
|
||
continue
|
||
}
|
||
|
||
_, err := stmt.Exec(
|
||
userID,
|
||
p.Module,
|
||
p.Action,
|
||
p.Allowed,
|
||
)
|
||
if err != nil {
|
||
log.Println("❌ INSERT ERROR:", err)
|
||
return err
|
||
}
|
||
}
|
||
|
||
if err := tx.Commit(); err != nil {
|
||
log.Println("❌ COMMIT ERROR:", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("✅ REPO SaveUserOverrides DONE")
|
||
return nil
|
||
|
||
}
|
||
|
||
/* =====================================================
|
||
RESOLUTION HELPERS (middleware için)
|
||
===================================================== */
|
||
|
||
// user override var mı? varsa *bool döner, yoksa nil
|
||
func (r *PermissionRepository) HasUserOverride(
|
||
userID int64,
|
||
module string,
|
||
action string,
|
||
) (*bool, error) {
|
||
|
||
var allowed bool
|
||
|
||
err := r.DB.QueryRow(`
|
||
SELECT allowed
|
||
FROM mk_sys_user_permissions
|
||
WHERE user_id=$1
|
||
AND module_code=$2
|
||
AND action=$3
|
||
`,
|
||
userID,
|
||
module,
|
||
action,
|
||
).Scan(&allowed)
|
||
|
||
if err == sql.ErrNoRows {
|
||
return nil, nil
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &allowed, nil
|
||
}
|
||
|
||
// roleIDs içinden OR logic: herhangi biri allowed=true ise true
|
||
func (r *PermissionRepository) HasRoleAccess(
|
||
roleIDs []int,
|
||
module string,
|
||
action string,
|
||
) (bool, error) {
|
||
|
||
if len(roleIDs) == 0 {
|
||
return false, nil
|
||
}
|
||
|
||
var count int
|
||
|
||
err := r.DB.QueryRow(`
|
||
SELECT COUNT(*)
|
||
FROM mk_sys_role_permissions
|
||
WHERE role_id = ANY($1)
|
||
AND module_code=$2
|
||
AND action=$3
|
||
AND allowed=true
|
||
`,
|
||
pq.Array(roleIDs),
|
||
module,
|
||
action,
|
||
).Scan(&count)
|
||
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
return count > 0, nil
|
||
}
|
||
|
||
// Final decision: user override varsa onu uygula, yoksa role bazlı
|
||
func (r *PermissionRepository) ResolvePermission(
|
||
userID int64,
|
||
roleIDs []int,
|
||
module string,
|
||
action string,
|
||
) (bool, error) {
|
||
|
||
override, err := r.HasUserOverride(userID, module, action)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
if override != nil {
|
||
return *override, nil
|
||
}
|
||
|
||
return r.HasRoleAccess(roleIDs, module, action)
|
||
}
|
||
func (r *PermissionRepository) GetUserOverrides(
|
||
userID int64,
|
||
) ([]UserPermissionOverride, error) {
|
||
|
||
rows, err := r.DB.Query(`
|
||
SELECT module_code, action, allowed
|
||
FROM mk_sys_user_permissions
|
||
WHERE user_id = $1
|
||
`, userID)
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var list []UserPermissionOverride
|
||
|
||
for rows.Next() {
|
||
|
||
var o UserPermissionOverride
|
||
|
||
if err := rows.Scan(
|
||
&o.Module,
|
||
&o.Action,
|
||
&o.Allowed,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
list = append(list, o)
|
||
}
|
||
|
||
return list, nil
|
||
}
|
||
func (r *PermissionRepository) UpdateUserOverrides(
|
||
list []UserPermission,
|
||
) error {
|
||
|
||
tx, err := r.DB.Begin()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
stmt, err := tx.Prepare(`
|
||
INSERT INTO mk_sys_user_permissions
|
||
(user_id, module_code, action, allowed)
|
||
|
||
VALUES ($1,$2,$3,$4)
|
||
|
||
ON CONFLICT (user_id, module_code, action)
|
||
DO UPDATE SET allowed = EXCLUDED.allowed
|
||
`)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer stmt.Close()
|
||
|
||
for _, p := range list {
|
||
|
||
_, err := stmt.Exec(
|
||
p.UserID,
|
||
p.Module,
|
||
p.Action,
|
||
p.Allowed,
|
||
)
|
||
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return tx.Commit()
|
||
}
|
||
func (r *PermissionRepository) ResolveEffectivePermission(
|
||
userID int64,
|
||
roleID int64,
|
||
deptCode string,
|
||
module string,
|
||
action string,
|
||
) (bool, error) {
|
||
|
||
// 1️⃣ USER OVERRIDE
|
||
var allowed bool
|
||
|
||
err := r.DB.QueryRow(`
|
||
SELECT allowed
|
||
FROM mk_sys_user_permissions
|
||
WHERE user_id = $1
|
||
AND module_code = $2
|
||
AND action = $3
|
||
`,
|
||
userID, module, action,
|
||
).Scan(&allowed)
|
||
|
||
if err == nil {
|
||
return allowed, nil
|
||
}
|
||
if err != sql.ErrNoRows {
|
||
return false, err
|
||
}
|
||
|
||
// ==================================================
|
||
// 2️⃣ ROLE + DEPARTMENT
|
||
// ==================================================
|
||
|
||
if len(deptCode) > 0 {
|
||
|
||
var allowed bool
|
||
|
||
err = r.DB.QueryRow(` -- 🔥 := DEĞİL =
|
||
SELECT allowed
|
||
FROM vw_role_dept_permissions
|
||
WHERE role_id = $1
|
||
AND department_code = ANY($2)
|
||
AND module_code = $3
|
||
AND action = $4
|
||
ORDER BY allowed DESC
|
||
LIMIT 1
|
||
`,
|
||
roleID,
|
||
pq.Array([]string{deptCode}),
|
||
module,
|
||
action,
|
||
).Scan(&allowed)
|
||
|
||
if err == nil {
|
||
|
||
log.Printf(
|
||
" ↳ ROLE+DEPT OVERRIDE = %v",
|
||
allowed,
|
||
)
|
||
|
||
return allowed, nil
|
||
}
|
||
|
||
if err != sql.ErrNoRows {
|
||
log.Println("❌ ROLE+DEPT ERR:", err)
|
||
return false, err
|
||
}
|
||
}
|
||
|
||
// 3️⃣ ROLE DEFAULT
|
||
err = r.DB.QueryRow(`
|
||
SELECT allowed
|
||
FROM mk_sys_role_permissions
|
||
WHERE role_id = $1
|
||
AND module_code = $2
|
||
AND action = $3
|
||
`,
|
||
roleID, module, action,
|
||
).Scan(&allowed)
|
||
|
||
if err == nil {
|
||
return allowed, nil
|
||
}
|
||
if err != sql.ErrNoRows {
|
||
return false, err
|
||
}
|
||
|
||
// 4️⃣ DENY
|
||
return false, nil
|
||
}
|
||
func (r *PermissionRepository) ResolvePermissionChain(
|
||
userID int64,
|
||
roleID int64,
|
||
deptCodes []string,
|
||
module string,
|
||
action string,
|
||
) (bool, error) {
|
||
|
||
log.Printf(
|
||
"🔐 PERM CHECK user=%d role=%d dept=%v %s:%s",
|
||
userID,
|
||
roleID,
|
||
deptCodes,
|
||
module,
|
||
action,
|
||
)
|
||
|
||
// ==================================================
|
||
// 1️⃣ USER OVERRIDE
|
||
// ==================================================
|
||
|
||
override, err := r.HasUserOverride(userID, module, action)
|
||
if err != nil {
|
||
log.Println("❌ USER OVERRIDE ERR:", err)
|
||
return false, err
|
||
}
|
||
|
||
if override != nil {
|
||
|
||
log.Printf(
|
||
" ↳ USER OVERRIDE = %v",
|
||
*override,
|
||
)
|
||
|
||
return *override, nil
|
||
}
|
||
|
||
// ==================================================
|
||
// 2️⃣ ROLE + DEPARTMENT
|
||
// ==================================================
|
||
|
||
if len(deptCodes) > 0 {
|
||
|
||
var allowed bool
|
||
|
||
err := r.DB.QueryRow(`
|
||
SELECT allowed
|
||
FROM vw_role_dept_permissions
|
||
WHERE role_id = $1
|
||
AND department_code IN (
|
||
SELECT UNNEST($2::text[])
|
||
)
|
||
AND module_code = $3
|
||
AND action = $4
|
||
ORDER BY allowed DESC
|
||
LIMIT 1
|
||
`,
|
||
roleID,
|
||
pq.Array(deptCodes),
|
||
module,
|
||
action,
|
||
).Scan(&allowed)
|
||
|
||
if err == nil {
|
||
|
||
log.Printf(
|
||
" ↳ ROLE+DEPT OVERRIDE = %v",
|
||
allowed,
|
||
)
|
||
|
||
return allowed, nil
|
||
}
|
||
|
||
if err != sql.ErrNoRows {
|
||
log.Println("❌ ROLE+DEPT ERR:", err)
|
||
return false, err
|
||
}
|
||
}
|
||
|
||
// ==================================================
|
||
// 3️⃣ ROLE DEFAULT
|
||
// ==================================================
|
||
|
||
var roleAllowed bool
|
||
|
||
err = r.DB.QueryRow(`
|
||
SELECT allowed
|
||
FROM mk_sys_role_permissions
|
||
WHERE role_id = $1
|
||
AND module_code = $2
|
||
AND action = $3
|
||
`,
|
||
roleID,
|
||
module,
|
||
action,
|
||
).Scan(&roleAllowed)
|
||
|
||
if err == nil {
|
||
|
||
log.Printf(
|
||
" ↳ ROLE DEFAULT = %v",
|
||
roleAllowed,
|
||
)
|
||
|
||
return roleAllowed, nil
|
||
}
|
||
|
||
if err != sql.ErrNoRows {
|
||
log.Println("❌ ROLE DEFAULT ERR:", err)
|
||
return false, err
|
||
}
|
||
|
||
// ==================================================
|
||
// 4️⃣ DENY
|
||
// ==================================================
|
||
|
||
log.Println(" ↳ NO RULE → DENY")
|
||
|
||
return false, nil
|
||
}
|