diff options
-rw-r--r-- | bridge/core/auth/credential.go | 56 | ||||
-rw-r--r-- | bridge/core/auth/options.go | 32 | ||||
-rw-r--r-- | bridge/core/auth/token.go | 20 | ||||
-rw-r--r-- | bridge/core/bridge.go | 13 | ||||
-rw-r--r-- | bridge/github/config.go | 65 | ||||
-rw-r--r-- | bridge/gitlab/config.go | 2 |
6 files changed, 78 insertions, 110 deletions
diff --git a/bridge/core/auth/credential.go b/bridge/core/auth/credential.go index fd026c5d..228eb006 100644 --- a/bridge/core/auth/credential.go +++ b/bridge/core/auth/credential.go @@ -14,9 +14,11 @@ import ( const ( configKeyPrefix = "git-bug.auth" configKeyKind = "kind" - configKeyUserId = "userid" configKeyTarget = "target" configKeyCreateTime = "createtime" + configKeyPrefixMeta = "meta." + + MetaKeyLogin = "login" ) type CredentialKind string @@ -32,19 +34,13 @@ func NewErrMultipleMatchCredential(matching []entity.Id) *entity.ErrMultipleMatc return entity.NewErrMultipleMatch("credential", matching) } -// Special Id to mark a credential as being associated to the default user, whoever it might be. -// The intended use is for the bridge configuration, to be able to create and store a credential -// with no identities created yet, and then select one with `git-bug user adopt` -const DefaultUserId = entity.Id("default-user") - type Credential interface { ID() entity.Id - UserId() entity.Id - updateUserId(id entity.Id) Target() string Kind() CredentialKind CreateTime() time.Time Validate() error + Metadata() map[string]string // Return all the specific properties of the credential that need to be saved into the configuration. // This does not include Target, User, Kind and CreateTime. @@ -120,6 +116,17 @@ func loadFromConfig(rawConfigs map[string]string, id entity.Id) (Credential, err return cred, nil } +func metaFromConfig(configs map[string]string) map[string]string { + result := make(map[string]string) + for key, val := range configs { + if strings.HasPrefix(key, configKeyPrefixMeta) { + key = strings.TrimPrefix(key, configKeyPrefixMeta) + result[key] = val + } + } + return result +} + // List load all existing credentials func List(repo repository.RepoConfig, opts ...Option) ([]Credential, error) { rawConfigs, err := repo.GlobalConfig().ReadAll(configKeyPrefix + ".") @@ -185,12 +192,6 @@ func Store(repo repository.RepoConfig, cred Credential) error { return err } - // UserId - err = repo.GlobalConfig().StoreString(prefix+configKeyUserId, cred.UserId().String()) - if err != nil { - return err - } - // Target err = repo.GlobalConfig().StoreString(prefix+configKeyTarget, cred.Target()) if err != nil { @@ -203,6 +204,14 @@ func Store(repo repository.RepoConfig, cred Credential) error { return err } + // Metadata + for key, val := range cred.Metadata() { + err := repo.GlobalConfig().StoreString(prefix+configKeyPrefixMeta+key, val) + if err != nil { + return err + } + } + // Custom for key, val := range confs { err := repo.GlobalConfig().StoreString(prefix+key, val) @@ -220,25 +229,6 @@ func Remove(repo repository.RepoConfig, id entity.Id) error { return repo.GlobalConfig().RemoveAll(keyPrefix) } -// ReplaceDefaultUser update all the credential attributed to the temporary "default user" -// with a real user Id -func ReplaceDefaultUser(repo repository.RepoConfig, id entity.Id) error { - list, err := List(repo, WithUserId(DefaultUserId)) - if err != nil { - return err - } - - for _, cred := range list { - cred.updateUserId(id) - err = Store(repo, cred) - if err != nil { - return err - } - } - - return nil -} - /* * Sorting */ diff --git a/bridge/core/auth/options.go b/bridge/core/auth/options.go index 7bcda68e..0c780dc1 100644 --- a/bridge/core/auth/options.go +++ b/bridge/core/auth/options.go @@ -1,14 +1,9 @@ package auth -import ( - "github.com/MichaelMure/git-bug/entity" - "github.com/MichaelMure/git-bug/identity" -) - type options struct { target string - userId entity.Id kind CredentialKind + meta map[string]string } type Option func(opts *options) @@ -26,12 +21,14 @@ func (opts *options) Match(cred Credential) bool { return false } - if opts.userId != "" && cred.UserId() != opts.userId { + if opts.kind != "" && cred.Kind() != opts.kind { return false } - if opts.kind != "" && cred.Kind() != opts.kind { - return false + for key, val := range opts.meta { + if v, ok := cred.Metadata()[key]; !ok || v != val { + return false + } } return true @@ -43,20 +40,17 @@ func WithTarget(target string) Option { } } -func WithUser(user identity.Interface) Option { - return func(opts *options) { - opts.userId = user.Id() - } -} - -func WithUserId(userId entity.Id) Option { +func WithKind(kind CredentialKind) Option { return func(opts *options) { - opts.userId = userId + opts.kind = kind } } -func WithKind(kind CredentialKind) Option { +func WithMeta(key string, val string) Option { return func(opts *options) { - opts.kind = kind + if opts.meta == nil { + opts.meta = make(map[string]string) + } + opts.meta[key] = val } } diff --git a/bridge/core/auth/token.go b/bridge/core/auth/token.go index 8333ef12..60137cd9 100644 --- a/bridge/core/auth/token.go +++ b/bridge/core/auth/token.go @@ -18,26 +18,25 @@ var _ Credential = &Token{} // Token holds an API access token data type Token struct { - userId entity.Id target string createTime time.Time Value string + meta map[string]string } // NewToken instantiate a new token -func NewToken(userId entity.Id, value, target string) *Token { +func NewToken(value, target string) *Token { return &Token{ - userId: userId, target: target, createTime: time.Now(), Value: value, + meta: make(map[string]string), } } func NewTokenFromConfig(conf map[string]string) *Token { token := &Token{} - token.userId = entity.Id(conf[configKeyUserId]) token.target = conf[configKeyTarget] if createTime, ok := conf[configKeyCreateTime]; ok { if t, err := repository.ParseTimestamp(createTime); err == nil { @@ -46,6 +45,7 @@ func NewTokenFromConfig(conf map[string]string) *Token { } token.Value = conf[tokenValueKey] + token.meta = metaFromConfig(conf) return token } @@ -55,14 +55,6 @@ func (t *Token) ID() entity.Id { return entity.Id(fmt.Sprintf("%x", sum)) } -func (t *Token) UserId() entity.Id { - return t.userId -} - -func (t *Token) updateUserId(id entity.Id) { - t.userId = id -} - func (t *Token) Target() string { return t.target } @@ -92,6 +84,10 @@ func (t *Token) Validate() error { return nil } +func (t *Token) Metadata() map[string]string { + return t.meta +} + func (t *Token) toConfig() map[string]string { return map[string]string{ tokenValueKey: t.Value, diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go index f606d2da..7891763f 100644 --- a/bridge/core/bridge.go +++ b/bridge/core/bridge.go @@ -32,12 +32,13 @@ var bridgeImpl map[string]reflect.Type // BridgeParams holds parameters to simplify the bridge configuration without // having to make terminal prompts. type BridgeParams struct { - Owner string - Project string - URL string - BaseURL string - CredPrefix string - TokenRaw string + Owner string // owner of the repo (Github) + Project string // name of the repo (Github, Launchpad) + URL string // complete URL of a repo (Github, Gitlab, Launchpad) + BaseURL string // base URL for self-hosted instance ( Gitlab) + CredPrefix string // ID prefix of the credential to use (Github, Gitlab) + TokenRaw string // pre-existing token to use (Github, Gitlab) + Login string // username for the passed credential (Github, Gitlab) } // Bridge is a wrapper around a BridgeImpl that will bind low-level diff --git a/bridge/github/config.go b/bridge/github/config.go index 8c4bf7c5..e51f244b 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -22,8 +22,6 @@ import ( "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bridge/core/auth" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/entity" - "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/input" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/colors" @@ -49,12 +47,6 @@ func (g *Github) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor conf := make(core.Configuration) var err error - - if (params.CredPrefix != "" || params.TokenRaw != "") && - (params.URL == "" && (params.Project == "" || params.Owner == "")) { - return nil, fmt.Errorf("you must provide a project URL or Owner/Name to configure this bridge with a token") - } - var owner string var project string @@ -87,15 +79,12 @@ func (g *Github) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor return nil, fmt.Errorf("invalid parameter owner: %v", owner) } - user, err := repo.GetUserIdentity() - if err != nil && err != identity.ErrNoIdentitySet { - return nil, err - } - - // default to a "to be filled" user Id if we don't have a valid one yet - userId := auth.DefaultUserId - if user != nil { - userId = user.Id() + login := params.Login + if login == "" { + login, err = input.Prompt("Github login", "", true, validateUsername) + if err != nil { + return nil, err + } } var cred auth.Credential @@ -106,13 +95,11 @@ func (g *Github) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor if err != nil { return nil, err } - if user != nil && cred.UserId() != user.Id() { - return nil, fmt.Errorf("selected credential don't match the user") - } case params.TokenRaw != "": - cred = auth.NewToken(userId, params.TokenRaw, target) + cred = auth.NewToken(params.TokenRaw, target) + cred.Metadata()[auth.MetaKeyLogin] = login default: - cred, err = promptTokenOptions(repo, userId, owner, project) + cred, err = promptTokenOptions(repo, login, owner, project) if err != nil { return nil, err } @@ -170,11 +157,11 @@ func (*Github) ValidateConfig(conf core.Configuration) error { return nil } -func requestToken(note, username, password string, scope string) (*http.Response, error) { - return requestTokenWith2FA(note, username, password, "", scope) +func requestToken(note, login, password string, scope string) (*http.Response, error) { + return requestTokenWith2FA(note, login, password, "", scope) } -func requestTokenWith2FA(note, username, password, otpCode string, scope string) (*http.Response, error) { +func requestTokenWith2FA(note, login, password, otpCode string, scope string) (*http.Response, error) { url := fmt.Sprintf("%s/authorizations", githubV3Url) params := struct { Scopes []string `json:"scopes"` @@ -196,7 +183,7 @@ func requestTokenWith2FA(note, username, password, otpCode string, scope string) return nil, err } - req.SetBasicAuth(username, password) + req.SetBasicAuth(login, password) req.Header.Set("Content-Type", "application/json") if otpCode != "" { @@ -240,9 +227,9 @@ func randomFingerprint() string { return string(b) } -func promptTokenOptions(repo repository.RepoConfig, userId entity.Id, owner, project string) (auth.Credential, error) { +func promptTokenOptions(repo repository.RepoConfig, login, owner, project string) (auth.Credential, error) { for { - creds, err := auth.List(repo, auth.WithUserId(userId), auth.WithTarget(target)) + creds, err := auth.List(repo, auth.WithTarget(target), auth.WithMeta(auth.MetaKeyLogin, login)) if err != nil { return nil, err } @@ -258,10 +245,11 @@ func promptTokenOptions(repo repository.RepoConfig, userId entity.Id, owner, pro fmt.Println("Existing tokens for Github:") for i, cred := range creds { token := cred.(*auth.Token) - fmt.Printf("[%d]: %s => %s (%s)\n", + fmt.Printf("[%d]: %s => %s (login: %s, %s)\n", i+3, colors.Cyan(token.ID().Human()), colors.Red(text.TruncateMax(token.Value, 10)), + token.Metadata()[auth.MetaKeyLogin], token.CreateTime().Format(time.RFC822), ) } @@ -289,13 +277,17 @@ func promptTokenOptions(repo repository.RepoConfig, userId entity.Id, owner, pro if err != nil { return nil, err } - return auth.NewToken(userId, value, target), nil + token := auth.NewToken(value, target) + token.Metadata()[auth.MetaKeyLogin] = login + return token, nil case 2: - value, err := loginAndRequestToken(owner, project) + value, err := loginAndRequestToken(login, owner, project) if err != nil { return nil, err } - return auth.NewToken(userId, value, target), nil + token := auth.NewToken(value, target) + token.Metadata()[auth.MetaKeyLogin] = login + return token, nil default: return creds[index-3], nil } @@ -328,7 +320,7 @@ func promptToken() (string, error) { return input.Prompt("Enter token", "token", "", input.Required, validator) } -func loginAndRequestToken(owner, project string) (string, error) { +func loginAndRequestToken(login, owner, project string) (string, error) { fmt.Println("git-bug will now generate an access token in your Github profile. Your credential are not stored and are only used to generate the token. The token is stored in the global git config.") fmt.Println() fmt.Println("The access scope depend on the type of repository.") @@ -345,11 +337,6 @@ func loginAndRequestToken(owner, project string) (string, error) { } isPublic := i == 0 - username, err := promptUsername() - if err != nil { - return "", err - } - password, err := input.PromptPassword("Password", "password", input.Required) if err != nil { return "", err @@ -369,7 +356,7 @@ func loginAndRequestToken(owner, project string) (string, error) { note := fmt.Sprintf("git-bug - %s/%s", owner, project) - resp, err := requestToken(note, username, password, scope) + resp, err := requestToken(note, login, password, scope) if err != nil { return "", err } diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index 5e345b31..0758074c 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -212,7 +212,7 @@ func validateBaseUrl(baseUrl string) (bool, error) { func promptTokenOptions(repo repository.RepoConfig, userId entity.Id, baseUrl string) (auth.Credential, error) { for { - creds, err := auth.List(repo, auth.WithUserId(userId), auth.WithTarget(target), auth.WithKind(auth.KindToken)) + creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken)) if err != nil { return nil, err } |