github.com/kbehouse/nsc@v0.0.6/cmd/revokeuser.go (about) 1 /* 2 * Copyright 2018-2020 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 "github.com/kbehouse/nsc/cmd/store" 24 cli "github.com/nats-io/cliprompts/v2" 25 "github.com/nats-io/jwt/v2" 26 "github.com/nats-io/nkeys" 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 p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx) 72 return nil 73 } 74 75 func (p *RevokeUserParams) PreInteractive(ctx ActionCtx) error { 76 return p.AccountContextParams.Edit(ctx) 77 } 78 79 func (p *RevokeUserParams) Load(ctx ActionCtx) error { 80 var err error 81 if err = p.AccountContextParams.Validate(ctx); err != nil { 82 return err 83 } 84 85 if p.user != "" { 86 entries, err := ListUsers(ctx.StoreCtx().Store, p.AccountContextParams.Name) 87 if err != nil { 88 return err 89 } 90 91 n := strings.ToLower(p.user) 92 for _, e := range entries { 93 if e.Err == nil && strings.ToLower(e.Name) == n { 94 p.userKey.publicKey = e.Claims.Claims().Subject 95 break 96 } 97 } 98 if p.userKey.publicKey == "" { 99 return fmt.Errorf("user %q not found", p.user) 100 } 101 } else if p.user == "" && p.userKey.publicKey == "" && !InteractiveFlag { 102 uc, err := ctx.StoreCtx().DefaultUserClaim(p.AccountContextParams.Name) 103 if err != nil { 104 return err 105 } 106 p.userKey.publicKey = uc.Subject 107 } 108 109 p.claim, err = ctx.StoreCtx().Store.ReadAccountClaim(p.AccountContextParams.Name) 110 return err 111 } 112 113 func buildUserPublicKeyChoices(accountName string, ctx ActionCtx) ([]PubKeyChoice, error) { 114 var choices []PubKeyChoice 115 keyToName := map[string]string{} 116 keyToName[jwt.All] = "All Users" 117 infos, err := ListUsers(ctx.StoreCtx().Store, accountName) 118 if err != nil { 119 return nil, err 120 } 121 for _, i := range infos { 122 if i.Err == nil { 123 pkc := PubKeyChoice{} 124 pkc.Key = i.Claims.Claims().Subject 125 pkc.Label = i.Name 126 choices = append(choices, pkc) 127 } 128 } 129 return choices, nil 130 } 131 132 func (p *RevokeUserParams) PostInteractive(ctx ActionCtx) error { 133 choices, err := buildUserPublicKeyChoices(p.AccountContextParams.Name, ctx) 134 byName := false 135 136 if len(choices) > 0 { 137 byName, err = cli.Confirm("Revoke a user by name", true) 138 if err != nil { 139 return err 140 } 141 } 142 if byName { 143 if err != nil || len(choices) == 0 { 144 return err 145 } 146 if err := p.userKey.Select("Select revoked user to clear", choices...); err != nil { 147 return err 148 } 149 } else if err := p.userKey.Edit(); err != nil { 150 return err 151 } 152 153 if p.at == 0 { 154 if _, err := cli.Prompt("revoke all credentials created before (0 is now, formats are RFC3339 or #seconds since epoch)", 155 fmt.Sprintf("%d", p.at), cli.Val(p.at.Set)); err != nil { 156 return err 157 } 158 } 159 if err := p.SignerParams.Edit(ctx); err != nil { 160 return err 161 } 162 163 return nil 164 } 165 166 func (p *RevokeUserParams) Validate(ctx ActionCtx) error { 167 if p.userKey.publicKey == "" && p.user == "" { 168 return fmt.Errorf("user or user-public-key is required") 169 } 170 if err := p.userKey.Valid(); err != nil { 171 return err 172 } 173 return p.SignerParams.Resolve(ctx) 174 } 175 176 func (p *RevokeUserParams) Run(ctx ActionCtx) (store.Status, error) { 177 if p.at == 0 { 178 p.claim.Revoke(p.userKey.publicKey) 179 } else { 180 p.claim.RevokeAt(p.userKey.publicKey, time.Unix(int64(p.at), 0)) 181 } 182 token, err := p.claim.Encode(p.signerKP) 183 if err != nil { 184 return nil, err 185 } 186 r := store.NewDetailedReport(true) 187 StoreAccountAndUpdateStatus(ctx, token, r) 188 if r.HasNoErrors() { 189 if p.userKey.publicKey == jwt.All { 190 when := int64(p.at) 191 if when == 0 { 192 when = time.Now().Unix() 193 } 194 r.AddOK("revoked all users issued before %s", time.Unix(when, 0).String()) 195 } else { 196 r.AddOK("revoked user %q", p.userKey.publicKey) 197 } 198 } 199 return r, nil 200 }