aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/core
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2020-02-09 20:08:12 +0100
committerMichael Muré <batolettre@gmail.com>2020-02-09 20:23:38 +0100
commitb3d3612393387c83fa43f908dbb8e2a71068c834 (patch)
treee4609e21dc74e535d45b38cd7d0504681c544160 /bridge/core
parentdca85b309a0a82e9993a345964d0831ab2876fb4 (diff)
parent3caffeef4d2ed25d4eb5d4bfd262f4fc3b92561f (diff)
downloadgit-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.go46
-rw-r--r--bridge/core/auth/credential_test.go41
-rw-r--r--bridge/core/auth/options.go32
-rw-r--r--bridge/core/auth/token.go29
-rw-r--r--bridge/core/bridge.go73
-rw-r--r--bridge/core/config.go46
-rw-r--r--bridge/core/export.go5
-rw-r--r--bridge/core/import.go5
-rw-r--r--bridge/core/interfaces.go5
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)