319 lines
8.3 KiB
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 = ¶ms.Subject
|
|
}
|
|
if params.Policy != "" {
|
|
pol = ¶ms.Policy
|
|
}
|
|
if params.NotBefore != "" {
|
|
nbf = ¶ms.NotBefore
|
|
}
|
|
if params.Expiration != "" {
|
|
exp = ¶ms.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 = ¶ms.InvocationCID
|
|
}
|
|
if params.Reason != "" {
|
|
reason = ¶ms.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,
|
|
}
|
|
}
|