@@ -26,7 +26,8 @@ const (
2626 appClientID = "databricks-cli"
2727
2828 // appRedirectAddr is the default address for the OAuth2 callback server.
29- appRedirectAddr = "localhost:8020"
29+ // Using ":0" tells the system to pick a random available port.
30+ appRedirectAddr = "localhost:0"
3031
3132 // listenerTimeout is the maximum amount of time to acquire listener on
3233 // appRedirectAddr.
@@ -57,6 +58,11 @@ type PersistentAuth struct {
5758 // ctx is the context to use for underlying operations. This is needed in
5859 // order to implement the oauth2.TokenSource interface.
5960 ctx context.Context
61+ // redirectAddr is the redirect address for OAuth2 callbacks. The value is
62+ // set to localhost:PORT by startListener which will dynamically assign a
63+ // random port. If a value is already provided, it will be used instead
64+ // (e.g. for testing).
65+ redirectAddr string
6066}
6167
6268type PersistentAuthOption func (* PersistentAuth )
@@ -128,9 +134,6 @@ func NewPersistentAuth(ctx context.Context, opts ...PersistentAuthOption) (*Pers
128134 return nil , fmt .Errorf ("cache: %w" , err )
129135 }
130136 }
131- if p .oAuthArgument == nil {
132- return nil , errors .New ("missing OAuthArgument" )
133- }
134137 if err := p .validateArg (); err != nil {
135138 return nil , err
136139 }
@@ -162,7 +165,9 @@ func (a *PersistentAuth) Token() (t *oauth2.Token, err error) {
162165 return nil , fmt .Errorf ("token refresh: %w" , err )
163166 }
164167 }
165- // do not print refresh token to end-user
168+
169+ // Do not include the refresh token for security reasons. Refresh tokens are
170+ // long-lived credentials that we do not want to expose unnecessarily.
166171 t .RefreshToken = ""
167172 return t , nil
168173}
@@ -269,12 +274,19 @@ func (a *PersistentAuth) Challenge() error {
269274// startListener starts a listener on appRedirectAddr, retrying if the address
270275// is already in use.
271276func (a * PersistentAuth ) startListener (ctx context.Context ) error {
277+ // Use the value of redirectURL if it is already set. This is only expected
278+ // in tests to set a fixed redirect URL.
279+ addr := a .redirectAddr
280+ if addr == "" {
281+ addr = appRedirectAddr
282+ }
283+
272284 listener , err := retries .Poll (ctx , listenerTimeout ,
273285 func () (* net.Listener , * retries.Err ) {
274286 var lc net.ListenConfig
275- l , err := lc .Listen (ctx , "tcp" , appRedirectAddr )
287+ l , err := lc .Listen (ctx , "tcp" , addr )
276288 if err != nil {
277- logger .Debugf (ctx , "failed to listen on %s: %v, retrying" , appRedirectAddr , err )
289+ logger .Debugf (ctx , "failed to listen on %s: %v, retrying" , addr , err )
278290 return nil , retries .Continue (err )
279291 }
280292 return & l , nil
@@ -283,6 +295,10 @@ func (a *PersistentAuth) startListener(ctx context.Context) error {
283295 return fmt .Errorf ("listener: %w" , err )
284296 }
285297 a .ln = * listener
298+
299+ // Get the actual address that was assigned (including the port).
300+ a .redirectAddr = a .ln .Addr ().String ()
301+ logger .Debugf (ctx , "OAuth callback server listening on %s" , a .redirectAddr )
286302 return nil
287303}
288304
@@ -296,6 +312,9 @@ func (a *PersistentAuth) Close() error {
296312// validateArg ensures that the OAuthArgument is either a WorkspaceOAuthArgument
297313// or an AccountOAuthArgument.
298314func (a * PersistentAuth ) validateArg () error {
315+ if a .oAuthArgument == nil {
316+ return errors .New ("missing OAuthArgument" )
317+ }
299318 _ , isWorkspaceArg := a .oAuthArgument .(WorkspaceOAuthArgument )
300319 _ , isAccountArg := a .oAuthArgument .(AccountOAuthArgument )
301320 if ! isWorkspaceArg && ! isAccountArg {
@@ -331,7 +350,7 @@ func (a *PersistentAuth) oauth2Config() (*oauth2.Config, error) {
331350 TokenURL : endpoints .TokenEndpoint ,
332351 AuthStyle : oauth2 .AuthStyleInParams ,
333352 },
334- RedirectURL : fmt .Sprintf ("http://%s" , appRedirectAddr ),
353+ RedirectURL : fmt .Sprintf ("http://%s" , a . redirectAddr ),
335354 Scopes : scopes ,
336355 }, nil
337356}
0 commit comments