Files

412 lines
12 KiB
Go

package keybase
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
)
type HandlerFunc func(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error)
type resourceHandlers map[string]HandlerFunc
var handlers = map[string]resourceHandlers{
"accounts": {
"list": handleAccountList,
"get": handleAccountGet,
"sign": handleAccountSign,
},
"credentials": {
"list": handleCredentialList,
"get": handleCredentialGet,
},
"sessions": {
"list": handleSessionList,
"revoke": handleSessionRevoke,
},
"grants": {
"list": handleGrantList,
"revoke": handleGrantRevoke,
},
"enclaves": {
"list": handleEnclaveList,
"get": handleEnclaveGet,
"sign": handleEnclaveSign,
"rotate": handleEnclaveRotate,
"archive": handleEnclaveArchive,
"delete": handleEnclaveDelete,
},
"delegations": {
"list": handleDelegationList,
"list_received": handleDelegationListReceived,
"list_command": handleDelegationListCommand,
"get": handleDelegationGet,
"revoke": handleDelegationRevoke,
"verify": handleDelegationVerify,
"cleanup": handleDelegationCleanup,
},
"ucans": {
"list": handleDelegationList,
"get": handleDelegationGet,
"revoke": handleDelegationRevoke,
"verify": handleDelegationVerify,
"cleanup": handleDelegationCleanup,
},
"verification_methods": {
"list": handleVerificationMethodList,
"get": handleVerificationMethodGet,
"delete": handleVerificationMethodDelete,
},
"services": {
"list": handleServiceList,
"get": handleServiceGet,
"get_by_id": handleServiceGetByID,
},
}
func Exec(ctx context.Context, resource, action, subject string) (json.RawMessage, error) {
am, err := NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
resHandlers, ok := handlers[resource]
if !ok {
return nil, fmt.Errorf("unknown resource: %s", resource)
}
handler, ok := resHandlers[action]
if !ok {
return nil, fmt.Errorf("unknown action for %s: %s", resource, action)
}
return handler(ctx, am, subject)
}
func handleAccountList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
accounts, err := am.ListAccounts(ctx)
if err != nil {
return nil, err
}
return json.Marshal(accounts)
}
func handleAccountGet(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (address) required for get action")
}
account, err := am.GetAccountByAddress(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(account)
}
func handleAccountSign(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (hex-encoded data) required for sign action")
}
enclaves, err := am.ListEnclaves(ctx)
if err != nil {
return nil, fmt.Errorf("list enclaves: %w", err)
}
if len(enclaves) == 0 {
return nil, errors.New("no enclave available for signing")
}
enc := enclaves[0]
signature, err := am.SignWithEnclave(ctx, enc.EnclaveID, []byte(subject))
if err != nil {
return nil, fmt.Errorf("sign: %w", err)
}
return json.Marshal(map[string]string{
"signature": fmt.Sprintf("%x", signature),
"enclave_id": enc.EnclaveID,
"public_key": enc.PublicKeyHex,
})
}
func handleCredentialList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
credentials, err := am.ListCredentials(ctx)
if err != nil {
return nil, err
}
return json.Marshal(credentials)
}
func handleCredentialGet(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (credential_id) required for get action")
}
credential, err := am.GetCredentialByID(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(credential)
}
func handleSessionList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
sessions, err := am.ListSessions(ctx)
if err != nil {
return nil, err
}
return json.Marshal(sessions)
}
func handleSessionRevoke(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (session_id) required for revoke action")
}
if err := am.RevokeSession(ctx, subject); err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"revoked": true})
}
func handleGrantList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
grants, err := am.ListGrants(ctx)
if err != nil {
return nil, err
}
return json.Marshal(grants)
}
func handleGrantRevoke(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (grant_id) required for revoke action")
}
var grantID int64
if _, err := fmt.Sscanf(subject, "%d", &grantID); err != nil {
return nil, fmt.Errorf("invalid grant_id: %w", err)
}
if err := am.RevokeGrant(ctx, grantID); err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"revoked": true})
}
func handleEnclaveList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
enclaves, err := am.ListEnclaves(ctx)
if err != nil {
return nil, err
}
return json.Marshal(enclaves)
}
func handleEnclaveGet(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (enclave_id) required for get action")
}
enc, err := am.GetEnclaveByID(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(enc)
}
func handleEnclaveSign(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (enclave_id:hex_data) required for sign action")
}
parts := strings.SplitN(subject, ":", 2)
if len(parts) != 2 {
return nil, errors.New("subject must be enclave_id:hex_data format")
}
enclaveID := parts[0]
data := []byte(parts[1])
signature, err := am.SignWithEnclave(ctx, enclaveID, data)
if err != nil {
return nil, err
}
enc, _ := am.GetEnclaveByID(ctx, enclaveID)
pubKeyHex := ""
if enc != nil {
pubKeyHex = enc.PublicKeyHex
}
return json.Marshal(map[string]string{
"signature": fmt.Sprintf("%x", signature),
"enclave_id": enclaveID,
"public_key": pubKeyHex,
})
}
func handleEnclaveRotate(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (enclave_id) required for rotate action")
}
if err := am.RotateEnclave(ctx, subject); err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"rotated": true})
}
func handleEnclaveArchive(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (enclave_id) required for archive action")
}
if err := am.ArchiveEnclave(ctx, subject); err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"archived": true})
}
func handleEnclaveDelete(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (enclave_id) required for delete action")
}
if err := am.DeleteEnclave(ctx, subject); err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"deleted": true})
}
func handleDelegationList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
delegations, err := am.ListDelegations(ctx)
if err != nil {
return nil, err
}
return json.Marshal(delegations)
}
func handleDelegationListReceived(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (audience DID) required for list_received action")
}
delegations, err := am.ListDelegationsByAudience(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(delegations)
}
func handleDelegationListCommand(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (command) required for list_command action")
}
delegations, err := am.ListDelegationsForCommand(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(delegations)
}
func handleDelegationGet(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (cid) required for get action")
}
delegation, err := am.GetDelegationByCID(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(delegation)
}
func handleDelegationRevoke(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (cid) required for revoke action")
}
kb := Get()
revokedBy := ""
if kb != nil {
revokedBy = kb.DID()
}
if err := am.RevokeDelegation(ctx, RevokeDelegationParams{
DelegationCID: subject,
RevokedBy: revokedBy,
Reason: "user revoked",
}); err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"revoked": true})
}
func handleDelegationVerify(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (cid) required for verify action")
}
revoked, err := am.IsDelegationRevoked(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"valid": !revoked, "revoked": revoked})
}
func handleDelegationCleanup(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
if err := am.CleanExpiredDelegations(ctx); err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"cleaned": true})
}
func handleVerificationMethodList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
vms, err := am.ListVerificationMethodsFull(ctx)
if err != nil {
return nil, err
}
return json.Marshal(vms)
}
func handleVerificationMethodGet(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (method_id) required for get action")
}
vm, err := am.GetVerificationMethod(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(vm)
}
func handleVerificationMethodDelete(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (method_id) required for delete action")
}
if err := am.DeleteVerificationMethod(ctx, subject); err != nil {
return nil, err
}
return json.Marshal(map[string]bool{"deleted": true})
}
func handleServiceList(ctx context.Context, am *ActionManager, _ string) (json.RawMessage, error) {
services, err := am.ListVerifiedServices(ctx)
if err != nil {
return nil, err
}
return json.Marshal(services)
}
func handleServiceGet(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (origin) required for get action")
}
svc, err := am.GetServiceByOrigin(ctx, subject)
if err != nil {
return nil, err
}
return json.Marshal(svc)
}
func handleServiceGetByID(ctx context.Context, am *ActionManager, subject string) (json.RawMessage, error) {
if subject == "" {
return nil, errors.New("subject (service_id) required for get_by_id action")
}
var serviceID int64
if _, err := fmt.Sscanf(subject, "%d", &serviceID); err != nil {
return nil, fmt.Errorf("invalid service_id: %w", err)
}
svc, err := am.GetServiceByID(ctx, serviceID)
if err != nil {
return nil, err
}
return json.Marshal(svc)
}