From feab9412dffe5772048aad29893c4cb01d566387 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Wed, 21 Nov 2018 18:56:12 +0100 Subject: WIP identity in git --- identity/version.go | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 identity/version.go (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go new file mode 100644 index 00000000..f76ec4c5 --- /dev/null +++ b/identity/version.go @@ -0,0 +1,105 @@ +package identity + +import ( + "crypto/rand" + "encoding/json" + "fmt" + "strings" + + "github.com/MichaelMure/git-bug/repository" + "github.com/MichaelMure/git-bug/util/git" + + "github.com/MichaelMure/git-bug/util/lamport" + "github.com/MichaelMure/git-bug/util/text" +) + +type Version struct { + // Private field so not serialized + commitHash git.Hash + + // The lamport time at which this version become effective + // The reference time is the bug edition lamport clock + Time lamport.Time `json:"time"` + + Name string `json:"name"` + Email string `json:"email"` + Login string `json:"login"` + AvatarUrl string `json:"avatar_url"` + + // The set of keys valid at that time, from this version onward, until they get removed + // in a new version. This allow to have multiple key for the same identity (e.g. one per + // device) as well as revoke key. + Keys []Key `json:"pub_keys"` + + // This optional array is here to ensure a better randomness of the identity id to avoid collisions. + // It has no functional purpose and should be ignored. + // It is advised to fill this array if there is not enough entropy, e.g. if there is no keys. + Nonce []byte `json:"nonce,omitempty"` +} + +func (v *Version) Validate() error { + if text.Empty(v.Name) && text.Empty(v.Login) { + return fmt.Errorf("either name or login should be set") + } + + if strings.Contains(v.Name, "\n") { + return fmt.Errorf("name should be a single line") + } + + if !text.Safe(v.Name) { + return fmt.Errorf("name is not fully printable") + } + + if strings.Contains(v.Login, "\n") { + return fmt.Errorf("login should be a single line") + } + + if !text.Safe(v.Login) { + return fmt.Errorf("login is not fully printable") + } + + if strings.Contains(v.Email, "\n") { + return fmt.Errorf("email should be a single line") + } + + if !text.Safe(v.Email) { + return fmt.Errorf("email is not fully printable") + } + + if v.AvatarUrl != "" && !text.ValidUrl(v.AvatarUrl) { + return fmt.Errorf("avatarUrl is not a valid URL") + } + + if len(v.Nonce) > 64 { + return fmt.Errorf("nonce is too big") + } + + return nil +} + +// Write will serialize and store the Version as a git blob and return +// its hash +func (v *Version) Write(repo repository.Repo) (git.Hash, error) { + data, err := json.Marshal(v) + + if err != nil { + return "", err + } + + hash, err := repo.StoreData(data) + + if err != nil { + return "", err + } + + return hash, nil +} + +func makeNonce(len int) []byte { + result := make([]byte, len) + _, err := rand.Read(result) + if err != nil { + panic(err) + } + return result +} -- cgit From 06d9c6872655b85f1a47599add92d49d570e7b2e Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Wed, 16 Jan 2019 21:23:49 +0100 Subject: identity: implement the loading from git --- identity/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go index f76ec4c5..3e84ece3 100644 --- a/identity/version.go +++ b/identity/version.go @@ -8,11 +8,11 @@ import ( "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/git" - "github.com/MichaelMure/git-bug/util/lamport" "github.com/MichaelMure/git-bug/util/text" ) +// Version is a complete set of informations about an Identity at a point in time. type Version struct { // Private field so not serialized commitHash git.Hash -- cgit From 3df4f46c71650c9d23b267c44afec16f1b759e92 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Thu, 17 Jan 2019 02:05:50 +0100 Subject: identity: add metadata support --- identity/version.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go index 3e84ece3..bc4561d9 100644 --- a/identity/version.go +++ b/identity/version.go @@ -12,7 +12,7 @@ import ( "github.com/MichaelMure/git-bug/util/text" ) -// Version is a complete set of informations about an Identity at a point in time. +// Version is a complete set of information about an Identity at a point in time. type Version struct { // Private field so not serialized commitHash git.Hash @@ -35,6 +35,9 @@ type Version struct { // It has no functional purpose and should be ignored. // It is advised to fill this array if there is not enough entropy, e.g. if there is no keys. Nonce []byte `json:"nonce,omitempty"` + + // A set of arbitrary key/value to store metadata about a version or about an Identity in general. + Metadata map[string]string `json:"metadata,omitempty"` } func (v *Version) Validate() error { @@ -103,3 +106,24 @@ func makeNonce(len int) []byte { } return result } + +// SetMetadata store arbitrary metadata about a version or an Identity in general +// If the Version has been commit to git already, it won't be overwritten. +func (v *Version) SetMetadata(key string, value string) { + if v.Metadata == nil { + v.Metadata = make(map[string]string) + } + + v.Metadata[key] = value +} + +// GetMetadata retrieve arbitrary metadata about the Version +func (v *Version) GetMetadata(key string) (string, bool) { + val, ok := v.Metadata[key] + return val, ok +} + +// AllMetadata return all metadata for this Identity +func (v *Version) AllMetadata() map[string]string { + return v.Metadata +} -- cgit From 14b240af8fef269d2c1d5dde2fff192b656c50f3 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 20 Jan 2019 15:41:27 +0100 Subject: identity: more cleaning and fixes after a code review --- identity/version.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 9 deletions(-) (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go index bc4561d9..d4afc893 100644 --- a/identity/version.go +++ b/identity/version.go @@ -10,34 +10,88 @@ import ( "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/lamport" "github.com/MichaelMure/git-bug/util/text" + "github.com/pkg/errors" ) +const formatVersion = 1 + // Version is a complete set of information about an Identity at a point in time. type Version struct { - // Private field so not serialized + // Not serialized commitHash git.Hash // The lamport time at which this version become effective // The reference time is the bug edition lamport clock - Time lamport.Time `json:"time"` + Time lamport.Time - Name string `json:"name"` - Email string `json:"email"` - Login string `json:"login"` - AvatarUrl string `json:"avatar_url"` + Name string + Email string + Login string + AvatarUrl string // The set of keys valid at that time, from this version onward, until they get removed // in a new version. This allow to have multiple key for the same identity (e.g. one per // device) as well as revoke key. - Keys []Key `json:"pub_keys"` + Keys []Key // This optional array is here to ensure a better randomness of the identity id to avoid collisions. // It has no functional purpose and should be ignored. // It is advised to fill this array if there is not enough entropy, e.g. if there is no keys. - Nonce []byte `json:"nonce,omitempty"` + Nonce []byte // A set of arbitrary key/value to store metadata about a version or about an Identity in general. - Metadata map[string]string `json:"metadata,omitempty"` + Metadata map[string]string +} + +type VersionJSON struct { + // Additional field to version the data + FormatVersion uint `json:"version"` + + Time lamport.Time `json:"time"` + Name string `json:"name"` + Email string `json:"email"` + Login string `json:"login"` + AvatarUrl string `json:"avatar_url"` + Keys []Key `json:"pub_keys"` + Nonce []byte `json:"nonce,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +func (v *Version) MarshalJSON() ([]byte, error) { + return json.Marshal(VersionJSON{ + FormatVersion: formatVersion, + Time: v.Time, + Name: v.Name, + Email: v.Email, + Login: v.Login, + AvatarUrl: v.AvatarUrl, + Keys: v.Keys, + Nonce: v.Nonce, + Metadata: v.Metadata, + }) +} + +func (v *Version) UnmarshalJSON(data []byte) error { + var aux VersionJSON + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + if aux.FormatVersion != formatVersion { + return fmt.Errorf("unknown format version %v", aux.FormatVersion) + } + + v.Time = aux.Time + v.Name = aux.Name + v.Email = aux.Email + v.Login = aux.Login + v.AvatarUrl = aux.AvatarUrl + v.Keys = aux.Keys + v.Nonce = aux.Nonce + v.Metadata = aux.Metadata + + return nil } func (v *Version) Validate() error { @@ -77,12 +131,24 @@ func (v *Version) Validate() error { return fmt.Errorf("nonce is too big") } + for _, k := range v.Keys { + if err := k.Validate(); err != nil { + return errors.Wrap(err, "invalid key") + } + } + return nil } // Write will serialize and store the Version as a git blob and return // its hash func (v *Version) Write(repo repository.Repo) (git.Hash, error) { + // make sure we don't write invalid data + err := v.Validate() + if err != nil { + return "", errors.Wrap(err, "validation error") + } + data, err := json.Marshal(v) if err != nil { -- cgit From 328a4e5abf3ec8ea41f89575fcfb83cf9f086c80 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 3 Feb 2019 19:55:35 +0100 Subject: identity: wip push/pull --- identity/version.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go index d4afc893..f8b9cc73 100644 --- a/identity/version.go +++ b/identity/version.go @@ -20,6 +20,8 @@ type Version struct { // Not serialized commitHash git.Hash + // Todo: add unix timestamp for ordering with identical lamport time ? + // The lamport time at which this version become effective // The reference time is the bug edition lamport clock Time lamport.Time -- cgit From 21048e785d976a04e26798e4a385ee675c95b88f Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Wed, 6 Feb 2019 22:06:42 +0100 Subject: identity: wip --- identity/version.go | 78 ++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go index f8b9cc73..90bf83f2 100644 --- a/identity/version.go +++ b/identity/version.go @@ -24,25 +24,25 @@ type Version struct { // The lamport time at which this version become effective // The reference time is the bug edition lamport clock - Time lamport.Time + time lamport.Time - Name string - Email string - Login string - AvatarUrl string + name string + email string + login string + avatarURL string // The set of keys valid at that time, from this version onward, until they get removed // in a new version. This allow to have multiple key for the same identity (e.g. one per // device) as well as revoke key. - Keys []Key + keys []Key // This optional array is here to ensure a better randomness of the identity id to avoid collisions. // It has no functional purpose and should be ignored. // It is advised to fill this array if there is not enough entropy, e.g. if there is no keys. - Nonce []byte + nonce []byte // A set of arbitrary key/value to store metadata about a version or about an Identity in general. - Metadata map[string]string + metadata map[string]string } type VersionJSON struct { @@ -62,14 +62,14 @@ type VersionJSON struct { func (v *Version) MarshalJSON() ([]byte, error) { return json.Marshal(VersionJSON{ FormatVersion: formatVersion, - Time: v.Time, - Name: v.Name, - Email: v.Email, - Login: v.Login, - AvatarUrl: v.AvatarUrl, - Keys: v.Keys, - Nonce: v.Nonce, - Metadata: v.Metadata, + Time: v.time, + Name: v.name, + Email: v.email, + Login: v.login, + AvatarUrl: v.avatarURL, + Keys: v.keys, + Nonce: v.nonce, + Metadata: v.metadata, }) } @@ -84,56 +84,56 @@ func (v *Version) UnmarshalJSON(data []byte) error { return fmt.Errorf("unknown format version %v", aux.FormatVersion) } - v.Time = aux.Time - v.Name = aux.Name - v.Email = aux.Email - v.Login = aux.Login - v.AvatarUrl = aux.AvatarUrl - v.Keys = aux.Keys - v.Nonce = aux.Nonce - v.Metadata = aux.Metadata + v.time = aux.Time + v.name = aux.Name + v.email = aux.Email + v.login = aux.Login + v.avatarURL = aux.AvatarUrl + v.keys = aux.Keys + v.nonce = aux.Nonce + v.metadata = aux.Metadata return nil } func (v *Version) Validate() error { - if text.Empty(v.Name) && text.Empty(v.Login) { + if text.Empty(v.name) && text.Empty(v.login) { return fmt.Errorf("either name or login should be set") } - if strings.Contains(v.Name, "\n") { + if strings.Contains(v.name, "\n") { return fmt.Errorf("name should be a single line") } - if !text.Safe(v.Name) { + if !text.Safe(v.name) { return fmt.Errorf("name is not fully printable") } - if strings.Contains(v.Login, "\n") { + if strings.Contains(v.login, "\n") { return fmt.Errorf("login should be a single line") } - if !text.Safe(v.Login) { + if !text.Safe(v.login) { return fmt.Errorf("login is not fully printable") } - if strings.Contains(v.Email, "\n") { + if strings.Contains(v.email, "\n") { return fmt.Errorf("email should be a single line") } - if !text.Safe(v.Email) { + if !text.Safe(v.email) { return fmt.Errorf("email is not fully printable") } - if v.AvatarUrl != "" && !text.ValidUrl(v.AvatarUrl) { + if v.avatarURL != "" && !text.ValidUrl(v.avatarURL) { return fmt.Errorf("avatarUrl is not a valid URL") } - if len(v.Nonce) > 64 { + if len(v.nonce) > 64 { return fmt.Errorf("nonce is too big") } - for _, k := range v.Keys { + for _, k := range v.keys { if err := k.Validate(); err != nil { return errors.Wrap(err, "invalid key") } @@ -178,20 +178,20 @@ func makeNonce(len int) []byte { // SetMetadata store arbitrary metadata about a version or an Identity in general // If the Version has been commit to git already, it won't be overwritten. func (v *Version) SetMetadata(key string, value string) { - if v.Metadata == nil { - v.Metadata = make(map[string]string) + if v.metadata == nil { + v.metadata = make(map[string]string) } - v.Metadata[key] = value + v.metadata[key] = value } // GetMetadata retrieve arbitrary metadata about the Version func (v *Version) GetMetadata(key string) (string, bool) { - val, ok := v.Metadata[key] + val, ok := v.metadata[key] return val, ok } // AllMetadata return all metadata for this Identity func (v *Version) AllMetadata() map[string]string { - return v.Metadata + return v.metadata } -- cgit From 71f9290fdae7551f3d3ada2179ece4084304d734 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Tue, 19 Feb 2019 00:19:27 +0100 Subject: identity: store the times properly --- identity/version.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go index 90bf83f2..6ffffd99 100644 --- a/identity/version.go +++ b/identity/version.go @@ -17,14 +17,11 @@ const formatVersion = 1 // Version is a complete set of information about an Identity at a point in time. type Version struct { - // Not serialized - commitHash git.Hash - - // Todo: add unix timestamp for ordering with identical lamport time ? - // The lamport time at which this version become effective // The reference time is the bug edition lamport clock - time lamport.Time + // It must be the first field in this struct due to https://github.com/golang/go/issues/599 + time lamport.Time + unixTime int64 name string email string @@ -43,6 +40,9 @@ type Version struct { // A set of arbitrary key/value to store metadata about a version or about an Identity in general. metadata map[string]string + + // Not serialized + commitHash git.Hash } type VersionJSON struct { @@ -50,6 +50,7 @@ type VersionJSON struct { FormatVersion uint `json:"version"` Time lamport.Time `json:"time"` + UnixTime int64 `json:"unix_time"` Name string `json:"name"` Email string `json:"email"` Login string `json:"login"` @@ -63,6 +64,7 @@ func (v *Version) MarshalJSON() ([]byte, error) { return json.Marshal(VersionJSON{ FormatVersion: formatVersion, Time: v.time, + UnixTime: v.unixTime, Name: v.name, Email: v.email, Login: v.login, @@ -85,6 +87,7 @@ func (v *Version) UnmarshalJSON(data []byte) error { } v.time = aux.Time + v.unixTime = aux.UnixTime v.name = aux.Name v.email = aux.Email v.login = aux.Login @@ -97,6 +100,10 @@ func (v *Version) UnmarshalJSON(data []byte) error { } func (v *Version) Validate() error { + if v.unixTime == 0 { + return fmt.Errorf("unix time not set") + } + if text.Empty(v.name) && text.Empty(v.login) { return fmt.Errorf("either name or login should be set") } -- cgit From 719303226096c905e602cb620dfdfbcf8fe106ad Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Tue, 19 Feb 2019 01:44:21 +0100 Subject: identity: fix tests --- identity/version.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go index 6ffffd99..1259ae9c 100644 --- a/identity/version.go +++ b/identity/version.go @@ -100,9 +100,13 @@ func (v *Version) UnmarshalJSON(data []byte) error { } func (v *Version) Validate() error { - if v.unixTime == 0 { + // time must be set after a commit + if v.commitHash != "" && v.unixTime == 0 { return fmt.Errorf("unix time not set") } + if v.commitHash != "" && v.time == 0 { + return fmt.Errorf("lamport time not set") + } if text.Empty(v.name) && text.Empty(v.login) { return fmt.Errorf("either name or login should be set") -- cgit From 46beb4b886761ff69abe2ce45c2498a4fafe50d9 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 24 Feb 2019 18:49:12 +0100 Subject: identity: another round of cleanups --- identity/version.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'identity/version.go') diff --git a/identity/version.go b/identity/version.go index 1259ae9c..95530767 100644 --- a/identity/version.go +++ b/identity/version.go @@ -51,11 +51,11 @@ type VersionJSON struct { Time lamport.Time `json:"time"` UnixTime int64 `json:"unix_time"` - Name string `json:"name"` - Email string `json:"email"` - Login string `json:"login"` - AvatarUrl string `json:"avatar_url"` - Keys []Key `json:"pub_keys"` + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + Login string `json:"login,omitempty"` + AvatarUrl string `json:"avatar_url,omitempty"` + Keys []Key `json:"pub_keys,omitempty"` Nonce []byte `json:"nonce,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } -- cgit