github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/create/create_git_api_token.go (about) 1 package create 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "time" 8 9 "github.com/olli-ai/jx/v2/pkg/cmd/create/options" 10 11 "github.com/olli-ai/jx/v2/pkg/cmd/helper" 12 13 "github.com/chromedp/cdproto/cdp" 14 "github.com/chromedp/chromedp" 15 "github.com/chromedp/chromedp/runner" 16 "github.com/jenkins-x/jx-logging/pkg/log" 17 "github.com/olli-ai/jx/v2/pkg/auth" 18 "github.com/olli-ai/jx/v2/pkg/cmd/opts" 19 "github.com/olli-ai/jx/v2/pkg/cmd/templates" 20 "github.com/olli-ai/jx/v2/pkg/gits" 21 "github.com/olli-ai/jx/v2/pkg/nodes" 22 "github.com/olli-ai/jx/v2/pkg/util" 23 "github.com/spf13/cobra" 24 "k8s.io/apimachinery/pkg/util/uuid" 25 ) 26 27 var ( 28 create_git_token_long = templates.LongDesc(` 29 Creates a new API Token for a user on a Git Server 30 `) 31 32 create_git_token_example = templates.Examples(` 33 # Add a new API Token for a user for the local Git server 34 # prompting the user to find and enter the API Token 35 jx create git token -n local someUserName 36 37 # Add a new API Token for a user for the local Git server 38 # using browser automation to login to the Git server 39 # with the username and password to find the API Token 40 jx create git token -n local -p somePassword someUserName 41 `) 42 ) 43 44 // CreateGitTokenOptions the command line options for the command 45 type CreateGitTokenOptions struct { 46 options.CreateOptions 47 48 ServerFlags opts.ServerFlags 49 Username string 50 Password string 51 ApiToken string 52 Timeout string 53 } 54 55 // NewCmdCreateGitToken creates a command 56 func NewCmdCreateGitToken(commonOpts *opts.CommonOptions) *cobra.Command { 57 options := &CreateGitTokenOptions{ 58 CreateOptions: options.CreateOptions{ 59 CommonOptions: commonOpts, 60 }, 61 } 62 63 cmd := &cobra.Command{ 64 Use: "token [username]", 65 Short: "Adds a new API token for a user on a Git server", 66 Aliases: []string{"api-token"}, 67 Long: create_git_token_long, 68 Example: create_git_token_example, 69 Run: func(cmd *cobra.Command, args []string) { 70 options.Cmd = cmd 71 options.Args = args 72 err := options.Run() 73 helper.CheckErr(err) 74 }, 75 } 76 options.ServerFlags.AddGitServerFlags(cmd) 77 cmd.Flags().StringVarP(&options.ApiToken, "api-token", "t", "", "The API Token for the user") 78 cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The User password to try automatically create a new API Token") 79 cmd.Flags().StringVarP(&options.Timeout, "timeout", "", "", "The timeout if using browser automation to generate the API token (by passing username and password)") 80 81 return cmd 82 } 83 84 // Run implements the command 85 func (o *CreateGitTokenOptions) Run() error { 86 args := o.Args 87 if len(args) > 0 { 88 o.Username = args[0] 89 } 90 if len(args) > 1 { 91 o.ApiToken = args[1] 92 } 93 authConfigSvc, err := o.GitAuthConfigService() 94 if err != nil { 95 return err 96 } 97 config := authConfigSvc.Config() 98 99 server, err := o.FindGitServer(config, &o.ServerFlags) 100 if err != nil { 101 return err 102 } 103 err = o.EnsureGitServiceCRD(server) 104 if err != nil { 105 return err 106 } 107 108 // TODO add the API thingy... 109 if o.Username == "" { 110 return fmt.Errorf("No Username specified") 111 } 112 113 userAuth := config.GetOrCreateUserAuth(server.URL, o.Username) 114 if o.ApiToken != "" { 115 userAuth.ApiToken = o.ApiToken 116 } 117 118 tokenUrl := gits.ProviderAccessTokenURL(server.Kind, server.URL, userAuth.Username) 119 120 if userAuth.IsInvalid() && o.Password != "" { 121 err = o.tryFindAPITokenFromBrowser(tokenUrl, userAuth) 122 } 123 124 if err != nil || userAuth.IsInvalid() { 125 f := func(username string) error { 126 tokenUrl := gits.ProviderAccessTokenURL(server.Kind, server.URL, username) 127 128 log.Logger().Infof("Please generate an API Token for %s server %s", server.Kind, server.Label()) 129 log.Logger().Infof("Click this URL %s\n", util.ColorInfo(tokenUrl)) 130 log.Logger().Infof("Then COPY the token and enter in into the form below:\n") 131 return nil 132 } 133 134 err = config.EditUserAuth(server.Label(), userAuth, o.Username, false, o.BatchMode, f, o.GetIOFileHandles()) 135 if err != nil { 136 return err 137 } 138 if userAuth.IsInvalid() { 139 return fmt.Errorf("You did not properly define the user authentication!") 140 } 141 } 142 143 config.CurrentServer = server.URL 144 err = authConfigSvc.SaveConfig() 145 if err != nil { 146 return err 147 } 148 149 log.Logger().Infof("Created user %s API Token for Git server %s at %s", 150 util.ColorInfo(o.Username), util.ColorInfo(server.Name), util.ColorInfo(server.URL)) 151 152 return nil 153 } 154 155 // lets try use the users browser to find the API token 156 func (o *CreateGitTokenOptions) tryFindAPITokenFromBrowser(tokenUrl string, userAuth *auth.UserAuth) error { 157 var ctxt context.Context 158 var cancel context.CancelFunc 159 if o.Timeout != "" { 160 duration, err := time.ParseDuration(o.Timeout) 161 if err != nil { 162 return err 163 } 164 ctxt, cancel = context.WithTimeout(context.Background(), duration) 165 } else { 166 ctxt, cancel = context.WithCancel(context.Background()) 167 } 168 defer cancel() 169 log.Logger().Infof("Trying to generate an API token for user: %s", util.ColorInfo(userAuth.Username)) 170 171 c, err := o.createChromeClient(ctxt) 172 if err != nil { 173 return err 174 } 175 176 err = c.Run(ctxt, chromedp.Tasks{ 177 chromedp.Navigate(tokenUrl), 178 }) 179 if err != nil { 180 return err 181 } 182 183 nodeSlice := []*cdp.Node{} 184 err = c.Run(ctxt, chromedp.Nodes("//input", &nodeSlice)) 185 if err != nil { 186 return err 187 } 188 189 login := false 190 for _, node := range nodeSlice { 191 name := node.AttributeValue("name") 192 if name == "user_name" { 193 login = true 194 } 195 } 196 197 if login { 198 err = o.captureScreenshot(ctxt, c, "screenshot-git-login.png", "//div") 199 if err != nil { 200 return err 201 } 202 log.Logger().Infof("logging in") 203 err = c.Run(ctxt, chromedp.Tasks{ 204 chromedp.WaitVisible("user_name", chromedp.ByID), 205 chromedp.SendKeys("user_name", userAuth.Username, chromedp.ByID), 206 chromedp.SendKeys("password", o.Password+"\n", chromedp.ByID), 207 }) 208 if err != nil { 209 return err 210 } 211 } 212 213 err = o.captureScreenshot(ctxt, c, "screenshot-git-api-token.png", "//div") 214 if err != nil { 215 return err 216 } 217 218 log.Logger().Info("Generating new token") 219 220 tokenId := "jx-" + string(uuid.NewUUID()) 221 generateNewTokenButtonSelector := "//div[normalize-space(text())='Generate New Token']" // #nosec 222 223 tokenResultDivSelector := "//div[@class='ui info message']/p" // #nosec 224 err = c.Run(ctxt, chromedp.Tasks{ 225 chromedp.WaitVisible(generateNewTokenButtonSelector), 226 chromedp.Click(generateNewTokenButtonSelector), 227 chromedp.WaitVisible("name", chromedp.ByID), 228 chromedp.SendKeys("name", tokenId+"\n", chromedp.ByID), 229 chromedp.WaitVisible(tokenResultDivSelector), 230 chromedp.Nodes(tokenResultDivSelector, &nodeSlice), 231 }) 232 if err != nil { 233 return err 234 } 235 token := "" 236 for _, node := range nodeSlice { 237 text := nodes.NodeText(node) 238 if text != "" && token == "" { 239 token = text 240 break 241 } 242 } 243 log.Logger().Info("Found API Token") 244 if token != "" { 245 userAuth.ApiToken = token 246 } 247 248 err = c.Shutdown(ctxt) 249 if err != nil { 250 return err 251 } 252 return nil 253 } 254 255 // lets try use the users browser to find the API token 256 func (o *CreateGitTokenOptions) createChromeClient(ctxt context.Context) (*chromedp.CDP, error) { 257 if o.BatchMode { 258 options := func(m map[string]interface{}) error { 259 m["remote-debugging-port"] = 9222 260 m["no-sandbox"] = true 261 m["headless"] = true 262 return nil 263 } 264 265 return chromedp.New(ctxt, chromedp.WithRunnerOptions(runner.CommandLineOption(options))) 266 } 267 return chromedp.New(ctxt) 268 } 269 270 func (o *CreateGitTokenOptions) captureScreenshot(ctxt context.Context, c *chromedp.CDP, screenshotFile string, selector interface{}, options ...chromedp.QueryOption) error { 271 log.Logger().Info("Creating a screenshot...") 272 273 var picture []byte 274 err := c.Run(ctxt, chromedp.Tasks{ 275 chromedp.Sleep(2 * time.Second), 276 chromedp.Screenshot(selector, &picture, options...), 277 }) 278 if err != nil { 279 return err 280 } 281 log.Logger().Info("Saving a screenshot...") 282 283 err = ioutil.WriteFile(screenshotFile, picture, util.DefaultWritePermissions) 284 if err != nil { 285 log.Logger().Fatal(err.Error()) 286 } 287 288 log.Logger().Infof("Saved screenshot: %s", util.ColorInfo(screenshotFile)) 289 return err 290 }