mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-11-13 18:03:53 +00:00
415 lines
12 KiB
Go
415 lines
12 KiB
Go
package security
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// 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
|
|
}
|