From be85b62050a8bf0f90719ad3347ec9316a13966b Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 11 Nov 2014 15:44:10 +0100 Subject: [PATCH 1/5] Added keys model, keys table and KeysList handler --- db/table_keys.go | 29 +++++++++++++++++ db/{table_sessions.go => table_tokens.go} | 4 +-- env/env.go | 2 ++ main.go | 8 +++++ models/key.go | 16 ++++++++++ routes/keys.go | 39 +++++++++++++++++++++++ 6 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 db/table_keys.go rename db/{table_sessions.go => table_tokens.go} (70%) create mode 100644 models/key.go diff --git a/db/table_keys.go b/db/table_keys.go new file mode 100644 index 0000000..34dfa7e --- /dev/null +++ b/db/table_keys.go @@ -0,0 +1,29 @@ +package db + +import ( + "github.com/lavab/api/models" +) + +type KeysTable struct { + RethinkCRUD +} + +func (k *KeysTable) FindByName(name string) ([]*models.Key, error) { + var results []*models.Key + + if err := k.FindByAndFetch("name", name, &results); err != nil { + return nil, err + } + + return results, nil +} + +func (k *KeysTable) FindByFingerprint(fp string) (*models.Key, error) { + var result models.Key + + if err := k.FindFetchOne(fp, &result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/db/table_sessions.go b/db/table_tokens.go similarity index 70% rename from db/table_sessions.go rename to db/table_tokens.go index a19ccb0..ddb9cd5 100644 --- a/db/table_sessions.go +++ b/db/table_tokens.go @@ -10,10 +10,10 @@ type TokensTable struct { } // GetToken returns a token with specified name -func (s *TokensTable) GetToken(id string) (*models.Token, error) { +func (t *TokensTable) GetToken(id string) (*models.Token, error) { var result models.Token - if err := s.FindFetchOne(id, &result); err != nil { + if err := t.FindFetchOne(id, &result); err != nil { return nil, err } diff --git a/env/env.go b/env/env.go index 53057ff..bd05854 100644 --- a/env/env.go +++ b/env/env.go @@ -18,4 +18,6 @@ var ( Accounts *db.AccountsTable // Tokens is the global instance of TokensTable Tokens *db.TokensTable + // Keys is the global instance of KeysTable + Keys *db.KeysTable ) diff --git a/main.go b/main.go index 0b5a414..10733c5 100644 --- a/main.go +++ b/main.go @@ -115,6 +115,13 @@ func main() { "tokens", ), } + env.Keys = &db.KeysTable{ + RethinkCRUD: db.NewCRUDTable( + rethinkSession, + rethinkOpts.Database, + "keys", + ), + } // Create a new goji mux mux := web.New() @@ -177,6 +184,7 @@ func main() { auth.Delete("/contacts/:id", routes.ContactsDelete) // Keys + mux.Get("/keys", routes.KeysList) auth.Post("/keys", routes.KeysCreate) mux.Get("/keys/:id", routes.KeysGet) auth.Post("/keys/:id/vote", routes.KeysVote) diff --git a/models/key.go b/models/key.go new file mode 100644 index 0000000..0d4b18a --- /dev/null +++ b/models/key.go @@ -0,0 +1,16 @@ +package models + +type Key struct { + Resource + Expiring + + // ID is the fingerprint + + Image []byte `json:"image" gorethink:"image"` + + // the actual key + Key string `json:"key" gorethink:"key"` + + // the actual id + ShortID string `json:"short_id" gorethink:"short_id"` +} diff --git a/routes/keys.go b/routes/keys.go index e22732a..e66703d 100644 --- a/routes/keys.go +++ b/routes/keys.go @@ -3,9 +3,48 @@ package routes import ( "net/http" + "github.com/lavab/api/env" "github.com/lavab/api/utils" ) +// KeysListResponse contains the result of the KeysList request +type KeysListResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Keys *[]string `json:"keys,omitempty"` +} + +// KeysList responds with the list of keys assigned to the spiecified email +func KeysList(w http.ResponseWriter, r *http.Request) { + user := r.URL.Query().Get("user") + if user == "" { + utils.JSONResponse(w, 409, &KeysListResponse{ + Success: false, + Message: "Invalid username", + }) + return + } + + keys, err := env.Keys.FindByName(user) + if err != nil { + utils.JSONResponse(w, 500, &KeysListResponse{ + Success: false, + Message: "Internal server error (KE/LI/01)", + }) + return + } + + keyIDs := []string{} + for _, key := range keys { + keyIDs = append(keyIDs, key.ID) + } + + utils.JSONResponse(w, 200, &KeysListResponse{ + Success: true, + Keys: &keyIDs, + }) +} + // KeysCreateResponse contains the result of the KeysCreate request. type KeysCreateResponse struct { Success bool `json:"success"` From b22c5fab89e46320308642e1fbceef5c91c51279 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 12 Nov 2014 17:56:58 +0100 Subject: [PATCH 2/5] Added GET /keys/:id --- db/table_keys.go | 58 ++++++------- routes/keys.go | 206 +++++++++++++++++++++++++++-------------------- 2 files changed, 147 insertions(+), 117 deletions(-) diff --git a/db/table_keys.go b/db/table_keys.go index 34dfa7e..37826bb 100644 --- a/db/table_keys.go +++ b/db/table_keys.go @@ -1,29 +1,29 @@ -package db - -import ( - "github.com/lavab/api/models" -) - -type KeysTable struct { - RethinkCRUD -} - -func (k *KeysTable) FindByName(name string) ([]*models.Key, error) { - var results []*models.Key - - if err := k.FindByAndFetch("name", name, &results); err != nil { - return nil, err - } - - return results, nil -} - -func (k *KeysTable) FindByFingerprint(fp string) (*models.Key, error) { - var result models.Key - - if err := k.FindFetchOne(fp, &result); err != nil { - return nil, err - } - - return &result, nil -} +package db + +import ( + "github.com/lavab/api/models" +) + +type KeysTable struct { + RethinkCRUD +} + +func (k *KeysTable) FindByName(name string) ([]*models.Key, error) { + var results []*models.Key + + if err := k.FindByAndFetch("name", name, &results); err != nil { + return nil, err + } + + return results, nil +} + +func (k *KeysTable) FindByFingerprint(fp string) (*models.Key, error) { + var result models.Key + + if err := k.FindFetchOne(fp, &result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/routes/keys.go b/routes/keys.go index e66703d..efb5c17 100644 --- a/routes/keys.go +++ b/routes/keys.go @@ -1,88 +1,118 @@ -package routes - -import ( - "net/http" - - "github.com/lavab/api/env" - "github.com/lavab/api/utils" -) - -// KeysListResponse contains the result of the KeysList request -type KeysListResponse struct { - Success bool `json:"success"` - Message string `json:"message,omitempty"` - Keys *[]string `json:"keys,omitempty"` -} - -// KeysList responds with the list of keys assigned to the spiecified email -func KeysList(w http.ResponseWriter, r *http.Request) { - user := r.URL.Query().Get("user") - if user == "" { - utils.JSONResponse(w, 409, &KeysListResponse{ - Success: false, - Message: "Invalid username", - }) - return - } - - keys, err := env.Keys.FindByName(user) - if err != nil { - utils.JSONResponse(w, 500, &KeysListResponse{ - Success: false, - Message: "Internal server error (KE/LI/01)", - }) - return - } - - keyIDs := []string{} - for _, key := range keys { - keyIDs = append(keyIDs, key.ID) - } - - utils.JSONResponse(w, 200, &KeysListResponse{ - Success: true, - Keys: &keyIDs, - }) -} - -// KeysCreateResponse contains the result of the KeysCreate request. -type KeysCreateResponse struct { - Success bool `json:"success"` - Message string `json:"message"` -} - -// KeysCreate does *something* - TODO -func KeysCreate(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(w, 501, &KeysCreateResponse{ - Success: false, - Message: "Sorry, not implemented yet", - }) -} - -// KeysGetResponse contains the result of the KeysGet request. -type KeysGetResponse struct { - Success bool `json:"success"` - Message string `json:"message"` -} - -// KeysGet does *something* - TODO -func KeysGet(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(w, 501, &KeysGetResponse{ - Success: false, - Message: "Sorry, not implemented yet", - }) -} - -// KeysVoteResponse contains the result of the KeysVote request. -type KeysVoteResponse struct { - Success bool `json:"success"` - Message string `json:"message"` -} - -// KeysVote does *something* - TODO -func KeysVote(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(w, 501, &KeysVoteResponse{ - Success: false, - Message: "Sorry, not implemented yet", - }) -} +package routes + +import ( + "net/http" + + "github.com/Sirupsen/logrus" + "github.com/zenazn/goji/web" + + "github.com/lavab/api/env" + "github.com/lavab/api/models" + "github.com/lavab/api/utils" +) + +// KeysListResponse contains the result of the KeysList request +type KeysListResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Keys *[]string `json:"keys,omitempty"` +} + +// KeysList responds with the list of keys assigned to the spiecified email +func KeysList(w http.ResponseWriter, r *http.Request) { + user := r.URL.Query().Get("user") + if user == "" { + utils.JSONResponse(w, 409, &KeysListResponse{ + Success: false, + Message: "Invalid username", + }) + return + } + + keys, err := env.Keys.FindByName(user) + if err != nil { + utils.JSONResponse(w, 500, &KeysListResponse{ + Success: false, + Message: "Internal server error (KE/LI/01)", + }) + return + } + + keyIDs := []string{} + for _, key := range keys { + keyIDs = append(keyIDs, key.ID) + } + + utils.JSONResponse(w, 200, &KeysListResponse{ + Success: true, + Keys: &keyIDs, + }) +} + +// KeysCreateResponse contains the result of the KeysCreate request. +type KeysCreateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// KeysCreate does *something* - TODO +func KeysCreate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &KeysCreateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} + +// KeysGetResponse contains the result of the KeysGet request. +type KeysGetResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Key *models.Key `json:"key,omitempty"` +} + +// KeysGet does *something* - TODO +func KeysGet(c web.C, w http.ResponseWriter, r *http.Request) { + // Get ID from the passed URL params + id, ok := c.URLParams["id"] + if !ok { + utils.JSONResponse(w, 404, &KeysGetResponse{ + Success: false, + Message: "Requested key does not exist on our server", + }) + return + } + + // Fetch the requested key from the database + key, err := env.Keys.FindByFingerprint(id) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Warn("Unable to fetch the requested key from the database") + + utils.JSONResponse(w, 404, &KeysGetResponse{ + Success: false, + Message: "Requested key does not exist on our server", + }) + return + } + + // Return the requested key + utils.JSONResponse(w, 200, &KeysGetResponse{ + Success: true, + Key: key, + }) +} + +// KeysVoteResponse contains the result of the KeysVote request. +type KeysVoteResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// KeysVote does *something* - TODO +func KeysVote(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &KeysVoteResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} From e2359f561780e73a9914d0c99c46969e495670b3 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 12 Nov 2014 18:55:42 +0100 Subject: [PATCH 3/5] Added POST /keys, doesn't seem to work --- db/table_keys.go | 2 +- models/key.go | 5 ++- routes/keys.go | 105 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/db/table_keys.go b/db/table_keys.go index 37826bb..d7418fe 100644 --- a/db/table_keys.go +++ b/db/table_keys.go @@ -11,7 +11,7 @@ type KeysTable struct { func (k *KeysTable) FindByName(name string) ([]*models.Key, error) { var results []*models.Key - if err := k.FindByAndFetch("name", name, &results); err != nil { + if err := k.FindByAndFetch("owner_name", name, &results); err != nil { return nil, err } diff --git a/models/key.go b/models/key.go index 0d4b18a..f0c6d86 100644 --- a/models/key.go +++ b/models/key.go @@ -11,6 +11,9 @@ type Key struct { // the actual key Key string `json:"key" gorethink:"key"` + OwnerName string `json:"owner_name" gorethink:"owner_name"` + // the actual id - ShortID string `json:"short_id" gorethink:"short_id"` + KeyID string `json:"key_id" gorethink:"key_id"` + KeyIDShort string `json:"key_id_short" gorethink:"key_id_short"` } diff --git a/routes/keys.go b/routes/keys.go index efb5c17..45cfbe1 100644 --- a/routes/keys.go +++ b/routes/keys.go @@ -1,10 +1,14 @@ package routes import ( + "fmt" "net/http" + "strings" "github.com/Sirupsen/logrus" "github.com/zenazn/goji/web" + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" "github.com/lavab/api/env" "github.com/lavab/api/models" @@ -49,17 +53,104 @@ func KeysList(w http.ResponseWriter, r *http.Request) { }) } +// KeysCreateRequest contains the data passed to the KeysCreate endpoint. +type KeysCreateRequest struct { + Key string `json:"key" schema:"key"` // gpg armored key + Image string `json:"image" schema:"image"` // todo +} + // KeysCreateResponse contains the result of the KeysCreate request. type KeysCreateResponse struct { - Success bool `json:"success"` - Message string `json:"message"` + Success bool `json:"success"` + Message string `json:"message"` + Key *models.Key `json:"key,omitempty"` } -// KeysCreate does *something* - TODO -func KeysCreate(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(w, 501, &KeysCreateResponse{ - Success: false, - Message: "Sorry, not implemented yet", +// KeysCreate appens a new key to the server +func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { + // Decode the request + var input KeysCreateRequest + err := utils.ParseRequest(r, &input) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Warn("Unable to decode a request") + + utils.JSONResponse(w, 409, &KeysCreateResponse{ + Success: false, + Message: "Invalid input format", + }) + return + } + + // Get the session + session := c.Env["session"].(*models.Token) + + // Parse the armored key + entity, err := openpgp.ReadEntity(packet.NewReader(strings.NewReader(input.Key))) + //block, err := armor.Decode(strings.NewReader(input.Key)) + if err != nil { + utils.JSONResponse(w, 409, &KeysCreateResponse{ + Success: false, + Message: "Invalid key format", + }) + + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Warn("Cannot parse an armored key") + return + } + + // Get the account from db + account, err := env.Accounts.GetAccount(session.Owner) + if err != nil { + utils.JSONResponse(w, 500, &KeysCreateResponse{ + Success: false, + Message: "Internal server error - KE/CR/01", + }) + + env.Log.WithFields(logrus.Fields{ + "error": err, + "id": session.Owner, + }).Error("Cannot fetch user from database") + return + } + + id := string(entity.PrimaryKey.Fingerprint[:]) + bitLength, _ := entity.PrimaryKey.BitLength() + key := &models.Key{ + Resource: models.MakeResource( + session.Owner, + fmt.Sprintf( + "%d/%s public key", + bitLength, + entity.PrimaryKey.KeyIdString(), + ), + ), + OwnerName: account.Name, + Key: input.Key, + KeyID: entity.PrimaryKey.KeyIdString(), + KeyIDShort: entity.PrimaryKey.KeyIdShortString(), + } + + key.ID = id + + if err := env.Keys.Insert(key); err != nil { + utils.JSONResponse(w, 500, &KeysCreateResponse{ + Success: false, + Message: "Internal server error - KE/CR/02", + }) + + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Could not insert a key to the database") + return + } + + utils.JSONResponse(w, 201, &KeysCreateResponse{ + Success: true, + Message: "A new key has been successfully inserted", + Key: key, }) } From e27d5a7d534f0321833d9f7ddd4da99c0a8cfd0f Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 12 Nov 2014 19:11:42 +0100 Subject: [PATCH 4/5] Fixed keys.go --- routes/keys.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/routes/keys.go b/routes/keys.go index 45cfbe1..2dcbaf5 100644 --- a/routes/keys.go +++ b/routes/keys.go @@ -1,6 +1,7 @@ package routes import ( + "encoding/hex" "fmt" "net/http" "strings" @@ -8,7 +9,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/zenazn/goji/web" "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/packet" "github.com/lavab/api/env" "github.com/lavab/api/models" @@ -87,9 +87,9 @@ func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { session := c.Env["session"].(*models.Token) // Parse the armored key - entity, err := openpgp.ReadEntity(packet.NewReader(strings.NewReader(input.Key))) + entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(input.Key)) //block, err := armor.Decode(strings.NewReader(input.Key)) - if err != nil { + if err != nil { //|| len(entityList.DecryptionKeys()) == 0 { utils.JSONResponse(w, 409, &KeysCreateResponse{ Success: false, Message: "Invalid key format", @@ -97,6 +97,7 @@ func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { env.Log.WithFields(logrus.Fields{ "error": err, + "list": entityList, }).Warn("Cannot parse an armored key") return } @@ -116,21 +117,23 @@ func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { return } - id := string(entity.PrimaryKey.Fingerprint[:]) - bitLength, _ := entity.PrimaryKey.BitLength() + publicKey := entityList[0] //entityList.DecryptionKeys()[0] + + id := hex.EncodeToString(publicKey.PrimaryKey.Fingerprint[:]) + bitLength, _ := publicKey.PrimaryKey.BitLength() key := &models.Key{ Resource: models.MakeResource( session.Owner, fmt.Sprintf( "%d/%s public key", bitLength, - entity.PrimaryKey.KeyIdString(), + publicKey.PrimaryKey.KeyIdString(), ), ), OwnerName: account.Name, Key: input.Key, - KeyID: entity.PrimaryKey.KeyIdString(), - KeyIDShort: entity.PrimaryKey.KeyIdShortString(), + KeyID: publicKey.PrimaryKey.KeyIdString(), + KeyIDShort: publicKey.PrimaryKey.KeyIdShortString(), } key.ID = id From 1ce035eca75fa6a5f02778d42564d8f8f0cd2f2d Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 12 Nov 2014 19:18:45 +0100 Subject: [PATCH 5/5] Cleaned keys.go --- routes/keys.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/routes/keys.go b/routes/keys.go index 2dcbaf5..e0308d4 100644 --- a/routes/keys.go +++ b/routes/keys.go @@ -24,6 +24,7 @@ type KeysListResponse struct { // KeysList responds with the list of keys assigned to the spiecified email func KeysList(w http.ResponseWriter, r *http.Request) { + // Get the username from the GET query user := r.URL.Query().Get("user") if user == "" { utils.JSONResponse(w, 409, &KeysListResponse{ @@ -33,6 +34,7 @@ func KeysList(w http.ResponseWriter, r *http.Request) { return } + // Find all keys owner by user keys, err := env.Keys.FindByName(user) if err != nil { utils.JSONResponse(w, 500, &KeysListResponse{ @@ -42,11 +44,13 @@ func KeysList(w http.ResponseWriter, r *http.Request) { return } + // Equivalent of _.keys(keys) in JavaScript with underscore.js keyIDs := []string{} for _, key := range keys { keyIDs = append(keyIDs, key.ID) } + // Respond with list of keys utils.JSONResponse(w, 200, &KeysListResponse{ Success: true, Keys: &keyIDs, @@ -88,8 +92,7 @@ func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { // Parse the armored key entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(input.Key)) - //block, err := armor.Decode(strings.NewReader(input.Key)) - if err != nil { //|| len(entityList.DecryptionKeys()) == 0 { + if err != nil { utils.JSONResponse(w, 409, &KeysCreateResponse{ Success: false, Message: "Invalid key format", @@ -117,10 +120,16 @@ func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { return } - publicKey := entityList[0] //entityList.DecryptionKeys()[0] + // Let's hope that the user is capable of sending proper armored keys + publicKey := entityList[0] + // Encode the fingerprint id := hex.EncodeToString(publicKey.PrimaryKey.Fingerprint[:]) + + // Get the key's bit length - should not return an error bitLength, _ := publicKey.PrimaryKey.BitLength() + + // Allocate a new key key := &models.Key{ Resource: models.MakeResource( session.Owner, @@ -136,8 +145,10 @@ func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { KeyIDShort: publicKey.PrimaryKey.KeyIdShortString(), } + // Update id as we can't do it directly during allocation key.ID = id + // Try to insert it into the database if err := env.Keys.Insert(key); err != nil { utils.JSONResponse(w, 500, &KeysCreateResponse{ Success: false, @@ -150,6 +161,7 @@ func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { return } + // Return the inserted key utils.JSONResponse(w, 201, &KeysCreateResponse{ Success: true, Message: "A new key has been successfully inserted",