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