nuts-foundation/nuts-node

View on GitHub
vcr/cmd/cmd.go

Summary

Maintainability
B
5 hrs
Test Coverage
B
88%
/*
 * Nuts node
 * Copyright (C) 2021 Nuts community
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package cmd

import (
    "encoding/json"
    "fmt"
    "github.com/nuts-foundation/nuts-node/vcr"
    "github.com/nuts-foundation/nuts-node/vcr/credential"
    "github.com/spf13/pflag"
    "strings"
    "time"

    "github.com/nuts-foundation/nuts-node/core"
    api "github.com/nuts-foundation/nuts-node/vcr/api/vcr/v2"

    "github.com/spf13/cobra"
)

// FlagSet contains flags relevant for the module
func FlagSet() *pflag.FlagSet {
    defs := vcr.DefaultConfig()
    flagSet := pflag.NewFlagSet("vcr", pflag.ContinueOnError)
    flagSet.String("vcr.openid4vci.definitionsdir", defs.OpenID4VCI.DefinitionsDIR, "Directory with the additional credential definitions the node could issue (experimental, may change without notice).")
    flagSet.Bool("vcr.openid4vci.enabled", defs.OpenID4VCI.Enabled, "Enable issuing and receiving credentials over OpenID4VCI.")
    flagSet.Duration("vcr.openid4vci.timeout", time.Second*30, "Time-out for OpenID4VCI HTTP client operations.")

    return flagSet
}

// Cmd contains sub-commands for the remote client
func Cmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "vcr",
        Short: "Verifiable Credential Registry commands",
    }
    cmd.AddCommand(trustCmd())
    cmd.AddCommand(untrustCmd())
    cmd.AddCommand(listTrustedCmd())
    cmd.AddCommand(listUntrustedCmd())
    cmd.AddCommand(issueVC())
    return cmd
}

func trustCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "trust [type] [issuer DID]",
        Short: "Trust VCs of a certain credential type when published by the given issuer.",
        Args:  cobra.ExactArgs(2),
        RunE: func(cmd *cobra.Command, args []string) error {
            cType := args[0]
            issuer := args[1]

            clientConfig := core.NewClientConfigForCommand(cmd)
            err := httpClient(clientConfig).Trust(cType, issuer)
            if err != nil {
                return fmt.Errorf("unable to trust issuer: %v", err)
            }

            cmd.Println(fmt.Sprintf("%s is now trusted as issuer of %s", issuer, cType))
            return nil
        },
    }
}

func untrustCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "untrust [type] [issuer DID]",
        Short: "Untrust VCs of a certain credential type when published by the given issuer.",
        Args:  cobra.ExactArgs(2),
        RunE: func(cmd *cobra.Command, args []string) error {
            cType := args[0]
            issuer := args[1]

            clientConfig := core.NewClientConfigForCommand(cmd)
            if err := httpClient(clientConfig).Untrust(cType, issuer); err != nil {
                return fmt.Errorf("unable to untrust issuer: %v", err)
            }

            cmd.Println(fmt.Sprintf("%s is no longer trusted as issuer of %s", issuer, cType))
            return nil
        },
    }
}

func listTrustedCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "list-trusted [type]",
        Short: "List trusted issuers for given credential type",
        Args:  cobra.ExactArgs(1),
        RunE: func(cmd *cobra.Command, args []string) error {
            cType := args[0]

            clientConfig := core.NewClientConfigForCommand(cmd)
            issuers, err := httpClient(clientConfig).Trusted(cType)
            if err != nil {
                return fmt.Errorf("unable to get list of trusted issuers: %v", err)
            }

            cmd.Println(strings.Join(issuers, "\n"))
            return nil
        },
    }
}

func listUntrustedCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "list-untrusted [type]",
        Short: "List untrusted issuers for given credential type",
        Args:  cobra.ExactArgs(1),
        RunE: func(cmd *cobra.Command, args []string) error {
            cType := args[0]

            clientConfig := core.NewClientConfigForCommand(cmd)

            issuers, err := httpClient(clientConfig).Untrusted(cType)
            if err != nil {
                return fmt.Errorf("unable to get list of untrusted issuers: %v", err)
            }

            cmd.Println(strings.Join(issuers, "\n"))
            return nil
        },
    }
}

func issueVC() *cobra.Command {
    var publish bool
    var visibilityStr string
    var expirationDate string
    result := &cobra.Command{
        Use:   "issue [context] [type] [issuer-did] [subject]",
        Short: "Issues a Verifiable Credential",
        Long: "Issues a Verifiable Credential as the given issuer (as DID). " +
            "The context must be a single JSON-LD context URI (e.g. '" + credential.NutsV1Context + "'). " +
            "The type must be a single VC type (not being VerifiableCredential). " +
            "The subject must be the credential subject in JSON format. " +
            "It prints the issued VC if successfully issued.",
        Example: `nuts vcr issue "` + credential.NutsV1Context + `" "NutsAuthorizationCredential" "did:nuts:1234" "{'id': 'did:nuts:4321', 'purposeOfUse': 'eOverdracht-sender', 'etc': 'etcetc'}"`,
        Args:    cobra.ExactArgs(4),
        RunE: func(cmd *cobra.Command, args []string) error {
            credentialSubject := make(map[string]interface{}, 0)
            if err := json.Unmarshal([]byte(args[3]), &credentialSubject); err != nil {
                return fmt.Errorf("invalid credential subject: %w", err)
            }
            request := api.IssueVCRequest{
                //Context:           &args[0],
                //Type:              args[1],
                Issuer:            args[2],
                CredentialSubject: credentialSubject,
                PublishToNetwork:  &publish,
            }
            // set @context
            if args[0] != "" {
                request.Context = new(api.IssueVCRequest_Context)
                if contexts := strings.Split(args[0], ","); len(contexts) != 1 {
                    if err := request.Context.FromIssueVCRequestContext1(contexts); err != nil {
                        return fmt.Errorf("invalid @context: %w", err)
                    }
                } else {
                    if err := request.Context.FromIssueVCRequestContext0(args[0]); err != nil {
                        return fmt.Errorf("invalid @context: %w", err)
                    }
                }
            }
            // set type
            if types := strings.Split(args[1], ","); len(types) != 1 {
                if err := request.Type.FromIssueVCRequestType1(types); err != nil {
                    return fmt.Errorf("invalid credential type: %w", err)
                }
            } else {
                if err := request.Type.FromIssueVCRequestType0(args[1]); err != nil {
                    return fmt.Errorf("invalid credential type: %w", err)
                }
            }

            if publish {
                visibility := api.IssueVCRequestVisibility(visibilityStr)
                request.Visibility = &visibility
            }
            if len(expirationDate) > 0 {
                request.ExpirationDate = &expirationDate
            }
            issuedVC, err := httpClient(core.NewClientConfigForCommand(cmd)).IssueVC(request)
            if err != nil {
                return err
            }
            formattedVC, _ := json.MarshalIndent(issuedVC, "", "  ")
            cmd.Println(string(formattedVC))
            return nil
        },
    }
    result.Flags().BoolVarP(&publish, "publish", "p", true, "Whether to publish the credential to the network.")
    result.Flags().StringVarP(&visibilityStr, "visibility", "v", "private", "Whether to publish the credential publicly ('public') or privately ('private').")
    result.Flags().StringVarP(&expirationDate, "expiration", "e", "", "Date in RFC3339 format when the VC expires.")
    return result
}

// httpClient creates a remote client
func httpClient(config core.ClientConfig) api.HTTPClient {
    return api.HTTPClient{
        ClientConfig: config,
    }
}