tokens: use a hash as token identifier instead of the token it self

amine created

Change summary

bridge/core/token.go               | 129 ++++++++++++++++++++++++-------
commands/bridge_token.go           |  71 +++++++---------
commands/bridge_token_add.go       |  10 -
commands/bridge_token_rm.go        |   2 
doc/man/git-bug-bridge-token-add.1 |  41 ++++++++++
doc/man/git-bug-bridge-token-rm.1  |  29 +++++++
doc/man/git-bug-bridge-token.1     |  35 ++++++++
doc/md/git-bug_bridge.md           |   2 
doc/md/git-bug_bridge_token.md     |  26 ++++++
doc/md/git-bug_bridge_token_add.md |  26 ++++++
doc/md/git-bug_bridge_token_rm.md  |  22 +++++
misc/powershell_completion/git-bug |  10 -
misc/zsh_completion/git-bug        |   6 
13 files changed, 319 insertions(+), 90 deletions(-)

Detailed changes

bridge/core/token.go 🔗

@@ -1,6 +1,8 @@
 package core
 
 import (
+	"crypto/sha256"
+	"encoding/json"
 	"fmt"
 	"regexp"
 	"strings"
@@ -10,12 +12,14 @@ import (
 
 const (
 	tokenConfigKeyPrefix = "git-bug.token"
-	tokenKeyTarget       = "target"
-	tokenKeyScopes       = "scopes"
+	tokenValueKey        = "value"
+	tokenTargetKey       = "target"
+	tokenScopesKey       = "scopes"
 )
 
 // Token holds an API access token data
 type Token struct {
+	ID     string
 	Value  string
 	Target string
 	Global bool
@@ -24,16 +28,46 @@ type Token struct {
 
 // NewToken instantiate a new token
 func NewToken(value, target string, global bool, scopes []string) *Token {
-	return &Token{
+	token := &Token{
 		Value:  value,
 		Target: target,
 		Global: global,
 		Scopes: scopes,
 	}
+
+	token.ID = hashToken(token)
+	return token
+}
+
+// Id return full token identifier. It will compute the Id if it's empty
+func (t *Token) Id() string {
+	if t.ID == "" {
+		t.ID = hashToken(t)
+	}
+
+	return t.ID
+}
+
+// HumanId return the truncated token id
+func (t *Token) HumanId() string {
+	return t.Id()[:6]
+}
+
+func hashToken(token *Token) string {
+	tokenJson, err := json.Marshal(&token)
+	if err != nil {
+		panic(err)
+	}
+
+	sum := sha256.Sum256(tokenJson)
+	return fmt.Sprintf("%x", sum)
 }
 
 // Validate ensure token important fields are valid
 func (t *Token) Validate() error {
+	if t.ID == "" {
+		return fmt.Errorf("missing id")
+	}
 	if t.Value == "" {
 		return fmt.Errorf("missing value")
 	}
@@ -46,8 +80,17 @@ func (t *Token) Validate() error {
 	return nil
 }
 
-func loadToken(repo repository.RepoConfig, value string, global bool) (*Token, error) {
-	keyPrefix := fmt.Sprintf("git-bug.token.%s.", value)
+// Kind return the type of the token as string
+func (t *Token) Kind() string {
+	if t.Global {
+		return "global"
+	}
+
+	return "local"
+}
+
+func loadToken(repo repository.RepoConfig, id string, global bool) (*Token, error) {
+	keyPrefix := fmt.Sprintf("git-bug.token.%s.", id)
 
 	readerFn := repo.ReadConfigs
 	if global {
@@ -62,19 +105,25 @@ func loadToken(repo repository.RepoConfig, value string, global bool) (*Token, e
 
 	// trim key prefix
 	for key, value := range configs {
+		delete(configs, key)
 		newKey := strings.TrimPrefix(key, keyPrefix)
 		configs[newKey] = value
-		delete(configs, key)
 	}
 
 	var ok bool
-	token := &Token{Value: value, Global: global}
-	token.Target, ok = configs[tokenKeyTarget]
+	token := &Token{ID: id, Global: global}
+
+	token.Value, ok = configs[tokenValueKey]
+	if !ok {
+		return nil, fmt.Errorf("empty token value")
+	}
+
+	token.Target, ok = configs[tokenTargetKey]
 	if !ok {
 		return nil, fmt.Errorf("empty token key")
 	}
 
-	scopesString, ok := configs[tokenKeyScopes]
+	scopesString, ok := configs[tokenScopesKey]
 	if !ok {
 		return nil, fmt.Errorf("missing scopes config")
 	}
@@ -84,13 +133,13 @@ func loadToken(repo repository.RepoConfig, value string, global bool) (*Token, e
 }
 
 // GetToken loads a token from repo config
-func GetToken(repo repository.RepoConfig, value string) (*Token, error) {
-	return loadToken(repo, value, false)
+func GetToken(repo repository.RepoConfig, id string) (*Token, error) {
+	return loadToken(repo, id, false)
 }
 
 // GetGlobalToken loads a token from the global config
-func GetGlobalToken(repo repository.RepoConfig, value string) (*Token, error) {
-	return loadToken(repo, value, true)
+func GetGlobalToken(repo repository.RepoConfig, id string) (*Token, error) {
+	return loadToken(repo, id, true)
 }
 
 func listTokens(repo repository.RepoConfig, global bool) ([]string, error) {
@@ -131,14 +180,29 @@ func listTokens(repo repository.RepoConfig, global bool) ([]string, error) {
 	return result, nil
 }
 
-// ListTokens return the list of stored tokens in the repo config
-func ListTokens(repo repository.RepoConfig) ([]string, error) {
-	return listTokens(repo, false)
-}
+// ListTokens return a map representing the stored tokens in the repo config and global config
+// along with their type (global: true, local:false)
+func ListTokens(repo repository.RepoConfig) (map[string]bool, error) {
+	localTokens, err := listTokens(repo, false)
+	if err != nil {
+		return nil, err
+	}
 
-// ListGlobalTokens return the list of stored tokens in the global config
-func ListGlobalTokens(repo repository.RepoConfig) ([]string, error) {
-	return listTokens(repo, true)
+	globalTokens, err := listTokens(repo, true)
+	if err != nil {
+		return nil, err
+	}
+
+	tokens := map[string]bool{}
+	for _, token := range localTokens {
+		tokens[token] = false
+	}
+
+	for _, token := range globalTokens {
+		tokens[token] = true
+	}
+
+	return tokens, nil
 }
 
 func storeToken(repo repository.RepoConfig, token *Token) error {
@@ -147,13 +211,19 @@ func storeToken(repo repository.RepoConfig, token *Token) error {
 		storeFn = repo.StoreGlobalConfig
 	}
 
-	storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.Value, tokenKeyTarget)
-	err := storeFn(storeTargetKey, token.Target)
+	storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.Id(), tokenValueKey)
+	err := storeFn(storeValueKey, token.Value)
+	if err != nil {
+		return err
+	}
+
+	storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.Id(), tokenTargetKey)
+	err = storeFn(storeTargetKey, token.Target)
 	if err != nil {
 		return err
 	}
 
-	storeScopesKey := fmt.Sprintf("git-bug.token.%s.%s", token.Value, tokenKeyScopes)
+	storeScopesKey := fmt.Sprintf("git-bug.token.%s.%s", token.Id(), tokenScopesKey)
 	return storeFn(storeScopesKey, strings.Join(token.Scopes, ","))
 }
 
@@ -162,19 +232,14 @@ func StoreToken(repo repository.RepoConfig, token *Token) error {
 	return storeToken(repo, token)
 }
 
-// StoreGlobalToken stores a token in global config
-func StoreGlobalToken(repo repository.RepoConfig, token *Token) error {
-	return storeToken(repo, token)
-}
-
 // RemoveToken removes a token from the repo config
-func RemoveToken(repo repository.RepoConfig, value string) error {
-	keyPrefix := fmt.Sprintf("git-bug.token.%s", value)
+func RemoveToken(repo repository.RepoConfig, id string) error {
+	keyPrefix := fmt.Sprintf("git-bug.token.%s", id)
 	return repo.RmConfigs(keyPrefix)
 }
 
 // RemoveGlobalToken removes a token from the repo config
-func RemoveGlobalToken(repo repository.RepoConfig, value string) error {
-	keyPrefix := fmt.Sprintf("git-bug.token.%s", value)
+func RemoveGlobalToken(repo repository.RepoConfig, id string) error {
+	keyPrefix := fmt.Sprintf("git-bug.token.%s", id)
 	return repo.RmGlobalConfigs(keyPrefix)
 }

commands/bridge_token.go 🔗

@@ -14,8 +14,8 @@ import (
 )
 
 var (
-	bridgeTokenLocalOnly  bool
-	bridgeTokenGlobalOnly bool
+	bridgeTokenLocal  bool
+	bridgeTokenGlobal bool
 )
 
 func runTokenBridge(cmd *cobra.Command, args []string) error {
@@ -26,55 +26,46 @@ func runTokenBridge(cmd *cobra.Command, args []string) error {
 	defer backend.Close()
 	interrupt.RegisterCleaner(backend.Close)
 
-	var tokens []*core.Token
-	if !bridgeTokenGlobalOnly {
-		localTokens, err := core.ListTokens(backend)
-		if err != nil {
-			return err
-		}
+	tokens, err := core.ListTokens(backend)
+	if err != nil {
+		return err
+	}
 
-		for _, id := range localTokens {
-			token, err := core.GetToken(repo, id)
-			if err != nil {
-				return err
-			}
-			tokens = append(tokens, token)
+	for token, global := range tokens {
+		// TODO: filter tokens using flags
+		getTokenFn := core.GetToken
+		if global {
+			getTokenFn = core.GetGlobalToken
 		}
-	}
 
-	if !bridgeTokenLocalOnly {
-		globalTokens, err := core.ListGlobalTokens(backend)
+		token, err := getTokenFn(repo, token)
 		if err != nil {
 			return err
 		}
-
-		for _, id := range globalTokens {
-			token, err := core.GetGlobalToken(repo, id)
-			if err != nil {
-				return err
-			}
-			tokens = append(tokens, token)
-		}
+		printToken(token)
 	}
 
-	for _, token := range tokens {
-		valueFmt := text.LeftPadMaxLine(token.Value, 20, 0)
-		targetFmt := text.LeftPadMaxLine(token.Target, 8, 0)
-		scopesFmt := text.LeftPadMaxLine(strings.Join(token.Scopes, ","), 20, 0)
-
-		fmt.Printf("%s %s %s %s\n",
-			valueFmt,
-			colors.Magenta(targetFmt),
-			colors.Yellow(token.Global),
-			scopesFmt,
-		)
-	}
 	return nil
 }
 
+func printToken(token *core.Token) {
+	idFmt := text.LeftPadMaxLine(token.HumanId(), 6, 0)
+	valueFmt := text.LeftPadMaxLine(token.Value, 8, 0)
+	targetFmt := text.LeftPadMaxLine(token.Target, 8, 0)
+	scopesFmt := text.LeftPadMaxLine(strings.Join(token.Scopes, ","), 20, 0)
+
+	fmt.Printf("%s %s %s %s %s\n",
+		idFmt,
+		valueFmt,
+		colors.Magenta(targetFmt),
+		colors.Yellow(token.Kind()),
+		scopesFmt,
+	)
+}
+
 var bridgeTokenCmd = &cobra.Command{
 	Use:     "token",
-	Short:   "Configure and use bridge tokens.",
+	Short:   "List all stored tokens.",
 	PreRunE: loadRepo,
 	RunE:    runTokenBridge,
 	Args:    cobra.NoArgs,
@@ -82,7 +73,7 @@ var bridgeTokenCmd = &cobra.Command{
 
 func init() {
 	bridgeCmd.AddCommand(bridgeTokenCmd)
-	bridgeTokenCmd.Flags().BoolVarP(&bridgeTokenLocalOnly, "local", "l", false, "")
-	bridgeTokenCmd.Flags().BoolVarP(&bridgeTokenGlobalOnly, "global", "g", false, "")
+	bridgeTokenCmd.Flags().BoolVarP(&bridgeTokenLocal, "local", "l", false, "")
+	bridgeTokenCmd.Flags().BoolVarP(&bridgeTokenGlobal, "global", "g", false, "")
 	bridgeTokenCmd.Flags().SortFlags = false
 }

commands/bridge_token_add.go 🔗

@@ -12,20 +12,18 @@ var (
 )
 
 func runBridgeTokenAdd(cmd *cobra.Command, args []string) error {
+	_ = bridgeToken.Id() // TODO: a better design to avoid doing this
+
 	if err := bridgeToken.Validate(); err != nil {
 		return errors.Wrap(err, "invalid token")
 	}
 
-	if bridgeToken.Global {
-		return core.StoreToken(repo, &bridgeToken)
-	}
-
-	return core.StoreGlobalToken(repo, &bridgeToken)
+	return core.StoreToken(repo, &bridgeToken)
 }
 
 var bridgeTokenAddCmd = &cobra.Command{
 	Use:     "add",
-	Short:   "Configure and use bridge tokens.",
+	Short:   "Create and store a new token",
 	PreRunE: loadRepo,
 	RunE:    runBridgeTokenAdd,
 	Args:    cobra.NoArgs,

commands/bridge_token_rm.go 🔗

@@ -18,7 +18,7 @@ func runBridgeTokenRm(cmd *cobra.Command, args []string) error {
 
 var bridgeTokenRmCmd = &cobra.Command{
 	Use:     "rm",
-	Short:   "Configure and use bridge tokens.",
+	Short:   "Remove token by Id.",
 	PreRunE: loadRepo,
 	RunE:    runBridgeTokenRm,
 	Args:    cobra.ExactArgs(1),

doc/man/git-bug-bridge-token-add.1 🔗

@@ -0,0 +1,41 @@
+.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" "" 
+.nh
+.ad l
+
+
+.SH NAME
+.PP
+git\-bug\-bridge\-token\-add \- Create and store a new token
+
+
+.SH SYNOPSIS
+.PP
+\fBgit\-bug bridge token add [flags]\fP
+
+
+.SH DESCRIPTION
+.PP
+Create and store a new token
+
+
+.SH OPTIONS
+.PP
+\fB\-g\fP, \fB\-\-global\fP[=false]
+
+.PP
+\fB\-v\fP, \fB\-\-value\fP=""
+
+.PP
+\fB\-t\fP, \fB\-\-target\fP=""
+
+.PP
+\fB\-s\fP, \fB\-\-scopes\fP=[]
+
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for add
+
+
+.SH SEE ALSO
+.PP
+\fBgit\-bug\-bridge\-token(1)\fP

doc/man/git-bug-bridge-token-rm.1 🔗

@@ -0,0 +1,29 @@
+.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" "" 
+.nh
+.ad l
+
+
+.SH NAME
+.PP
+git\-bug\-bridge\-token\-rm \- Remove token by Id.
+
+
+.SH SYNOPSIS
+.PP
+\fBgit\-bug bridge token rm [flags]\fP
+
+
+.SH DESCRIPTION
+.PP
+Remove token by Id.
+
+
+.SH OPTIONS
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for rm
+
+
+.SH SEE ALSO
+.PP
+\fBgit\-bug\-bridge\-token(1)\fP

doc/man/git-bug-bridge-token.1 🔗

@@ -0,0 +1,35 @@
+.TH "GIT-BUG" "1" "Apr 2019" "Generated from git-bug's source code" "" 
+.nh
+.ad l
+
+
+.SH NAME
+.PP
+git\-bug\-bridge\-token \- List all stored tokens.
+
+
+.SH SYNOPSIS
+.PP
+\fBgit\-bug bridge token [flags]\fP
+
+
+.SH DESCRIPTION
+.PP
+List all stored tokens.
+
+
+.SH OPTIONS
+.PP
+\fB\-l\fP, \fB\-\-local\fP[=false]
+
+.PP
+\fB\-g\fP, \fB\-\-global\fP[=false]
+
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for token
+
+
+.SH SEE ALSO
+.PP
+\fBgit\-bug\-bridge(1)\fP, \fBgit\-bug\-bridge\-token\-add(1)\fP, \fBgit\-bug\-bridge\-token\-rm(1)\fP

doc/md/git-bug_bridge.md 🔗

@@ -23,5 +23,5 @@ git-bug bridge [flags]
 * [git-bug bridge pull](git-bug_bridge_pull.md)	 - Pull updates.
 * [git-bug bridge push](git-bug_bridge_push.md)	 - Push updates.
 * [git-bug bridge rm](git-bug_bridge_rm.md)	 - Delete a configured bridge.
-* [git-bug bridge token](git-bug_bridge_token.md)	 - Configure and use bridge tokens.
+* [git-bug bridge token](git-bug_bridge_token.md)	 - List all stored tokens.
 

doc/md/git-bug_bridge_token.md 🔗

@@ -0,0 +1,26 @@
+## git-bug bridge token
+
+List all stored tokens.
+
+### Synopsis
+
+List all stored tokens.
+
+```
+git-bug bridge token [flags]
+```
+
+### Options
+
+```
+  -l, --local    
+  -g, --global   
+  -h, --help     help for token
+```
+
+### SEE ALSO
+
+* [git-bug bridge](git-bug_bridge.md)	 - Configure and use bridges to other bug trackers.
+* [git-bug bridge token add](git-bug_bridge_token_add.md)	 - Create and store a new token
+* [git-bug bridge token rm](git-bug_bridge_token_rm.md)	 - Remove token by Id.
+

doc/md/git-bug_bridge_token_add.md 🔗

@@ -0,0 +1,26 @@
+## git-bug bridge token add
+
+Create and store a new token
+
+### Synopsis
+
+Create and store a new token
+
+```
+git-bug bridge token add [flags]
+```
+
+### Options
+
+```
+  -g, --global               
+  -v, --value string         
+  -t, --target string        
+  -s, --scopes stringArray   
+  -h, --help                 help for add
+```
+
+### SEE ALSO
+
+* [git-bug bridge token](git-bug_bridge_token.md)	 - List all stored tokens.
+

doc/md/git-bug_bridge_token_rm.md 🔗

@@ -0,0 +1,22 @@
+## git-bug bridge token rm
+
+Remove token by Id.
+
+### Synopsis
+
+Remove token by Id.
+
+```
+git-bug bridge token rm [flags]
+```
+
+### Options
+
+```
+  -h, --help   help for rm
+```
+
+### SEE ALSO
+
+* [git-bug bridge token](git-bug_bridge_token.md)	 - List all stored tokens.
+

misc/powershell_completion/git-bug 🔗

@@ -22,7 +22,6 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
             [CompletionResult]::new('commands', 'commands', [CompletionResultType]::ParameterValue, 'Display available commands.')
             [CompletionResult]::new('comment', 'comment', [CompletionResultType]::ParameterValue, 'Display or add comments to a bug.')
             [CompletionResult]::new('deselect', 'deselect', [CompletionResultType]::ParameterValue, 'Clear the implicitly selected bug.')
-            [CompletionResult]::new('export', 'export', [CompletionResultType]::ParameterValue, '')
             [CompletionResult]::new('label', 'label', [CompletionResultType]::ParameterValue, 'Display, add or remove labels to/from a bug.')
             [CompletionResult]::new('ls', 'ls', [CompletionResultType]::ParameterValue, 'List bugs.')
             [CompletionResult]::new('ls-id', 'ls-id', [CompletionResultType]::ParameterValue, 'List bug identifiers.')
@@ -53,7 +52,7 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
             [CompletionResult]::new('pull', 'pull', [CompletionResultType]::ParameterValue, 'Pull updates.')
             [CompletionResult]::new('push', 'push', [CompletionResultType]::ParameterValue, 'Push updates.')
             [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Delete a configured bridge.')
-            [CompletionResult]::new('token', 'token', [CompletionResultType]::ParameterValue, 'Configure and use bridge tokens.')
+            [CompletionResult]::new('token', 'token', [CompletionResultType]::ParameterValue, 'List all stored tokens.')
             break
         }
         'git-bug;bridge;configure' {
@@ -90,8 +89,8 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
             [CompletionResult]::new('--local', 'local', [CompletionResultType]::ParameterName, '')
             [CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, '')
             [CompletionResult]::new('--global', 'global', [CompletionResultType]::ParameterName, '')
-            [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Configure and use bridge tokens.')
-            [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Configure and use bridge tokens.')
+            [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Create and store a new token')
+            [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Remove token by Id.')
             break
         }
         'git-bug;bridge;token;add' {
@@ -127,9 +126,6 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
         'git-bug;deselect' {
             break
         }
-        'git-bug;export' {
-            break
-        }
         'git-bug;label' {
             [CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a label to a bug.')
             [CompletionResult]::new('rm', 'rm', [CompletionResultType]::ParameterValue, 'Remove a label from a bug.')

misc/zsh_completion/git-bug 🔗

@@ -118,7 +118,7 @@ function _git-bug_bridge {
       "pull:Pull updates."
       "push:Push updates."
       "rm:Delete a configured bridge."
-      "token:Configure and use bridge tokens."
+      "token:List all stored tokens."
     )
     _describe "command" commands
     ;;
@@ -181,8 +181,8 @@ function _git-bug_bridge_token {
   case $state in
   cmnds)
     commands=(
-      "add:Configure and use bridge tokens."
-      "rm:Configure and use bridge tokens."
+      "add:Create and store a new token"
+      "rm:Remove token by Id."
     )
     _describe "command" commands
     ;;