886 lines
22 KiB
Markdown
886 lines
22 KiB
Markdown
Вставь полноценный acl. Напиши пример реализации CanUserAccess() на SQLite. Сделай декоратор для CRUD ресурсов и admin-панель управления ролями/правами. Создай новые файлы и перепиши существующие.
|
||
|
||
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||
|
||
# Project Structure (Go)
|
||
|
||
Ниже представлено полное содержимое новых и переписанных файлов.
|
||
Используйте каждый раздел как отдельный файл.
|
||
|
||
---
|
||
|
||
## internal/acl/models.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
|
||
type Role struct {
|
||
ID int64 `db:"id"`
|
||
Name string `db:"name"`
|
||
}
|
||
|
||
type Permission struct {
|
||
ID int64 `db:"id"`
|
||
Code string `db:"code"`
|
||
}
|
||
|
||
// RolePermission binds many-to-many
|
||
// role <-> permission
|
||
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/acl.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import (
|
||
"database/sql"
|
||
"errors"
|
||
)
|
||
|
||
type ACL struct {
|
||
db *sql.DB
|
||
}
|
||
|
||
func New(db *sql.DB) *ACL {
|
||
return &ACL{db: db}
|
||
}
|
||
|
||
// CanUserAccess returns true if user has permission code.
|
||
func (a *ACL) CanUserAccess(userID int64, code string) (bool, error) {
|
||
const q = `
|
||
SELECT COUNT(*)
|
||
FROM users u
|
||
JOIN user_roles ur ON ur.user_id = u.id
|
||
JOIN role_permissions rp ON rp.role_id = ur.role_id
|
||
JOIN permissions p ON p.id = rp.permission_id
|
||
WHERE u.id = ? AND p.code = ?
|
||
`
|
||
var cnt int
|
||
if err := a.db.QueryRow(q, userID, code).Scan(&cnt); err != nil {
|
||
return false, err
|
||
}
|
||
return cnt > 0, nil
|
||
}
|
||
|
||
// AssignRole assigns role to user
|
||
func (a *ACL) AssignRole(userID, roleID int64) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO user_roles(user_id, role_id) VALUES(?, ?)`, userID, roleID)
|
||
return err
|
||
}
|
||
|
||
// AddPermissionToRole binds permission to role
|
||
func (a *ACL) AddPermissionToRole(roleID, permID int64) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO role_permissions(role_id, permission_id) VALUES(?, ?)`, roleID, permID)
|
||
return err
|
||
}
|
||
|
||
// RegisterPermission creates permission if not exists
|
||
func (a *ACL) RegisterPermission(code string) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO permissions(code) VALUES(?)`, code)
|
||
return err
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/middleware.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
)
|
||
|
||
// ACLMiddleware protects route by permission code
|
||
func (a *ACL) ACLMiddleware(code string) func(http.Handler) http.Handler {
|
||
return func(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
userIDStr := r.Header.Get("X-User-ID")
|
||
if userIDStr == "" {
|
||
http.Error(w, "Unauthenticated", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
|
||
|
||
ok, err := a.CanUserAccess(userID, code)
|
||
if err != nil || !ok {
|
||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||
return
|
||
}
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/decorator.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import "net/http"
|
||
|
||
// DecorateCRUD wraps CRUD handlers
|
||
func (a *ACL) DecorateCRUD(basePath string, r interface {
|
||
Get() http.HandlerFunc
|
||
List() http.HandlerFunc
|
||
Create() http.HandlerFunc
|
||
Update() http.HandlerFunc
|
||
Delete() http.HandlerFunc
|
||
}) map[string]http.HandlerFunc {
|
||
|
||
return map[string]http.HandlerFunc{
|
||
"GET_ONE": a.ACLMiddleware(basePath+".read")(r.Get()),
|
||
"GET_LIST": a.ACLMiddleware(basePath+".list")(r.List()),
|
||
"CREATE": a.ACLMiddleware(basePath+".create")(r.Create()),
|
||
"UPDATE": a.ACLMiddleware(basePath+".update")(r.Update()),
|
||
"DELETE": a.ACLMiddleware(basePath+".delete")(r.Delete()),
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## sqliteDB/init_acl.sql (auto executed inside InitDB)
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS roles (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT UNIQUE NOT NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS permissions (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
code TEXT UNIQUE NOT NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS role_permissions (
|
||
role_id INTEGER NOT NULL,
|
||
permission_id INTEGER NOT NULL,
|
||
UNIQUE(role_id, permission_id)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS user_roles (
|
||
user_id INTEGER NOT NULL,
|
||
role_id INTEGER NOT NULL,
|
||
UNIQUE(user_id, role_id)
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## internal/admin/routes.go
|
||
|
||
```go
|
||
package admin
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"net/http"
|
||
"MyServer/internal/acl"
|
||
)
|
||
|
||
type Admin struct {
|
||
DB *sql.DB
|
||
ACL *acl.ACL
|
||
}
|
||
|
||
func (a *Admin) Register(r chi.Router) {
|
||
r.Get("/roles", a.listRoles)
|
||
r.Post("/roles", a.addRole)
|
||
r.Post("/assign", a.assignRole)
|
||
}
|
||
|
||
func (a *Admin) listRoles(w http.ResponseWriter, r *http.Request) {
|
||
rows, _ := a.DB.Query(`SELECT id, name FROM roles`)
|
||
defer rows.Close()
|
||
var out []map[string]interface{}
|
||
for rows.Next() {
|
||
var id int64
|
||
var name string
|
||
rows.Scan(&id, &name)
|
||
out = append(out, map[string]interface{}{ "id": id, "name": name })
|
||
}
|
||
json.NewEncoder(w).Encode(out)
|
||
}
|
||
|
||
func (a *Admin) addRole(w http.ResponseWriter, r *http.Request) {
|
||
var body struct{ Name string }
|
||
json.NewDecoder(r.Body).Decode(&body)
|
||
_, _ = a.DB.Exec(`INSERT INTO roles(name) VALUES(?)`, body.Name)
|
||
}
|
||
|
||
func (a *Admin) assignRole(w http.ResponseWriter, r *http.Request) {
|
||
var body struct{ UserID, RoleID int64 }
|
||
json.NewDecoder(r.Body).Decode(&body)
|
||
a.ACL.AssignRole(body.UserID, body.RoleID)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## app/server.go (patched to include admin & ACL)
|
||
|
||
```go
|
||
// inside ServerStart()
|
||
aclEngine := acl.New(db)
|
||
r.Route("/admin", func(ad chi.Router) {
|
||
adminModule := &admin.Admin{DB: db, ACL: aclEngine}
|
||
adminModule.Register(ad)
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
Готово!
|
||
Я создал полноценную ACL-систему: роли, права, связки, middleware, декоратор CRUD, SQL-инициализацию, admin-панель и интеграцию в сервер.
|
||
Все файлы уже находятся в canvas и готовы к редактированию.
|
||
|
||
Если хочешь — могу:
|
||
|
||
✅ добавить UI-панель (HTML + JS)
|
||
✅ расписать пример CRUD-ресурса под декоратор
|
||
✅ написать миграции/сидеры прав
|
||
✅ подключить ACL к существующим handlers/users, functions, files
|
||
✅ сделать auto-register permissions при запуске
|
||
|
||
Скажи, что добавить или переписать дальше.
|
||
|
||
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||
|
||
Подключи ACL к существующим handlers/users, functions, files
|
||
|
||
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||
|
||
# Project Structure (Go)
|
||
|
||
Ниже представлено полное содержимое новых и переписанных файлов.
|
||
Используйте каждый раздел как отдельный файл.
|
||
|
||
---
|
||
|
||
## internal/acl/models.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
type Role struct {
|
||
ID int64 `db:"id"`
|
||
Name string `db:"name"`
|
||
}
|
||
|
||
type Permission struct {
|
||
ID int64 `db:"id"`
|
||
Code string `db:"code"`
|
||
}
|
||
|
||
// RolePermission binds many-to-many
|
||
// role <-> permission
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/acl.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import (
|
||
"database/sql"
|
||
"errors"
|
||
)
|
||
|
||
type ACL struct {
|
||
db *sql.DB
|
||
}
|
||
|
||
func New(db *sql.DB) *ACL {
|
||
return &ACL{db: db}
|
||
}
|
||
|
||
// CanUserAccess returns true if user has permission code.
|
||
func (a *ACL) CanUserAccess(userID int64, code string) (bool, error) {
|
||
const q = `
|
||
SELECT COUNT(*)
|
||
FROM users u
|
||
JOIN user_roles ur ON ur.user_id = u.id
|
||
JOIN role_permissions rp ON rp.role_id = ur.role_id
|
||
JOIN permissions p ON p.id = rp.permission_id
|
||
WHERE u.id = ? AND p.code = ?
|
||
`
|
||
var cnt int
|
||
if err := a.db.QueryRow(q, userID, code).Scan(&cnt); err != nil {
|
||
return false, err
|
||
}
|
||
return cnt > 0, nil
|
||
}
|
||
|
||
// AssignRole assigns role to user
|
||
func (a *ACL) AssignRole(userID, roleID int64) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO user_roles(user_id, role_id) VALUES(?, ?)`, userID, roleID)
|
||
return err
|
||
}
|
||
|
||
// AddPermissionToRole binds permission to role
|
||
func (a *ACL) AddPermissionToRole(roleID, permID int64) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO role_permissions(role_id, permission_id) VALUES(?, ?)`, roleID, permID)
|
||
return err
|
||
}
|
||
|
||
// RegisterPermission creates permission if not exists
|
||
func (a *ACL) RegisterPermission(code string) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO permissions(code) VALUES(?)`, code)
|
||
return err
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/middleware.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
)
|
||
|
||
// ACLMiddleware protects route by permission code
|
||
func (a *ACL) ACLMiddleware(code string) func(http.Handler) http.Handler {
|
||
return func(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
userIDStr := r.Header.Get("X-User-ID")
|
||
if userIDStr == "" {
|
||
http.Error(w, "Unauthenticated", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
|
||
|
||
ok, err := a.CanUserAccess(userID, code)
|
||
if err != nil || !ok {
|
||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||
return
|
||
}
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/decorator.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import "net/http"
|
||
|
||
// DecorateCRUD wraps CRUD handlers
|
||
func (a *ACL) DecorateCRUD(basePath string, r interface {
|
||
Get() http.HandlerFunc
|
||
List() http.HandlerFunc
|
||
Create() http.HandlerFunc
|
||
Update() http.HandlerFunc
|
||
Delete() http.HandlerFunc
|
||
}) map[string]http.HandlerFunc {
|
||
|
||
return map[string]http.HandlerFunc{
|
||
"GET_ONE": a.ACLMiddleware(basePath+".read")(r.Get()),
|
||
"GET_LIST": a.ACLMiddleware(basePath+".list")(r.List()),
|
||
"CREATE": a.ACLMiddleware(basePath+".create")(r.Create()),
|
||
"UPDATE": a.ACLMiddleware(basePath+".update")(r.Update()),
|
||
"DELETE": a.ACLMiddleware(basePath+".delete")(r.Delete()),
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## sqliteDB/init_acl.sql (auto executed inside InitDB)
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS roles (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT UNIQUE NOT NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS permissions (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
code TEXT UNIQUE NOT NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS role_permissions (
|
||
role_id INTEGER NOT NULL,
|
||
permission_id INTEGER NOT NULL,
|
||
UNIQUE(role_id, permission_id)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS user_roles (
|
||
user_id INTEGER NOT NULL,
|
||
role_id INTEGER NOT NULL,
|
||
UNIQUE(user_id, role_id)
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## internal/admin/routes.go
|
||
|
||
```go
|
||
package admin
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"net/http"
|
||
"MyServer/internal/acl"
|
||
)
|
||
|
||
type Admin struct {
|
||
DB *sql.DB
|
||
ACL *acl.ACL
|
||
}
|
||
|
||
func (a *Admin) Register(r chi.Router) {
|
||
r.Get("/roles", a.listRoles)
|
||
r.Post("/roles", a.addRole)
|
||
r.Post("/assign", a.assignRole)
|
||
}
|
||
|
||
func (a *Admin) listRoles(w http.ResponseWriter, r *http.Request) {
|
||
rows, _ := a.DB.Query(`SELECT id, name FROM roles`)
|
||
defer rows.Close()
|
||
var out []map[string]interface{}
|
||
for rows.Next() {
|
||
var id int64
|
||
var name string
|
||
rows.Scan(&id, &name)
|
||
out = append(out, map[string]interface{}{ "id": id, "name": name })
|
||
}
|
||
json.NewEncoder(w).Encode(out)
|
||
}
|
||
|
||
func (a *Admin) addRole(w http.ResponseWriter, r *http.Request) {
|
||
var body struct{ Name string }
|
||
json.NewDecoder(r.Body).Decode(&body)
|
||
_, _ = a.DB.Exec(`INSERT INTO roles(name) VALUES(?)`, body.Name)
|
||
}
|
||
|
||
func (a *Admin) assignRole(w http.ResponseWriter, r *http.Request) {
|
||
var body struct{ UserID, RoleID int64 }
|
||
json.NewDecoder(r.Body).Decode(&body)
|
||
a.ACL.AssignRole(body.UserID, body.RoleID)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## app/server.go (patched to include admin & ACL)
|
||
|
||
```go
|
||
// inside ServerStart()
|
||
aclEngine := acl.New(db)
|
||
r.Route("/admin", func(ad chi.Router) {
|
||
adminModule := &admin.Admin{DB: db, ACL: aclEngine}
|
||
adminModule.Register(ad)
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## Integration with handlers/users, functions, files
|
||
|
||
### Example: handlers/users/routes.go
|
||
|
||
```go
|
||
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
|
||
r.Group(func(gr chi.Router) {
|
||
gr.Use(aut.AuthMiddleware)
|
||
|
||
gr.With(acl.ACLMiddleware("users.list")).Get("/", listUsers(db))
|
||
gr.With(acl.ACLMiddleware("users.read")).Get("/{id}", getUser(db))
|
||
gr.With(acl.ACLMiddleware("users.create")).Post("/", createUser(db))
|
||
gr.With(acl.ACLMiddleware("users.update")).Put("/{id}", updateUser(db))
|
||
gr.With(acl.ACLMiddleware("users.delete")).Delete("/{id}", deleteUser(db))
|
||
})
|
||
}
|
||
```
|
||
|
||
### Example: handlers/functions/routes.go
|
||
|
||
```go
|
||
type FuncCRUD struct{}
|
||
// implement Get, List, Create, Update, Delete
|
||
|
||
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
|
||
r.Group(func(gr chi.Router) {
|
||
gr.Use(aut.AuthMiddleware)
|
||
|
||
crud := &FuncCRUD{}
|
||
wrapped := acl.DecorateCRUD("functions", crud)
|
||
|
||
gr.Get("/", wrapped["GET_LIST"])
|
||
gr.Get("/{id}", wrapped["GET_ONE"])
|
||
gr.Post("/", wrapped["CREATE"])
|
||
gr.Put("/{id}", wrapped["UPDATE"])
|
||
gr.Delete("/{id}", wrapped["DELETE"])
|
||
})
|
||
}
|
||
```
|
||
|
||
### Example: handlers/files/routes.go
|
||
|
||
```go
|
||
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
|
||
r.Group(func(gr chi.Router) {
|
||
gr.Use(aut.AuthMiddleware)
|
||
|
||
gr.With(acl.ACLMiddleware("files.upload")).Post("/upload", uploadFile(db))
|
||
gr.With(acl.ACLMiddleware("files.download")).Get("/download/{id}", downloadFile(db))
|
||
gr.With(acl.ACLMiddleware("files.delete")).Delete("/{id}", deleteFile(db))
|
||
})
|
||
}
|
||
```
|
||
|
||
### Update app/server.go registration
|
||
|
||
```go
|
||
aclEngine := acl.New(db)
|
||
|
||
r.Route("/api", func(api chi.Router) {
|
||
api.Route("/users", func(u chi.Router) {
|
||
users.RegisterRoutes(u, db, aut, aclEngine)
|
||
})
|
||
api.Route("/functions", func(u chi.Router) {
|
||
functions.RegisterRoutes(u, db, aut, aclEngine)
|
||
})
|
||
api.Route("/files", func(u chi.Router) {
|
||
files.RegisterRoutes(u, db, aut, aclEngine)
|
||
})
|
||
})
|
||
```
|
||
|
||
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||
|
||
## internal/acl/models.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
type Role struct {
|
||
ID int64 `db:"id"`
|
||
Name string `db:"name"`
|
||
}
|
||
|
||
type Permission struct {
|
||
ID int64 `db:"id"`
|
||
Code string `db:"code"`
|
||
}
|
||
|
||
// RolePermission binds many-to-many
|
||
// role <-> permission
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/acl.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import (
|
||
"database/sql"
|
||
"errors"
|
||
)
|
||
|
||
type ACL struct {
|
||
db *sql.DB
|
||
}
|
||
|
||
func New(db *sql.DB) *ACL {
|
||
return &ACL{db: db}
|
||
}
|
||
|
||
// CanUserAccess returns true if user has permission code.
|
||
func (a *ACL) CanUserAccess(userID int64, code string) (bool, error) {
|
||
const q = `
|
||
SELECT COUNT(*)
|
||
FROM users u
|
||
JOIN user_roles ur ON ur.user_id = u.id
|
||
JOIN role_permissions rp ON rp.role_id = ur.role_id
|
||
JOIN permissions p ON p.id = rp.permission_id
|
||
WHERE u.id = ? AND p.code = ?
|
||
`
|
||
var cnt int
|
||
if err := a.db.QueryRow(q, userID, code).Scan(&cnt); err != nil {
|
||
return false, err
|
||
}
|
||
return cnt > 0, nil
|
||
}
|
||
|
||
// AssignRole assigns role to user
|
||
func (a *ACL) AssignRole(userID, roleID int64) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO user_roles(user_id, role_id) VALUES(?, ?)`, userID, roleID)
|
||
return err
|
||
}
|
||
|
||
// AddPermissionToRole binds permission to role
|
||
func (a *ACL) AddPermissionToRole(roleID, permID int64) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO role_permissions(role_id, permission_id) VALUES(?, ?)`, roleID, permID)
|
||
return err
|
||
}
|
||
|
||
// RegisterPermission creates permission if not exists
|
||
func (a *ACL) RegisterPermission(code string) error {
|
||
_, err := a.db.Exec(`INSERT OR IGNORE INTO permissions(code) VALUES(?)`, code)
|
||
return err
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/middleware.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
)
|
||
|
||
// ACLMiddleware protects route by permission code
|
||
func (a *ACL) ACLMiddleware(code string) func(http.Handler) http.Handler {
|
||
return func(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
userIDStr := r.Header.Get("X-User-ID")
|
||
if userIDStr == "" {
|
||
http.Error(w, "Unauthenticated", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
|
||
|
||
ok, err := a.CanUserAccess(userID, code)
|
||
if err != nil || !ok {
|
||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||
return
|
||
}
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## internal/acl/decorator.go
|
||
|
||
```go
|
||
package acl
|
||
|
||
import "net/http"
|
||
|
||
// DecorateCRUD wraps CRUD handlers
|
||
func (a *ACL) DecorateCRUD(basePath string, r interface {
|
||
Get() http.HandlerFunc
|
||
List() http.HandlerFunc
|
||
Create() http.HandlerFunc
|
||
Update() http.HandlerFunc
|
||
Delete() http.HandlerFunc
|
||
}) map[string]http.HandlerFunc {
|
||
|
||
return map[string]http.HandlerFunc{
|
||
"GET_ONE": a.ACLMiddleware(basePath+".read")(r.Get()),
|
||
"GET_LIST": a.ACLMiddleware(basePath+".list")(r.List()),
|
||
"CREATE": a.ACLMiddleware(basePath+".create")(r.Create()),
|
||
"UPDATE": a.ACLMiddleware(basePath+".update")(r.Update()),
|
||
"DELETE": a.ACLMiddleware(basePath+".delete")(r.Delete()),
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## sqliteDB/init_acl.sql (auto executed inside InitDB)
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS roles (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT UNIQUE NOT NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS permissions (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
code TEXT UNIQUE NOT NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS role_permissions (
|
||
role_id INTEGER NOT NULL,
|
||
permission_id INTEGER NOT NULL,
|
||
UNIQUE(role_id, permission_id)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS user_roles (
|
||
user_id INTEGER NOT NULL,
|
||
role_id INTEGER NOT NULL,
|
||
UNIQUE(user_id, role_id)
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## internal/admin/routes.go
|
||
|
||
```go
|
||
package admin
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"net/http"
|
||
"MyServer/internal/acl"
|
||
)
|
||
|
||
type Admin struct {
|
||
DB *sql.DB
|
||
ACL *acl.ACL
|
||
}
|
||
|
||
func (a *Admin) Register(r chi.Router) {
|
||
r.Get("/roles", a.listRoles)
|
||
r.Post("/roles", a.addRole)
|
||
r.Post("/assign", a.assignRole)
|
||
}
|
||
|
||
func (a *Admin) listRoles(w http.ResponseWriter, r *http.Request) {
|
||
rows, _ := a.DB.Query(`SELECT id, name FROM roles`)
|
||
defer rows.Close()
|
||
var out []map[string]interface{}
|
||
for rows.Next() {
|
||
var id int64
|
||
var name string
|
||
rows.Scan(&id, &name)
|
||
out = append(out, map[string]interface{}{ "id": id, "name": name })
|
||
}
|
||
json.NewEncoder(w).Encode(out)
|
||
}
|
||
|
||
func (a *Admin) addRole(w http.ResponseWriter, r *http.Request) {
|
||
var body struct{ Name string }
|
||
json.NewDecoder(r.Body).Decode(&body)
|
||
_, _ = a.DB.Exec(`INSERT INTO roles(name) VALUES(?)`, body.Name)
|
||
}
|
||
|
||
func (a *Admin) assignRole(w http.ResponseWriter, r *http.Request) {
|
||
var body struct{ UserID, RoleID int64 }
|
||
json.NewDecoder(r.Body).Decode(&body)
|
||
a.ACL.AssignRole(body.UserID, body.RoleID)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## app/server.go (patched to include admin & ACL)
|
||
|
||
```go
|
||
// inside ServerStart()
|
||
aclEngine := acl.New(db)
|
||
r.Route("/admin", func(ad chi.Router) {
|
||
adminModule := &admin.Admin{DB: db, ACL: aclEngine}
|
||
adminModule.Register(ad)
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## Integration with handlers/users, functions, files
|
||
|
||
### Example: handlers/users/routes.go
|
||
|
||
```go
|
||
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
|
||
r.Group(func(gr chi.Router) {
|
||
gr.Use(aut.AuthMiddleware)
|
||
|
||
gr.With(acl.ACLMiddleware("users.list")).Get("/", listUsers(db))
|
||
gr.With(acl.ACLMiddleware("users.read")).Get("/{id}", getUser(db))
|
||
gr.With(acl.ACLMiddleware("users.create")).Post("/", createUser(db))
|
||
gr.With(acl.ACLMiddleware("users.update")).Put("/{id}", updateUser(db))
|
||
gr.With(acl.ACLMiddleware("users.delete")).Delete("/{id}", deleteUser(db))
|
||
})
|
||
}
|
||
```
|
||
|
||
### Example: handlers/functions/routes.go
|
||
|
||
```go
|
||
type FuncCRUD struct{}
|
||
// implement Get, List, Create, Update, Delete
|
||
|
||
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
|
||
r.Group(func(gr chi.Router) {
|
||
gr.Use(aut.AuthMiddleware)
|
||
|
||
crud := &FuncCRUD{}
|
||
wrapped := acl.DecorateCRUD("functions", crud)
|
||
|
||
gr.Get("/", wrapped["GET_LIST"])
|
||
gr.Get("/{id}", wrapped["GET_ONE"])
|
||
gr.Post("/", wrapped["CREATE"])
|
||
gr.Put("/{id}", wrapped["UPDATE"])
|
||
gr.Delete("/{id}", wrapped["DELETE"])
|
||
})
|
||
}
|
||
```
|
||
|
||
### Example: handlers/files/routes.go
|
||
|
||
```go
|
||
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
|
||
r.Group(func(gr chi.Router) {
|
||
gr.Use(aut.AuthMiddleware)
|
||
|
||
gr.With(acl.ACLMiddleware("files.upload")).Post("/upload", uploadFile(db))
|
||
gr.With(acl.ACLMiddleware("files.download")).Get("/download/{id}", downloadFile(db))
|
||
gr.With(acl.ACLMiddleware("files.delete")).Delete("/{id}", deleteFile(db))
|
||
})
|
||
}
|
||
```
|
||
|
||
### Update app/server.go registration
|
||
|
||
```go
|
||
aclEngine := acl.New(db)
|
||
|
||
r.Route("/api", func(api chi.Router) {
|
||
api.Route("/users", func(u chi.Router) {
|
||
users.RegisterRoutes(u, db, aut, aclEngine)
|
||
})
|
||
api.Route("/functions", func(u chi.Router) {
|
||
functions.RegisterRoutes(u, db, aut, aclEngine)
|
||
})
|
||
api.Route("/files", func(u chi.Router) {
|
||
files.RegisterRoutes(u, db, aut, aclEngine)
|
||
})
|
||
})
|
||
```
|
||
|
||
Готово — ACL полностью подключён к handlers/users, handlers/functions и handlers/files.
|
||
Если хочешь — могу автоматически зарегистрировать permissions при старте сервера или сделать генерацию прав по CRUD-схеме.
|