github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/revokeuser.go (about)

     1  /*
     2   * Copyright 2018-2022 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package cmd
    17  
    18  import (
    19  	"fmt"
    20  	"strings"
    21  	"time"
    22  
    23  	cli "github.com/nats-io/cliprompts/v2"
    24  	"github.com/nats-io/jwt/v2"
    25  	"github.com/nats-io/nkeys"
    26  	"github.com/nats-io/nsc/v2/cmd/store"
    27  	"github.com/spf13/cobra"
    28  )
    29  
    30  func createRevokeUserCmd() *cobra.Command {
    31  	var params RevokeUserParams
    32  	cmd := &cobra.Command{
    33  		Use:          "add-user",
    34  		Aliases:      []string{"add_user"},
    35  		Short:        "Revoke a user",
    36  		Args:         MaxArgs(0),
    37  		SilenceUsage: true,
    38  		RunE: func(cmd *cobra.Command, args []string) error {
    39  			return RunAction(cmd, args, &params)
    40  		},
    41  	}
    42  	cmd.Flags().StringVarP(&params.user, "name", "n", "", "user name")
    43  	cmd.Flags().VarP(&params.at, "at", "", "revokes all user credentials created"+
    44  		" or edited before a Unix timestamp ('0' is treated as now, accepted formats are RFC3339 or #seconds since epoch)")
    45  	params.userKey.BindFlags("user-public-key", "u", nkeys.PrefixByteUser, cmd)
    46  	params.AccountContextParams.BindFlags(cmd)
    47  
    48  	return cmd
    49  }
    50  
    51  func init() {
    52  	revokeCmd.AddCommand(createRevokeUserCmd())
    53  }
    54  
    55  // RevokeUserParams hold the info necessary to add a user to the revocation list in an account
    56  type RevokeUserParams struct {
    57  	AccountContextParams
    58  	at      dateTime
    59  	user    string
    60  	userKey PubKeyParams
    61  	claim   *jwt.AccountClaims
    62  	SignerParams
    63  }
    64  
    65  func (p *RevokeUserParams) SetDefaults(ctx ActionCtx) error {
    66  	if p.userKey.publicKey != "" && p.user != "" {
    67  		return fmt.Errorf("user and user-public-key are mutually exclusive")
    68  	}
    69  	p.userKey.AllowWildcard = true
    70  	p.AccountContextParams.SetDefaults(ctx)
    71  	if err := p.userKey.SetDefaults(ctx); err != nil {
    72  		return err
    73  	}
    74  	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx)
    75  	return nil
    76  }
    77  
    78  func (p *RevokeUserParams) PreInteractive(ctx ActionCtx) error {
    79  	return p.AccountContextParams.Edit(ctx)
    80  }
    81  
    82  func (p *RevokeUserParams) Load(ctx ActionCtx) error {
    83  	var err error
    84  	if err = p.AccountContextParams.Validate(ctx); err != nil {
    85  		return err
    86  	}
    87  
    88  	if p.user != "" {
    89  		entries, err := ListUsers(ctx.StoreCtx().Store, p.AccountContextParams.Name)
    90  		if err != nil {
    91  			return err
    92  		}
    93  
    94  		n := strings.ToLower(p.user)
    95  		for _, e := range entries {
    96  			if e.Err == nil && strings.ToLower(e.Name) == n {
    97  				p.userKey.publicKey = e.Claims.Claims().Subject
    98  				break
    99  			}
   100  		}
   101  		if p.userKey.publicKey == "" {
   102  			return fmt.Errorf("user %q not found", p.user)
   103  		}
   104  	} else if p.user == "" && p.userKey.publicKey == "" && !InteractiveFlag {
   105  		uc, err := ctx.StoreCtx().DefaultUserClaim(p.AccountContextParams.Name)
   106  		if err != nil {
   107  			return err
   108  		}
   109  		p.userKey.publicKey = uc.Subject
   110  	}
   111  
   112  	p.claim, err = ctx.StoreCtx().Store.ReadAccountClaim(p.AccountContextParams.Name)
   113  	return err
   114  }
   115  
   116  func buildUserPublicKeyChoices(accountName string, ctx ActionCtx) ([]PubKeyChoice, error) {
   117  	var choices []PubKeyChoice
   118  	keyToName := map[string]string{}
   119  	keyToName[jwt.All] = "All Users"
   120  	infos, err := ListUsers(ctx.StoreCtx().Store, accountName)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	for _, i := range infos {
   125  		if i.Err == nil {
   126  			pkc := PubKeyChoice{}
   127  			pkc.Key = i.Claims.Claims().Subject
   128  			pkc.Label = i.Name
   129  			choices = append(choices, pkc)
   130  		}
   131  	}
   132  	return choices, nil
   133  }
   134  
   135  func (p *RevokeUserParams) PostInteractive(ctx ActionCtx) error {
   136  	choices, err := buildUserPublicKeyChoices(p.AccountContextParams.Name, ctx)
   137  	byName := false
   138  
   139  	if len(choices) > 0 {
   140  		byName, err = cli.Confirm("Revoke a user by name", true)
   141  		if err != nil {
   142  			return err
   143  		}
   144  	}
   145  	if byName {
   146  		if err != nil || len(choices) == 0 {
   147  			return err
   148  		}
   149  		if err := p.userKey.Select("Select revoked user to clear", choices...); err != nil {
   150  			return err
   151  		}
   152  	} else if err := p.userKey.Edit(); err != nil {
   153  		return err
   154  	}
   155  
   156  	if p.at == 0 {
   157  		if _, err := cli.Prompt("revoke all credentials created before (0 is now, formats are RFC3339 or #seconds since epoch)",
   158  			fmt.Sprintf("%d", p.at), cli.Val(p.at.Set)); err != nil {
   159  			return err
   160  		}
   161  	}
   162  	if err := p.SignerParams.Edit(ctx); err != nil {
   163  		return err
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func (p *RevokeUserParams) Validate(ctx ActionCtx) error {
   170  	if p.userKey.publicKey == "" && p.user == "" {
   171  		return fmt.Errorf("user or user-public-key is required")
   172  	}
   173  	if err := p.userKey.Valid(); err != nil {
   174  		return err
   175  	}
   176  	return p.SignerParams.Resolve(ctx)
   177  }
   178  
   179  func (p *RevokeUserParams) Run(ctx ActionCtx) (store.Status, error) {
   180  	if p.at == 0 {
   181  		p.claim.Revoke(p.userKey.publicKey)
   182  	} else {
   183  		p.claim.RevokeAt(p.userKey.publicKey, time.Unix(int64(p.at), 0))
   184  	}
   185  	token, err := p.claim.Encode(p.signerKP)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	r := store.NewDetailedReport(true)
   190  	StoreAccountAndUpdateStatus(ctx, token, r)
   191  	if r.HasNoErrors() {
   192  		if p.userKey.publicKey == jwt.All {
   193  			when := int64(p.at)
   194  			if when == 0 {
   195  				when = time.Now().Unix()
   196  			}
   197  			r.AddOK("revoked all users issued before %s", time.Unix(when, 0).String())
   198  		} else {
   199  			r.AddOK("revoked user %q", p.userKey.publicKey)
   200  		}
   201  	}
   202  	return r, nil
   203  }