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  }