From 021742a488ccc339e48317e27e5fd7266f2a8103 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Tue, 21 Apr 2026 11:06:24 +0300 Subject: [PATCH] feat: Add Casbin RBAC service with policy management, permission checking, and default role configurations - Add CasbinService struct with enforcer/db fields and NewCasbinService constructor with GORM adapter initialization - Add Casbin model configuration with request_definition/policy_definition/role_definition/policy_effect/matchers for RBAC - Add initializeDefaultPolicies with admin (full access), operator (manage services/monitoring), and viewer (read-only) role permissions - Add CheckPermission method --- apps/api-server/internal/rbac/casbin.go | 211 ++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 apps/api-server/internal/rbac/casbin.go diff --git a/apps/api-server/internal/rbac/casbin.go b/apps/api-server/internal/rbac/casbin.go new file mode 100644 index 0000000..95f0ef6 --- /dev/null +++ b/apps/api-server/internal/rbac/casbin.go @@ -0,0 +1,211 @@ +package rbac + +import ( + "context" + "fmt" + "log" + + "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" + gormadapter "github.com/casbin/gorm-adapter/v3" + "gorm.io/gorm" + + "github.com/nutcas3/telecom-platform/apps/api-server/internal/models" +) + +// CasbinService provides sophisticated RBAC using Casbin +type CasbinService struct { + enforcer *casbin.Enforcer + db *gorm.DB +} + +// NewCasbinService creates a new Casbin service +func NewCasbinService(db *gorm.DB) (*CasbinService, error) { + // Initialize GORM adapter for Casbin + adapter, err := gormadapter.NewAdapterByDB(db) + if err != nil { + return nil, fmt.Errorf("failed to create Casbin adapter: %w", err) + } + + // Casbin model configuration for RBAC + modelText := ` +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act + ` + + // Create model from text + m, err := model.NewModelFromString(modelText) + if err != nil { + return nil, fmt.Errorf("failed to create Casbin model: %w", err) + } + + // Create enforcer + enforcer, err := casbin.NewEnforcer(m, adapter) + if err != nil { + return nil, fmt.Errorf("failed to create Casbin enforcer: %w", err) + } + + service := &CasbinService{ + enforcer: enforcer, + db: db, + } + + // Initialize default policies + if err := service.initializeDefaultPolicies(); err != nil { + log.Printf("Warning: failed to initialize default policies: %v", err) + } + + return service, nil +} + +// initializeDefaultPolicies sets up default RBAC policies +func (s *CasbinService) initializeDefaultPolicies() error { + // Default role permissions + defaultPolicies := [][]string{ + // Admin permissions (full access) + {"admin", "/v1/services", "GET"}, + {"admin", "/v1/services", "POST"}, + {"admin", "/v1/monitoring", "GET"}, + {"admin", "/v1/deploy", "GET"}, + {"admin", "/v1/deploy", "POST"}, + {"admin", "/v1/plugins", "GET"}, + {"admin", "/v1/plugins", "POST"}, + {"admin", "/v1/plugins", "DELETE"}, + {"admin", "/v1/automation", "GET"}, + {"admin", "/v1/automation", "POST"}, + {"admin", "/v1/billing", "GET"}, + {"admin", "/v1/billing", "POST"}, + {"admin", "/v1/config", "GET"}, + {"admin", "/v1/config", "POST"}, + {"admin", "/v1/chaos", "GET"}, + {"admin", "/v1/chaos", "POST"}, + + // Operator permissions (can manage services and monitoring) + {"operator", "/v1/services", "GET"}, + {"operator", "/v1/services", "POST"}, + {"operator", "/v1/monitoring", "GET"}, + {"operator", "/v1/deploy", "GET"}, + {"operator", "/v1/plugins", "GET"}, + {"operator", "/v1/automation", "GET"}, + {"operator", "/v1/billing", "GET"}, + {"operator", "/v1/config", "GET"}, + {"operator", "/v1/chaos", "GET"}, + + // Viewer permissions (read-only access) + {"viewer", "/v1/services", "GET"}, + {"viewer", "/v1/monitoring", "GET"}, + {"viewer", "/v1/deploy", "GET"}, + {"viewer", "/v1/plugins", "GET"}, + {"viewer", "/v1/automation", "GET"}, + {"viewer", "/v1/billing", "GET"}, + {"viewer", "/v1/config", "GET"}, + {"viewer", "/v1/chaos", "GET"}, + } + + // Add policies if they don't exist + for _, policy := range defaultPolicies { + ok, err := s.enforcer.AddPolicy(policy) + if err != nil { + return fmt.Errorf("failed to add policy %v: %w", policy, err) + } + if ok { + log.Printf("Added default policy: %v", policy) + } + } + + return nil +} + +// CheckPermission checks if a user has permission for a specific action on a resource +func (s *CasbinService) CheckPermission(ctx context.Context, userID uint, resource, action string) (bool, error) { + // Get user with role + var user models.User + if err := s.db.WithContext(ctx).First(&user, userID).Error; err != nil { + return false, fmt.Errorf("failed to get user: %w", err) + } + + // Check user's role for permission + allowed, err := s.enforcer.Enforce(user.Role, resource, action) + if err != nil { + return false, fmt.Errorf("failed to enforce policy: %w", err) + } + + return allowed, nil +} + +// AddPolicy adds a new policy to Casbin +func (s *CasbinService) AddPolicy(role, resource, action string) error { + ok, err := s.enforcer.AddPolicy([]string{role, resource, action}) + if err != nil { + return fmt.Errorf("failed to add policy: %w", err) + } + if !ok { + return fmt.Errorf("policy already exists") + } + return nil +} + +// RemovePolicy removes a policy from Casbin +func (s *CasbinService) RemovePolicy(role, resource, action string) error { + ok, err := s.enforcer.RemovePolicy([]string{role, resource, action}) + if err != nil { + return fmt.Errorf("failed to remove policy: %w", err) + } + if !ok { + return fmt.Errorf("policy does not exist") + } + return nil +} + +// GetPolicies returns all policies +func (s *CasbinService) GetPolicies() ([][]string, error) { + policies, err := s.enforcer.GetPolicy() + return policies, err +} + +// GetRolesForUser returns all roles assigned to a user +func (s *CasbinService) GetRolesForUser(userID uint) ([]string, error) { + var user models.User + if err := s.db.First(&user, userID).Error; err != nil { + return nil, fmt.Errorf("failed to get user: %w", err) + } + + return []string{user.Role}, nil +} + +// AssignRole assigns a role to a user +func (s *CasbinService) AssignRole(userID uint, roleName string) error { + // This would typically involve updating the user_roles junction table + // For now, we'll assume this is handled by the user service + return nil +} + +// RemoveRole removes a role from a user +func (s *CasbinService) RemoveRole(userID uint, roleName string) error { + // This would typically involve updating the user_roles junction table + // For now, we'll assume this is handled by the user service + return nil +} + +// GetPermissionsForRole returns all permissions for a role +func (s *CasbinService) GetPermissionsForRole(role string) ([][]string, error) { + permissions, err := s.enforcer.GetPermissionsForUser(role) + return permissions, err +} + +// HasPermissionForRole checks if a role has a specific permission +func (s *CasbinService) HasPermissionForRole(role, resource, action string) (bool, error) { + return s.enforcer.Enforce(role, resource, action) +}