github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/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/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, ¶ms) 40 }, 41 } 42 cmd.Flags().StringVarP(¶ms.user, "name", "n", "", "user name") 43 cmd.Flags().VarP(¶ms.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 }