2016-09-08 13:11:39 -04:00
package command
import (
"bufio"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"runtime"
"strings"
"golang.org/x/net/context"
2017-01-11 16:54:52 -05:00
"github.com/docker/distribution/reference"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
)
// ElectAuthServer returns the default registry to use (by asking the daemon)
2016-09-09 15:38:00 -04:00
func ElectAuthServer ( ctx context . Context , cli * DockerCli ) string {
2016-09-08 13:11:39 -04:00
// The daemon `/info` endpoint informs us of the default registry being
// used. This is essential in cross-platforms environment, where for
// example a Linux client might be interacting with a Windows daemon, hence
// the default registry URL might be Windows specific.
serverAddress := registry . IndexServer
2016-09-09 15:38:00 -04:00
if info , err := cli . Client ( ) . Info ( ctx ) ; err != nil {
fmt . Fprintf ( cli . Out ( ) , "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n" , err , serverAddress )
2016-09-08 13:11:39 -04:00
} else {
serverAddress = info . IndexServerAddress
}
return serverAddress
}
// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
func EncodeAuthToBase64 ( authConfig types . AuthConfig ) ( string , error ) {
buf , err := json . Marshal ( authConfig )
if err != nil {
return "" , err
}
return base64 . URLEncoding . EncodeToString ( buf ) , nil
}
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
// for the given command.
2016-09-09 15:38:00 -04:00
func RegistryAuthenticationPrivilegedFunc ( cli * DockerCli , index * registrytypes . IndexInfo , cmdName string ) types . RequestPrivilegeFunc {
2016-09-08 13:11:39 -04:00
return func ( ) ( string , error ) {
2016-09-09 15:38:00 -04:00
fmt . Fprintf ( cli . Out ( ) , "\nPlease login prior to %s:\n" , cmdName )
2016-09-08 13:11:39 -04:00
indexServer := registry . GetAuthConfigKey ( index )
2016-09-09 15:38:00 -04:00
isDefaultRegistry := indexServer == ElectAuthServer ( context . Background ( ) , cli )
authConfig , err := ConfigureAuth ( cli , "" , "" , indexServer , isDefaultRegistry )
2016-09-08 13:11:39 -04:00
if err != nil {
return "" , err
}
return EncodeAuthToBase64 ( authConfig )
}
}
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
// default index, it uses the default index name for the daemon's platform,
// not the client's platform.
2016-09-09 15:38:00 -04:00
func ResolveAuthConfig ( ctx context . Context , cli * DockerCli , index * registrytypes . IndexInfo ) types . AuthConfig {
2016-09-08 13:11:39 -04:00
configKey := index . Name
if index . Official {
2016-09-09 15:38:00 -04:00
configKey = ElectAuthServer ( ctx , cli )
2016-09-08 13:11:39 -04:00
}
2016-08-18 17:23:10 -04:00
a , _ := cli . CredentialsStore ( configKey ) . Get ( configKey )
2016-09-08 13:11:39 -04:00
return a
}
// ConfigureAuth returns an AuthConfig from the specified user, password and server.
2016-09-09 15:38:00 -04:00
func ConfigureAuth ( cli * DockerCli , flUser , flPassword , serverAddress string , isDefaultRegistry bool ) ( types . AuthConfig , error ) {
2016-09-08 13:11:39 -04:00
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
if runtime . GOOS == "windows" {
cli . in = NewInStream ( os . Stdin )
}
if ! isDefaultRegistry {
serverAddress = registry . ConvertToHostname ( serverAddress )
}
2016-08-18 17:23:10 -04:00
authconfig , err := cli . CredentialsStore ( serverAddress ) . Get ( serverAddress )
2016-09-08 13:11:39 -04:00
if err != nil {
return authconfig , err
}
// Some links documenting this:
// - https://code.google.com/archive/p/mintty/issues/56
// - https://github.com/docker/docker/issues/15272
// - https://mintty.github.io/ (compatibility)
// Linux will hit this if you attempt `cat | docker login`, and Windows
// will hit this if you attempt docker login from mintty where stdin
// is a pipe, not a character based console.
if flPassword == "" && ! cli . In ( ) . IsTerminal ( ) {
return authconfig , fmt . Errorf ( "Error: Cannot perform an interactive login from a non TTY device" )
}
authconfig . Username = strings . TrimSpace ( authconfig . Username )
if flUser = strings . TrimSpace ( flUser ) ; flUser == "" {
if isDefaultRegistry {
// if this is a default registry (docker hub), then display the following message.
2016-09-09 15:38:00 -04:00
fmt . Fprintln ( cli . Out ( ) , "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one." )
2016-09-08 13:11:39 -04:00
}
2016-09-09 15:38:00 -04:00
promptWithDefault ( cli . Out ( ) , "Username" , authconfig . Username )
flUser = readInput ( cli . In ( ) , cli . Out ( ) )
2016-09-08 13:11:39 -04:00
flUser = strings . TrimSpace ( flUser )
if flUser == "" {
flUser = authconfig . Username
}
}
if flUser == "" {
return authconfig , fmt . Errorf ( "Error: Non-null Username Required" )
}
if flPassword == "" {
oldState , err := term . SaveState ( cli . In ( ) . FD ( ) )
if err != nil {
return authconfig , err
}
2016-09-09 15:38:00 -04:00
fmt . Fprintf ( cli . Out ( ) , "Password: " )
2016-09-08 13:11:39 -04:00
term . DisableEcho ( cli . In ( ) . FD ( ) , oldState )
2016-09-09 15:38:00 -04:00
flPassword = readInput ( cli . In ( ) , cli . Out ( ) )
fmt . Fprint ( cli . Out ( ) , "\n" )
2016-09-08 13:11:39 -04:00
term . RestoreTerminal ( cli . In ( ) . FD ( ) , oldState )
if flPassword == "" {
return authconfig , fmt . Errorf ( "Error: Password Required" )
}
}
authconfig . Username = flUser
authconfig . Password = flPassword
authconfig . ServerAddress = serverAddress
authconfig . IdentityToken = ""
return authconfig , nil
}
2016-09-09 10:49:52 -04:00
func readInput ( in io . Reader , out io . Writer ) string {
reader := bufio . NewReader ( in )
line , _ , err := reader . ReadLine ( )
2016-09-08 13:11:39 -04:00
if err != nil {
2016-09-09 10:49:52 -04:00
fmt . Fprintln ( out , err . Error ( ) )
os . Exit ( 1 )
2016-09-08 13:11:39 -04:00
}
2016-09-09 10:49:52 -04:00
return string ( line )
}
2016-09-09 15:38:00 -04:00
func promptWithDefault ( out io . Writer , prompt string , configDefault string ) {
2016-09-09 10:49:52 -04:00
if configDefault == "" {
2016-09-09 15:38:00 -04:00
fmt . Fprintf ( out , "%s: " , prompt )
2016-09-09 10:49:52 -04:00
} else {
2016-09-09 15:38:00 -04:00
fmt . Fprintf ( out , "%s (%s): " , prompt , configDefault )
2016-09-08 13:11:39 -04:00
}
}
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
2016-09-09 15:38:00 -04:00
func RetrieveAuthTokenFromImage ( ctx context . Context , cli * DockerCli , image string ) ( string , error ) {
2016-09-08 13:11:39 -04:00
// Retrieve encoded auth token from the image reference
2016-09-09 15:38:00 -04:00
authConfig , err := resolveAuthConfigFromImage ( ctx , cli , image )
2016-09-08 13:11:39 -04:00
if err != nil {
return "" , err
}
encodedAuth , err := EncodeAuthToBase64 ( authConfig )
if err != nil {
return "" , err
}
return encodedAuth , nil
}
2016-09-09 10:49:52 -04:00
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
2016-09-09 15:38:00 -04:00
func resolveAuthConfigFromImage ( ctx context . Context , cli * DockerCli , image string ) ( types . AuthConfig , error ) {
2017-01-11 16:54:52 -05:00
registryRef , err := reference . ParseNormalizedNamed ( image )
2016-09-08 13:11:39 -04:00
if err != nil {
2016-09-09 10:49:52 -04:00
return types . AuthConfig { } , err
2016-09-08 13:11:39 -04:00
}
2016-09-09 10:49:52 -04:00
repoInfo , err := registry . ParseRepositoryInfo ( registryRef )
if err != nil {
return types . AuthConfig { } , err
}
2016-09-09 15:38:00 -04:00
return ResolveAuthConfig ( ctx , cli , repoInfo . Index ) , nil
2016-09-08 13:11:39 -04:00
}