diff --git a/internal/awsprofile/assumer_aws_sso.go b/internal/awsprofile/assumer_aws_sso.go index ff8213e..89d7ad1 100644 --- a/internal/awsprofile/assumer_aws_sso.go +++ b/internal/awsprofile/assumer_aws_sso.go @@ -2,8 +2,10 @@ package awsprofile import ( "context" + "os/exec" "time" + "github.com/AndreZiviani/aws-fuzzy/internal/afconfig" "github.com/AndreZiviani/aws-fuzzy/internal/securestorage" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" @@ -142,6 +144,11 @@ func (c *Profile) SSOLogin(ctx context.Context, configOpts ConfigOpts) (aws.Cred // SSODeviceCodeFlowFromStartUrl contains all the steps to complete a device code flow to retrieve an SSO token func SSODeviceCodeFlowFromStartUrl(ctx context.Context, cfg aws.Config, startUrl string, profile string, printOnly bool) (*securestorage.SSOToken, error) { + afcfg, err := afconfig.NewLoadedConfig() + if err != nil { + return nil, err + } + ssooidcClient := ssooidc.NewFromConfig(cfg) register, err := ssooidcClient.RegisterClient(ctx, &ssooidc.RegisterClientInput{ @@ -170,9 +177,25 @@ func SSODeviceCodeFlowFromStartUrl(ctx context.Context, cfg aws.Config, startUrl clio.Info(url) } - err = LaunchBrowser(url, profile, "sso", printOnly) - if err != nil { - return nil, err + if afcfg.CustomSSOBrowserPath != "" { + cmd := exec.Command(afcfg.CustomSSOBrowserPath, url) + err = cmd.Start() + if err != nil { + // fail silently + clio.Debug(err.Error()) + } else { + // detach from this new process because it continues to run + err = cmd.Process.Release() + if err != nil { + // fail silently + clio.Debug(err.Error()) + } + } + } else { + err = LaunchBrowser(url, profile, "sso", printOnly) + if err != nil { + return nil, err + } } clio.Info("Awaiting authentication in the browser...") diff --git a/internal/awsprofile/browser.go b/internal/awsprofile/browser.go index 3844cde..c3a967d 100644 --- a/internal/awsprofile/browser.go +++ b/internal/awsprofile/browser.go @@ -41,6 +41,9 @@ func LaunchBrowser(url string, profile string, flow string, printOnly bool) erro } var containerName string + var l gassume.Launcher + finalUrl := url + if flow == "sso" { if p.AWSConfig.SSOSession != nil { containerName = p.AWSConfig.SSOSession.Name @@ -51,27 +54,28 @@ func LaunchBrowser(url string, profile string, flow string, printOnly bool) erro containerName = p.Name } - var l gassume.Launcher - finalUrl := url - switch cfg.DefaultBrowser { case gbrowser.ChromeKey: l = glauncher.ChromeProfile{ + BrowserType: gbrowser.ChromeKey, ExecutablePath: browserPath, UserDataPath: path.Join(configDir, "chromium-profiles", "1"), // held over for backwards compatibility, "1" indicates Chrome profiles } case gbrowser.BraveKey: l = glauncher.ChromeProfile{ + BrowserType: gbrowser.BraveKey, ExecutablePath: browserPath, UserDataPath: path.Join(configDir, "chromium-profiles", "2"), // held over for backwards compatibility, "2" indicates Brave profiles } case gbrowser.EdgeKey: l = glauncher.ChromeProfile{ + BrowserType: gbrowser.EdgeKey, ExecutablePath: browserPath, UserDataPath: path.Join(configDir, "chromium-profiles", "3"), // held over for backwards compatibility, "3" indicates Edge profiles } case gbrowser.ChromiumKey: l = glauncher.ChromeProfile{ + BrowserType: gbrowser.ChromiumKey, ExecutablePath: browserPath, UserDataPath: path.Join(configDir, "chromium-profiles", "4"), // held over for backwards compatibility, "4" indicates Chromium profiles } @@ -112,6 +116,14 @@ func LaunchBrowser(url string, profile string, flow string, printOnly bool) erro } finalUrl = fmt.Sprintf("ext+granted-containers:name=%s&url=%s&color=%s&icon=%s", containerName, neturl.QueryEscape(url), color, icon) + case gbrowser.SafariKey: + l = glauncher.Safari{} + case gbrowser.ArcKey: + l = glauncher.Arc{} + case gbrowser.FirefoxDevEditionKey: + l = glauncher.FirefoxDevEdition{ + ExecutablePath: browserPath, + } case gbrowser.StdoutKey: fmt.Println(finalUrl) return nil diff --git a/internal/sso/browser.go b/internal/sso/browser.go index b63baf9..eb5dda3 100644 --- a/internal/sso/browser.go +++ b/internal/sso/browser.go @@ -13,15 +13,15 @@ import ( gbrowser "github.com/common-fate/granted/pkg/browser" "github.com/common-fate/granted/pkg/testable" opentracing "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" "golang.org/x/text/cases" "golang.org/x/text/language" ) -func NewBrowser(browser string, verbose bool) *Browser { +func NewBrowser(browser string, ssoBrowser string, verbose bool) *Browser { b := Browser{ - Browser: browser, - Verbose: verbose, + Browser: browser, + SSOBrowser: ssoBrowser, + Verbose: verbose, } return &b @@ -38,77 +38,116 @@ func (p *Browser) Execute(ctx context.Context) error { spanSso, ctx := opentracing.StartSpanFromContextWithTracer(ctx, tracer, "ssobrowsercmd") defer spanSso.Finish() + withStdio := survey.WithStdio(os.Stdin, os.Stderr, os.Stderr) browser := p.Browser + ssoBrowser := p.SSOBrowser + + conf, err := afconfig.NewLoadedConfig() + if err != nil { + return err + } if browser == "" { browser, err = gbrowser.HandleManualBrowserSelection() if err != nil { return err } + + err, browserKey, browserPath := p.GetBrowserSelection(withStdio, browser) + if err != nil { + return err + } + + conf.DefaultBrowser = browserKey + conf.CustomBrowserPath = browserPath + + err = conf.Save() + if err != nil { + return err + } + + clio.Successf("aws-fuzzy will default to using %s", browserKey) } - return p.ConfigureBrowserSelection(browser, "") + if ssoBrowser == "" { + bpIn := survey.Confirm{ + Message: "Use a different browser than your default browser for SSO login?", + Default: false, + Help: "For example, if you normally use a password manager in Chrome for your AWS login but Chrome is not your default browser, you would choose to use Chrome for SSO logins.", + } + var confirm bool + err := testable.AskOne(&bpIn, &confirm, withStdio) + if err != nil { + return err + } + + browserPath := "" + browserKey := "" + + if confirm { + ssoBrowser, err = gbrowser.HandleManualBrowserSelection() + if err != nil { + return err + } + + err, browserKey, browserPath = p.GetBrowserSelection(withStdio, ssoBrowser) + if err != nil { + return err + } + + clio.Successf("aws-fuzzy will use %s for SSO login prompts.", browserKey) + } + + conf.CustomSSOBrowserPath = browserPath + + err = conf.Save() + if err != nil { + return err + } + } + + return nil } -func (p *Browser) ConfigureBrowserSelection(browserName string, path string) error { +func (p *Browser) GetBrowserSelection(stdio survey.AskOpt, browserName string) (error, string, string) { + var browserPath string browserKey := gbrowser.GetBrowserKey(browserName) - withStdio := survey.WithStdio(os.Stdin, os.Stderr, os.Stderr) title := cases.Title(language.AmericanEnglish) browserTitle := title.String(strings.ToLower(browserKey)) - // We allow users to configure a custom install path is we cannot detect the installation - browserPath := path + // detect installation if browserKey != gbrowser.FirefoxStdoutKey && browserKey != gbrowser.StdoutKey { - if browserPath != "" { - _, err := os.Stat(browserPath) - if err != nil { - return errors.Wrap(err, "provided path is invalid") - } - } else { - customBrowserPath, detected := gbrowser.DetectInstallation(browserKey) - if !detected { - clio.Warnf("aws-fuzzy could not detect an existing installation of %s at known installation paths for your system", browserTitle) - clio.Info("If you have already installed this browser, you can specify the path to the executable manually") - validPath := false - for !validPath { - // prompt for custom path - bpIn := survey.Input{Message: fmt.Sprintf("Please enter the full path to your browser installation for %s:", browserTitle)} - clio.NewLine() - err := testable.AskOne(&bpIn, &customBrowserPath, withStdio) - if err != nil { - return err - } - if _, err := os.Stat(customBrowserPath); err == nil { - validPath = true - } else { - clio.Error("The path you entered is not valid") - } + customBrowserPath, detected := gbrowser.DetectInstallation(browserKey) + if !detected { + clio.Warnf("aws-fuzzy could not detect an existing installation of %s at known installation paths for your system", browserTitle) + clio.Info("If you have already installed this browser, you can specify the path to the executable manually") + validPath := false + for !validPath { + // prompt for custom path + bpIn := survey.Input{Message: fmt.Sprintf("Please enter the full path to your browser installation for %s:", browserTitle)} + clio.NewLine() + err := testable.AskOne(&bpIn, &customBrowserPath, stdio) + if err != nil { + return err, "", "" + } + if _, err := os.Stat(customBrowserPath); err == nil { + validPath = true + } else { + clio.Error("The path you entered is not valid") } } - browserPath = customBrowserPath } + browserPath = customBrowserPath if browserKey == gbrowser.FirefoxKey { err := gbrowser.RunFirefoxExtensionPrompts(browserPath) if err != nil { - return err + return err, "", "" } } } - //save the detected browser as the default - conf, err := afconfig.NewLoadedConfig() - if err != nil { - return err - } - conf.DefaultBrowser = browserKey - conf.CustomBrowserPath = browserPath - err = conf.Save() - if err != nil { - return err - } - clio.Successf("Granted will default to using %s", browserTitle) - return nil + return nil, browserKey, browserPath } diff --git a/internal/sso/main.go b/internal/sso/main.go index df0395d..cd91301 100644 --- a/internal/sso/main.go +++ b/internal/sso/main.go @@ -26,8 +26,9 @@ type Console struct { } type Browser struct { - Browser string - Verbose bool + Browser string + SSOBrowser string + Verbose bool } type Configure struct { @@ -96,10 +97,12 @@ func Command() *cli.Command { Usage: "Configure default browser", Flags: []cli.Flag{ &cli.StringFlag{Name: "browser", Aliases: []string{"b"}, Usage: "Specify a default browser without prompts, e.g '-b firefox', '-b chrome'"}, + &cli.StringFlag{Name: "sso-browser", Aliases: []string{"s"}, Usage: "Specify a sso browser without prompts, e.g '-s firefox', '-s chrome'"}, &cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "Enable verbose messages"}, }, Action: func(c *cli.Context) error { browser := NewBrowser(c.String("browser"), + c.String("sso-browser"), c.Bool("verbose"), )