aboutsummaryrefslogblamecommitdiffstats
path: root/bridge/core/token.go
blob: 28c64f5c4f9ea5edc7fee5fb8f7d01463e58923a (plain) (tree)
1
2
3
4
5
6
7
8
9


            
                       
                

                
              
                 

              
                                               
                                                   



                                              

                                       
                                           

 





                                                                              
                                       
                   


                            

 
                                   
                                            
                      


                                       
         

 
                                
                                                        
                                                

 

                                                   
                          
                                                  

                           

                                                   
                                                                     

                                                          
                                   
                                                   



                  

                                                                          
                                                         
 
                                  
                                                                 
                       

                                                                       

         
                          

                                            

                                                            

         











                                                                                
 




                                                                                 

         






                                                       

         

                                                              

         

                                            
         

                                           

 
                                         
                                                                  
                                                                               

                               


















                                                                    
                                                
                              
                                                       

         

                                              


                          






































                                                                                           



















                                                                                        





















                                                                                         
                                                         



                                      
                                         





                                   

                                                                 
                                                                                               
                                                                          



                          
                                                                                                 
                                                                           



                          

                                                                                                    

 
                                                   
                                                                  
                                                        
                                                       
 





















                                                                                               
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
}