Skip to content

Commit

Permalink
Add WithOpenURL option for AcquireTokenInteractive (#422)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellismg authored Jul 10, 2023
1 parent d3239ba commit 1357b1d
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 40 deletions.
42 changes: 32 additions & 10 deletions apps/public/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ func (pca Client) RemoveAccount(ctx context.Context, account Account) error {
// interactiveAuthOptions contains the optional parameters used to acquire an access token for interactive auth code flow.
type interactiveAuthOptions struct {
claims, domainHint, loginHint, redirectURI, tenantID string
openURL func(url string) error
}

// AcquireInteractiveOption is implemented by options for AcquireTokenInteractive
Expand Down Expand Up @@ -565,10 +566,33 @@ func WithRedirectURI(redirectURI string) interface {
}
}

// WithOpenURL allows you to provide a function to open the browser to complete the interactive login, instead of launching the system default browser.
func WithOpenURL(openURL func(url string) error) interface {
AcquireInteractiveOption
options.CallOption
} {
return struct {
AcquireInteractiveOption
options.CallOption
}{
CallOption: options.NewCallOption(
func(a any) error {
switch t := a.(type) {
case *interactiveAuthOptions:
t.openURL = openURL
default:
return fmt.Errorf("unexpected options type %T", a)
}
return nil
},
),
}
}

// AcquireTokenInteractive acquires a security token from the authority using the default web browser to select the account.
// https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#interactive-and-non-interactive-authentication
//
// Options: [WithDomainHint], [WithLoginHint], [WithRedirectURI], [WithTenantID]
// Options: [WithDomainHint], [WithLoginHint], [WithOpenURL], [WithRedirectURI], [WithTenantID]
func (pca Client) AcquireTokenInteractive(ctx context.Context, scopes []string, opts ...AcquireInteractiveOption) (AuthResult, error) {
o := interactiveAuthOptions{}
if err := options.ApplyOptions(&o, opts); err != nil {
Expand All @@ -587,6 +611,9 @@ func (pca Client) AcquireTokenInteractive(ctx context.Context, scopes []string,
return AuthResult{}, err
}
}
if o.openURL == nil {
o.openURL = browser.OpenURL
}
authParams, err := pca.base.AuthParams.WithTenant(o.tenantID)
if err != nil {
return AuthResult{}, err
Expand All @@ -600,7 +627,7 @@ func (pca Client) AcquireTokenInteractive(ctx context.Context, scopes []string,
authParams.DomainHint = o.domainHint
authParams.State = uuid.New().String()
authParams.Prompt = "select_account"
res, err := pca.browserLogin(ctx, redirectURL, authParams)
res, err := pca.browserLogin(ctx, redirectURL, authParams, o.openURL)
if err != nil {
return AuthResult{}, err
}
Expand All @@ -624,11 +651,6 @@ type interactiveAuthResult struct {
redirectURI string
}

// provides a test hook to simulate opening a browser
var browserOpenURL = func(authURL string) error {
return browser.OpenURL(authURL)
}

// parses the port number from the provided URL.
// returns 0 if nil or no port is specified.
func parsePort(u *url.URL) (int, error) {
Expand All @@ -642,8 +664,8 @@ func parsePort(u *url.URL) (int, error) {
return strconv.Atoi(p)
}

// browserLogin launches the system browser for interactive login
func (pca Client) browserLogin(ctx context.Context, redirectURI *url.URL, params authority.AuthParams) (interactiveAuthResult, error) {
// browserLogin calls openURL and waits for a user to log in
func (pca Client) browserLogin(ctx context.Context, redirectURI *url.URL, params authority.AuthParams, openURL func(string) error) (interactiveAuthResult, error) {
// start local redirect server so login can call us back
port, err := parsePort(redirectURI)
if err != nil {
Expand All @@ -660,7 +682,7 @@ func (pca Client) browserLogin(ctx context.Context, redirectURI *url.URL, params
return interactiveAuthResult{}, err
}
// open browser window so user can select credentials
if err := browserOpenURL(authURL); err != nil {
if err := openURL(authURL); err != nil {
return interactiveAuthResult{}, err
}
// now wait until the logic calls us back
Expand Down
38 changes: 8 additions & 30 deletions apps/public/public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ func fakeBrowserOpenURL(authURL string) error {
}

func TestAcquireTokenInteractive(t *testing.T) {
realBrowserOpenURL := browserOpenURL
defer func() { browserOpenURL = realBrowserOpenURL }()
browserOpenURL = fakeBrowserOpenURL
client, err := New("some_client_id")
if err != nil {
t.Fatal(err)
Expand All @@ -73,7 +70,7 @@ func TestAcquireTokenInteractive(t *testing.T) {
client.base.Token.Authority = &fake.Authority{}
client.base.Token.Resolver = &fake.ResolveEndpoints{}
client.base.Token.WSTrust = &fake.WSTrust{}
_, err = client.AcquireTokenInteractive(context.Background(), []string{"the_scope"})
_, err = client.AcquireTokenInteractive(context.Background(), []string{"the_scope"}, WithOpenURL(fakeBrowserOpenURL))
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -198,11 +195,6 @@ func TestAcquireTokenSilentWithoutAccount(t *testing.T) {
}

func TestAcquireTokenWithTenantID(t *testing.T) {
// replacing browserOpenURL with a fake for the duration of this test enables testing AcquireTokenInteractive
realBrowserOpenURL := browserOpenURL
defer func() { browserOpenURL = realBrowserOpenURL }()
browserOpenURL = fakeBrowserOpenURL

accessToken := "*"
clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`))
uuid1 := "00000000-0000-0000-0000-000000000000"
Expand Down Expand Up @@ -255,7 +247,7 @@ func TestAcquireTokenWithTenantID(t *testing.T) {
case "devicecode":
dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope, WithTenantID(test.tenant))
case "interactive":
ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithTenantID(test.tenant))
ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithTenantID(test.tenant), WithOpenURL(fakeBrowserOpenURL))
case "password":
ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithTenantID(test.tenant))
default:
Expand Down Expand Up @@ -304,11 +296,6 @@ func TestAcquireTokenWithTenantID(t *testing.T) {
}

func TestWithInstanceDiscovery(t *testing.T) {
// replacing browserOpenURL with a fake for the duration of this test enables testing AcquireTokenInteractive
realBrowserOpenURL := browserOpenURL
defer func() { browserOpenURL = realBrowserOpenURL }()
browserOpenURL = fakeBrowserOpenURL

accessToken := "*"
clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`))
host := "stack.local"
Expand Down Expand Up @@ -346,7 +333,7 @@ func TestWithInstanceDiscovery(t *testing.T) {
case "devicecode":
dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope)
case "interactive":
ar, err = client.AcquireTokenInteractive(ctx, tokenScope)
ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithOpenURL(fakeBrowserOpenURL))
case "password":
ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password")
default:
Expand Down Expand Up @@ -460,11 +447,6 @@ func TestWithCache(t *testing.T) {
}

func TestWithClaims(t *testing.T) {
// replacing browserOpenURL with a fake for the duration of this test enables testing AcquireTokenInteractive
realBrowserOpenURL := browserOpenURL
defer func() { browserOpenURL = realBrowserOpenURL }()
browserOpenURL = fakeBrowserOpenURL

clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`))
lmo, tenant := "login.microsoftonline.com", "tenant"
authority := fmt.Sprintf(authorityFmt, lmo, tenant)
Expand Down Expand Up @@ -562,7 +544,7 @@ func TestWithClaims(t *testing.T) {
case "devicecode":
dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope, WithClaims(test.claims))
case "interactive":
ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithClaims(test.claims))
ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithClaims(test.claims), WithOpenURL(fakeBrowserOpenURL))
case "password":
ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithClaims(test.claims))
case "passwordFederated":
Expand Down Expand Up @@ -662,8 +644,6 @@ func TestWithPortAuthority(t *testing.T) {
}

func TestWithLoginHint(t *testing.T) {
realBrowserOpenURL := browserOpenURL
defer func() { browserOpenURL = realBrowserOpenURL }()
upn := "user@localhost"
client, err := New("client-id")
if err != nil {
Expand All @@ -690,7 +670,7 @@ func TestWithLoginHint(t *testing.T) {
}
return err
}
browserOpenURL = func(authURL string) error {
browserOpenURL := func(authURL string) error {
called = true
parsed, err := url.Parse(authURL)
if err != nil {
Expand All @@ -707,7 +687,7 @@ func TestWithLoginHint(t *testing.T) {
// this helper validates the other params and completes the redirect
return fakeBrowserOpenURL(authURL)
}
acquireOpts := []AcquireInteractiveOption{}
acquireOpts := []AcquireInteractiveOption{WithOpenURL(browserOpenURL)}
urlOpts := []AuthCodeURLOption{}
if expectHint {
acquireOpts = append(acquireOpts, WithLoginHint(upn))
Expand Down Expand Up @@ -736,8 +716,6 @@ func TestWithLoginHint(t *testing.T) {
}

func TestWithDomainHint(t *testing.T) {
realBrowserOpenURL := browserOpenURL
defer func() { browserOpenURL = realBrowserOpenURL }()
domain := "contoso.com"
client, err := New("client-id")
if err != nil {
Expand All @@ -764,7 +742,7 @@ func TestWithDomainHint(t *testing.T) {
}
return err
}
browserOpenURL = func(authURL string) error {
browserOpenURL := func(authURL string) error {
called = true
parsed, err := url.Parse(authURL)
if err != nil {
Expand All @@ -781,7 +759,7 @@ func TestWithDomainHint(t *testing.T) {
// this helper validates the other params and completes the redirect
return fakeBrowserOpenURL(authURL)
}
var acquireOpts []AcquireInteractiveOption
acquireOpts := []AcquireInteractiveOption{WithOpenURL(browserOpenURL)}
var urlOpts []AuthCodeURLOption
if expectHint {
acquireOpts = append(acquireOpts, WithDomainHint(domain))
Expand Down

0 comments on commit 1357b1d

Please sign in to comment.