token.go

  1package core
  2
  3import (
  4	"crypto/sha256"
  5	"errors"
  6	"fmt"
  7	"regexp"
  8	"sort"
  9	"strings"
 10	"time"
 11
 12	"github.com/MichaelMure/git-bug/entity"
 13	"github.com/MichaelMure/git-bug/repository"
 14)
 15
 16const (
 17	tokenConfigKeyPrefix = "git-bug.token"
 18	tokenValueKey        = "value"
 19	tokenTargetKey       = "target"
 20	tokenCreateTimeKey   = "createtime"
 21)
 22
 23var ErrTokenNotExist = errors.New("token doesn't exist")
 24
 25func NewErrMultipleMatchToken(matching []entity.Id) *entity.ErrMultipleMatch {
 26	return entity.NewErrMultipleMatch("token", matching)
 27}
 28
 29// Token holds an API access token data
 30type Token struct {
 31	Value      string
 32	Target     string
 33	CreateTime time.Time
 34}
 35
 36// NewToken instantiate a new token
 37func NewToken(value, target string) *Token {
 38	return &Token{
 39		Value:      value,
 40		Target:     target,
 41		CreateTime: time.Now(),
 42	}
 43}
 44
 45func (t *Token) ID() entity.Id {
 46	sum := sha256.Sum256([]byte(t.Target + t.Value))
 47	return entity.Id(fmt.Sprintf("%x", sum))
 48}
 49
 50// Validate ensure token important fields are valid
 51func (t *Token) Validate() error {
 52	if t.Value == "" {
 53		return fmt.Errorf("missing value")
 54	}
 55	if t.Target == "" {
 56		return fmt.Errorf("missing target")
 57	}
 58	if t.CreateTime.IsZero() || t.CreateTime.Equal(time.Time{}) {
 59		return fmt.Errorf("missing creation time")
 60	}
 61	if !TargetExist(t.Target) {
 62		return fmt.Errorf("unknown target")
 63	}
 64	return nil
 65}
 66
 67// LoadToken loads a token from the repo config
 68func LoadToken(repo repository.RepoCommon, id entity.Id) (*Token, error) {
 69	keyPrefix := fmt.Sprintf("git-bug.token.%s.", id)
 70
 71	// read token config pairs
 72	rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix)
 73	if err != nil {
 74		// Not exactly right due to the limitation of ReadAll()
 75		return nil, ErrTokenNotExist
 76	}
 77
 78	// trim key prefix
 79	configs := make(map[string]string)
 80	for key, value := range rawconfigs {
 81		newKey := strings.TrimPrefix(key, keyPrefix)
 82		configs[newKey] = value
 83	}
 84
 85	token := &Token{}
 86
 87	token.Value = configs[tokenValueKey]
 88	token.Target = configs[tokenTargetKey]
 89	if createTime, ok := configs[tokenCreateTimeKey]; ok {
 90		if t, err := repository.ParseTimestamp(createTime); err == nil {
 91			token.CreateTime = t
 92		}
 93	}
 94
 95	return token, nil
 96}
 97
 98// LoadTokenPrefix load a token from the repo config with a prefix
 99func LoadTokenPrefix(repo repository.RepoCommon, prefix string) (*Token, error) {
100	tokens, err := ListTokens(repo)
101	if err != nil {
102		return nil, err
103	}
104
105	// preallocate but empty
106	matching := make([]entity.Id, 0, 5)
107
108	for _, id := range tokens {
109		if id.HasPrefix(prefix) {
110			matching = append(matching, id)
111		}
112	}
113
114	if len(matching) > 1 {
115		return nil, NewErrMultipleMatchToken(matching)
116	}
117
118	if len(matching) == 0 {
119		return nil, ErrTokenNotExist
120	}
121
122	return LoadToken(repo, matching[0])
123}
124
125// ListTokens return a map representing the stored tokens in the repo config and global config
126// along with their type (global: true, local:false)
127func ListTokens(repo repository.RepoCommon) ([]entity.Id, error) {
128	configs, err := repo.GlobalConfig().ReadAll(tokenConfigKeyPrefix + ".")
129	if err != nil {
130		return nil, err
131	}
132
133	re, err := regexp.Compile(tokenConfigKeyPrefix + `.([^.]+)`)
134	if err != nil {
135		panic(err)
136	}
137
138	set := make(map[string]interface{})
139
140	for key := range configs {
141		res := re.FindStringSubmatch(key)
142
143		if res == nil {
144			continue
145		}
146
147		set[res[1]] = nil
148	}
149
150	result := make([]entity.Id, 0, len(set))
151	for key := range set {
152		result = append(result, entity.Id(key))
153	}
154
155	sort.Sort(entity.Alphabetical(result))
156
157	return result, nil
158}
159
160// StoreToken stores a token in the repo config
161func StoreToken(repo repository.RepoCommon, token *Token) error {
162	storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenValueKey)
163	err := repo.GlobalConfig().StoreString(storeValueKey, token.Value)
164	if err != nil {
165		return err
166	}
167
168	storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenTargetKey)
169	err = repo.GlobalConfig().StoreString(storeTargetKey, token.Target)
170	if err != nil {
171		return err
172	}
173
174	createTimeKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenCreateTimeKey)
175	return repo.GlobalConfig().StoreTimestamp(createTimeKey, token.CreateTime)
176}
177
178// RemoveToken removes a token from the repo config
179func RemoveToken(repo repository.RepoCommon, id entity.Id) error {
180	keyPrefix := fmt.Sprintf("git-bug.token.%s", id)
181	return repo.GlobalConfig().RemoveAll(keyPrefix)
182}