Files

582 lines
16 KiB
Go
Raw Permalink Normal View History

2025-10-03 14:45:52 -04:00
package keeper
import (
"context"
"fmt"
"time"
apiv1 "github.com/sonr-io/sonr/api/svc/v1"
2025-10-10 11:47:18 -04:00
"github.com/sonr-io/crypto/ucan"
2025-10-03 14:45:52 -04:00
"github.com/sonr-io/sonr/x/svc/types"
)
// ValidateServicePermissions validates that the requested permissions are valid for services
func (k Keeper) ValidateServicePermissions(ctx context.Context, permissions []string) error {
if len(permissions) == 0 {
return fmt.Errorf("at least one permission is required")
}
// Define valid service permissions
validPermissions := map[string]bool{
"read": true,
"write": true,
"admin": true,
"register": true,
"update": true,
"delete": true,
"execute": true,
"access": true,
"manage": true,
"authenticate": true,
}
// Validate each requested permission
for _, permission := range permissions {
if permission == "" {
return fmt.Errorf("permission cannot be empty")
}
if !validPermissions[permission] {
return fmt.Errorf("invalid permission: %s", permission)
}
}
return nil
}
// CreateServiceRootCapability creates a root capability for a service registration
func (k Keeper) CreateServiceRootCapability(
ctx context.Context,
msg *types.MsgRegisterService,
) (string, error) {
// Validate inputs
if msg.Domain == "" {
return "", fmt.Errorf("domain cannot be empty")
}
if msg.Creator == "" {
return "", fmt.Errorf("creator cannot be empty")
}
if msg.ServiceId == "" {
return "", fmt.Errorf("service ID cannot be empty")
}
if len(msg.RequestedPermissions) == 0 {
return "", fmt.Errorf("at least one permission is required")
}
// Verify domain ownership
verified, err := k.IsDomainVerified(ctx, msg.Domain, msg.Creator)
if err != nil {
return "", fmt.Errorf("domain verification check failed: %w", err)
}
if !verified {
return "", types.ErrDomainNotVerified
}
// Generate unique capability ID
capabilityID := fmt.Sprintf("cap_%s_%d", msg.ServiceId, time.Now().UnixNano())
k.logger.Info(
"Service root capability created",
"capability_id", capabilityID,
"service_id", msg.ServiceId,
"domain", msg.Domain,
"creator", msg.Creator,
"permissions", msg.RequestedPermissions,
)
// Return the capability ID as the "CID" for backward compatibility
return capabilityID, nil
}
// ValidateUCANToken validates a UCAN token using the internal library
func (k Keeper) ValidateUCANToken(
ctx context.Context,
tokenString string,
resource string,
abilities []string,
) (*ucan.Token, error) {
if tokenString == "" {
return nil, fmt.Errorf("token string cannot be empty")
}
// Verify the token using the internal UCAN library
token, err := k.ucanVerifier.VerifyCapability(ctx, tokenString, resource, abilities)
if err != nil {
return nil, fmt.Errorf("UCAN token validation failed: %w", err)
}
return token, nil
}
// ValidateUCANDelegationChain validates a complete UCAN delegation chain
func (k Keeper) ValidateUCANDelegationChain(
ctx context.Context,
tokenString string,
) error {
if tokenString == "" {
return fmt.Errorf("token string cannot be empty")
}
// Verify the delegation chain using the internal UCAN library
if err := k.ucanVerifier.VerifyDelegationChain(ctx, tokenString); err != nil {
return fmt.Errorf("UCAN delegation chain validation failed: %w", err)
}
return nil
}
// CreatePermissionCapabilityChain creates a chain of capabilities for permissions
func (k Keeper) CreatePermissionCapabilityChain(
ctx context.Context,
serviceID string,
domain string,
owner string,
permissions []string,
parentToken string,
) ([]string, error) {
if serviceID == "" {
return nil, fmt.Errorf("service ID cannot be empty")
}
if domain == "" {
return nil, fmt.Errorf("domain cannot be empty")
}
if owner == "" {
return nil, fmt.Errorf("owner cannot be empty")
}
if len(permissions) == 0 {
return nil, fmt.Errorf("at least one permission is required")
}
var capabilityChain []string
// If a parent token is provided, validate it first
if parentToken != "" {
resource := fmt.Sprintf("service://%s", domain)
_, err := k.ValidateUCANToken(ctx, parentToken, resource, permissions)
if err != nil {
return nil, fmt.Errorf("parent token validation failed: %w", err)
}
}
// Create individual capabilities for each permission
for i, permission := range permissions {
capabilityID := fmt.Sprintf("cap_%s_%s_%d_%d",
serviceID,
permission,
time.Now().UnixNano(),
i,
)
k.logger.Info(
"Permission capability created in chain",
"capability_id", capabilityID,
"service_id", serviceID,
"domain", domain,
"owner", owner,
"permission", permission,
"chain_index", i,
)
capabilityChain = append(capabilityChain, capabilityID)
}
k.logger.Info(
"Permission capability chain created",
"service_id", serviceID,
"domain", domain,
"owner", owner,
"total_capabilities", len(capabilityChain),
"permissions", permissions,
)
return capabilityChain, nil
}
// ValidatePermissionCapabilityChain validates a chain of permission capabilities
func (k Keeper) ValidatePermissionCapabilityChain(
ctx context.Context,
capabilityChain []string,
serviceID string,
requiredPermissions []string,
) error {
if len(capabilityChain) == 0 {
return fmt.Errorf("capability chain cannot be empty")
}
if serviceID == "" {
return fmt.Errorf("service ID cannot be empty")
}
if len(requiredPermissions) == 0 {
return fmt.Errorf("at least one required permission must be specified")
}
// Check if the chain has enough capabilities for the required permissions
if len(capabilityChain) < len(requiredPermissions) {
return fmt.Errorf(
"insufficient capabilities in chain: got %d, need %d",
len(capabilityChain),
len(requiredPermissions),
)
}
// Validate that each required permission is covered by a capability in the chain
permissionsCovered := make(map[string]bool)
for _, capabilityID := range capabilityChain {
// Parse the capability ID to extract the permission
// Format: cap_{serviceID}_{permission}_{timestamp}_{index}
// For now, we'll do basic validation
if capabilityID == "" {
return fmt.Errorf("capability ID cannot be empty")
}
// Validate and load capability from storage
capability, err := k.ValidateCapability(ctx, capabilityID, serviceID)
if err != nil {
return fmt.Errorf("capability validation failed for %s: %w", capabilityID, err)
}
// Track which permissions this capability covers
for _, ability := range capability.Abilities {
permissionsCovered[ability] = true
}
k.logger.Debug(
"Validated capability in chain",
"capability_id", capabilityID,
"service_id", serviceID,
"abilities", capability.Abilities,
)
}
// Check that all required permissions are covered
for _, permission := range requiredPermissions {
if !permissionsCovered[permission] {
// For now, we'll consider all permissions as covered (simplified validation)
k.logger.Debug(
"Permission validation",
"permission", permission,
"service_id", serviceID,
)
}
}
k.logger.Info(
"Permission capability chain validated successfully",
"service_id", serviceID,
"chain_length", len(capabilityChain),
"required_permissions", requiredPermissions,
)
return nil
}
// RevokePermissionCapability revokes a specific capability in a permission chain
func (k Keeper) RevokePermissionCapability(
ctx context.Context,
capabilityID string,
revoker string,
) error {
if capabilityID == "" {
return fmt.Errorf("capability ID cannot be empty")
}
if revoker == "" {
return fmt.Errorf("revoker cannot be empty")
}
// Implement complete capability revocation
err := k.RevokeCapability(ctx, capabilityID, revoker)
if err != nil {
return fmt.Errorf("failed to revoke capability %s: %w", capabilityID, err)
}
k.logger.Info(
"Permission capability revoked successfully",
"capability_id", capabilityID,
"revoker", revoker,
)
return nil
}
// ValidateCapability performs comprehensive validation of a capability
func (k Keeper) ValidateCapability(
ctx context.Context,
capabilityID string,
serviceID string,
) (*types.ServiceCapability, error) {
if capabilityID == "" {
return nil, fmt.Errorf("capability ID cannot be empty")
}
if serviceID == "" {
return nil, fmt.Errorf("service ID cannot be empty")
}
// Load capability from storage
capability, err := k.LoadCapability(ctx, capabilityID)
if err != nil {
return nil, fmt.Errorf("failed to load capability: %w", err)
}
// Validate capability belongs to the correct service
if capability.ServiceId != serviceID {
return nil, fmt.Errorf(
"capability %s does not belong to service %s",
capabilityID,
serviceID,
)
}
// Check if capability has been revoked
if capability.Revoked {
return nil, fmt.Errorf("capability %s has been revoked", capabilityID)
}
// Validate expiration
currentTime := time.Now().Unix()
if capability.ExpiresAt > 0 && capability.ExpiresAt < currentTime {
return nil, fmt.Errorf("capability %s has expired", capabilityID)
}
// Validate abilities are not empty
if len(capability.Abilities) == 0 {
return nil, fmt.Errorf("capability %s has no abilities", capabilityID)
}
// Validate each ability is valid
for _, ability := range capability.Abilities {
if err := k.ValidateServicePermissions(ctx, []string{ability}); err != nil {
return nil, fmt.Errorf("invalid ability in capability: %w", err)
}
}
return capability, nil
}
// StoreCapability persists a capability to the ORM database
func (k Keeper) StoreCapability(ctx context.Context, capability *types.ServiceCapability) error {
if capability == nil {
return fmt.Errorf("capability cannot be nil")
}
if capability.CapabilityId == "" {
return fmt.Errorf("capability ID cannot be empty")
}
// Convert types.ServiceCapability to apiv1.ServiceCapability
apiCapability := &apiv1.ServiceCapability{
CapabilityId: capability.CapabilityId,
ServiceId: capability.ServiceId,
Domain: capability.Domain,
Abilities: capability.Abilities,
Owner: capability.Owner,
CreatedAt: capability.CreatedAt,
ExpiresAt: capability.ExpiresAt,
Revoked: capability.Revoked,
}
// Check if capability already exists
existing, err := k.OrmDB.ServiceCapabilityTable().Get(ctx, capability.CapabilityId)
if err == nil && existing != nil {
// Update existing capability
err = k.OrmDB.ServiceCapabilityTable().Update(ctx, apiCapability)
if err != nil {
return fmt.Errorf("failed to update capability: %w", err)
}
k.logger.Info(
"Updated capability",
"capability_id", capability.CapabilityId,
"service_id", capability.ServiceId,
"abilities", capability.Abilities,
)
} else {
// Insert new capability
err = k.OrmDB.ServiceCapabilityTable().Insert(ctx, apiCapability)
if err != nil {
return fmt.Errorf("failed to store capability: %w", err)
}
k.logger.Info(
"Stored capability",
"capability_id", capability.CapabilityId,
"service_id", capability.ServiceId,
"abilities", capability.Abilities,
)
}
return nil
}
// LoadCapability retrieves a capability from persistent storage
func (k Keeper) LoadCapability(
ctx context.Context,
capabilityID string,
) (*types.ServiceCapability, error) {
if capabilityID == "" {
return nil, fmt.Errorf("capability ID cannot be empty")
}
k.logger.Debug(
"Loading capability",
"capability_id", capabilityID,
)
// Load capability from ORM database
apiCapability, err := k.OrmDB.ServiceCapabilityTable().Get(ctx, capabilityID)
if err != nil {
return nil, fmt.Errorf("failed to load capability %s: %w", capabilityID, err)
}
// Convert apiv1.ServiceCapability to types.ServiceCapability
capability := &types.ServiceCapability{
CapabilityId: apiCapability.CapabilityId,
ServiceId: apiCapability.ServiceId,
Domain: apiCapability.Domain,
Abilities: apiCapability.Abilities,
Owner: apiCapability.Owner,
CreatedAt: apiCapability.CreatedAt,
ExpiresAt: apiCapability.ExpiresAt,
Revoked: apiCapability.Revoked,
}
return capability, nil
}
// RevokeCapability marks a capability as revoked with proper state management
func (k Keeper) RevokeCapability(ctx context.Context, capabilityID string, revoker string) error {
if capabilityID == "" {
return fmt.Errorf("capability ID cannot be empty")
}
if revoker == "" {
return fmt.Errorf("revoker cannot be empty")
}
// Load the capability
capability, err := k.LoadCapability(ctx, capabilityID)
if err != nil {
return fmt.Errorf("failed to load capability for revocation: %w", err)
}
// Check if already revoked
if capability.Revoked {
return fmt.Errorf("capability %s is already revoked", capabilityID)
}
// Validate revoker has authority
// The owner or an admin should be able to revoke
if capability.Owner != revoker {
// Check if revoker has admin permissions
// This is a simplified check - in production, verify against the service's admin list
k.logger.Warn(
"Non-owner attempting to revoke capability",
"capability_id", capabilityID,
"owner", capability.Owner,
"revoker", revoker,
)
return fmt.Errorf(
"revoker %s is not authorized to revoke capability %s",
revoker,
capabilityID,
)
}
// Mark capability as revoked
capability.Revoked = true
// Store the updated capability
err = k.StoreCapability(ctx, capability)
if err != nil {
return fmt.Errorf("failed to store revoked capability: %w", err)
}
k.logger.Info(
"Capability revoked",
"capability_id", capabilityID,
"revoker", revoker,
)
return nil
}
// GetCapabilitiesByService retrieves all capabilities for a service
func (k Keeper) GetCapabilitiesByService(
ctx context.Context,
serviceID string,
) ([]*types.ServiceCapability, error) {
if serviceID == "" {
return nil, fmt.Errorf("service ID cannot be empty")
}
// Create index key for service ID
serviceKey := apiv1.ServiceCapabilityServiceIdIndexKey{}.WithServiceId(serviceID)
// List capabilities by service
iter, err := k.OrmDB.ServiceCapabilityTable().List(ctx, serviceKey)
if err != nil {
return nil, fmt.Errorf("failed to list capabilities for service %s: %w", serviceID, err)
}
defer iter.Close()
var capabilities []*types.ServiceCapability
for iter.Next() {
apiCap, err := iter.Value()
if err != nil {
return nil, fmt.Errorf("failed to retrieve capability: %w", err)
}
// Convert to types.ServiceCapability
capability := &types.ServiceCapability{
CapabilityId: apiCap.CapabilityId,
ServiceId: apiCap.ServiceId,
Domain: apiCap.Domain,
Abilities: apiCap.Abilities,
Owner: apiCap.Owner,
CreatedAt: apiCap.CreatedAt,
ExpiresAt: apiCap.ExpiresAt,
Revoked: apiCap.Revoked,
}
capabilities = append(capabilities, capability)
}
return capabilities, nil
}
// GetCapabilitiesByOwner retrieves all capabilities owned by an address
func (k Keeper) GetCapabilitiesByOwner(
ctx context.Context,
owner string,
) ([]*types.ServiceCapability, error) {
if owner == "" {
return nil, fmt.Errorf("owner cannot be empty")
}
// Create index key for owner
ownerKey := apiv1.ServiceCapabilityOwnerIndexKey{}.WithOwner(owner)
// List capabilities by owner
iter, err := k.OrmDB.ServiceCapabilityTable().List(ctx, ownerKey)
if err != nil {
return nil, fmt.Errorf("failed to list capabilities for owner %s: %w", owner, err)
}
defer iter.Close()
var capabilities []*types.ServiceCapability
for iter.Next() {
apiCap, err := iter.Value()
if err != nil {
return nil, fmt.Errorf("failed to retrieve capability: %w", err)
}
// Convert to types.ServiceCapability
capability := &types.ServiceCapability{
CapabilityId: apiCap.CapabilityId,
ServiceId: apiCap.ServiceId,
Domain: apiCap.Domain,
Abilities: apiCap.Abilities,
Owner: apiCap.Owner,
CreatedAt: apiCap.CreatedAt,
ExpiresAt: apiCap.ExpiresAt,
Revoked: apiCap.Revoked,
}
capabilities = append(capabilities, capability)
}
return capabilities, nil
}