Skip to content

Commit

Permalink
Merge pull request #816 from eliat123/add_browser_type
Browse files Browse the repository at this point in the history
Added support for browser-type and browser-executable-path
  • Loading branch information
mapkon authored Nov 11, 2023
2 parents 4cd70e5 + 86cd2fd commit 0b61e6e
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 7 deletions.
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"}
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" {
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

0 comments on commit 0b61e6e

Please sign in to comment.