Files
crypto/ucan/capability.go

861 lines
23 KiB
Go

// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation
// for decentralized authorization and capability delegation in the Sonr network.
// This package handles JWT-based tokens, cryptographic verification, and resource capabilities.
package ucan
import (
"encoding/json"
"fmt"
"strings"
"time"
)
// Token represents a UCAN JWT token with parsed claims
type Token struct {
Raw string `json:"raw"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
ExpiresAt int64 `json:"exp,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Attenuations []Attenuation `json:"att"`
Proofs []Proof `json:"prf,omitempty"`
Facts []Fact `json:"fct,omitempty"`
}
// Attenuation represents a UCAN capability attenuation
type Attenuation struct {
Capability Capability `json:"can"`
Resource Resource `json:"with"`
}
// Proof represents a UCAN delegation proof (either JWT or CID)
type Proof string
// Fact represents arbitrary facts in UCAN tokens
type Fact struct {
Data json.RawMessage `json:"data"`
}
// Capability defines what actions can be performed
type Capability interface {
// GetActions returns the list of actions this capability grants
GetActions() []string
// Grants checks if this capability grants the required abilities
Grants(abilities []string) bool
// Contains checks if this capability contains another capability
Contains(other Capability) bool
// String returns a string representation
String() string
}
// Resource defines what resource the capability applies to
type Resource interface {
// GetScheme returns the resource scheme (e.g., "https", "ipfs")
GetScheme() string
// GetValue returns the resource value/path
GetValue() string
// GetURI returns the full URI string
GetURI() string
// Matches checks if this resource matches another resource
Matches(other Resource) bool
}
// SimpleCapability implements Capability for single actions
type SimpleCapability struct {
Action string `json:"action"`
}
// GetActions returns the single action
func (c *SimpleCapability) GetActions() []string {
return []string{c.Action}
}
// Grants checks if the capability grants all required abilities
func (c *SimpleCapability) Grants(abilities []string) bool {
if len(abilities) != 1 {
return false
}
return c.Action == abilities[0] || c.Action == "*"
}
// Contains checks if this capability contains another capability
func (c *SimpleCapability) Contains(other Capability) bool {
if c.Action == "*" {
return true
}
otherActions := other.GetActions()
if len(otherActions) != 1 {
return false
}
return c.Action == otherActions[0]
}
// String returns string representation
func (c *SimpleCapability) String() string {
return c.Action
}
// MultiCapability implements Capability for multiple actions
type MultiCapability struct {
Actions []string `json:"actions"`
}
// GetActions returns all actions
func (c *MultiCapability) GetActions() []string {
return c.Actions
}
// Grants checks if the capability grants all required abilities
func (c *MultiCapability) Grants(abilities []string) bool {
actionSet := make(map[string]bool)
for _, action := range c.Actions {
actionSet[action] = true
}
// Check if we have wildcard permission
if actionSet["*"] {
return true
}
// Check each required ability
for _, ability := range abilities {
if !actionSet[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *MultiCapability) Contains(other Capability) bool {
actionSet := make(map[string]bool)
for _, action := range c.Actions {
actionSet[action] = true
}
// Wildcard contains everything
if actionSet["*"] {
return true
}
// Check if all other actions are contained
for _, otherAction := range other.GetActions() {
if !actionSet[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *MultiCapability) String() string {
return strings.Join(c.Actions, ",")
}
// SimpleResource implements Resource for basic URI resources
type SimpleResource struct {
Scheme string `json:"scheme"`
Value string `json:"value"`
URI string `json:"uri"`
}
// GetScheme returns the resource scheme
func (r *SimpleResource) GetScheme() string {
return r.Scheme
}
// GetValue returns the resource value
func (r *SimpleResource) GetValue() string {
return r.Value
}
// GetURI returns the full URI
func (r *SimpleResource) GetURI() string {
return r.URI
}
// Matches checks if resources are equivalent
func (r *SimpleResource) Matches(other Resource) bool {
return r.URI == other.GetURI()
}
// VaultResource represents vault-specific resources with metadata
type VaultResource struct {
SimpleResource
VaultAddress string `json:"vault_address,omitempty"`
EnclaveDataCID string `json:"enclave_data_cid,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ServiceResource represents service-specific resources
type ServiceResource struct {
SimpleResource
ServiceID string `json:"service_id"`
Domain string `json:"domain"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// CreateSimpleAttenuation creates a basic attenuation
func CreateSimpleAttenuation(action, resourceURI string) Attenuation {
return Attenuation{
Capability: &SimpleCapability{Action: action},
Resource: parseResourceURI(resourceURI),
}
}
// CreateMultiAttenuation creates an attenuation with multiple actions
func CreateMultiAttenuation(actions []string, resourceURI string) Attenuation {
return Attenuation{
Capability: &MultiCapability{Actions: actions},
Resource: parseResourceURI(resourceURI),
}
}
// CreateVaultAttenuation creates a vault-specific attenuation
func CreateVaultAttenuation(actions []string, enclaveDataCID, vaultAddress string) Attenuation {
resource := &VaultResource{
SimpleResource: SimpleResource{
Scheme: "ipfs",
Value: enclaveDataCID,
URI: fmt.Sprintf("ipfs://%s", enclaveDataCID),
},
VaultAddress: vaultAddress,
EnclaveDataCID: enclaveDataCID,
}
return Attenuation{
Capability: &MultiCapability{Actions: actions},
Resource: resource,
}
}
// CreateServiceAttenuation creates a service-specific attenuation
func CreateServiceAttenuation(actions []string, serviceID, domain string) Attenuation {
resourceURI := fmt.Sprintf("service://%s", serviceID)
resource := &ServiceResource{
SimpleResource: SimpleResource{
Scheme: "service",
Value: serviceID,
URI: resourceURI,
},
ServiceID: serviceID,
Domain: domain,
}
return Attenuation{
Capability: &MultiCapability{Actions: actions},
Resource: resource,
}
}
// parseResourceURI creates a Resource from URI string
func parseResourceURI(uri string) Resource {
parts := strings.SplitN(uri, "://", 2)
if len(parts) != 2 {
return &SimpleResource{
Scheme: "unknown",
Value: uri,
URI: uri,
}
}
return &SimpleResource{
Scheme: parts[0],
Value: parts[1],
URI: uri,
}
}
// CapabilityTemplate provides validation and construction utilities
type CapabilityTemplate struct {
AllowedActions map[string][]string `json:"allowed_actions"` // resource_type -> []actions
DefaultExpiration time.Duration `json:"default_expiration"` // default token lifetime
MaxExpiration time.Duration `json:"max_expiration"` // maximum allowed lifetime
}
// NewCapabilityTemplate creates a new capability template
func NewCapabilityTemplate() *CapabilityTemplate {
return &CapabilityTemplate{
AllowedActions: make(map[string][]string),
DefaultExpiration: 24 * time.Hour,
MaxExpiration: 30 * 24 * time.Hour, // 30 days
}
}
// AddAllowedActions adds allowed actions for a resource type
func (ct *CapabilityTemplate) AddAllowedActions(resourceType string, actions []string) {
ct.AllowedActions[resourceType] = actions
}
// ValidateAttenuation validates an attenuation against the template
func (ct *CapabilityTemplate) ValidateAttenuation(att Attenuation) error {
resourceType := att.Resource.GetScheme()
allowedActions, exists := ct.AllowedActions[resourceType]
if !exists {
// Allow unknown resource types for backward compatibility
return nil
}
// Create action set for efficient lookup
actionSet := make(map[string]bool)
for _, action := range allowedActions {
actionSet[action] = true
}
// Check if all capability actions are allowed
for _, action := range att.Capability.GetActions() {
if action == "*" {
// Wildcard requires explicit permission
if !actionSet["*"] {
return fmt.Errorf("wildcard action not allowed for resource type %s", resourceType)
}
continue
}
if !actionSet[action] {
return fmt.Errorf("action %s not allowed for resource type %s", action, resourceType)
}
}
return nil
}
// ValidateExpiration validates token expiration time
func (ct *CapabilityTemplate) ValidateExpiration(expiresAt int64) error {
if expiresAt == 0 {
return nil // No expiration is allowed
}
now := time.Now()
expiry := time.Unix(expiresAt, 0)
if expiry.Before(now) {
return fmt.Errorf("token expiration is in the past")
}
if expiry.Sub(now) > ct.MaxExpiration {
return fmt.Errorf("token expiration exceeds maximum allowed duration")
}
return nil
}
// GetDefaultExpirationTime returns the default expiration timestamp
func (ct *CapabilityTemplate) GetDefaultExpirationTime() int64 {
return time.Now().Add(ct.DefaultExpiration).Unix()
}
// StandardVaultTemplate returns a standard template for vault operations
func StandardVaultTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions(
"ipfs",
[]string{"read", "write", "sign", "export", "import", "delete", VaultAdminAction},
)
template.AddAllowedActions(
"vault",
[]string{"read", "write", "sign", "export", "import", "delete", "admin", "*"},
)
return template
}
// StandardServiceTemplate returns a standard template for service operations
func StandardServiceTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions(
"service",
[]string{"read", "write", "admin", "register", "update", "delete"},
)
template.AddAllowedActions("https", []string{"read", "write"})
template.AddAllowedActions("http", []string{"read", "write"})
return template
}
// AttenuationList provides utilities for working with multiple attenuations
type AttenuationList []Attenuation
// Contains checks if the list contains attenuations for a specific resource
func (al AttenuationList) Contains(resourceURI string) bool {
for _, att := range al {
if att.Resource.GetURI() == resourceURI {
return true
}
}
return false
}
// GetCapabilitiesForResource returns all capabilities for a specific resource
func (al AttenuationList) GetCapabilitiesForResource(resourceURI string) []Capability {
var capabilities []Capability
for _, att := range al {
if att.Resource.GetURI() == resourceURI {
capabilities = append(capabilities, att.Capability)
}
}
return capabilities
}
// CanPerform checks if the attenuations allow specific actions on a resource
func (al AttenuationList) CanPerform(resourceURI string, actions []string) bool {
capabilities := al.GetCapabilitiesForResource(resourceURI)
for _, cap := range capabilities {
if cap.Grants(actions) {
return true
}
}
return false
}
// IsSubsetOf checks if this list is a subset of another list
func (al AttenuationList) IsSubsetOf(parent AttenuationList) bool {
for _, childAtt := range al {
if !parent.containsAttenuation(childAtt) {
return false
}
}
return true
}
// containsAttenuation checks if the list contains an equivalent attenuation
func (al AttenuationList) containsAttenuation(att Attenuation) bool {
for _, parentAtt := range al {
if parentAtt.Resource.Matches(att.Resource) {
if parentAtt.Capability.Contains(att.Capability) {
return true
}
}
}
return false
}
// Module-Specific Capability Types
// DIDCapability implements Capability for DID module operations
type DIDCapability struct {
Action string `json:"action"`
Actions []string `json:"actions,omitempty"`
Caveats []string `json:"caveats,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// GetActions returns the actions this DID capability grants
func (c *DIDCapability) GetActions() []string {
if len(c.Actions) > 0 {
return c.Actions
}
return []string{c.Action}
}
// Grants checks if this capability grants the required abilities
func (c *DIDCapability) Grants(abilities []string) bool {
if c.Action == "*" {
return true
}
grantedActions := make(map[string]bool)
for _, action := range c.GetActions() {
grantedActions[action] = true
}
for _, ability := range abilities {
if !grantedActions[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *DIDCapability) Contains(other Capability) bool {
if c.Action == "*" {
return true
}
ourActions := make(map[string]bool)
for _, action := range c.GetActions() {
ourActions[action] = true
}
for _, otherAction := range other.GetActions() {
if !ourActions[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *DIDCapability) String() string {
if len(c.Actions) > 1 {
return strings.Join(c.Actions, ",")
}
return c.Action
}
// DWNCapability implements Capability for DWN module operations
type DWNCapability struct {
Action string `json:"action"`
Actions []string `json:"actions,omitempty"`
Caveats []string `json:"caveats,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// GetActions returns the actions this DWN capability grants
func (c *DWNCapability) GetActions() []string {
if len(c.Actions) > 0 {
return c.Actions
}
return []string{c.Action}
}
// Grants checks if this capability grants the required abilities
func (c *DWNCapability) Grants(abilities []string) bool {
if c.Action == "*" {
return true
}
grantedActions := make(map[string]bool)
for _, action := range c.GetActions() {
grantedActions[action] = true
}
for _, ability := range abilities {
if !grantedActions[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *DWNCapability) Contains(other Capability) bool {
if c.Action == "*" {
return true
}
ourActions := make(map[string]bool)
for _, action := range c.GetActions() {
ourActions[action] = true
}
for _, otherAction := range other.GetActions() {
if !ourActions[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *DWNCapability) String() string {
if len(c.Actions) > 1 {
return strings.Join(c.Actions, ",")
}
return c.Action
}
// DEXCapability implements Capability for DEX module operations
type DEXCapability struct {
Action string `json:"action"`
Actions []string `json:"actions,omitempty"`
Caveats []string `json:"caveats,omitempty"`
MaxAmount string `json:"max_amount,omitempty"` // For swap limits
Metadata map[string]string `json:"metadata,omitempty"`
}
// GetActions returns the actions this DEX capability grants
func (c *DEXCapability) GetActions() []string {
if len(c.Actions) > 0 {
return c.Actions
}
return []string{c.Action}
}
// Grants checks if this capability grants the required abilities
func (c *DEXCapability) Grants(abilities []string) bool {
if c.Action == "*" {
return true
}
grantedActions := make(map[string]bool)
for _, action := range c.GetActions() {
grantedActions[action] = true
}
for _, ability := range abilities {
if !grantedActions[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *DEXCapability) Contains(other Capability) bool {
if c.Action == "*" {
return true
}
ourActions := make(map[string]bool)
for _, action := range c.GetActions() {
ourActions[action] = true
}
for _, otherAction := range other.GetActions() {
if !ourActions[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *DEXCapability) String() string {
if len(c.Actions) > 1 {
return strings.Join(c.Actions, ",")
}
return c.Action
}
// Module-Specific Resource Types
// DIDResource represents DID-specific resources
type DIDResource struct {
SimpleResource
DIDMethod string `json:"did_method,omitempty"`
DIDSubject string `json:"did_subject,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// DWNResource represents DWN-specific resources
type DWNResource struct {
SimpleResource
RecordType string `json:"record_type,omitempty"`
Protocol string `json:"protocol,omitempty"`
Owner string `json:"owner,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// DEXResource represents DEX-specific resources
type DEXResource struct {
SimpleResource
PoolID string `json:"pool_id,omitempty"`
AssetPair string `json:"asset_pair,omitempty"`
OrderID string `json:"order_id,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// Enhanced ServiceResource adds delegation capabilities
func (r *ServiceResource) SupportsDelegate() bool {
return r.Metadata != nil && r.Metadata["supports_delegation"] == "true"
}
// Module-Specific Capability Templates
// StandardDIDTemplate returns a standard template for DID operations
func StandardDIDTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions("did", []string{
"create", "register", "update", "deactivate", "revoke",
"add-verification-method", "remove-verification-method",
"add-service", "remove-service", "issue-credential",
"revoke-credential", "link-wallet", "register-webauthn", "*",
})
return template
}
// StandardDWNTemplate returns a standard template for DWN operations
func StandardDWNTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions("dwn", []string{
"records-write", "records-delete", "protocols-configure",
"permissions-grant", "permissions-revoke", "create", "read",
"update", "delete", "*",
})
return template
}
// EnhancedServiceTemplate returns enhanced service template with delegation support
func EnhancedServiceTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions("service", []string{
"register", "update", "delete", "verify-domain",
"initiate-domain-verification", "delegate", "*",
})
template.AddAllowedActions("svc", []string{
"register", "verify-domain", "delegate", "*",
})
template.AddAllowedActions("https", []string{"read", "write"})
template.AddAllowedActions("http", []string{"read", "write"})
return template
}
// StandardDEXTemplate returns a standard template for DEX operations
func StandardDEXTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions("dex", []string{
"register-account", "swap", "provide-liquidity", "remove-liquidity",
"create-limit-order", "cancel-order", "*",
})
template.AddAllowedActions("pool", []string{
"swap", "provide-liquidity", "remove-liquidity", "*",
})
return template
}
// Module-Specific Attenuation Constructors
// CreateDIDAttenuation creates a DID-specific attenuation
func CreateDIDAttenuation(actions []string, didPattern string, caveats []string) Attenuation {
resourceURI := fmt.Sprintf("did:%s", didPattern)
resource := &DIDResource{
SimpleResource: SimpleResource{
Scheme: "did",
Value: didPattern,
URI: resourceURI,
},
}
return Attenuation{
Capability: &DIDCapability{
Actions: actions,
Caveats: caveats,
},
Resource: resource,
}
}
// CreateDWNAttenuation creates a DWN-specific attenuation
func CreateDWNAttenuation(actions []string, recordPattern string, caveats []string) Attenuation {
resourceURI := fmt.Sprintf("dwn:records/%s", recordPattern)
resource := &DWNResource{
SimpleResource: SimpleResource{
Scheme: "dwn",
Value: fmt.Sprintf("records/%s", recordPattern),
URI: resourceURI,
},
RecordType: recordPattern,
}
return Attenuation{
Capability: &DWNCapability{
Actions: actions,
Caveats: caveats,
},
Resource: resource,
}
}
// CreateDEXAttenuation creates a DEX-specific attenuation
func CreateDEXAttenuation(actions []string, poolPattern string, caveats []string, maxAmount string) Attenuation {
resourceURI := fmt.Sprintf("dex:pool/%s", poolPattern)
resource := &DEXResource{
SimpleResource: SimpleResource{
Scheme: "dex",
Value: fmt.Sprintf("pool/%s", poolPattern),
URI: resourceURI,
},
PoolID: poolPattern,
}
return Attenuation{
Capability: &DEXCapability{
Actions: actions,
Caveats: caveats,
MaxAmount: maxAmount,
},
Resource: resource,
}
}
// Cross-Module Capability Composition
// CrossModuleCapability allows composing capabilities across modules
type CrossModuleCapability struct {
Modules map[string]Capability `json:"modules"`
}
// GetActions returns all actions across all modules
func (c *CrossModuleCapability) GetActions() []string {
var actions []string
for _, cap := range c.Modules {
actions = append(actions, cap.GetActions()...)
}
return actions
}
// Grants checks if required abilities are granted across modules
func (c *CrossModuleCapability) Grants(abilities []string) bool {
allActions := make(map[string]bool)
for _, cap := range c.Modules {
for _, action := range cap.GetActions() {
allActions[action] = true
}
}
for _, ability := range abilities {
if !allActions[ability] {
return false
}
}
return true
}
// Contains checks if this cross-module capability contains another
func (c *CrossModuleCapability) Contains(other Capability) bool {
// For cross-module capabilities, check each module
if otherCross, ok := other.(*CrossModuleCapability); ok {
for module, otherCap := range otherCross.Modules {
if ourCap, exists := c.Modules[module]; exists {
if !ourCap.Contains(otherCap) {
return false
}
} else {
return false
}
}
return true
}
// For single capabilities, check if any module contains it
for _, cap := range c.Modules {
if cap.Contains(other) {
return true
}
}
return false
}
// String returns string representation
func (c *CrossModuleCapability) String() string {
var moduleStrs []string
for module, cap := range c.Modules {
moduleStrs = append(moduleStrs, fmt.Sprintf("%s:%s", module, cap.String()))
}
return strings.Join(moduleStrs, ";")
}
// Gasless Transaction Support
// GaslessCapability wraps other capabilities with gasless transaction support
type GaslessCapability struct {
Capability
AllowGasless bool `json:"allow_gasless"`
GasLimit uint64 `json:"gas_limit,omitempty"`
}
// SupportsGasless returns whether this capability supports gasless transactions
func (c *GaslessCapability) SupportsGasless() bool {
return c.AllowGasless
}
// GetGasLimit returns the gas limit for gasless transactions
func (c *GaslessCapability) GetGasLimit() uint64 {
return c.GasLimit
}