credential.go

  1package auth
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"regexp"
  7	"strings"
  8	"time"
  9
 10	"github.com/MichaelMure/git-bug/entity"
 11	"github.com/MichaelMure/git-bug/repository"
 12)
 13
 14const (
 15	configKeyPrefix     = "git-bug.auth"
 16	configKeyKind       = "kind"
 17	configKeyUserId     = "userid"
 18	configKeyTarget     = "target"
 19	configKeyCreateTime = "createtime"
 20)
 21
 22type CredentialKind string
 23
 24const (
 25	KindToken         = "token"
 26	KindLoginPassword = "login-password"
 27)
 28
 29var ErrCredentialNotExist = errors.New("credential doesn't exist")
 30
 31func NewErrMultipleMatchCredential(matching []entity.Id) *entity.ErrMultipleMatch {
 32	return entity.NewErrMultipleMatch("credential", matching)
 33}
 34
 35type Credential interface {
 36	ID() entity.Id
 37	UserId() entity.Id
 38	Target() string
 39	Kind() CredentialKind
 40	CreateTime() time.Time
 41	Validate() error
 42
 43	// Return all the specific properties of the credential that need to be saved into the configuration.
 44	// This does not include Target, User, Kind and CreateTime.
 45	ToConfig() map[string]string
 46}
 47
 48// Load loads a credential from the repo config
 49func LoadWithId(repo repository.RepoConfig, id entity.Id) (Credential, error) {
 50	keyPrefix := fmt.Sprintf("%s.%s.", configKeyPrefix, id)
 51
 52	// read token config pairs
 53	rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix)
 54	if err != nil {
 55		// Not exactly right due to the limitation of ReadAll()
 56		return nil, ErrCredentialNotExist
 57	}
 58
 59	return loadFromConfig(rawconfigs, id)
 60}
 61
 62// LoadWithPrefix load a credential from the repo config with a prefix
 63func LoadWithPrefix(repo repository.RepoConfig, prefix string) (Credential, error) {
 64	creds, err := List(repo)
 65	if err != nil {
 66		return nil, err
 67	}
 68
 69	// preallocate but empty
 70	matching := make([]Credential, 0, 5)
 71
 72	for _, cred := range creds {
 73		if cred.ID().HasPrefix(prefix) {
 74			matching = append(matching, cred)
 75		}
 76	}
 77
 78	if len(matching) > 1 {
 79		ids := make([]entity.Id, len(matching))
 80		for i, cred := range matching {
 81			ids[i] = cred.ID()
 82		}
 83		return nil, NewErrMultipleMatchCredential(ids)
 84	}
 85
 86	if len(matching) == 0 {
 87		return nil, ErrCredentialNotExist
 88	}
 89
 90	return matching[0], nil
 91}
 92
 93func loadFromConfig(rawConfigs map[string]string, id entity.Id) (Credential, error) {
 94	keyPrefix := fmt.Sprintf("%s.%s.", configKeyPrefix, id)
 95
 96	// trim key prefix
 97	configs := make(map[string]string)
 98	for key, value := range rawConfigs {
 99		newKey := strings.TrimPrefix(key, keyPrefix)
100		configs[newKey] = value
101	}
102
103	var cred Credential
104
105	switch configs[configKeyKind] {
106	case KindToken:
107		cred = NewTokenFromConfig(configs)
108	case KindLoginPassword:
109	default:
110		return nil, fmt.Errorf("unknown credential type %s", configs[configKeyKind])
111	}
112
113	return cred, nil
114}
115
116// List load all existing credentials
117func List(repo repository.RepoConfig, opts ...Option) ([]Credential, error) {
118	rawConfigs, err := repo.GlobalConfig().ReadAll(configKeyPrefix + ".")
119	if err != nil {
120		return nil, err
121	}
122
123	re, err := regexp.Compile(configKeyPrefix + `.([^.]+).([^.]+)`)
124	if err != nil {
125		panic(err)
126	}
127
128	mapped := make(map[string]map[string]string)
129
130	for key, val := range rawConfigs {
131		res := re.FindStringSubmatch(key)
132		if res == nil {
133			continue
134		}
135		if mapped[res[1]] == nil {
136			mapped[res[1]] = make(map[string]string)
137		}
138		mapped[res[1]][res[2]] = val
139	}
140
141	matcher := matcher(opts)
142
143	var credentials []Credential
144	for id, kvs := range mapped {
145		cred, err := loadFromConfig(kvs, entity.Id(id))
146		if err != nil {
147			return nil, err
148		}
149		if matcher.Match(cred) {
150			credentials = append(credentials, cred)
151		}
152	}
153
154	return credentials, nil
155}
156
157// IdExist return whether a credential id exist or not
158func IdExist(repo repository.RepoConfig, id entity.Id) bool {
159	_, err := LoadWithId(repo, id)
160	return err == nil
161}
162
163// PrefixExist return whether a credential id prefix exist or not
164func PrefixExist(repo repository.RepoConfig, prefix string) bool {
165	_, err := LoadWithPrefix(repo, prefix)
166	return err == nil
167}
168
169// Store stores a credential in the global git config
170func Store(repo repository.RepoConfig, cred Credential) error {
171	confs := cred.ToConfig()
172
173	prefix := fmt.Sprintf("%s.%s.", configKeyPrefix, cred.ID())
174
175	// Kind
176	err := repo.GlobalConfig().StoreString(prefix+configKeyKind, string(cred.Kind()))
177	if err != nil {
178		return err
179	}
180
181	// UserId
182	err = repo.GlobalConfig().StoreString(prefix+configKeyUserId, cred.UserId().String())
183	if err != nil {
184		return err
185	}
186
187	// Target
188	err = repo.GlobalConfig().StoreString(prefix+configKeyTarget, cred.Target())
189	if err != nil {
190		return err
191	}
192
193	// CreateTime
194	err = repo.GlobalConfig().StoreTimestamp(prefix+configKeyCreateTime, cred.CreateTime())
195	if err != nil {
196		return err
197	}
198
199	// Custom
200	for key, val := range confs {
201		err := repo.GlobalConfig().StoreString(prefix+key, val)
202		if err != nil {
203			return err
204		}
205	}
206
207	return nil
208}
209
210// Remove removes a credential from the global git config
211func Remove(repo repository.RepoConfig, id entity.Id) error {
212	keyPrefix := fmt.Sprintf("%s.%s", configKeyPrefix, id)
213	return repo.GlobalConfig().RemoveAll(keyPrefix)
214}
215
216/*
217 * Sorting
218 */
219
220type ById []Credential
221
222func (b ById) Len() int {
223	return len(b)
224}
225
226func (b ById) Less(i, j int) bool {
227	return b[i].ID() < b[j].ID()
228}
229
230func (b ById) Swap(i, j int) {
231	b[i], b[j] = b[j], b[i]
232}