Repository: nats-io/nsc
Pull request #253 (2020-01-29 19:19:37)

Added some usage examples documenting use of --max-responses.
Simplified --max-responses so that the cli itself attempts the validation of the flag as a number.

aricart: Figured out how to allow the usages of a bare flag (with a default of `1`) or with an arbitrary value.

This means that we can do `--allow-pub-response` and `--allow-pub-response=10`.

Patch:
diff --git a/cmd/adduser.go b/cmd/adduser.go
index 36ef743..2730ce0 100644
--- a/cmd/adduser.go
+++ b/cmd/adduser.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -37,7 +37,32 @@ func CreateAddUserCmd() *cobra.Command {
 		Short:        "Add an user to the account",
 		Args:         cobra.MaximumNArgs(1),
 		SilenceUsage: true,
-		Example:      params.longHelp(),
+		Example: `# Add user with a previously generated public key:
+nsc add user --name <n> --public-key <nkey>
+# Note: that unless you specify the seed, the key won't be stored in the keyring.'
+
+# Set permissions so that the user can publish and/or subscribe to the specified subjects or wildcards:
+nsc add user --name <n> --allow-pubsub <subject>,...
+nsc add user --name <n> --allow-pub <subject>,...
+nsc add user --name <n> --allow-sub <subject>,...
+
+# Set permissions so that the user cannot publish nor subscribe to the specified subjects or wildcards:
+nsc add user --name <n> --deny-pubsub <subject>,...
+nsc add user --name <n> --deny-pub <subject>,...
+nsc add user --name <n> --deny-sub <subject>,...
+
+# To dynamically allow publishing to reply subjects, this works well for service responders:
+nsc add user --name <n> --allow-pub-response
+
+# A permission to publish a response can be removed after a duration from when 
+# the message was received:
+nsc add user --name <n> --allow-pub-response --response-ttl 5s
+
+# If the service publishes multiple response messages, you can specify:
+nsc add user --name <n> --allow-pub-response=5
+# See 'nsc edit export --response-type --help' to enable multiple
+# responses between accounts
+`,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return RunAction(cmd, args, &params)
 		},
@@ -51,9 +76,6 @@ func CreateAddUserCmd() *cobra.Command {
 	cmd.Flags().StringSliceVarP(&params.denyPubsub, "deny-pubsub", "", nil, "deny publish and subscribe permissions - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringSliceVarP(&params.denySubs, "deny-sub", "", nil, "deny subscribe permissions - comma separated list or option can be specified multiple times")
 
-	cmd.Flags().StringVarP(&params.respTTL, "response-ttl", "", "", "max response permission ttl for responding to requests (global to all requests for user)")
-	cmd.Flags().StringVarP(&params.respMax, "max-responses", "", "", "max number of responses for a request (global to all requests for the user)")
-
 	cmd.Flags().StringSliceVarP(&params.tags, "tag", "", nil, "tags for user - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringSliceVarP(&params.src, "source-network", "", nil, "source network for connection - comma separated list or option can be specified multiple times")
 
@@ -62,6 +84,7 @@ func CreateAddUserCmd() *cobra.Command {
 
 	params.TimeParams.BindFlags(cmd)
 	params.AccountContextParams.BindFlags(cmd)
+	params.ResponsePermsParams.bindSetFlags(cmd)
 
 	return cmd
 }
@@ -236,7 +259,7 @@ func (p *AddUserParams) editUserClaim(c interface{}, ctx ActionCtx) error {
 		uc.Expires, _ = p.TimeParams.ExpiryDate()
 	}
 
-	if _, err := p.ResponsePermsParams.Run(uc); err != nil {
+	if _, err := p.ResponsePermsParams.Run(uc, ctx); err != nil {
 		return err
 	}
 
@@ -264,10 +287,25 @@ func (p *AddUserParams) editUserClaim(c interface{}, ctx ActionCtx) error {
 
 type ResponsePermsParams struct {
 	respTTL string
-	respMax string
+	respMax int
 	rmResp  bool
 }
 
+func (p *ResponsePermsParams) bindSetFlags(cmd *cobra.Command) {
+	cmd.Flags().StringVarP(&p.respTTL, "response-ttl", "", "", "max ttl for responding to requests (global) - [#ms(millis) | #s(econds) | m(inutes) | h(ours)]")
+
+	cmd.Flags().IntVarP(&p.respMax, "allow-pub-response", "", 0, "client can publish only to requests up-to n responses (global)")
+	cmd.Flag("allow-pub-response").NoOptDefVal = "1"
+
+	cmd.Flags().IntVarP(&p.respMax, "max-responses", "", 0, "client can publish only to requests up-to n responses (global)")
+	cmd.Flag("max-responses").Hidden = true
+	cmd.Flag("max-responses").Deprecated = "use --allow-pub-n-responses or --allow-pub-response"
+}
+
+func (p *ResponsePermsParams) bindRemoveFlags(cmd *cobra.Command) {
+	cmd.Flags().BoolVarP(&p.rmResp, "rm-response-perms", "", false, "remove response settings")
+}
+
 func (p *ResponsePermsParams) maxResponseValidator(s string) error {
 	_, err := p.parseMaxResponse(s)
 	return err
@@ -303,46 +341,46 @@ func (p *ResponsePermsParams) Edit(hasPerm bool) error {
 	}
 	if ok {
 		if hasPerm {
-			p.rmResp, err = cli.Confirm("delete response permissions", p.rmResp)
+			p.rmResp, err = cli.Confirm("delete response permission", p.rmResp)
 			if err != nil {
 				return err
 			}
 		}
 		if !p.rmResp {
-			p.respMax, err = cli.Prompt("Number of max responses", p.respMax, cli.Val(p.maxResponseValidator))
-			p.respTTL, err = cli.Prompt("Response Permission TTL", p.respTTL, cli.Val(p.ttlValidator))
+			s, err := cli.Prompt("Max number of responses", fmt.Sprintf("%d", p.respMax), cli.Val(p.maxResponseValidator))
+			if err != nil {
+				return err
+			}
+			p.respMax, _ = p.parseMaxResponse(s)
+			p.respTTL, err = cli.Prompt("Response TTL", p.respTTL, cli.Val(p.ttlValidator))
+			if err != nil {
+				return err
+			}
 		}
 	}
 	return nil
 }
 
 func (p *ResponsePermsParams) Validate() error {
-	if err := p.maxResponseValidator(p.respMax); err != nil {
-		return err
-	}
 	if err := p.ttlValidator(p.respTTL); err != nil {
 		return err
 	}
 	return nil
 }
 
-func (p *ResponsePermsParams) Run(uc *jwt.UserClaims) (*store.Report, error) {
+func (p *ResponsePermsParams) Run(uc *jwt.UserClaims, ctx ActionCtx) (*store.Report, error) {
 	r := store.NewDetailedReport(true)
 	if p.rmResp {
 		uc.Resp = nil
 		r.AddOK("removed response permissions")
 		return r, nil
 	}
-	if p.respMax != "" {
-		v, err := p.parseMaxResponse(p.respMax)
-		if err != nil {
-			return nil, err
-		}
+	if ctx.CurrentCmd().Flag("max-responses").Changed || p.respMax != 0 {
 		if uc.Resp == nil {
 			uc.Resp = &jwt.ResponsePermission{}
 		}
-		uc.Resp.MaxMsgs = v
-		r.AddOK("set max responses to %d", v)
+		uc.Resp.MaxMsgs = p.respMax
+		r.AddOK("set max responses to %d", p.respMax)
 	}
 
 	if p.respTTL != "" {
diff --git a/cmd/edituser.go b/cmd/edituser.go
index d991908..0782731 100644
--- a/cmd/edituser.go
+++ b/cmd/edituser.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -29,8 +29,36 @@ import (
 func createEditUserCmd() *cobra.Command {
 	var params EditUserParams
 	cmd := &cobra.Command{
-		Use:          "user",
-		Short:        "Edit an user",
+		Use:   "user",
+		Short: "Edit an user",
+		Long: `# Edit permissions so that the user can publish and/or subscribe to the specified subjects or wildcards:
+nsc edit user --name <n> --allow-pubsub <subject>,...
+nsc edit user --name <n> --allow-pub <subject>,...
+nsc edit user --name <n> --allow-sub <subject>,...
+
+# Set permissions so that the user cannot publish nor subscribe to the specified subjects or wildcards:
+nsc edit user --name <n> --deny-pubsub <subject>,...
+nsc edit user --name <n> --deny-pub <subject>,...
+nsc edit user --name <n> --deny-sub <subject>,...
+
+# Remove a previously set permissions
+nsc edit user --name <n> --rm <subject>,...
+
+# To dynamically allow publishing to reply subjects, this works well for service responders:
+nsc edit user --name <n> --allow-pub-response
+
+# A permission to publish a response can be removed after a duration from when 
+# the message was received:
+nsc edit user --name <n> --allow-pub-response --response-ttl 5s
+
+# If the service publishes multiple response messages, you can specify:
+nsc edit user --name <n> --allow-pub-response=5
+# See 'nsc edit export --response-type --help' to enable multiple
+# responses between accounts.
+
+# To remove response settings:
+nsc edit user --name <n> --rm-response-perms
+`,
 		Args:         cobra.MaximumNArgs(1),
 		SilenceUsage: true,
 		RunE: func(cmd *cobra.Command, args []string) error {
@@ -48,10 +76,6 @@ func createEditUserCmd() *cobra.Command {
 	cmd.Flags().StringSliceVarP(&params.denyPubsub, "deny-pubsub", "", nil, "add deny publish and subscribe permissions - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringSliceVarP(&params.denySubs, "deny-sub", "", nil, "add deny subscribe permissions - comma separated list or option can be specified multiple times")
 
-	cmd.Flags().StringVarP(&params.respTTL, "response-ttl", "", "", "max ttl for responding to requests (global to all requests for user)")
-	cmd.Flags().StringVarP(&params.respMax, "max-responses", "", "", "max number of responses for a request (global to all requests for the user)")
-	cmd.Flags().BoolVarP(&params.rmResp, "rm-response-perms", "", false, "remove response settings")
-
 	cmd.Flags().StringSliceVarP(&params.tags, "tag", "", nil, "add tags for user - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringSliceVarP(&params.rmTags, "rm-tag", "", nil, "remove tag - comma separated list or option can be specified multiple times")
 
@@ -64,6 +88,8 @@ func createEditUserCmd() *cobra.Command {
 
 	params.AccountContextParams.BindFlags(cmd)
 	params.GenericClaimsParams.BindFlags(cmd)
+	params.ResponsePermsParams.bindSetFlags(cmd)
+	params.ResponsePermsParams.bindRemoveFlags(cmd)
 
 	return cmd
 }
@@ -101,7 +127,7 @@ func (p *EditUserParams) SetDefaults(ctx ActionCtx) error {
 
 	if !InteractiveFlag && ctx.NothingToDo("start", "expiry", "rm", "allow-pub", "allow-sub", "allow-pubsub",
 		"deny-pub", "deny-sub", "deny-pubsub", "tag", "rm-tag", "source-network", "rm-source-network", "payload",
-		"rm-response-perms", "max-responses", "response-ttl") {
+		"rm-response-perms", "max-responses", "response-ttl", "allow-pub-response") {
 		ctx.CurrentCmd().SilenceUsage = false
 		return fmt.Errorf("specify an edit option")
 	}
@@ -280,7 +306,7 @@ func (p *EditUserParams) Run(ctx ActionCtx) (store.Status, error) {
 	sort.Strings(srcList)
 	p.claim.Src = strings.Join(srcList, ",")
 
-	s, err := p.ResponsePermsParams.Run(p.claim)
+	s, err := p.ResponsePermsParams.Run(p.claim, ctx)
 	if err != nil {
 		return nil, err
 	}
diff --git a/go.sum b/go.sum
index 8360b31..fa609aa 100644
--- a/go.sum
+++ b/go.sum
@@ -64,8 +64,6 @@ github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KH
 github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/nats-io/cliprompts/v2 v2.0.0-20191203200115-ae8d3743105d h1:2+6rW0YuYCNV3BOB5iHI+85pLB29CBWrS1RPsQsfyCE=
-github.com/nats-io/cliprompts/v2 v2.0.0-20191203200115-ae8d3743105d/go.mod h1:oweZn7AeaVJYKlNHfCIhznJVsdySLSng55vfuINE/d0=
 github.com/nats-io/cliprompts/v2 v2.0.0-20191226174129-372d79b36768 h1:sdr8zfPeN/b4/TDoxbD9ARAtyP92zDFDvj87MeAAyjk=
 github.com/nats-io/cliprompts/v2 v2.0.0-20191226174129-372d79b36768/go.mod h1:oweZn7AeaVJYKlNHfCIhznJVsdySLSng55vfuINE/d0=
 github.com/nats-io/jwt v0.2.6 h1:eAyoYvGgGLXR2EpnsBUvi/FcFrBqN6YKFVbOoEfPN4k=


Test patch:
diff --git a/cmd/adduser_test.go b/cmd/adduser_test.go
index 57384ee..a015693 100644
--- a/cmd/adduser_test.go
+++ b/cmd/adduser_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2019 The NATS Authors
+ * Copyright 2018-2020 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -222,3 +222,19 @@ func Test_AddUserWithResponsePerms(t *testing.T) {
 	d, _ := time.ParseDuration("2ms")
 	require.Equal(t, d, uc.Resp.Expires)
 }
+
+func Test_AddUserWithResponsePerms2(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "A")
+
+	_, _, err := ExecuteCmd(CreateAddUserCmd(), "U", "--allow-pub-response", "--response-ttl", "2ms")
+	require.NoError(t, err)
+
+	uc, err := ts.Store.ReadUserClaim("A", "U")
+	require.NoError(t, err)
+	require.NotNil(t, uc.Resp)
+	require.Equal(t, 1, uc.Resp.MaxMsgs)
+	d, _ := time.ParseDuration("2ms")
+	require.Equal(t, d, uc.Resp.Expires)
+}
diff --git a/cmd/edituser_test.go b/cmd/edituser_test.go
index 53b2572..6f34de7 100644
--- a/cmd/edituser_test.go
+++ b/cmd/edituser_test.go
@@ -303,3 +303,31 @@ func Test_EditUserResponsePermissions(t *testing.T) {
 	require.NoError(t, err)
 	require.Nil(t, uc.Resp)
 }
+
+func Test_EditUserResponsePermissions2(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "A")
+
+	_, _, err := ExecuteCmd(CreateAddUserCmd(), "U", "--allow-pub-response", "--response-ttl", "2ms")
+	require.NoError(t, err)
+	uc, err := ts.Store.ReadUserClaim("A", "U")
+	require.NoError(t, err)
+	require.NotNil(t, uc.Resp)
+	require.Equal(t, 1, uc.Resp.MaxMsgs)
+
+	_, _, err = ExecuteCmd(createEditUserCmd(), "U", "--allow-pub-response=100", "--response-ttl", "2ms")
+	require.NoError(t, err)
+
+	uc, err = ts.Store.ReadUserClaim("A", "U")
+	require.NoError(t, err)
+	require.NotNil(t, uc.Resp)
+	require.Equal(t, 100, uc.Resp.MaxMsgs)
+
+	_, _, err = ExecuteCmd(createEditUserCmd(), "--rm-response-perms")
+	require.NoError(t, err)
+
+	uc, err = ts.Store.ReadUserClaim("A", "U")
+	require.NoError(t, err)
+	require.Nil(t, uc.Resp)
+}