aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2020-01-07 22:07:25 +0100
committerMichael Muré <batolettre@gmail.com>2020-02-08 17:18:28 +0100
commit26f0152384f77d2bfd16c6762f5618bc966809a6 (patch)
tree82854841d2865e1bb52e08beeb392507495e7525
parentdb893494bb1492a3d9e32787a5ada1dd8f639ef3 (diff)
downloadgit-bug-26f0152384f77d2bfd16c6762f5618bc966809a6.tar.gz
WIP
-rw-r--r--bridge/core/auth/credential.go56
-rw-r--r--bridge/core/auth/options.go32
-rw-r--r--bridge/core/auth/token.go20
-rw-r--r--bridge/core/bridge.go13
-rw-r--r--bridge/github/config.go65
-rw-r--r--bridge/gitlab/config.go2
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
}