version.go

  1package identity
  2
  3import (
  4	"crypto/rand"
  5	"encoding/json"
  6	"fmt"
  7	"strings"
  8
  9	"github.com/MichaelMure/git-bug/repository"
 10	"github.com/MichaelMure/git-bug/util/git"
 11	"github.com/MichaelMure/git-bug/util/lamport"
 12	"github.com/MichaelMure/git-bug/util/text"
 13)
 14
 15// Version is a complete set of information about an Identity at a point in time.
 16type Version struct {
 17	// Private field so not serialized
 18	commitHash git.Hash
 19
 20	// The lamport time at which this version become effective
 21	// The reference time is the bug edition lamport clock
 22	Time lamport.Time `json:"time"`
 23
 24	Name      string `json:"name"`
 25	Email     string `json:"email"`
 26	Login     string `json:"login"`
 27	AvatarUrl string `json:"avatar_url"`
 28
 29	// The set of keys valid at that time, from this version onward, until they get removed
 30	// in a new version. This allow to have multiple key for the same identity (e.g. one per
 31	// device) as well as revoke key.
 32	Keys []Key `json:"pub_keys"`
 33
 34	// This optional array is here to ensure a better randomness of the identity id to avoid collisions.
 35	// It has no functional purpose and should be ignored.
 36	// It is advised to fill this array if there is not enough entropy, e.g. if there is no keys.
 37	Nonce []byte `json:"nonce,omitempty"`
 38
 39	// A set of arbitrary key/value to store metadata about a version or about an Identity in general.
 40	Metadata map[string]string `json:"metadata,omitempty"`
 41}
 42
 43func (v *Version) Validate() error {
 44	if text.Empty(v.Name) && text.Empty(v.Login) {
 45		return fmt.Errorf("either name or login should be set")
 46	}
 47
 48	if strings.Contains(v.Name, "\n") {
 49		return fmt.Errorf("name should be a single line")
 50	}
 51
 52	if !text.Safe(v.Name) {
 53		return fmt.Errorf("name is not fully printable")
 54	}
 55
 56	if strings.Contains(v.Login, "\n") {
 57		return fmt.Errorf("login should be a single line")
 58	}
 59
 60	if !text.Safe(v.Login) {
 61		return fmt.Errorf("login is not fully printable")
 62	}
 63
 64	if strings.Contains(v.Email, "\n") {
 65		return fmt.Errorf("email should be a single line")
 66	}
 67
 68	if !text.Safe(v.Email) {
 69		return fmt.Errorf("email is not fully printable")
 70	}
 71
 72	if v.AvatarUrl != "" && !text.ValidUrl(v.AvatarUrl) {
 73		return fmt.Errorf("avatarUrl is not a valid URL")
 74	}
 75
 76	if len(v.Nonce) > 64 {
 77		return fmt.Errorf("nonce is too big")
 78	}
 79
 80	return nil
 81}
 82
 83// Write will serialize and store the Version as a git blob and return
 84// its hash
 85func (v *Version) Write(repo repository.Repo) (git.Hash, error) {
 86	data, err := json.Marshal(v)
 87
 88	if err != nil {
 89		return "", err
 90	}
 91
 92	hash, err := repo.StoreData(data)
 93
 94	if err != nil {
 95		return "", err
 96	}
 97
 98	return hash, nil
 99}
100
101func makeNonce(len int) []byte {
102	result := make([]byte, len)
103	_, err := rand.Read(result)
104	if err != nil {
105		panic(err)
106	}
107	return result
108}
109
110// SetMetadata store arbitrary metadata about a version or an Identity in general
111// If the Version has been commit to git already, it won't be overwritten.
112func (v *Version) SetMetadata(key string, value string) {
113	if v.Metadata == nil {
114		v.Metadata = make(map[string]string)
115	}
116
117	v.Metadata[key] = value
118}
119
120// GetMetadata retrieve arbitrary metadata about the Version
121func (v *Version) GetMetadata(key string) (string, bool) {
122	val, ok := v.Metadata[key]
123	return val, ok
124}
125
126// AllMetadata return all metadata for this Identity
127func (v *Version) AllMetadata() map[string]string {
128	return v.Metadata
129}