Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 1 addition & 33 deletions internal/authflow/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"io"
"net/http"
"net/url"
"os"
"regexp"
"strings"

Expand All @@ -28,37 +27,7 @@ var (
jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`)
)

type iconfig interface {
Get(string, string) (string, error)
Set(string, string, string)
Write() error
}

func AuthFlowWithConfig(cfg iconfig, IO *iostreams.IOStreams, hostname, notice string, additionalScopes []string, isInteractive bool) (string, error) {
// TODO this probably shouldn't live in this package. It should probably be in a new package that
// depends on both iostreams and config.

// FIXME: this duplicates `factory.browserLauncher()`
Comment on lines -38 to -41
Copy link
Contributor Author

@samcoe samcoe Feb 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed both of these as part of the refactor by having the AuthFlow take a browser.Browser as an input and moving the writing of the config out of this flow and having the caller be responsible for writing it.

browserLauncher := os.Getenv("GH_BROWSER")
if browserLauncher == "" {
browserLauncher, _ = cfg.Get("", "browser")
}
if browserLauncher == "" {
browserLauncher = os.Getenv("BROWSER")
}

token, userLogin, err := authFlow(hostname, IO, notice, additionalScopes, isInteractive, browserLauncher)
if err != nil {
return "", err
}

cfg.Set(hostname, "user", userLogin)
cfg.Set(hostname, "oauth_token", token)

return token, cfg.Write()
}

func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, additionalScopes []string, isInteractive bool, browserLauncher string) (string, string, error) {
func AuthFlow(oauthHost string, IO *iostreams.IOStreams, notice string, additionalScopes []string, isInteractive bool, b browser.Browser) (string, string, error) {
w := IO.ErrOut
cs := IO.ColorScheme()

Expand Down Expand Up @@ -106,7 +75,6 @@ func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, addition
fmt.Fprintf(w, "%s to open %s in your browser... ", cs.Bold("Press Enter"), oauthHost)
_ = waitForEnter(IO.In)

b := browser.New(browserLauncher, IO.Out, IO.ErrOut)
if err := b.Browse(authURL); err != nil {
fmt.Fprintf(w, "%s Failed opening a web browser at %s\n", cs.Red("!"), authURL)
fmt.Fprintf(w, " %s\n", err)
Expand Down
137 changes: 113 additions & 24 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,29 @@ import (
)

const (
hosts = "hosts"
aliases = "aliases"
aliases = "aliases"
hosts = "hosts"
oauthToken = "oauth_token"
)

// This interface describes interacting with some persistent configuration for gh.
//
//go:generate moq -rm -out config_mock.go . Config
type Config interface {
AuthToken(string) (string, string)
Get(string, string) (string, error)
GetOrDefault(string, string) (string, error)
Set(string, string, string)
UnsetHost(string)
Hosts() []string
DefaultHost() (string, string)
Aliases() *AliasConfig
Write() error

Aliases() *AliasConfig
Authentication() *AuthConfig

// This is deprecated and will be removed, do not use!
// Please use cfg.Authentication().Token()
AuthToken(string) (string, string)
// This is deprecated and will be removed, do not use!
// Please use cfg.Authentication().DefaultHost()
DefaultHost() (string, string)
Comment on lines +29 to +34
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would have removed these but it would have ballooned the size of this PR. Will do it in a follow up cleanup PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

func NewConfig() (Config, error) {
Expand All @@ -41,10 +47,6 @@ type cfg struct {
cfg *ghConfig.Config
}

func (c *cfg) AuthToken(hostname string) (string, string) {
return ghAuth.TokenForHost(hostname)
}

func (c *cfg) Get(hostname, key string) (string, error) {
if hostname != "" {
val, err := c.cfg.Get([]string{hosts, hostname, key})
Expand Down Expand Up @@ -86,27 +88,26 @@ func (c *cfg) Set(hostname, key, value string) {
c.cfg.Set([]string{hosts, hostname, key}, value)
}

func (c *cfg) UnsetHost(hostname string) {
if hostname == "" {
return
}
_ = c.cfg.Remove([]string{hosts, hostname})
func (c *cfg) Write() error {
return ghConfig.Write(c.cfg)
}

func (c *cfg) Hosts() []string {
return ghAuth.KnownHosts()
func (c *cfg) Aliases() *AliasConfig {
return &AliasConfig{cfg: c.cfg}
}

func (c *cfg) DefaultHost() (string, string) {
return ghAuth.DefaultHost()
func (c *cfg) Authentication() *AuthConfig {
return &AuthConfig{cfg: c.cfg}
}

func (c *cfg) Aliases() *AliasConfig {
return &AliasConfig{cfg: c.cfg}
// This is deprecated and will be removed, do not use.
func (c *cfg) AuthToken(hostname string) (string, string) {
return c.Authentication().Token(hostname)
}

func (c *cfg) Write() error {
return ghConfig.Write(c.cfg)
// This is deprecated and will be removed, do not use.
func (c *cfg) DefaultHost() (string, string) {
return c.Authentication().DefaultHost()
}

func defaultFor(key string) string {
Expand All @@ -127,6 +128,94 @@ func defaultExists(key string) bool {
return false
}

// AuthConfig is used for interacting with some persistent configuration for gh,
// with knowledge on how to access encrypted storage when neccesarry.
// Behavior is scoped to authentication specific tasks.
type AuthConfig struct {
cfg *ghConfig.Config
hostsOverride func() []string
tokenOverride func(string) (string, string)
Comment on lines +136 to +137
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried a new approach here to have the overrides live inside the struct rather than create an interface that can be mocked out. Any function that calls a ghAuth can have an override.

}

// Token will retrieve the auth token for the given hostname,
// searching environment variables, plain text config, and
// lastly encypted storage.
func (c *AuthConfig) Token(hostname string) (string, string) {
if c.tokenOverride != nil {
return c.tokenOverride(hostname)
}
return ghAuth.TokenForHost(hostname)
}

// SetToken will override any token resolution and return the given
// token and source for all calls to Token. Use for testing purposes only.
func (c *AuthConfig) SetToken(token, source string) {
c.tokenOverride = func(_ string) (string, string) {
return token, source
}
}

// User will retrieve the username for the logged in user at the given hostname.
func (c *AuthConfig) User(hostname string) (string, error) {
return c.cfg.Get([]string{hosts, hostname, "user"})
}

// GitProtocol will retrieve the git protocol for the logged in user at the given hostname.
// If none is set it will return the default value.
func (c *AuthConfig) GitProtocol(hostname string) (string, error) {
key := "git_protocol"
val, err := c.cfg.Get([]string{hosts, hostname, key})
if err == nil {
return val, err
}
return defaultFor(key), nil
}

func (c *AuthConfig) Hosts() []string {
if c.hostsOverride != nil {
return c.hostsOverride()
}
return ghAuth.KnownHosts()
}

// SetHosts will override any hosts resolution and return the given
// hosts for all calls to Hosts. Use for testing purposes only.
func (c *AuthConfig) SetHosts(hosts []string) {
c.hostsOverride = func() []string {
return hosts
}
}

func (c *AuthConfig) DefaultHost() (string, string) {
return ghAuth.DefaultHost()
}

// Login will set user, git protocol, and auth token for the given hostname.
// If the encrypt option is specified it will first try to store the auth token
// in encrypted storage and will fall back to the plain text config file.
func (c *AuthConfig) Login(hostname, username, token, gitProtocol string, encrypt bool) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm assuming the encrypt flag will be implemented in a follow up?

if token != "" {
c.cfg.Set([]string{hosts, hostname, oauthToken}, token)
}
if username != "" {
c.cfg.Set([]string{hosts, hostname, "user"}, username)
}
if gitProtocol != "" {
c.cfg.Set([]string{hosts, hostname, "git_protocol"}, gitProtocol)
}
return ghConfig.Write(c.cfg)
}

// Logout will remove user, git protocol, and auth token for the given hostname.
// It will remove the auth token from the encrypted storage if it exists there.
func (c *AuthConfig) Logout(hostname string) error {
if hostname == "" {
return nil
}
_ = c.cfg.Remove([]string{hosts, hostname})
return ghConfig.Write(c.cfg)
}

type AliasConfig struct {
cfg *ghConfig.Config
}
Expand Down
Loading