@@ -10,6 +10,7 @@ import (
1010 "github.com/docker/cli/cli/command"
1111 "github.com/docker/cli/cli/command/completion"
1212 configtypes "github.com/docker/cli/cli/config/types"
13+ "github.com/docker/cli/cli/internal/oauth/manager"
1314 registrytypes "github.com/docker/docker/api/types/registry"
1415 "github.com/docker/docker/client"
1516 "github.com/docker/docker/errdefs"
@@ -79,70 +80,145 @@ func verifyloginOptions(dockerCli command.Cli, opts *loginOptions) error {
7980 return nil
8081}
8182
82- func runLogin (ctx context.Context , dockerCli command.Cli , opts loginOptions ) error { //nolint:gocyclo
83- clnt := dockerCli .Client ()
83+ func runLogin (ctx context.Context , dockerCli command.Cli , opts loginOptions ) error {
8484 if err := verifyloginOptions (dockerCli , & opts ); err != nil {
8585 return err
8686 }
8787 var (
8888 serverAddress string
89- response registrytypes.AuthenticateOKBody
89+ response * registrytypes.AuthenticateOKBody
9090 )
91- if opts .serverAddress != "" && opts .serverAddress != registry .DefaultNamespace {
91+ if opts .serverAddress != "" &&
92+ opts .serverAddress != registry .DefaultNamespace &&
93+ opts .serverAddress != registry .DefaultRegistryHost {
9294 serverAddress = opts .serverAddress
9395 } else {
9496 serverAddress = registry .IndexServer
9597 }
96-
9798 isDefaultRegistry := serverAddress == registry .IndexServer
99+
100+ // attempt login with current (stored) credentials
98101 authConfig , err := command .GetDefaultAuthConfig (dockerCli .ConfigFile (), opts .user == "" && opts .password == "" , serverAddress , isDefaultRegistry )
99102 if err == nil && authConfig .Username != "" && authConfig .Password != "" {
100- response , err = loginWithCredStoreCreds (ctx , dockerCli , & authConfig )
103+ response , err = loginWithStoredCredentials (ctx , dockerCli , authConfig )
101104 }
105+
106+ // if we failed to authenticate with stored credentials (or didn't have stored credentials),
107+ // prompt the user for new credentials
102108 if err != nil || authConfig .Username == "" || authConfig .Password == "" {
103- err = command . ConfigureAuth (ctx , dockerCli , opts . user , opts . password , & authConfig , isDefaultRegistry )
109+ response , err = loginUser (ctx , dockerCli , opts , authConfig . Username , serverAddress )
104110 if err != nil {
105111 return err
106112 }
113+ }
114+
115+ if response != nil && response .Status != "" {
116+ _ , _ = fmt .Fprintln (dockerCli .Out (), response .Status )
117+ }
118+ return nil
119+ }
107120
108- response , err = clnt .RegistryLogin (ctx , authConfig )
109- if err != nil && client .IsErrConnectionFailed (err ) {
110- // If the server isn't responding (yet) attempt to login purely client side
111- response , err = loginClientSide (ctx , authConfig )
121+ func loginWithStoredCredentials (ctx context.Context , dockerCli command.Cli , authConfig registrytypes.AuthConfig ) (* registrytypes.AuthenticateOKBody , error ) {
122+ _ , _ = fmt .Fprintf (dockerCli .Out (), "Authenticating with existing credentials...\n " )
123+ response , err := dockerCli .Client ().RegistryLogin (ctx , authConfig )
124+ if err != nil {
125+ if errdefs .IsUnauthorized (err ) {
126+ _ , _ = fmt .Fprintf (dockerCli .Err (), "Stored credentials invalid or expired\n " )
127+ } else {
128+ _ , _ = fmt .Fprintf (dockerCli .Err (), "Login did not succeed, error: %s\n " , err )
112129 }
113- // If we (still) have an error, give up
114- if err != nil {
115- return err
130+ }
131+
132+ if response .IdentityToken != "" {
133+ authConfig .Password = ""
134+ authConfig .IdentityToken = response .IdentityToken
135+ }
136+
137+ if err := storeCredentials (dockerCli , authConfig ); err != nil {
138+ return nil , err
139+ }
140+
141+ return & response , err
142+ }
143+
144+ func loginUser (ctx context.Context , dockerCli command.Cli , opts loginOptions , defaultUsername , serverAddress string ) (* registrytypes.AuthenticateOKBody , error ) {
145+ // If we're logging into the index server and the user didn't provide a username or password, use the device flow
146+ if serverAddress == registry .IndexServer && opts .user == "" && opts .password == "" {
147+ response , err := loginWithDeviceCodeFlow (ctx , dockerCli )
148+ // if the error represents a failure to initiate the device-code flow,
149+ // then we fallback to regular cli credentials login
150+ if ! errors .Is (err , manager .ErrDeviceLoginStartFail ) {
151+ return response , err
116152 }
153+ fmt .Fprint (dockerCli .Err (), "Failed to start web-based login - falling back to command line login...\n \n " )
117154 }
155+
156+ return loginWithUsernameAndPassword (ctx , dockerCli , opts , defaultUsername , serverAddress )
157+ }
158+
159+ func loginWithUsernameAndPassword (ctx context.Context , dockerCli command.Cli , opts loginOptions , defaultUsername , serverAddress string ) (* registrytypes.AuthenticateOKBody , error ) {
160+ // Prompt user for credentials
161+ authConfig , err := command .PromptUserForCredentials (ctx , dockerCli , opts .user , opts .password , defaultUsername , serverAddress )
162+ if err != nil {
163+ return nil , err
164+ }
165+
166+ response , err := loginWithRegistry (ctx , dockerCli , authConfig )
167+ if err != nil {
168+ return nil , err
169+ }
170+
118171 if response .IdentityToken != "" {
119172 authConfig .Password = ""
120173 authConfig .IdentityToken = response .IdentityToken
121174 }
175+ if err = storeCredentials (dockerCli , authConfig ); err != nil {
176+ return nil , err
177+ }
178+
179+ return & response , nil
180+ }
181+
182+ func loginWithDeviceCodeFlow (ctx context.Context , dockerCli command.Cli ) (* registrytypes.AuthenticateOKBody , error ) {
183+ store := dockerCli .ConfigFile ().GetCredentialsStore (registry .IndexServer )
184+ authConfig , err := manager .NewManager (store ).LoginDevice (ctx , dockerCli .Err ())
185+ if err != nil {
186+ return nil , err
187+ }
188+
189+ response , err := loginWithRegistry (ctx , dockerCli , registrytypes .AuthConfig (* authConfig ))
190+ if err != nil {
191+ return nil , err
192+ }
193+
194+ if err = storeCredentials (dockerCli , registrytypes .AuthConfig (* authConfig )); err != nil {
195+ return nil , err
196+ }
122197
123- creds := dockerCli .ConfigFile ().GetCredentialsStore (serverAddress )
198+ return & response , nil
199+ }
200+
201+ func storeCredentials (dockerCli command.Cli , authConfig registrytypes.AuthConfig ) error {
202+ creds := dockerCli .ConfigFile ().GetCredentialsStore (authConfig .ServerAddress )
124203 if err := creds .Store (configtypes .AuthConfig (authConfig )); err != nil {
125204 return errors .Errorf ("Error saving credentials: %v" , err )
126205 }
127206
128- if response .Status != "" {
129- fmt .Fprintln (dockerCli .Out (), response .Status )
130- }
131207 return nil
132208}
133209
134- func loginWithCredStoreCreds (ctx context.Context , dockerCli command.Cli , authConfig * registrytypes.AuthConfig ) (registrytypes.AuthenticateOKBody , error ) {
135- fmt .Fprintf (dockerCli .Out (), "Authenticating with existing credentials...\n " )
136- cliClient := dockerCli .Client ()
137- response , err := cliClient .RegistryLogin (ctx , * authConfig )
210+ func loginWithRegistry (ctx context.Context , dockerCli command.Cli , authConfig registrytypes.AuthConfig ) (registrytypes.AuthenticateOKBody , error ) {
211+ response , err := dockerCli .Client ().RegistryLogin (ctx , authConfig )
212+ if err != nil && client .IsErrConnectionFailed (err ) {
213+ // If the server isn't responding (yet) attempt to login purely client side
214+ response , err = loginClientSide (ctx , authConfig )
215+ }
216+ // If we (still) have an error, give up
138217 if err != nil {
139- if errdefs .IsUnauthorized (err ) {
140- fmt .Fprintf (dockerCli .Err (), "Stored credentials invalid or expired\n " )
141- } else {
142- fmt .Fprintf (dockerCli .Err (), "Login did not succeed, error: %s\n " , err )
143- }
218+ return registrytypes.AuthenticateOKBody {}, err
144219 }
145- return response , err
220+
221+ return response , nil
146222}
147223
148224func loginClientSide (ctx context.Context , auth registrytypes.AuthConfig ) (registrytypes.AuthenticateOKBody , error ) {
0 commit comments