Repository: nats-io/nsc
Pull request #322 (2020-11-16 22:33:39)

[feat] select from multiple keys when signing user
Signed-off-by: Matthias Hanel <mh@synadia.com>


Patch:
diff --git a/cmd/adduser.go b/cmd/adduser.go
index 12246f9..4eb8f9d 100644
--- a/cmd/adduser.go
+++ b/cmd/adduser.go
@@ -174,17 +174,35 @@ func (p *AddUserParams) PreInteractive(ctx ActionCtx) error {
 		return err
 	}
 
-	if err = p.SignerParams.Edit(ctx); err != nil {
+	signers, err := validUserSigners(ctx, p.Name)
+	if err != nil {
 		return err
 	}
-
-	return nil
+	p.SignerParams.SetPrompt("select the key to sign the user")
+	return p.SignerParams.SelectFromSigners(ctx, signers)
 }
 
 func (p *AddUserParams) Load(_ ActionCtx) error {
 	return nil
 }
 
+func validUserSigners(ctx ActionCtx, accName string) ([]string, error) {
+	ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName)
+	if err != nil {
+		return nil, err
+	}
+	var signers []string
+	if ctx.StoreCtx().KeyStore.HasPrivateKey(ac.Subject) {
+		signers = append(signers, ac.Subject)
+	}
+	for _, signingKey := range ac.SigningKeys {
+		if ctx.StoreCtx().KeyStore.HasPrivateKey(signingKey) {
+			signers = append(signers, signingKey)
+		}
+	}
+	return signers, nil
+}
+
 func (p *AddUserParams) PostInteractive(_ ActionCtx) error {
 	return nil
 }
diff --git a/cmd/edituser.go b/cmd/edituser.go
index 04bd0b9..9056f2c 100644
--- a/cmd/edituser.go
+++ b/cmd/edituser.go
@@ -156,7 +156,12 @@ func (p *EditUserParams) PreInteractive(ctx ActionCtx) error {
 		}
 	}
 
-	return nil
+	signers, err := validUserSigners(ctx, p.Name)
+	if err != nil {
+		return err
+	}
+	p.SignerParams.SetPrompt("select the key to sign the user")
+	return p.SignerParams.SelectFromSigners(ctx, signers)
 }
 
 func (p *EditUserParams) Load(ctx ActionCtx) error {
@@ -211,9 +216,6 @@ func (p *EditUserParams) PostInteractive(ctx ActionCtx) error {
 	if err := p.GenericClaimsParams.Edit(p.claim.Tags); err != nil {
 		return err
 	}
-	if err := p.SignerParams.Edit(ctx); err != nil {
-		return err
-	}
 	return nil
 }
 


Test patch:
diff --git a/cmd/adduser_test.go b/cmd/adduser_test.go
index 4ed9b1f..629f82e 100644
--- a/cmd/adduser_test.go
+++ b/cmd/adduser_test.go
@@ -389,6 +389,44 @@ func Test_AddUserWithSigningKeyOnly(t *testing.T) {
 	_, _, err = ExecuteCmd(HoistRootFlags(CreateAddUserCmd()), "--name", "AAA")
 	require.NoError(t, err)
 
-	_, err = ts.Store.ReadUserClaim("A", "AAA")
+	claim, err := ts.Store.ReadUserClaim("A", "AAA")
 	require.NoError(t, err)
+	require.NotEmpty(t, claim.IssuerAccount)
+	require.NotEqual(t, claim.Issuer, claim.IssuerAccount)
+	require.Equal(t, claim.Issuer, pk)
+}
+
+func Test_AddUserWithSigningKeyInteractive(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	// create a signing key
+	kp, err := nkeys.CreateAccount()
+	require.NoError(t, err)
+	_, err = ts.KeyStore.Store(kp)
+	require.NoError(t, err)
+	pk, err := kp.PublicKey()
+	require.NoError(t, err)
+	require.True(t, ts.KeyStore.HasPrivateKey(pk))
+
+	ts.AddAccount(t, "A")
+	_, _, err = ExecuteCmd(createEditAccount(), "--sk", pk)
+	require.NoError(t, err)
+
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.NotNil(t, ac)
+	require.True(t, ts.KeyStore.HasPrivateKey(ac.Subject))
+
+	inputs := []interface{}{"AAA", true, "0", "0", 1}
+	cmd := CreateAddUserCmd()
+	HoistRootFlags(cmd)
+	_, _, err = ExecuteInteractiveCmd(cmd, inputs)
+	require.NoError(t, err)
+
+	claim, err := ts.Store.ReadUserClaim("A", "AAA")
+	require.NoError(t, err)
+	require.NotEmpty(t, claim.IssuerAccount)
+	require.NotEqual(t, claim.Issuer, claim.IssuerAccount)
+	require.Equal(t, claim.Issuer, pk)
 }
diff --git a/cmd/edituser_test.go b/cmd/edituser_test.go
index 237caef..b3f2b98 100644
--- a/cmd/edituser_test.go
+++ b/cmd/edituser_test.go
@@ -20,6 +20,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/nats-io/nkeys"
+
 	cli "github.com/nats-io/cliprompts/v2"
 	"github.com/nats-io/jwt"
 	"github.com/stretchr/testify/require"
@@ -365,3 +367,78 @@ func Test_EditUserBearerToken(t *testing.T) {
 	require.NoError(t, err)
 	require.False(t, u.BearerToken)
 }
+
+func Test_EditUserWithSigningKeyOnly(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	// create a signing key
+	kp, err := nkeys.CreateAccount()
+	require.NoError(t, err)
+	_, err = ts.KeyStore.Store(kp)
+	require.NoError(t, err)
+	pk, err := kp.PublicKey()
+	require.NoError(t, err)
+	require.True(t, ts.KeyStore.HasPrivateKey(pk))
+
+	ts.AddAccount(t, "A")
+	_, _, err = ExecuteCmd(createEditAccount(), "--sk", pk)
+	require.NoError(t, err)
+
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.NotNil(t, ac)
+	ts.KeyStore.Remove(ac.Subject)
+	require.False(t, ts.KeyStore.HasPrivateKey(ac.Subject))
+
+	_, _, err = ExecuteCmd(HoistRootFlags(CreateAddUserCmd()), "--name", "AAA")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(HoistRootFlags(createEditUserCmd()), "--name", "AAA", "--payload", "5")
+	require.NoError(t, err)
+
+	claim, err := ts.Store.ReadUserClaim("A", "AAA")
+	require.NoError(t, err)
+	require.Equal(t, claim.Limits.Payload, int64(5))
+	require.NotEmpty(t, claim.IssuerAccount)
+	require.NotEqual(t, claim.Issuer, claim.IssuerAccount)
+	require.Equal(t, claim.Issuer, pk)
+}
+
+func Test_EditUserWithSigningKeyInteractive(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	// create a signing key
+	kp, err := nkeys.CreateAccount()
+	require.NoError(t, err)
+	_, err = ts.KeyStore.Store(kp)
+	require.NoError(t, err)
+	pk, err := kp.PublicKey()
+	require.NoError(t, err)
+	require.True(t, ts.KeyStore.HasPrivateKey(pk))
+
+	ts.AddAccount(t, "A")
+	_, _, err = ExecuteCmd(createEditAccount(), "--sk", pk)
+	require.NoError(t, err)
+
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	require.NotNil(t, ac)
+	require.True(t, ts.KeyStore.HasPrivateKey(ac.Subject))
+
+	_, _, err = ExecuteCmd(HoistRootFlags(CreateAddUserCmd()), "--name", "AAA")
+	require.NoError(t, err)
+
+	inputs := []interface{}{1, "5", "0", "0", false}
+	cmd := createEditUserCmd()
+	HoistRootFlags(cmd)
+	_, _, err = ExecuteInteractiveCmd(cmd, inputs, "--name", "AAA")
+	require.NoError(t, err)
+
+	claim, err := ts.Store.ReadUserClaim("A", "AAA")
+	require.NoError(t, err)
+	require.Equal(t, claim.Limits.Payload, int64(5))
+	require.NotEmpty(t, claim.IssuerAccount)
+	require.NotEqual(t, claim.Issuer, claim.IssuerAccount)
+	require.Equal(t, claim.Issuer, pk)
+}