diff options
author | Michael Muré <batolettre@gmail.com> | 2020-02-09 20:08:12 +0100 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2020-02-09 20:23:38 +0100 |
commit | b3d3612393387c83fa43f908dbb8e2a71068c834 (patch) | |
tree | e4609e21dc74e535d45b38cd7d0504681c544160 /bridge/core | |
parent | dca85b309a0a82e9993a345964d0831ab2876fb4 (diff) | |
parent | 3caffeef4d2ed25d4eb5d4bfd262f4fc3b92561f (diff) | |
download | git-bug-b3d3612393387c83fa43f908dbb8e2a71068c834.tar.gz |
Merge remote-tracking branch 'origin/master' into cheshirekow-jira
Diffstat (limited to 'bridge/core')
-rw-r--r-- | bridge/core/auth/credential.go | 46 | ||||
-rw-r--r-- | bridge/core/auth/credential_test.go | 41 | ||||
-rw-r--r-- | bridge/core/auth/options.go | 32 | ||||
-rw-r--r-- | bridge/core/auth/token.go | 29 | ||||
-rw-r--r-- | bridge/core/bridge.go | 73 | ||||
-rw-r--r-- | bridge/core/config.go | 46 | ||||
-rw-r--r-- | bridge/core/export.go | 5 | ||||
-rw-r--r-- | bridge/core/import.go | 5 | ||||
-rw-r--r-- | bridge/core/interfaces.go | 5 |
9 files changed, 197 insertions, 85 deletions
diff --git a/bridge/core/auth/credential.go b/bridge/core/auth/credential.go index a462a116..c1255aa6 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 @@ -34,15 +36,18 @@ func NewErrMultipleMatchCredential(matching []entity.Id) *entity.ErrMultipleMatc type Credential interface { ID() entity.Id - UserId() entity.Id Target() string Kind() CredentialKind CreateTime() time.Time Validate() error + Metadata() map[string]string + GetMetadata(key string) (string, bool) + SetMetadata(key string, value 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. - ToConfig() map[string]string + // This does not include Target, Kind, CreateTime and Metadata. + toConfig() map[string]string } // Load loads a credential from the repo config @@ -90,6 +95,7 @@ func LoadWithPrefix(repo repository.RepoConfig, prefix string) (Credential, erro return matching[0], nil } +// loadFromConfig is a helper to construct a Credential from the set of git configs func loadFromConfig(rawConfigs map[string]string, id entity.Id) (Credential, error) { keyPrefix := fmt.Sprintf("%s.%s.", configKeyPrefix, id) @@ -113,6 +119,20 @@ 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 + } + } + if len(result) == 0 { + return nil + } + return result +} + // List load all existing credentials func List(repo repository.RepoConfig, opts ...Option) ([]Credential, error) { rawConfigs, err := repo.GlobalConfig().ReadAll(configKeyPrefix + ".") @@ -120,7 +140,7 @@ func List(repo repository.RepoConfig, opts ...Option) ([]Credential, error) { return nil, err } - re, err := regexp.Compile(configKeyPrefix + `.([^.]+).([^.]+)`) + re, err := regexp.Compile(`^` + configKeyPrefix + `\.([^.]+)\.([^.]+(?:\.[^.]+)*)$`) if err != nil { panic(err) } @@ -168,7 +188,7 @@ func PrefixExist(repo repository.RepoConfig, prefix string) bool { // Store stores a credential in the global git config func Store(repo repository.RepoConfig, cred Credential) error { - confs := cred.ToConfig() + confs := cred.toConfig() prefix := fmt.Sprintf("%s.%s.", configKeyPrefix, cred.ID()) @@ -178,12 +198,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 { @@ -196,6 +210,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) diff --git a/bridge/core/auth/credential_test.go b/bridge/core/auth/credential_test.go index f91d273d..2f8806c9 100644 --- a/bridge/core/auth/credential_test.go +++ b/bridge/core/auth/credential_test.go @@ -7,32 +7,23 @@ import ( "github.com/stretchr/testify/require" "github.com/MichaelMure/git-bug/entity" - "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" ) func TestCredential(t *testing.T) { repo := repository.NewMockRepoForTest() - user1 := identity.NewIdentity("user1", "email") - err := user1.Commit(repo) - assert.NoError(t, err) - - user2 := identity.NewIdentity("user2", "email") - err = user2.Commit(repo) - assert.NoError(t, err) - - storeToken := func(user identity.Interface, val string, target string) *Token { - token := NewToken(user.Id(), val, target) - err = Store(repo, token) + storeToken := func(val string, target string) *Token { + token := NewToken(val, target) + err := Store(repo, token) require.NoError(t, err) return token } - token := storeToken(user1, "foobar", "github") + token := storeToken("foobar", "github") // Store + Load - err = Store(repo, token) + err := Store(repo, token) assert.NoError(t, err) token2, err := LoadWithId(repo, token.ID()) @@ -50,8 +41,8 @@ func TestCredential(t *testing.T) { token.createTime = token3.CreateTime() assert.Equal(t, token, token3) - token4 := storeToken(user1, "foo", "gitlab") - token5 := storeToken(user2, "bar", "github") + token4 := storeToken("foo", "gitlab") + token5 := storeToken("bar", "github") // List + options creds, err := List(repo, WithTarget("github")) @@ -62,14 +53,6 @@ func TestCredential(t *testing.T) { assert.NoError(t, err) sameIds(t, creds, []Credential{token4}) - creds, err = List(repo, WithUser(user1)) - assert.NoError(t, err) - sameIds(t, creds, []Credential{token, token4}) - - creds, err = List(repo, WithUserId(user1.Id())) - assert.NoError(t, err) - sameIds(t, creds, []Credential{token, token4}) - creds, err = List(repo, WithKind(KindToken)) assert.NoError(t, err) sameIds(t, creds, []Credential{token, token4, token5}) @@ -78,6 +61,16 @@ func TestCredential(t *testing.T) { assert.NoError(t, err) sameIds(t, creds, []Credential{}) + // Metadata + + token4.SetMetadata("key", "value") + err = Store(repo, token4) + assert.NoError(t, err) + + creds, err = List(repo, WithMeta("key", "value")) + assert.NoError(t, err) + sameIds(t, creds, []Credential{token4}) + // Exist exist := IdExist(repo, token.ID()) assert.True(t, exist) diff --git a/bridge/core/auth/options.go b/bridge/core/auth/options.go index 7bcda68e..74189874 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.GetMetadata(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 12a3bfc0..42f960bf 100644 --- a/bridge/core/auth/token.go +++ b/bridge/core/auth/token.go @@ -18,16 +18,15 @@ 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, @@ -37,7 +36,6 @@ func NewToken(userId entity.Id, value, target string) *Token { 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 +44,7 @@ func NewTokenFromConfig(conf map[string]string) *Token { } token.Value = conf[tokenValueKey] + token.meta = metaFromConfig(conf) return token } @@ -55,10 +54,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) Target() string { return t.target } @@ -88,7 +83,23 @@ func (t *Token) Validate() error { return nil } -func (t *Token) ToConfig() map[string]string { +func (t *Token) Metadata() map[string]string { + return t.meta +} + +func (t *Token) GetMetadata(key string) (string, bool) { + val, ok := t.meta[key] + return val, ok +} + +func (t *Token) SetMetadata(key string, value string) { + if t.meta == nil { + t.meta = make(map[string]string) + } + t.meta[key] = value +} + +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 95c0c2c4..ac0d47d7 100644 --- a/bridge/core/bridge.go +++ b/bridge/core/bridge.go @@ -28,28 +28,31 @@ const ( ) var bridgeImpl map[string]reflect.Type +var bridgeLoginMetaKey map[string]string // 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 // implementation with utility code to provide high-level functions. type Bridge struct { - Name string - repo *cache.RepoCache - impl BridgeImpl - importer Importer - exporter Exporter - conf Configuration - initDone bool + Name string + repo *cache.RepoCache + impl BridgeImpl + importer Importer + exporter Exporter + conf Configuration + initImportDone bool + initExportDone bool } // Register will register a new BridgeImpl @@ -57,7 +60,11 @@ func Register(impl BridgeImpl) { if bridgeImpl == nil { bridgeImpl = make(map[string]reflect.Type) } + if bridgeLoginMetaKey == nil { + bridgeLoginMetaKey = make(map[string]string) + } bridgeImpl[impl.Target()] = reflect.TypeOf(impl) + bridgeLoginMetaKey[impl.Target()] = impl.LoginMetaKey() } // Targets return all known bridge implementation target @@ -79,6 +86,18 @@ func TargetExist(target string) bool { return ok } +// LoginMetaKey return the metadata key used to store the remote bug-tracker login +// on the user identity. The corresponding value is used to match identities and +// credentials. +func LoginMetaKey(target string) (string, error) { + metaKey, ok := bridgeLoginMetaKey[target] + if !ok { + return "", fmt.Errorf("unknown bridge target %v", target) + } + + return metaKey, nil +} + // Instantiate a new Bridge for a repo, from the given target and name func NewBridge(repo *cache.RepoCache, target string, name string) (*Bridge, error) { implType, ok := bridgeImpl[target] @@ -273,8 +292,25 @@ func (b *Bridge) getExporter() Exporter { return b.exporter } -func (b *Bridge) ensureInit() error { - if b.initDone { +func (b *Bridge) ensureImportInit() error { + if b.initImportDone { + return nil + } + + importer := b.getImporter() + if importer != nil { + err := importer.Init(b.repo, b.conf) + if err != nil { + return err + } + } + + b.initImportDone = true + return nil +} + +func (b *Bridge) ensureExportInit() error { + if b.initExportDone { return nil } @@ -294,8 +330,7 @@ func (b *Bridge) ensureInit() error { } } - b.initDone = true - + b.initExportDone = true return nil } @@ -313,7 +348,7 @@ func (b *Bridge) ImportAllSince(ctx context.Context, since time.Time) (<-chan Im return nil, err } - err = b.ensureInit() + err = b.ensureImportInit() if err != nil { return nil, err } @@ -367,7 +402,7 @@ func (b *Bridge) ExportAll(ctx context.Context, since time.Time) (<-chan ExportR return nil, err } - err = b.ensureInit() + err = b.ensureExportInit() if err != nil { return nil, err } diff --git a/bridge/core/config.go b/bridge/core/config.go new file mode 100644 index 00000000..afcda560 --- /dev/null +++ b/bridge/core/config.go @@ -0,0 +1,46 @@ +package core + +import ( + "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/identity" +) + +func FinishConfig(repo *cache.RepoCache, metaKey string, login string) error { + // if no user exist with the given login metadata + _, err := repo.ResolveIdentityImmutableMetadata(metaKey, login) + if err != nil && err != identity.ErrIdentityNotExist { + // real error + return err + } + if err == nil { + // found an already valid user, all good + return nil + } + + // if a default user exist, tag it with the login + user, err := repo.GetUserIdentity() + if err != nil && err != identity.ErrNoIdentitySet { + // real error + return err + } + if err == nil { + // found one + user.SetMetadata(metaKey, login) + return user.CommitAsNeeded() + } + + // otherwise create a user with that metadata + i, err := repo.NewIdentityFromGitUserRaw(map[string]string{ + metaKey: login, + }) + if err != nil { + return err + } + + err = repo.SetUserIdentity(i) + if err != nil { + return err + } + + return nil +} diff --git a/bridge/core/export.go b/bridge/core/export.go index ef7a2e57..fa531c5f 100644 --- a/bridge/core/export.go +++ b/bridge/core/export.go @@ -27,9 +27,12 @@ const ( // Nothing changed on the bug ExportEventNothing + // Something wrong happened during export that is worth notifying to the user + // but not severe enough to consider the export a failure. + ExportEventWarning + // Error happened during export ExportEventError - ExportEventWarning ) // ExportResult is an event that is emitted during the export process, to diff --git a/bridge/core/import.go b/bridge/core/import.go index 3b1f3ac3..0b0b4c68 100644 --- a/bridge/core/import.go +++ b/bridge/core/import.go @@ -30,9 +30,12 @@ const ( // Identity has been created ImportEventIdentity + // Something wrong happened during import that is worth notifying to the user + // but not severe enough to consider the import a failure. + ImportEventWarning + // Error happened during import ImportEventError - ImportEventWarning ) // ImportResult is an event that is emitted during the import process, to diff --git a/bridge/core/interfaces.go b/bridge/core/interfaces.go index 77e0a7b9..ab2f3977 100644 --- a/bridge/core/interfaces.go +++ b/bridge/core/interfaces.go @@ -13,6 +13,11 @@ type BridgeImpl interface { // Target return the target of the bridge (e.g.: "github") Target() string + // LoginMetaKey return the metadata key used to store the remote bug-tracker login + // on the user identity. The corresponding value is used to match identities and + // credentials. + LoginMetaKey() string + // Configure handle the user interaction and return a key/value configuration // for future use Configure(repo *cache.RepoCache, params BridgeParams) (Configuration, error) |