ResolveSpec/pkg/security/callbacks_example.go
2025-11-11 11:03:02 +02:00

416 lines
13 KiB
Go

package security
import (
"fmt"
"net/http"
"strconv"
"strings"
// DBM "github.com/bitechdev/GoCore/pkg/models"
)
// This file provides example implementations of the required security callbacks.
// Copy these functions and modify them to match your authentication and database schema.
// =============================================================================
// EXAMPLE 1: Simple Header-Based Authentication
// =============================================================================
// ExampleAuthenticateFromHeader extracts user ID from X-User-ID header
func ExampleAuthenticateFromHeader(r *http.Request) (userID int, roles string, err error) {
userIDStr := r.Header.Get("X-User-ID")
if userIDStr == "" {
return 0, "", fmt.Errorf("X-User-ID header not provided")
}
userID, err = strconv.Atoi(userIDStr)
if err != nil {
return 0, "", fmt.Errorf("invalid user ID format: %v", err)
}
// Optionally extract roles
roles = r.Header.Get("X-User-Roles") // comma-separated: "admin,manager"
return userID, roles, nil
}
// =============================================================================
// EXAMPLE 2: JWT Token Authentication
// =============================================================================
// ExampleAuthenticateFromJWT parses a JWT token and extracts user info
// You'll need to import a JWT library like github.com/golang-jwt/jwt/v5
func ExampleAuthenticateFromJWT(r *http.Request) (userID int, roles string, err error) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return 0, "", fmt.Errorf("authorization header not provided")
}
// Extract Bearer token
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if tokenString == authHeader {
return 0, "", fmt.Errorf("invalid authorization header format")
}
// TODO: Parse and validate JWT token
// Example using github.com/golang-jwt/jwt/v5:
//
// token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// return []byte(os.Getenv("JWT_SECRET")), nil
// })
//
// if err != nil || !token.Valid {
// return 0, "", fmt.Errorf("invalid token: %v", err)
// }
//
// claims := token.Claims.(jwt.MapClaims)
// userID = int(claims["user_id"].(float64))
// roles = claims["roles"].(string)
return 0, "", fmt.Errorf("JWT parsing not implemented - see example above")
}
// =============================================================================
// EXAMPLE 3: Session Cookie Authentication
// =============================================================================
// ExampleAuthenticateFromSession validates a session cookie
func ExampleAuthenticateFromSession(r *http.Request) (userID int, roles string, err error) {
sessionCookie, err := r.Cookie("session_id")
if err != nil {
return 0, "", fmt.Errorf("session cookie not found")
}
// TODO: Validate session against your session store (Redis, database, etc.)
// Example:
//
// session, err := sessionStore.Get(sessionCookie.Value)
// if err != nil {
// return 0, "", fmt.Errorf("invalid session")
// }
//
// userID = session.UserID
// roles = session.Roles
_ = sessionCookie // Suppress unused warning until implemented
return 0, "", fmt.Errorf("session validation not implemented - see example above")
}
// =============================================================================
// EXAMPLE 4: Column Security - Database Implementation
// =============================================================================
// ExampleLoadColumnSecurityFromDatabase loads column security rules from database
// This implementation assumes the following database schema:
//
// CREATE TABLE core.secacces (
// rid_secacces SERIAL PRIMARY KEY,
// rid_hub INTEGER,
// control TEXT, -- Format: "schema.table.column"
// accesstype TEXT, -- "mask" or "hide"
// jsonvalue JSONB -- Masking configuration
// );
//
// CREATE TABLE core.hub_link (
// rid_hub_parent INTEGER, -- Security group ID
// rid_hub_child INTEGER, -- User ID
// parent_hubtype TEXT -- 'secgroup'
// );
func ExampleLoadColumnSecurityFromDatabase(pUserID int, pSchema, pTablename string) ([]ColumnSecurity, error) {
colSecList := make([]ColumnSecurity, 0)
// getExtraFilters := func(pStr string) map[string]string {
// mp := make(map[string]string, 0)
// for i, val := range strings.Split(pStr, ",") {
// if i <= 1 {
// continue
// }
// vals := strings.Split(val, ":")
// if len(vals) > 1 {
// mp[vals[0]] = vals[1]
// }
// }
// return mp
// }
// rows, err := DBM.DBConn.Raw(fmt.Sprintf(`
// SELECT a.rid_secacces, a.control, a.accesstype, a.jsonvalue
// FROM core.secacces a
// WHERE a.rid_hub IN (
// SELECT l.rid_hub_parent
// FROM core.hub_link l
// WHERE l.parent_hubtype = 'secgroup'
// AND l.rid_hub_child = ?
// )
// AND control ILIKE '%s.%s%%'
// `, pSchema, pTablename), pUserID).Rows()
// defer func() {
// if rows != nil {
// rows.Close()
// }
// }()
// if err != nil {
// return colSecList, fmt.Errorf("failed to fetch column security from SQL: %v", err)
// }
// for rows.Next() {
// var rid int
// var jsondata []byte
// var control, accesstype string
// err = rows.Scan(&rid, &control, &accesstype, &jsondata)
// if err != nil {
// return colSecList, fmt.Errorf("failed to scan column security: %v", err)
// }
// parts := strings.Split(control, ",")
// ids := strings.Split(parts[0], ".")
// if len(ids) < 3 {
// continue
// }
// jsonvalue := make(map[string]interface{})
// if len(jsondata) > 1 {
// err = json.Unmarshal(jsondata, &jsonvalue)
// if err != nil {
// logger.Error("Failed to parse json: %v", err)
// }
// }
// colsec := ColumnSecurity{
// Schema: pSchema,
// Tablename: pTablename,
// UserID: pUserID,
// Path: ids[2:],
// ExtraFilters: getExtraFilters(control),
// Accesstype: accesstype,
// Control: control,
// ID: int(rid),
// }
// // Parse masking configuration from JSON
// if v, ok := jsonvalue["start"]; ok {
// if value, ok := v.(float64); ok {
// colsec.MaskStart = int(value)
// }
// }
// if v, ok := jsonvalue["end"]; ok {
// if value, ok := v.(float64); ok {
// colsec.MaskEnd = int(value)
// }
// }
// if v, ok := jsonvalue["invert"]; ok {
// if value, ok := v.(bool); ok {
// colsec.MaskInvert = value
// }
// }
// if v, ok := jsonvalue["char"]; ok {
// if value, ok := v.(string); ok {
// colsec.MaskChar = value
// }
// }
// colSecList = append(colSecList, colsec)
// }
return colSecList, nil
}
// =============================================================================
// EXAMPLE 5: Column Security - In-Memory/Static Configuration
// =============================================================================
// ExampleLoadColumnSecurityFromConfig loads column security from static config
func ExampleLoadColumnSecurityFromConfig(pUserID int, pSchema, pTablename string) ([]ColumnSecurity, error) {
// Example: Define security rules in code or load from config file
securityRules := map[string][]ColumnSecurity{
"public.employees": {
{
Schema: "public",
Tablename: "employees",
Path: []string{"ssn"},
Accesstype: "mask",
MaskStart: 5,
MaskEnd: 0,
MaskChar: "*",
},
{
Schema: "public",
Tablename: "employees",
Path: []string{"salary"},
Accesstype: "hide",
},
},
"public.customers": {
{
Schema: "public",
Tablename: "customers",
Path: []string{"credit_card"},
Accesstype: "mask",
MaskStart: 12,
MaskEnd: 0,
MaskChar: "*",
},
},
}
key := fmt.Sprintf("%s.%s", pSchema, pTablename)
rules, ok := securityRules[key]
if !ok {
return []ColumnSecurity{}, nil // No rules for this table
}
// Filter by user ID if needed
// For this example, all rules apply to all users
return rules, nil
}
// =============================================================================
// EXAMPLE 6: Row Security - Database Implementation
// =============================================================================
// ExampleLoadRowSecurityFromDatabase loads row security rules from database
// This implementation assumes a PostgreSQL function:
//
// CREATE FUNCTION core.api_sec_rowtemplate(
// p_schema TEXT,
// p_table TEXT,
// p_userid INTEGER
// ) RETURNS TABLE (
// p_retval INTEGER,
// p_errmsg TEXT,
// p_template TEXT,
// p_block BOOLEAN
// );
func ExampleLoadRowSecurityFromDatabase(pUserID int, pSchema, pTablename string) (RowSecurity, error) {
record := RowSecurity{
Schema: pSchema,
Tablename: pTablename,
UserID: pUserID,
}
// rows, err := DBM.DBConn.Raw(`
// SELECT r.p_retval, r.p_errmsg, r.p_template, r.p_block
// FROM core.api_sec_rowtemplate(?, ?, ?) r
// `, pSchema, pTablename, pUserID).Rows()
// defer func() {
// if rows != nil {
// rows.Close()
// }
// }()
// if err != nil {
// return record, fmt.Errorf("failed to fetch row security from SQL: %v", err)
// }
// for rows.Next() {
// var retval int
// var errmsg string
// err = rows.Scan(&retval, &errmsg, &record.Template, &record.HasBlock)
// if err != nil {
// return record, fmt.Errorf("failed to scan row security: %v", err)
// }
// if retval != 0 {
// return RowSecurity{}, fmt.Errorf("api_sec_rowtemplate error: %s", errmsg)
// }
// }
return record, nil
}
// =============================================================================
// EXAMPLE 7: Row Security - Static Configuration
// =============================================================================
// ExampleLoadRowSecurityFromConfig loads row security from static config
func ExampleLoadRowSecurityFromConfig(pUserID int, pSchema, pTablename string) (RowSecurity, error) {
// Define row security templates based on entity
templates := map[string]string{
"public.orders": "user_id = {UserID}", // Users see only their orders
"public.documents": "user_id = {UserID} OR is_public = true", // Users see their docs + public docs
"public.employees": "department_id IN (SELECT department_id FROM user_departments WHERE user_id = {UserID})", // Complex filter
}
// Define blocked entities (no access at all)
blockedEntities := map[string][]int{
"public.admin_logs": {}, // All users blocked (empty list = block all)
"public.audit_logs": {1, 2, 3}, // Block users 1, 2, 3
}
key := fmt.Sprintf("%s.%s", pSchema, pTablename)
// Check if entity is blocked for this user
if blockedUsers, ok := blockedEntities[key]; ok {
if len(blockedUsers) == 0 {
// Block all users
return RowSecurity{
Schema: pSchema,
Tablename: pTablename,
UserID: pUserID,
HasBlock: true,
}, nil
}
// Check if specific user is blocked
for _, blockedUserID := range blockedUsers {
if blockedUserID == pUserID {
return RowSecurity{
Schema: pSchema,
Tablename: pTablename,
UserID: pUserID,
HasBlock: true,
}, nil
}
}
}
// Get template for this entity
template, ok := templates[key]
if !ok {
// No row security defined - allow all rows
return RowSecurity{
Schema: pSchema,
Tablename: pTablename,
UserID: pUserID,
Template: "",
HasBlock: false,
}, nil
}
return RowSecurity{
Schema: pSchema,
Tablename: pTablename,
UserID: pUserID,
Template: template,
HasBlock: false,
}, nil
}
// =============================================================================
// SETUP HELPER: Configure All Callbacks
// =============================================================================
// SetupCallbacksExample shows how to configure all callbacks
func SetupCallbacksExample() {
// Option 1: Use database-backed security (production)
GlobalSecurity.AuthenticateCallback = ExampleAuthenticateFromJWT
GlobalSecurity.LoadColumnSecurityCallback = ExampleLoadColumnSecurityFromDatabase
GlobalSecurity.LoadRowSecurityCallback = ExampleLoadRowSecurityFromDatabase
// Option 2: Use static configuration (development/testing)
// GlobalSecurity.AuthenticateCallback = ExampleAuthenticateFromHeader
// GlobalSecurity.LoadColumnSecurityCallback = ExampleLoadColumnSecurityFromConfig
// GlobalSecurity.LoadRowSecurityCallback = ExampleLoadRowSecurityFromConfig
// Option 3: Mix and match
// GlobalSecurity.AuthenticateCallback = ExampleAuthenticateFromJWT
// GlobalSecurity.LoadColumnSecurityCallback = ExampleLoadColumnSecurityFromConfig
// GlobalSecurity.LoadRowSecurityCallback = ExampleLoadRowSecurityFromDatabase
}