aboutsummaryrefslogblamecommitdiffstats
path: root/bridge/core/auth/credential.go
blob: 2327a6fc48b840a6020ae2923674b1d5f76753a7 (plain) (tree)
1
2
3
4
5
6
7
8


            
                         
                       

                
                 







                                                   





                                           
 

                                   




                          
                                                  
                                                  
                                                           









                                                                                   
                             
                       
                              
                     
                        
 
                                    

                                              

                                                                                                             
                                                                            
                                    


                                               

                                                                                
 

                                                    

                                                 


                               
 
                           


                                                                      

                                                                                     






                                            







                                                                     
                 






                                                 
















                                                              


                                                                     
 


                                               


                           
                                                     
                       
                                                    
                       
                                                    
                               
                                                            
                
                                                                                              

         



                                                                     



                                     

                                                                                  



                               
                                
 


                                                              

                                
 



                                                    
 
                                         











                                                               
                                                              




                                                                 
                                                                   




                                                     
                                                                


                                                      
 






                                                                                 
                                               
                                                     

         


                                        

         



                                                            


                                                         

                                                                    


















                                    
package auth

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/MichaelMure/git-bug/entity"
	"github.com/MichaelMure/git-bug/repository"
)

const (
	keyringKeyPrefix     = "auth-"
	keyringKeyKind       = "kind"
	keyringKeyTarget     = "target"
	keyringKeyCreateTime = "createtime"
	keyringKeySalt       = "salt"
	keyringKeyPrefixMeta = "meta."

	MetaKeyLogin   = "login"
	MetaKeyBaseURL = "base-url"
)

type CredentialKind string

const (
	KindToken         CredentialKind = "token"
	KindLogin         CredentialKind = "login"
	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
	Kind() CredentialKind
	Target() string
	CreateTime() time.Time
	Salt() []byte
	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, Kind, CreateTime, Metadata or Salt.
	toConfig() map[string]string
}

// Load loads a credential from the repo config
func LoadWithId(repo repository.RepoKeyring, id entity.Id) (Credential, error) {
	key := fmt.Sprintf("%s%s", keyringKeyPrefix, id)

	item, err := repo.Keyring().Get(key)
	if err == repository.ErrKeyringKeyNotFound {
		return nil, ErrCredentialNotExist
	}
	if err != nil {
		return nil, err
	}

	return decode(item)
}

// LoadWithPrefix load a credential from the repo config with a prefix
func LoadWithPrefix(repo repository.RepoKeyring, prefix string) (Credential, error) {
	keys, err := repo.Keyring().Keys()
	if err != nil {
		return nil, err
	}

	// preallocate but empty
	matching := make([]Credential, 0, 5)

	for _, key := range keys {
		if !strings.HasPrefix(key, keyringKeyPrefix+prefix) {
			continue
		}

		item, err := repo.Keyring().Get(key)
		if err != nil {
			return nil, err
		}

		cred, err := decode(item)
		if err != nil {
			return nil, err
		}

		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
}

// decode is a helper to construct a Credential from the keyring Item
func decode(item repository.Item) (Credential, error) {
	data := make(map[string]string)

	err := json.Unmarshal(item.Data, &data)
	if err != nil {
		return nil, err
	}

	var cred Credential
	switch CredentialKind(data[keyringKeyKind]) {
	case KindToken:
		cred, err = NewTokenFromConfig(data)
	case KindLogin:
		cred, err = NewLoginFromConfig(data)
	case KindLoginPassword:
		cred, err = NewLoginPasswordFromConfig(data)
	default:
		return nil, fmt.Errorf("unknown credential type \"%s\"", data[keyringKeyKind])
	}

	if err != nil {
		return nil, fmt.Errorf("loading credential: %v", err)
	}

	return cred, nil
}

// List load all existing credentials
func List(repo repository.RepoKeyring, opts ...ListOption) ([]Credential, error) {
	keys, err := repo.Keyring().Keys()
	if err != nil {
		return nil, err
	}

	matcher := matcher(opts)

	var credentials []Credential
	for _, key := range keys {
		if !strings.HasPrefix(key, keyringKeyPrefix) {
			continue
		}

		item, err := repo.Keyring().Get(key)
		if err != nil {
			return nil, err
		}

		cred, err := decode(item)
		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.RepoKeyring, 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.RepoKeyring, prefix string) bool {
	_, err := LoadWithPrefix(repo, prefix)
	return err == nil
}

// Store stores a credential in the global git config
func Store(repo repository.RepoKeyring, cred Credential) error {
	if len(cred.Salt()) != 16 {
		panic("credentials need to be salted")
	}

	confs := cred.toConfig()

	confs[keyringKeyKind] = string(cred.Kind())
	confs[keyringKeyTarget] = cred.Target()
	confs[keyringKeyCreateTime] = strconv.Itoa(int(cred.CreateTime().Unix()))
	confs[keyringKeySalt] = base64.StdEncoding.EncodeToString(cred.Salt())

	for key, val := range cred.Metadata() {
		confs[keyringKeyPrefixMeta+key] = val
	}

	data, err := json.Marshal(confs)
	if err != nil {
		return err
	}

	return repo.Keyring().Set(repository.Item{
		Key:  keyringKeyPrefix + cred.ID().String(),
		Data: data,
	})
}

// Remove removes a credential from the global git config
func Remove(repo repository.RepoKeyring, id entity.Id) error {
	return repo.Keyring().Remove(keyringKeyPrefix + id.String())
}

/*
 * 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]
}