Files
probo/pkg/iam/policy/action.go
Bryan Frimin e61d72f15d Rewrite permission system
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
2026-01-17 10:07:41 -08:00

127 lines
3.9 KiB
Go

// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package policy
import (
"fmt"
"strings"
)
// Action represents a permission action in the format "service:resource:operation"
// Examples: "iam:identity:get", "documents:document:write", "risks:risk:delete"
type Action string
// ActionDefinition provides metadata about an action for documentation and validation.
type ActionDefinition struct {
Action Action
Service string // e.g., "iam", "documents", "risks"
Resource string // e.g., "identity", "document", "risk"
Operation string // e.g., "get", "list", "create", "update", "delete"
Description string
}
// ActionRegistry holds all registered actions and provides lookup/validation.
type ActionRegistry struct {
actions map[Action]ActionDefinition
}
// NewActionRegistry creates a new empty action registry.
func NewActionRegistry() *ActionRegistry {
return &ActionRegistry{
actions: make(map[Action]ActionDefinition),
}
}
// Register adds an action definition to the registry.
// Returns an error if the action is already registered.
func (r *ActionRegistry) Register(def ActionDefinition) error {
if _, exists := r.actions[def.Action]; exists {
return fmt.Errorf("action %q already registered", def.Action)
}
// Validate action format
if err := validateActionFormat(def.Action); err != nil {
return fmt.Errorf("invalid action format: %w", err)
}
r.actions[def.Action] = def
return nil
}
// MustRegister is like Register but panics on error.
// Useful for setting up registries in application startup.
func (r *ActionRegistry) MustRegister(def ActionDefinition) {
if err := r.Register(def); err != nil {
panic(err)
}
}
// Get returns the definition for an action, or false if not found.
func (r *ActionRegistry) Get(action Action) (ActionDefinition, bool) {
def, ok := r.actions[action]
return def, ok
}
// Exists checks if an action is registered.
func (r *ActionRegistry) Exists(action Action) bool {
_, ok := r.actions[action]
return ok
}
// All returns all registered action definitions.
func (r *ActionRegistry) All() []ActionDefinition {
result := make([]ActionDefinition, 0, len(r.actions))
for _, def := range r.actions {
result = append(result, def)
}
return result
}
// ByService returns all actions for a given service.
func (r *ActionRegistry) ByService(service string) []ActionDefinition {
var result []ActionDefinition
for _, def := range r.actions {
if def.Service == service {
result = append(result, def)
}
}
return result
}
// validateActionFormat ensures action follows "service:resource:operation" format.
func validateActionFormat(action Action) error {
parts := strings.Split(string(action), ":")
if len(parts) != 3 {
return fmt.Errorf("action must have format 'service:resource:operation', got %q", action)
}
for i, part := range parts {
if part == "" {
return fmt.Errorf("action part %d is empty in %q", i, action)
}
}
return nil
}
// ParseAction extracts service, resource, and operation from an action string.
func ParseAction(action Action) (service, resource, operation string, err error) {
parts := strings.Split(string(action), ":")
if len(parts) != 3 {
return "", "", "", fmt.Errorf("invalid action format: %q", action)
}
return parts[0], parts[1], parts[2], nil
}