diff options
Diffstat (limited to 'bridge/core')
-rw-r--r-- | bridge/core/auth/credential.go | 232 | ||||
-rw-r--r-- | bridge/core/auth/credential_test.go | 109 | ||||
-rw-r--r-- | bridge/core/auth/options.go | 62 | ||||
-rw-r--r-- | bridge/core/auth/token.go | 95 | ||||
-rw-r--r-- | bridge/core/bridge.go | 32 | ||||
-rw-r--r-- | bridge/core/interfaces.go | 7 | ||||
-rw-r--r-- | bridge/core/token.go | 296 |
7 files changed, 512 insertions, 321 deletions
diff --git a/bridge/core/auth/credential.go b/bridge/core/auth/credential.go new file mode 100644 index 00000000..a462a116 --- /dev/null +++ b/bridge/core/auth/credential.go @@ -0,0 +1,232 @@ +package auth + +import ( + "errors" + "fmt" + "regexp" + "strings" + "time" + + "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/repository" +) + +const ( + configKeyPrefix = "git-bug.auth" + configKeyKind = "kind" + configKeyUserId = "userid" + configKeyTarget = "target" + configKeyCreateTime = "createtime" +) + +type CredentialKind string + +const ( + KindToken CredentialKind = "token" + KindLoginPassword CredentialKind = "login-password" +) + +var ErrCredentialNotExist = errors.New("credential doesn't exist") + +func NewErrMultipleMatchCredential(matching []entity.Id) *entity.ErrMultipleMatch { + return entity.NewErrMultipleMatch("credential", matching) +} + +type Credential interface { + ID() entity.Id + UserId() entity.Id + Target() string + Kind() CredentialKind + CreateTime() time.Time + Validate() error + + // 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 +} + +// Load loads a credential from the repo config +func LoadWithId(repo repository.RepoConfig, id entity.Id) (Credential, error) { + keyPrefix := fmt.Sprintf("%s.%s.", configKeyPrefix, id) + + // read token config pairs + rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix) + if err != nil { + // Not exactly right due to the limitation of ReadAll() + return nil, ErrCredentialNotExist + } + + return loadFromConfig(rawconfigs, id) +} + +// LoadWithPrefix load a credential from the repo config with a prefix +func LoadWithPrefix(repo repository.RepoConfig, prefix string) (Credential, error) { + creds, err := List(repo) + if err != nil { + return nil, err + } + + // preallocate but empty + matching := make([]Credential, 0, 5) + + for _, cred := range creds { + if cred.ID().HasPrefix(prefix) { + matching = append(matching, cred) + } + } + + if len(matching) > 1 { + ids := make([]entity.Id, len(matching)) + for i, cred := range matching { + ids[i] = cred.ID() + } + return nil, NewErrMultipleMatchCredential(ids) + } + + if len(matching) == 0 { + return nil, ErrCredentialNotExist + } + + return matching[0], nil +} + +func loadFromConfig(rawConfigs map[string]string, id entity.Id) (Credential, error) { + keyPrefix := fmt.Sprintf("%s.%s.", configKeyPrefix, id) + + // trim key prefix + configs := make(map[string]string) + for key, value := range rawConfigs { + newKey := strings.TrimPrefix(key, keyPrefix) + configs[newKey] = value + } + + var cred Credential + + switch CredentialKind(configs[configKeyKind]) { + case KindToken: + cred = NewTokenFromConfig(configs) + case KindLoginPassword: + default: + return nil, fmt.Errorf("unknown credential type %s", configs[configKeyKind]) + } + + return cred, nil +} + +// List load all existing credentials +func List(repo repository.RepoConfig, opts ...Option) ([]Credential, error) { + rawConfigs, err := repo.GlobalConfig().ReadAll(configKeyPrefix + ".") + if err != nil { + return nil, err + } + + re, err := regexp.Compile(configKeyPrefix + `.([^.]+).([^.]+)`) + if err != nil { + panic(err) + } + + mapped := make(map[string]map[string]string) + + for key, val := range rawConfigs { + res := re.FindStringSubmatch(key) + if res == nil { + continue + } + if mapped[res[1]] == nil { + mapped[res[1]] = make(map[string]string) + } + mapped[res[1]][res[2]] = val + } + + matcher := matcher(opts) + + var credentials []Credential + for id, kvs := range mapped { + cred, err := loadFromConfig(kvs, entity.Id(id)) + if err != nil { + return nil, err + } + if matcher.Match(cred) { + credentials = append(credentials, cred) + } + } + + return credentials, nil +} + +// IdExist return whether a credential id exist or not +func IdExist(repo repository.RepoConfig, id entity.Id) bool { + _, err := LoadWithId(repo, id) + return err == nil +} + +// PrefixExist return whether a credential id prefix exist or not +func PrefixExist(repo repository.RepoConfig, prefix string) bool { + _, err := LoadWithPrefix(repo, prefix) + return err == nil +} + +// Store stores a credential in the global git config +func Store(repo repository.RepoConfig, cred Credential) error { + confs := cred.ToConfig() + + prefix := fmt.Sprintf("%s.%s.", configKeyPrefix, cred.ID()) + + // Kind + err := repo.GlobalConfig().StoreString(prefix+configKeyKind, string(cred.Kind())) + if err != nil { + 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 { + return err + } + + // CreateTime + err = repo.GlobalConfig().StoreTimestamp(prefix+configKeyCreateTime, cred.CreateTime()) + if err != nil { + return err + } + + // Custom + for key, val := range confs { + err := repo.GlobalConfig().StoreString(prefix+key, val) + if err != nil { + return err + } + } + + return nil +} + +// Remove removes a credential from the global git config +func Remove(repo repository.RepoConfig, id entity.Id) error { + keyPrefix := fmt.Sprintf("%s.%s", configKeyPrefix, id) + return repo.GlobalConfig().RemoveAll(keyPrefix) +} + +/* + * Sorting + */ + +type ById []Credential + +func (b ById) Len() int { + return len(b) +} + +func (b ById) Less(i, j int) bool { + return b[i].ID() < b[j].ID() +} + +func (b ById) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} diff --git a/bridge/core/auth/credential_test.go b/bridge/core/auth/credential_test.go new file mode 100644 index 00000000..f91d273d --- /dev/null +++ b/bridge/core/auth/credential_test.go @@ -0,0 +1,109 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "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) + require.NoError(t, err) + return token + } + + token := storeToken(user1, "foobar", "github") + + // Store + Load + err = Store(repo, token) + assert.NoError(t, err) + + token2, err := LoadWithId(repo, token.ID()) + assert.NoError(t, err) + assert.Equal(t, token.createTime.Unix(), token2.CreateTime().Unix()) + token.createTime = token2.CreateTime() + assert.Equal(t, token, token2) + + prefix := string(token.ID())[:10] + + // LoadWithPrefix + token3, err := LoadWithPrefix(repo, prefix) + assert.NoError(t, err) + assert.Equal(t, token.createTime.Unix(), token3.CreateTime().Unix()) + token.createTime = token3.CreateTime() + assert.Equal(t, token, token3) + + token4 := storeToken(user1, "foo", "gitlab") + token5 := storeToken(user2, "bar", "github") + + // List + options + creds, err := List(repo, WithTarget("github")) + assert.NoError(t, err) + sameIds(t, creds, []Credential{token, token5}) + + creds, err = List(repo, WithTarget("gitlab")) + 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}) + + creds, err = List(repo, WithKind(KindLoginPassword)) + assert.NoError(t, err) + sameIds(t, creds, []Credential{}) + + // Exist + exist := IdExist(repo, token.ID()) + assert.True(t, exist) + + exist = PrefixExist(repo, prefix) + assert.True(t, exist) + + // Remove + err = Remove(repo, token.ID()) + assert.NoError(t, err) + + creds, err = List(repo) + assert.NoError(t, err) + sameIds(t, creds, []Credential{token4, token5}) +} + +func sameIds(t *testing.T, a []Credential, b []Credential) { + t.Helper() + + ids := func(creds []Credential) []entity.Id { + result := make([]entity.Id, len(creds)) + for i, cred := range creds { + result[i] = cred.ID() + } + return result + } + + assert.ElementsMatch(t, ids(a), ids(b)) +} diff --git a/bridge/core/auth/options.go b/bridge/core/auth/options.go new file mode 100644 index 00000000..7bcda68e --- /dev/null +++ b/bridge/core/auth/options.go @@ -0,0 +1,62 @@ +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 +} + +type Option func(opts *options) + +func matcher(opts []Option) *options { + result := &options{} + for _, opt := range opts { + opt(result) + } + return result +} + +func (opts *options) Match(cred Credential) bool { + if opts.target != "" && cred.Target() != opts.target { + return false + } + + if opts.userId != "" && cred.UserId() != opts.userId { + return false + } + + if opts.kind != "" && cred.Kind() != opts.kind { + return false + } + + return true +} + +func WithTarget(target string) Option { + return func(opts *options) { + opts.target = target + } +} + +func WithUser(user identity.Interface) Option { + return func(opts *options) { + opts.userId = user.Id() + } +} + +func WithUserId(userId entity.Id) Option { + return func(opts *options) { + opts.userId = userId + } +} + +func WithKind(kind CredentialKind) Option { + return func(opts *options) { + opts.kind = kind + } +} diff --git a/bridge/core/auth/token.go b/bridge/core/auth/token.go new file mode 100644 index 00000000..12a3bfc0 --- /dev/null +++ b/bridge/core/auth/token.go @@ -0,0 +1,95 @@ +package auth + +import ( + "crypto/sha256" + "fmt" + "time" + + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/repository" +) + +const ( + tokenValueKey = "value" +) + +var _ Credential = &Token{} + +// Token holds an API access token data +type Token struct { + userId entity.Id + target string + createTime time.Time + Value string +} + +// NewToken instantiate a new token +func NewToken(userId entity.Id, value, target string) *Token { + return &Token{ + userId: userId, + target: target, + createTime: time.Now(), + Value: value, + } +} + +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 { + token.createTime = t + } + } + + token.Value = conf[tokenValueKey] + + return token +} + +func (t *Token) ID() entity.Id { + sum := sha256.Sum256([]byte(t.target + t.Value)) + return entity.Id(fmt.Sprintf("%x", sum)) +} + +func (t *Token) UserId() entity.Id { + return t.userId +} + +func (t *Token) Target() string { + return t.target +} + +func (t *Token) Kind() CredentialKind { + return KindToken +} + +func (t *Token) CreateTime() time.Time { + return t.createTime +} + +// Validate ensure token important fields are valid +func (t *Token) Validate() error { + if t.Value == "" { + return fmt.Errorf("missing value") + } + if t.target == "" { + return fmt.Errorf("missing target") + } + if t.createTime.IsZero() || t.createTime.Equal(time.Time{}) { + return fmt.Errorf("missing creation time") + } + if !core.TargetExist(t.target) { + return fmt.Errorf("unknown target") + } + return nil +} + +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 1cad10e9..9a46e7b1 100644 --- a/bridge/core/bridge.go +++ b/bridge/core/bridge.go @@ -13,7 +13,6 @@ import ( "github.com/pkg/errors" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" ) @@ -21,10 +20,9 @@ var ErrImportNotSupported = errors.New("import is not supported") var ErrExportNotSupported = errors.New("export is not supported") const ( - ConfigKeyTarget = "target" - ConfigKeyToken = "token" - ConfigKeyTokenId = "token-id" - MetaKeyOrigin = "origin" + ConfigKeyTarget = "target" + + MetaKeyOrigin = "origin" bridgeConfigKeyPrefix = "git-bug.bridge" ) @@ -37,9 +35,8 @@ type BridgeParams struct { Owner string Project string URL string - Token string - TokenId string - TokenStdin bool + CredPrefix string + TokenRaw string } // Bridge is a wrapper around a BridgeImpl that will bind low-level @@ -143,7 +140,7 @@ func DefaultBridge(repo *cache.RepoCache) (*Bridge, error) { // ConfiguredBridges return the list of bridge that are configured for the given // repo -func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) { +func ConfiguredBridges(repo repository.RepoConfig) ([]string, error) { configs, err := repo.LocalConfig().ReadAll(bridgeConfigKeyPrefix + ".") if err != nil { return nil, errors.Wrap(err, "can't read configured bridges") @@ -178,7 +175,7 @@ func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) { } // Check if a bridge exist -func BridgeExist(repo repository.RepoCommon, name string) bool { +func BridgeExist(repo repository.RepoConfig, name string) bool { keyPrefix := fmt.Sprintf("git-bug.bridge.%s.", name) conf, err := repo.LocalConfig().ReadAll(keyPrefix) @@ -187,7 +184,7 @@ func BridgeExist(repo repository.RepoCommon, name string) bool { } // Remove a configured bridge -func RemoveBridge(repo repository.RepoCommon, name string) error { +func RemoveBridge(repo repository.RepoConfig, name string) error { re, err := regexp.Compile(`^[a-zA-Z0-9]+`) if err != nil { panic(err) @@ -242,7 +239,7 @@ func (b *Bridge) ensureConfig() error { return nil } -func loadConfig(repo repository.RepoCommon, name string) (Configuration, error) { +func loadConfig(repo repository.RepoConfig, name string) (Configuration, error) { keyPrefix := fmt.Sprintf("git-bug.bridge.%s.", name) pairs, err := repo.LocalConfig().ReadAll(keyPrefix) @@ -280,16 +277,9 @@ func (b *Bridge) ensureInit() error { return nil } - token, err := LoadToken(b.repo, entity.Id(b.conf[ConfigKeyTokenId])) - if err != nil { - return err - } - - b.conf[ConfigKeyToken] = token.Value - importer := b.getImporter() if importer != nil { - err := importer.Init(b.conf) + err := importer.Init(b.repo, b.conf) if err != nil { return err } @@ -297,7 +287,7 @@ func (b *Bridge) ensureInit() error { exporter := b.getExporter() if exporter != nil { - err := exporter.Init(b.conf) + err := exporter.Init(b.repo, b.conf) if err != nil { return err } diff --git a/bridge/core/interfaces.go b/bridge/core/interfaces.go index 047f3880..77e0a7b9 100644 --- a/bridge/core/interfaces.go +++ b/bridge/core/interfaces.go @@ -5,7 +5,6 @@ import ( "time" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/repository" ) type Configuration map[string]string @@ -16,7 +15,7 @@ type BridgeImpl interface { // Configure handle the user interaction and return a key/value configuration // for future use - Configure(repo repository.RepoCommon, params BridgeParams) (Configuration, error) + Configure(repo *cache.RepoCache, params BridgeParams) (Configuration, error) // ValidateConfig check the configuration for error ValidateConfig(conf Configuration) error @@ -29,11 +28,11 @@ type BridgeImpl interface { } type Importer interface { - Init(conf Configuration) error + Init(repo *cache.RepoCache, conf Configuration) error ImportAll(ctx context.Context, repo *cache.RepoCache, since time.Time) (<-chan ImportResult, error) } type Exporter interface { - Init(conf Configuration) error + Init(repo *cache.RepoCache, conf Configuration) error ExportAll(ctx context.Context, repo *cache.RepoCache, since time.Time) (<-chan ExportResult, error) } diff --git a/bridge/core/token.go b/bridge/core/token.go deleted file mode 100644 index 28c64f5c..00000000 --- a/bridge/core/token.go +++ /dev/null @@ -1,296 +0,0 @@ -package core - -import ( - "crypto/sha256" - "errors" - "fmt" - "regexp" - "sort" - "strings" - "time" - - "github.com/MichaelMure/git-bug/entity" - "github.com/MichaelMure/git-bug/repository" -) - -const ( - tokenConfigKeyPrefix = "git-bug.token" - tokenValueKey = "value" - tokenTargetKey = "target" - tokenCreateTimeKey = "createtime" -) - -var ErrTokenNotExist = errors.New("token doesn't exist") - -func NewErrMultipleMatchToken(matching []entity.Id) *entity.ErrMultipleMatch { - return entity.NewErrMultipleMatch("token", matching) -} - -// Token holds an API access token data -type Token struct { - Value string - Target string - CreateTime time.Time -} - -// NewToken instantiate a new token -func NewToken(value, target string) *Token { - return &Token{ - Value: value, - Target: target, - CreateTime: time.Now(), - } -} - -func (t *Token) ID() entity.Id { - sum := sha256.Sum256([]byte(t.Target + t.Value)) - return entity.Id(fmt.Sprintf("%x", sum)) -} - -// Validate ensure token important fields are valid -func (t *Token) Validate() error { - if t.Value == "" { - return fmt.Errorf("missing value") - } - if t.Target == "" { - return fmt.Errorf("missing target") - } - if t.CreateTime.IsZero() || t.CreateTime.Equal(time.Time{}) { - return fmt.Errorf("missing creation time") - } - if !TargetExist(t.Target) { - return fmt.Errorf("unknown target") - } - return nil -} - -// LoadToken loads a token from the repo config -func LoadToken(repo repository.RepoCommon, id entity.Id) (*Token, error) { - keyPrefix := fmt.Sprintf("git-bug.token.%s.", id) - - // read token config pairs - rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix) - if err != nil { - // Not exactly right due to the limitation of ReadAll() - return nil, ErrTokenNotExist - } - - // trim key prefix - configs := make(map[string]string) - for key, value := range rawconfigs { - newKey := strings.TrimPrefix(key, keyPrefix) - configs[newKey] = value - } - - token := &Token{} - - token.Value = configs[tokenValueKey] - token.Target = configs[tokenTargetKey] - if createTime, ok := configs[tokenCreateTimeKey]; ok { - if t, err := repository.ParseTimestamp(createTime); err == nil { - token.CreateTime = t - } - } - - return token, nil -} - -// LoadTokenPrefix load a token from the repo config with a prefix -func LoadTokenPrefix(repo repository.RepoCommon, prefix string) (*Token, error) { - tokens, err := ListTokens(repo) - if err != nil { - return nil, err - } - - // preallocate but empty - matching := make([]entity.Id, 0, 5) - - for _, id := range tokens { - if id.HasPrefix(prefix) { - matching = append(matching, id) - } - } - - if len(matching) > 1 { - return nil, NewErrMultipleMatchToken(matching) - } - - if len(matching) == 0 { - return nil, ErrTokenNotExist - } - - return LoadToken(repo, matching[0]) -} - -// ListTokens list all existing token ids -func ListTokens(repo repository.RepoCommon) ([]entity.Id, error) { - configs, err := repo.GlobalConfig().ReadAll(tokenConfigKeyPrefix + ".") - if err != nil { - return nil, err - } - - re, err := regexp.Compile(tokenConfigKeyPrefix + `.([^.]+)`) - if err != nil { - panic(err) - } - - set := make(map[string]interface{}) - - for key := range configs { - res := re.FindStringSubmatch(key) - - if res == nil { - continue - } - - set[res[1]] = nil - } - - result := make([]entity.Id, 0, len(set)) - for key := range set { - result = append(result, entity.Id(key)) - } - - sort.Sort(entity.Alphabetical(result)) - - return result, nil -} - -// ListTokensWithTarget list all token ids associated with the target -func ListTokensWithTarget(repo repository.RepoCommon, target string) ([]entity.Id, error) { - var ids []entity.Id - tokensIds, err := ListTokens(repo) - if err != nil { - return nil, err - } - - for _, tokenId := range tokensIds { - token, err := LoadToken(repo, tokenId) - if err != nil { - return nil, err - } - - if token.Target == target { - ids = append(ids, tokenId) - } - } - return ids, nil -} - -// LoadTokens load all existing tokens -func LoadTokens(repo repository.RepoCommon) ([]*Token, error) { - tokensIds, err := ListTokens(repo) - if err != nil { - return nil, err - } - - var tokens []*Token - for _, id := range tokensIds { - token, err := LoadToken(repo, id) - if err != nil { - return nil, err - } - tokens = append(tokens, token) - } - return tokens, nil -} - -// LoadTokensWithTarget load all existing tokens for a given target -func LoadTokensWithTarget(repo repository.RepoCommon, target string) ([]*Token, error) { - tokensIds, err := ListTokens(repo) - if err != nil { - return nil, err - } - - var tokens []*Token - for _, id := range tokensIds { - token, err := LoadToken(repo, id) - if err != nil { - return nil, err - } - if token.Target == target { - tokens = append(tokens, token) - } - } - return tokens, nil -} - -// TokenIdExist return wether token id exist or not -func TokenIdExist(repo repository.RepoCommon, id entity.Id) bool { - _, err := LoadToken(repo, id) - return err == nil -} - -// TokenExist return wether there is a token with a certain value or not -func TokenExist(repo repository.RepoCommon, value string) bool { - tokens, err := LoadTokens(repo) - if err != nil { - return false - } - for _, token := range tokens { - if token.Value == value { - return true - } - } - return false -} - -// TokenExistWithTarget same as TokenExist but restrict search for a given target -func TokenExistWithTarget(repo repository.RepoCommon, value string, target string) bool { - tokens, err := LoadTokensWithTarget(repo, target) - if err != nil { - return false - } - for _, token := range tokens { - if token.Value == value { - return true - } - } - return false -} - -// StoreToken stores a token in the repo config -func StoreToken(repo repository.RepoCommon, token *Token) error { - storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenValueKey) - err := repo.GlobalConfig().StoreString(storeValueKey, token.Value) - if err != nil { - return err - } - - storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenTargetKey) - err = repo.GlobalConfig().StoreString(storeTargetKey, token.Target) - if err != nil { - return err - } - - createTimeKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenCreateTimeKey) - return repo.GlobalConfig().StoreTimestamp(createTimeKey, token.CreateTime) -} - -// RemoveToken removes a token from the repo config -func RemoveToken(repo repository.RepoCommon, id entity.Id) error { - keyPrefix := fmt.Sprintf("git-bug.token.%s", id) - return repo.GlobalConfig().RemoveAll(keyPrefix) -} - -// LoadOrCreateToken will try to load a token matching the same value or create it -func LoadOrCreateToken(repo repository.RepoCommon, target, tokenValue string) (*Token, error) { - tokens, err := LoadTokensWithTarget(repo, target) - if err != nil { - return nil, err - } - - for _, token := range tokens { - if token.Value == tokenValue { - return token, nil - } - } - - token := NewToken(tokenValue, target) - err = StoreToken(repo, token) - if err != nil { - return nil, err - } - - return token, nil -} |