Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for browser-type and browser-executable-path #816

Merged
merged 14 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ARCH=$(shell uname -m)
OS?=$(shell uname)
ITERATION := 1

GOLANGCI_VERSION = 1.45.2
GOLANGCI_VERSION = 1.53.2
GORELEASER := $(shell command -v goreleaser 2> /dev/null)

SOURCE_FILES?=$$(go list ./... | grep -v /vendor/)
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ Flags:
-a, --idp-account="default" The name of the configured IDP account. (env: SAML2AWS_IDP_ACCOUNT)
--idp-provider=IDP-PROVIDER
The configured IDP provider. (env: SAML2AWS_IDP_PROVIDER)
--browser-type=BROWSER-TYPE
The browser type to use when IDP provider is set to 'Browser'. if not set 'chromium' will be used. (env: SAML2AWS_BROWSER_TYPE)
--browser-executable-path=BROWSER-EXECUTABLE-PATH
The browser full path when IDP provider is set to 'Browser'. If set, no browser download will be performed and the executable path will be used instead. (env: SAML2AWS_BROWSER_EXECUTABLE_PATH)
--mfa=MFA The name of the mfa. (env: SAML2AWS_MFA)
-s, --skip-verify Skip verification of server certificate. (env: SAML2AWS_SKIP_VERIFY)
--url=URL The URL of the SAML IDP server used to login. (env: SAML2AWS_URL)
Expand Down
2 changes: 2 additions & 0 deletions cmd/saml2aws/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ func main() {
app.Flag("config", "Path/filename of saml2aws config file (env: SAML2AWS_CONFIGFILE)").Envar("SAML2AWS_CONFIGFILE").StringVar(&commonFlags.ConfigFile)
app.Flag("idp-account", "The name of the configured IDP account. (env: SAML2AWS_IDP_ACCOUNT)").Envar("SAML2AWS_IDP_ACCOUNT").Short('a').Default("default").StringVar(&commonFlags.IdpAccount)
app.Flag("idp-provider", "The configured IDP provider. (env: SAML2AWS_IDP_PROVIDER)").Envar("SAML2AWS_IDP_PROVIDER").EnumVar(&commonFlags.IdpProvider, "Akamai", "AzureAD", "ADFS", "ADFS2", "Browser", "GoogleApps", "Ping", "JumpCloud", "Okta", "OneLogin", "PSU", "KeyCloak", "F5APM", "Shibboleth", "ShibbolethECP", "NetIQ", "Auth0")
app.Flag("browser-type", "The configured browser type when the IDP provider is set to Browser. if not set 'chromium' will be used. (env: SAML2AWS_BROWSER_TYPE)").Envar("SAML2AWS_BROWSER_TYPE").EnumVar(&commonFlags.BrowserType, "chromium", "firefox", "webkit", "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", "msedge-canary")
app.Flag("browser-executable-path", "The configured browser full path when the IDP provider is set to Browser. If set, no browser download will be performed and the executable path will be used instead. (env: SAML2AWS_BROWSER_EXECUTABLE_PATH)").Envar("SAML2AWS_BROWSER_EXECUTABLE_PATH").StringVar(&commonFlags.BrowserExecutablePath)
app.Flag("mfa", "The name of the mfa. (env: SAML2AWS_MFA)").Envar("SAML2AWS_MFA").StringVar(&commonFlags.MFA)
app.Flag("skip-verify", "Skip verification of server certificate. (env: SAML2AWS_SKIP_VERIFY)").Envar("SAML2AWS_SKIP_VERIFY").Short('s').BoolVar(&commonFlags.SkipVerify)
app.Flag("url", "The URL of the SAML IDP server used to login. (env: SAML2AWS_URL)").Envar("SAML2AWS_URL").StringVar(&commonFlags.URL)
Expand Down
2 changes: 2 additions & 0 deletions pkg/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type IDPAccount struct {
URL string `ini:"url"`
Username string `ini:"username"`
Provider string `ini:"provider"`
BrowserType string `ini:"browser_type,omitempty"` // used by 'Browser' Provider
BrowserExecutablePath string `ini:"browser_executable_path,omitempty"` // used by 'Browser' Provider
MFA string `ini:"mfa"`
MFAIPAddress string `ini:"mfa_ip_address"` // used by OneLogin
SkipVerify bool `ini:"skip_verify"`
Expand Down
10 changes: 10 additions & 0 deletions pkg/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type CommonFlags struct {
ConfigFile string
IdpAccount string
IdpProvider string
BrowserType string
BrowserExecutablePath string
MFA string
MFAIPAddress string
MFAToken string
Expand Down Expand Up @@ -73,6 +75,14 @@ func ApplyFlagOverrides(commonFlags *CommonFlags, account *cfg.IDPAccount) {
account.Provider = commonFlags.IdpProvider
}

if commonFlags.BrowserType != "" {
account.BrowserType = commonFlags.BrowserType
}

if commonFlags.BrowserExecutablePath != "" {
account.BrowserExecutablePath = commonFlags.BrowserExecutablePath
}

if commonFlags.MFA != "" {
account.MFA = commonFlags.MFA
}
Expand Down
55 changes: 49 additions & 6 deletions pkg/provider/browser/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package browser

import (
"errors"
"fmt"
"net/url"
"regexp"
"strings"

"github.com/playwright-community/playwright-go"
"github.com/sirupsen/logrus"
Expand All @@ -17,7 +19,9 @@ const DEFAULT_TIMEOUT float64 = 300000

// Client client for browser based Identity Provider
type Client struct {
Headless bool
BrowserType string
BrowserExecutablePath string
Headless bool
// Setup alternative directory to download playwright browsers to
BrowserDriverDir string
Timeout int
Expand All @@ -26,12 +30,24 @@ type Client struct {
// New create new browser based client
func New(idpAccount *cfg.IDPAccount) (*Client, error) {
return &Client{
Headless: idpAccount.Headless,
BrowserDriverDir: idpAccount.BrowserDriverDir,
Timeout: idpAccount.Timeout,
Headless: idpAccount.Headless,
BrowserDriverDir: idpAccount.BrowserDriverDir,
BrowserType: strings.ToLower(idpAccount.BrowserType),
BrowserExecutablePath: idpAccount.BrowserExecutablePath,
Timeout: idpAccount.Timeout,
}, nil
}

// contains checks if a string is present in a slice
func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}

return false
}
func (cl *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error) {
runOptions := playwright.RunOptions{}
if cl.BrowserDriverDir != "" {
Expand All @@ -56,10 +72,37 @@ func (cl *Client) Authenticate(loginDetails *creds.LoginDetails) (string, error)
Headless: playwright.Bool(cl.Headless),
}

// currently using Chromium as it is widely supported for Identity providers
validBrowserTypes := []string{"chromium", "firefox", "webkit", "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", "msedge-canary"}
Copy link
Contributor

@gliptak gliptak Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why differentiate all these browsertypes if playwright only supports firefox, chrome and webkit?

I see #816 (comment)

maybe the if statement below is to be updated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @gliptak, Thank you for your review.

Playwright does support all of those browser types, see:
https://playwright.dev/docs/browsers and https://github.com/playwright-community/playwright-go/blob/main/generated-structs.go#L691C4-L695

The main idea behind this PR is that the user can choose a browser type which is already installed on their system.

If you choose a browser you do not have on your system, you will get a relevant error message.

For example, I tried to set --browser-type=msedge. Since I did not have msedge installed I got the following notification:

INFO[0000] Setting browser type: msedge                  provider=browser
Error authenticating to IdP.: could not send message: Chromium distribution 'msedge' is not found at /Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge
Run "npx playwright install msedge"

After I installed msedge, it used the installed one:

INFO[0000] Setting browser type: msedge                  provider=browser
INFO[0001] opening browser                               URL="[IDP_URL]" provider=browser
INFO[0021] waiting ...                                   provider=browser
INFO[0049] clean up browser                              provider=browser

Furthermore, If your browser is installed on a non-default location, you can utilize --browser-executable-path, and it will start the browser from that path.

Hope this explains it.

if len(cl.BrowserType) > 0 && !contains(validBrowserTypes, cl.BrowserType) {
return "", fmt.Errorf("invalid browser-type: '%s', only %s are allowed", cl.BrowserType, validBrowserTypes)
}

if cl.BrowserType != "" {
logger.Info(fmt.Sprintf("Setting browser type: %s", cl.BrowserType))
launchOptions.Channel = playwright.String(cl.BrowserType)
}

// Default browser is Chromium as it is widely supported for Identity providers,
// It can also be set to the other playwright browsers: Firefox and WebKit
browserType := pw.Chromium
if cl.BrowserType == "firefox" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eliat123 would there be more browsertypes mapped here?

https://playwright.dev/docs/browsers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gliptak Nope. Only these browsertypes are supported.

Ref: https://github.com/playwright-community/playwright-go/blob/ddc7abdd06db016d63664de1ab08870195085ddb/playwright.go#L21-L23

According to https://playwright.dev/docs/browsers :

  1. Chromium is for "chromium", "chrome", "msedge", "chrome-beta", "msedge-beta" or "msedge-dev".
  2. Firefox is for the recent Firefox Stable build.
  3. Webkit is for the recent WebKit build.

Chromium will be the default option, but if "webkit" or "firefox" are selected, they will be used instead. This ensures the most available options.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be browserType be pw.Chromium when cl.BrowserType == 'msedge' and is this as expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gliptak The answer is yes.

Here is the section from Playwright docs about Google Chrome & Microsoft Edge:

While Playwright can download and use the recent Chromium build, 
it can operate against the branded Google Chrome and Microsoft Edge browsers available on the machine (note that Playwright doesn't install them by default). 
In particular, the current Playwright version will support Stable and Beta channels of these browsers.

Available channels are 'chrome', 'msedge', 'chrome-beta', 'msedge-beta' or 'msedge-dev'.

ref: https://playwright.dev/docs/browsers#google-chrome--microsoft-edge

browserType = pw.Firefox
} else if cl.BrowserType == "webkit" {
browserType = pw.WebKit
}

// You can set the path to a browser executable to run instead of the playwright-go bundled one. If `executablePath`
// is a relative path, then it is resolved relative to the current working directory.
// Note that Playwright only works with the bundled Chromium, Firefox or WebKit, use at your own risk. see:
if len(cl.BrowserExecutablePath) > 0 {
logger.Info(fmt.Sprintf("Setting browser executable path: %s", cl.BrowserExecutablePath))
launchOptions.ExecutablePath = &cl.BrowserExecutablePath
}

// currently using the main browsers supported by Playwright: Chromium, Firefox or Webkit
//
// this is a sandboxed browser window so password managers and addons are separate
browser, err := pw.Chromium.Launch(launchOptions)
browser, err := browserType.Launch(launchOptions)
if err != nil {
return "", err
}
Expand Down
39 changes: 39 additions & 0 deletions pkg/provider/browser/browser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,45 @@ func TestValidate(t *testing.T) {
assert.Equal(t, resp, response)
}

func TestInvalidBrowserType(t *testing.T) {
currentSAMLResponse := getSAMLResponse
defer func() {
getSAMLResponse = currentSAMLResponse
}()
getSAMLResponse = fakeSAMLResponse
account := &cfg.IDPAccount{
BrowserType: "invalid",
}
client, err := New(account)
assert.Nil(t, err)
loginDetails := &creds.LoginDetails{
URL: "https://google.com/",
DownloadBrowser: true,
}
_, err = client.Authenticate(loginDetails)
assert.Error(t, err)
assert.ErrorContains(t, err, "invalid browser-type: 'invalid', only [chromium firefox webkit chrome chrome-beta chrome-dev chrome-canary msedge msedge-beta msedge-dev msedge-canary] are allowed")
}

func TestInvalidBrowserExecutablePath(t *testing.T) {
currentSAMLResponse := getSAMLResponse
defer func() {
getSAMLResponse = currentSAMLResponse
}()
getSAMLResponse = fakeSAMLResponse
account := &cfg.IDPAccount{
BrowserExecutablePath: "FAKEPATH",
}
client, err := New(account)
assert.Nil(t, err)
loginDetails := &creds.LoginDetails{
URL: "https://google.com/",
}
_, err = client.Authenticate(loginDetails)
assert.Error(t, err)
assert.ErrorContains(t, err, "Failed to launch chromium because executable doesn't exist at FAKEPATH")
}

// Test that if download directory does not have browsers, it fails with expected error message
func TestNoBrowserDriverFail(t *testing.T) {
account := &cfg.IDPAccount{
Expand Down
Loading