Files
motr-enclave/internal/keybase/actions_delegation.go

319 lines
8.3 KiB
Go

package keybase
import (
"context"
"fmt"
)
// =============================================================================
// DELEGATION ACTIONS (UCAN v1.0.0-rc.1)
// =============================================================================
// DelegationResult represents a delegation in API responses.
type DelegationResult struct {
ID int64 `json:"id"`
CID string `json:"cid"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
Subject string `json:"sub,omitempty"`
Command string `json:"cmd"`
Policy string `json:"pol,omitempty"`
NotBefore string `json:"nbf,omitempty"`
Expiration string `json:"exp,omitempty"`
IsRoot bool `json:"is_root"`
IsPowerline bool `json:"is_powerline"`
CreatedAt string `json:"created_at"`
}
// StoreDelegationParams contains parameters for storing a delegation.
type StoreDelegationParams struct {
CID string `json:"cid"`
Envelope []byte `json:"envelope"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
Subject string `json:"sub,omitempty"`
Command string `json:"cmd"`
Policy string `json:"pol,omitempty"`
NotBefore string `json:"nbf,omitempty"`
Expiration string `json:"exp,omitempty"`
IsRoot bool `json:"is_root"`
IsPowerline bool `json:"is_powerline"`
}
// StoreDelegation stores a new UCAN delegation envelope.
func (am *ActionManager) StoreDelegation(ctx context.Context, params StoreDelegationParams) (*DelegationResult, error) {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if am.kb.didID == 0 {
return nil, fmt.Errorf("DID not initialized")
}
var sub, pol, nbf, exp *string
if params.Subject != "" {
sub = &params.Subject
}
if params.Policy != "" {
pol = &params.Policy
}
if params.NotBefore != "" {
nbf = &params.NotBefore
}
if params.Expiration != "" {
exp = &params.Expiration
}
isRoot := int64(0)
if params.IsRoot {
isRoot = 1
}
isPowerline := int64(0)
if params.IsPowerline {
isPowerline = 1
}
d, err := am.kb.queries.CreateDelegation(ctx, CreateDelegationParams{
DidID: am.kb.didID,
Cid: params.CID,
Envelope: params.Envelope,
Iss: params.Issuer,
Aud: params.Audience,
Sub: sub,
Cmd: params.Command,
Pol: pol,
Nbf: nbf,
Exp: exp,
IsRoot: isRoot,
IsPowerline: isPowerline,
})
if err != nil {
return nil, fmt.Errorf("create delegation: %w", err)
}
return delegationToResult(d), nil
}
// GetDelegationByCID retrieves a delegation by its CID.
func (am *ActionManager) GetDelegationByCID(ctx context.Context, cid string) (*DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
d, err := am.kb.queries.GetDelegationByCID(ctx, cid)
if err != nil {
return nil, fmt.Errorf("get delegation: %w", err)
}
return delegationToResult(d), nil
}
// GetDelegationEnvelope retrieves the raw CBOR envelope for a delegation.
func (am *ActionManager) GetDelegationEnvelope(ctx context.Context, cid string) ([]byte, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
envelope, err := am.kb.queries.GetDelegationEnvelopeByCID(ctx, cid)
if err != nil {
return nil, fmt.Errorf("get delegation envelope: %w", err)
}
return envelope, nil
}
// ListDelegations returns all active delegations for the current DID.
func (am *ActionManager) ListDelegations(ctx context.Context) ([]DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return []DelegationResult{}, nil
}
delegations, err := am.kb.queries.ListDelegationsByDID(ctx, am.kb.didID)
if err != nil {
return nil, fmt.Errorf("list delegations: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(d)
}
return results, nil
}
// ListDelegationsByIssuer returns delegations issued by a specific DID.
func (am *ActionManager) ListDelegationsByIssuer(ctx context.Context, issuer string) ([]DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
delegations, err := am.kb.queries.ListDelegationsByIssuer(ctx, issuer)
if err != nil {
return nil, fmt.Errorf("list delegations by issuer: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(d)
}
return results, nil
}
// ListDelegationsByAudience returns delegations granted to a specific DID.
func (am *ActionManager) ListDelegationsByAudience(ctx context.Context, audience string) ([]DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
delegations, err := am.kb.queries.ListDelegationsByAudience(ctx, audience)
if err != nil {
return nil, fmt.Errorf("list delegations by audience: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(d)
}
return results, nil
}
// ListDelegationsForCommand returns delegations that grant a specific command.
func (am *ActionManager) ListDelegationsForCommand(ctx context.Context, cmd string) ([]DelegationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return []DelegationResult{}, nil
}
delegations, err := am.kb.queries.ListDelegationsForCommand(ctx, ListDelegationsForCommandParams{
DidID: am.kb.didID,
Cmd: cmd,
Cmd_2: cmd,
})
if err != nil {
return nil, fmt.Errorf("list delegations for command: %w", err)
}
results := make([]DelegationResult, len(delegations))
for i, d := range delegations {
results[i] = *delegationToResult(d)
}
return results, nil
}
// IsDelegationRevoked checks if a delegation has been revoked.
func (am *ActionManager) IsDelegationRevoked(ctx context.Context, cid string) (bool, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
revoked, err := am.kb.queries.IsDelegationRevoked(ctx, cid)
if err != nil {
return false, fmt.Errorf("check revocation: %w", err)
}
return revoked == 1, nil
}
// RevokeDelegationParams contains parameters for revoking a delegation.
type RevokeDelegationParams struct {
DelegationCID string `json:"delegation_cid"`
RevokedBy string `json:"revoked_by"`
InvocationCID string `json:"invocation_cid,omitempty"`
Reason string `json:"reason,omitempty"`
}
// RevokeDelegation revokes a delegation by creating a revocation record.
func (am *ActionManager) RevokeDelegation(ctx context.Context, params RevokeDelegationParams) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
var invocationCID, reason *string
if params.InvocationCID != "" {
invocationCID = &params.InvocationCID
}
if params.Reason != "" {
reason = &params.Reason
}
err := am.kb.queries.CreateRevocation(ctx, CreateRevocationParams{
DelegationCid: params.DelegationCID,
RevokedBy: params.RevokedBy,
InvocationCid: invocationCID,
Reason: reason,
})
if err != nil {
return fmt.Errorf("create revocation: %w", err)
}
return nil
}
// DeleteDelegation deletes a delegation from the database.
func (am *ActionManager) DeleteDelegation(ctx context.Context, cid string) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if am.kb.didID == 0 {
return fmt.Errorf("DID not initialized")
}
err := am.kb.queries.DeleteDelegation(ctx, DeleteDelegationParams{
Cid: cid,
DidID: am.kb.didID,
})
if err != nil {
return fmt.Errorf("delete delegation: %w", err)
}
return nil
}
// CleanExpiredDelegations removes delegations expired more than 30 days ago.
func (am *ActionManager) CleanExpiredDelegations(ctx context.Context) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if err := am.kb.queries.CleanExpiredDelegations(ctx); err != nil {
return fmt.Errorf("clean expired delegations: %w", err)
}
return nil
}
// delegationToResult converts a UcanDelegation to DelegationResult.
func delegationToResult(d UcanDelegation) *DelegationResult {
subject := ""
if d.Sub != nil {
subject = *d.Sub
}
policy := ""
if d.Pol != nil {
policy = *d.Pol
}
notBefore := ""
if d.Nbf != nil {
notBefore = *d.Nbf
}
expiration := ""
if d.Exp != nil {
expiration = *d.Exp
}
return &DelegationResult{
ID: d.ID,
CID: d.Cid,
Issuer: d.Iss,
Audience: d.Aud,
Subject: subject,
Command: d.Cmd,
Policy: policy,
NotBefore: notBefore,
Expiration: expiration,
IsRoot: d.IsRoot == 1,
IsPowerline: d.IsPowerline == 1,
CreatedAt: d.CreatedAt,
}
}