From 7eb7cb8f61202698b133c32d274aecdd2d8a2c0b Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Thu, 20 Jan 2022 15:19:26 -0800 Subject: [PATCH] creating contact from identity message --- doc/api.oa3 | 6 + net/server/go.mod | 1 + net/server/go.sum | 2 + net/server/internal/api_addCard.go | 112 +++++++++++++++++++ net/server/internal/api_authorize.go | 8 +- net/server/internal/api_contact.go | 5 - net/server/internal/api_getProfile.go | 8 +- net/server/internal/api_getProfileMessage.go | 8 +- net/server/internal/api_setProfile.go | 11 +- net/server/internal/api_status.go | 6 +- net/server/internal/appValues.go | 6 + net/server/internal/authUtil.go | 21 +++- net/server/internal/contact_test.go | 51 +++++++++ net/server/internal/models.go | 2 + net/server/internal/store/schema.go | 11 +- net/server/internal/ucConnectContact_test.go | 81 +++----------- 16 files changed, 230 insertions(+), 109 deletions(-) create mode 100644 net/server/internal/api_addCard.go create mode 100644 net/server/internal/contact_test.go diff --git a/doc/api.oa3 b/doc/api.oa3 index bcea40dc..f28d6a49 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -4089,8 +4089,12 @@ components: CardProfile: type: object required: + - guid + - version - node properties: + guid: + type: string handle: type: string name: @@ -4104,6 +4108,8 @@ components: format: int64 imageSet: type: boolean + version: + type: string node: type: string diff --git a/net/server/go.mod b/net/server/go.mod index d84187a3..919998a7 100644 --- a/net/server/go.mod +++ b/net/server/go.mod @@ -4,6 +4,7 @@ go 1.17 require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect diff --git a/net/server/go.sum b/net/server/go.sum index 1d0abe57..f275ff33 100644 --- a/net/server/go.sum +++ b/net/server/go.sum @@ -2,6 +2,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= diff --git a/net/server/internal/api_addCard.go b/net/server/internal/api_addCard.go new file mode 100644 index 00000000..5d7ecced --- /dev/null +++ b/net/server/internal/api_addCard.go @@ -0,0 +1,112 @@ +package databag + +import ( + "errors" + "net/http" + "gorm.io/gorm" + "github.com/google/uuid" + "databag/internal/store" +) + +func AddCard(w http.ResponseWriter, r *http.Request) { + + account, code, err := BearerAppToken(r, false) + if err != nil { + ErrResponse(w, code, err) + return + } + + var message DataMessage + if err := ParseRequest(r, w, &message); err != nil { + ErrResponse(w, http.StatusBadRequest, err) + return + } + + var identity Identity + guid, messageType, _, err := ReadDataMessage(&message, &identity) + if messageType != APP_MSGIDENTITY || err != nil { + ErrResponse(w, http.StatusBadRequest, err) + return + } + + var card store.Card + if err := store.DB.Preload("Groups").Where("account_id = ? AND guid = ?", account.ID, guid).First(&card).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + ErrResponse(w, http.StatusInternalServerError, err) + return + } + } else { + + // update record if revision changed + if identity.Revision > card.ProfileRevision { + card.Username = identity.Handle + card.Name = identity.Name + card.Description = identity.Description + card.Location = identity.Location + card.Image = identity.Image + card.Version = identity.Version + card.Node = identity.Node + card.ProfileRevision = identity.Revision + if err := store.DB.Save(&card).Error; err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return + } + } + WriteResponse(w, getCardModel(&card)) + return + } + + // save new record + card.CardId = uuid.New().String() + card.AccountID = account.ID + card.Guid = guid + card.Username = identity.Handle + card.Name = identity.Name + card.Description = identity.Description + card.Location = identity.Location + card.Image = identity.Image + card.Version = identity.Version + card.Node = identity.Node + card.ProfileRevision = identity.Revision + card.Status = APP_CARDCONFIRMED + if err := store.DB.Save(&card).Error; err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return + } + + WriteResponse(w, getCardModel(&card)) +} + +func getCardModel(card *store.Card) *Card { + + // populate group id list + var groups []string; + for _, group := range card.Groups { + groups = append(groups, group.GroupId) + } + + return &Card{ + CardId: card.CardId, + ProfileRevision: card.RemoteProfile, + ContentRevision: card.RemoteContent, + CardProfile: &CardProfile{ + Guid: card.Guid, + Handle: card.Username, + Name: card.Name, + Description: card.Description, + Location: card.Location, + Revision: card.ProfileRevision, + ImageSet: card.Image != "", + Version: card.Version, + Node: card.Node, + }, + CardData: &CardData { + Revision: card.DataRevision, + Status: card.Status, + Notes: card.Notes, + Groups: groups, + }, + } +} + + diff --git a/net/server/internal/api_authorize.go b/net/server/internal/api_authorize.go index c68012f5..8aca42b7 100644 --- a/net/server/internal/api_authorize.go +++ b/net/server/internal/api_authorize.go @@ -6,13 +6,9 @@ import ( func Authorize(w http.ResponseWriter, r *http.Request) { - account, res := BearerAppToken(r, true); + account, code, res := BearerAppToken(r, true); if res != nil { - ErrResponse(w, http.StatusUnauthorized, res) - return - } - if account.Disabled { - ErrResponse(w, http.StatusGone, res) + ErrResponse(w, code, res) return } detail := account.AccountDetail diff --git a/net/server/internal/api_contact.go b/net/server/internal/api_contact.go index 709c5eeb..95524208 100644 --- a/net/server/internal/api_contact.go +++ b/net/server/internal/api_contact.go @@ -13,11 +13,6 @@ import ( "net/http" ) -func AddCard(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) -} - func ClearCardGroup(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) diff --git a/net/server/internal/api_getProfile.go b/net/server/internal/api_getProfile.go index ab3a10ee..0194980f 100644 --- a/net/server/internal/api_getProfile.go +++ b/net/server/internal/api_getProfile.go @@ -6,13 +6,9 @@ import ( func GetProfile(w http.ResponseWriter, r *http.Request) { - account, err := BearerAppToken(r, true); + account, code, err := BearerAppToken(r, true); if err != nil { - ErrResponse(w, http.StatusUnauthorized, err) - return - } - if account.Disabled { - ErrResponse(w, http.StatusGone, nil) + ErrResponse(w, code, err) return } detail := account.AccountDetail diff --git a/net/server/internal/api_getProfileMessage.go b/net/server/internal/api_getProfileMessage.go index fa8ea34b..4244d698 100644 --- a/net/server/internal/api_getProfileMessage.go +++ b/net/server/internal/api_getProfileMessage.go @@ -6,13 +6,9 @@ import ( func GetProfileMessage(w http.ResponseWriter, r *http.Request) { - account, res := BearerAppToken(r, true); + account, code, res := BearerAppToken(r, true); if res != nil { - ErrResponse(w, http.StatusUnauthorized, res) - return - } - if account.Disabled { - ErrResponse(w, http.StatusGone, res) + ErrResponse(w, code, res) return } detail := account.AccountDetail diff --git a/net/server/internal/api_setProfile.go b/net/server/internal/api_setProfile.go index ce319bcc..6ab4e77e 100644 --- a/net/server/internal/api_setProfile.go +++ b/net/server/internal/api_setProfile.go @@ -8,13 +8,9 @@ import ( func SetProfile(w http.ResponseWriter, r *http.Request) { - account, err := BearerAppToken(r, true); + account, code, err := BearerAppToken(r, true); if err != nil { - ErrResponse(w, http.StatusUnauthorized, err) - return - } - if account.Disabled { - ErrResponse(w, http.StatusGone, nil) + ErrResponse(w, code, err) return } detail := account.AccountDetail @@ -47,7 +43,6 @@ func SetProfile(w http.ResponseWriter, r *http.Request) { } SetStatus(account) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + WriteResponse(w, nil) } diff --git a/net/server/internal/api_status.go b/net/server/internal/api_status.go index e8fd6f41..db830229 100644 --- a/net/server/internal/api_status.go +++ b/net/server/internal/api_status.go @@ -59,7 +59,7 @@ func Status(w http.ResponseWriter, r *http.Request) { } // send current version - rev := getRevision(app.Account) + rev := getRevision(&app.Account) var msg []byte msg, err = json.Marshal(rev) if err != nil { @@ -95,7 +95,7 @@ func Status(w http.ResponseWriter, r *http.Request) { } } -func getRevision(account store.Account) Revision { +func getRevision(account *store.Account) Revision { var r Revision r.Profile = account.ProfileRevision r.Content = account.ContentRevision @@ -111,7 +111,7 @@ func ExitStatus() { wsExit <- true } -func SetStatus(account store.Account) { +func SetStatus(account *store.Account) { // get revisions for the account rev := getRevision(account); diff --git a/net/server/internal/appValues.go b/net/server/internal/appValues.go index 77ccfbcf..05ea49ce 100644 --- a/net/server/internal/appValues.go +++ b/net/server/internal/appValues.go @@ -13,3 +13,9 @@ const APP_MSGAUTHENTICATE = "authenticate" const APP_MSGIDENTITY = "identity" const APP_MSGCONNECT = "connect" const APP_MSGDISCONNECT = "disconnect" +const APP_CARDPENDING = "pending" +const APP_CARDCONFIRMED = "confirmed" +const APP_CARDREQUESTED = "requested" +const APP_CARDCONNECTING = "connecting" +const APP_CARDCONNECTED = "connected" + diff --git a/net/server/internal/authUtil.go b/net/server/internal/authUtil.go index 0c84b60f..da9eeed8 100644 --- a/net/server/internal/authUtil.go +++ b/net/server/internal/authUtil.go @@ -6,6 +6,7 @@ import ( "time" "net/http" "encoding/base64" + "gorm.io/gorm" "golang.org/x/crypto/bcrypt" "databag/internal/store" ) @@ -81,7 +82,7 @@ func BearerAccountToken(r *http.Request) (store.AccountToken, error) { return accountToken, nil } -func BearerAppToken(r *http.Request, detail bool) (store.Account, error) { +func BearerAppToken(r *http.Request, detail bool) (*store.Account, int, error) { // parse bearer authentication auth := r.Header.Get("Authorization") @@ -91,14 +92,26 @@ func BearerAppToken(r *http.Request, detail bool) (store.Account, error) { var app store.App if detail { if err := store.DB.Preload("Account.AccountDetail").Where("token = ?", token).First(&app).Error; err != nil { - return app.Account, err + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, http.StatusNotFound, err + } else { + return nil, http.StatusInternalServerError, err + } } } else { if err := store.DB.Preload("Account").Where("token = ?", token).First(&app).Error; err != nil { - return app.Account, err + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, http.StatusNotFound, err + } else { + return nil, http.StatusInternalServerError, err + } } } - return app.Account, nil + if app.Account.Disabled { + return nil, http.StatusGone, errors.New("account is inactive") + } + + return &app.Account, http.StatusOK, nil } func BasicCredentials(r *http.Request) (string, []byte, error) { diff --git a/net/server/internal/contact_test.go b/net/server/internal/contact_test.go new file mode 100644 index 00000000..5cec7db7 --- /dev/null +++ b/net/server/internal/contact_test.go @@ -0,0 +1,51 @@ +package databag + +import ( + "testing" + "strconv" + "github.com/stretchr/testify/assert" +) + +func AddTestContacts(t *testing.T, prefix string, count int) []string { + + var access []string + app := AppData{ + Name: "Appy", + Description: "A test app", + Url: "http://app.example.com", + }; + + for i := 0; i < count; i++ { + var token string + var login = prefix + strconv.Itoa(i) + ":pass" + + // get account token + r, w, _ := NewRequest("POST", "/admin/accounts", nil) + SetBasicAuth(r, "admin:pass") + AddNodeAccount(w, r) + assert.NoError(t, ReadResponse(w, &token)) + + // set account profile + r, w, _ = NewRequest("GET", "/account/profile", nil) + SetBearerAuth(r, token); + SetCredentials(r, login) + AddAccount(w, r) + assert.NoError(t, ReadResponse(w, nil)) + + // acquire new token for attaching app + r, w, _ = NewRequest("POST", "/account/apps", nil) + SetBasicAuth(r, login); + AddAccountApp(w, r); + assert.NoError(t, ReadResponse(w, &token)) + + // attach app with token + r, w, _ = NewRequest("PUT", "/account/apps", &app) + SetBearerAuth(r, token) + SetAccountApp(w, r) + assert.NoError(t, ReadResponse(w, &token)) + + access = append(access, token) + } + + return access +} diff --git a/net/server/internal/models.go b/net/server/internal/models.go index 4d3ddca4..d3e04330 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -85,12 +85,14 @@ type CardData struct { } type CardProfile struct { + Guid string `json:"guid"` Handle string `json:"handle,omitempty"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` Location string `json:"location,omitempty"` Revision int64 `json:"revision,omitempty"` ImageSet bool `json:"imageSet,omitempty"` + Version string `json:"version"` Node string `json:"node"` } diff --git a/net/server/internal/store/schema.go b/net/server/internal/store/schema.go index ef1c8adf..6e556375 100644 --- a/net/server/internal/store/schema.go +++ b/net/server/internal/store/schema.go @@ -115,19 +115,24 @@ type Label struct { type Card struct { ID uint `gorm:"primaryKey;not null;unique;autoIncrement"` CardId string `gorm:"not null;index:card,unique"` - AccountID uint `gorm:"not null;index:card,unique"` - GUID string `gorm:"not null"` + AccountID uint `gorm:"not null;index:card,unique;index:guid,unqiue"` + Guid string `gorm:"not null;index:guid,unique"` Username string Name string Description string Location string - Revision uint64 `gorm:"not null"` Image string + Version string `gorm:"not null"` Node string `gorm:"not null"` + ProfileRevision int64 `gorm:"not null"` Status string `gorm:"not null"` Token string + Notes string + DataRevision int64 `gorm:"not null"` Created int64 `gorm:"autoCreateTime"` Updated int64 `gorm:"autoUpdateTime"` + RemoteProfile int64 + RemoteContent int64 Account Account Groups []Group `gorm:"many2many:card_groups;"` } diff --git a/net/server/internal/ucConnectContact_test.go b/net/server/internal/ucConnectContact_test.go index 076a83fc..f2328647 100644 --- a/net/server/internal/ucConnectContact_test.go +++ b/net/server/internal/ucConnectContact_test.go @@ -7,81 +7,26 @@ import ( func TestConnectContact(t *testing.T) { - app := AppData{ - Name: "Appy", - Description: "A test app", - Url: "http://app.example.com", - }; - var token string - - // get account token - r, w, _ := NewRequest("POST", "/admin/accounts", nil) - SetBasicAuth(r, "admin:pass") - AddNodeAccount(w, r) - assert.NoError(t, ReadResponse(w, &token)) - - // set account profile - r, w, _ = NewRequest("GET", "/account/profile", nil) - SetBearerAuth(r, token); - SetCredentials(r, "connecta:pass") - AddAccount(w, r) - assert.NoError(t, ReadResponse(w, nil)) - - // acquire new token for attaching app - r, w, _ = NewRequest("POST", "/account/apps", nil) - SetBasicAuth(r, "attachapp:pass"); - AddAccountApp(w, r); - assert.NoError(t, ReadResponse(w, &token)) - - // attach app with token - r, w, _ = NewRequest("PUT", "/account/apps", &app) - SetBearerAuth(r, token) - SetAccountApp(w, r) - var aToken string - assert.NoError(t, ReadResponse(w, &aToken)) - - // get account token - r, w, _ = NewRequest("POST", "/admin/accounts", nil) - SetBasicAuth(r, "admin:pass") - AddNodeAccount(w, r) - assert.NoError(t, ReadResponse(w, &token)) - - // set account profile - r, w, _ = NewRequest("GET", "/account/profile", nil) - SetBearerAuth(r, token); - SetCredentials(r, "connectb:pass") - AddAccount(w, r) - assert.NoError(t, ReadResponse(w, nil)) - - // acquire new token for attaching app - r, w, _ = NewRequest("POST", "/account/apps", nil) - SetBasicAuth(r, "connectb:pass"); - AddAccountApp(w, r); - assert.NoError(t, ReadResponse(w, &token)) - - // attach app with token - r, w, _ = NewRequest("PUT", "/account/apps", &app) - SetBearerAuth(r, token) - SetAccountApp(w, r) - var bToken string - assert.NoError(t, ReadResponse(w, &bToken)) + // create some contacts for this test + access := AddTestContacts(t, "connect", 2) // get B identity message - r, w, _ = NewRequest("GET", "/profile/message", nil) - SetBearerAuth(r, bToken) + r, w, _ := NewRequest("GET", "/profile/message", nil) + SetBearerAuth(r, access[0]) GetProfileMessage(w, r) var msg DataMessage assert.NoError(t, ReadResponse(w, &msg)) - var identity Identity - guid, messageType, ts, err := ReadDataMessage(&msg, &identity) + // add B card in A + r, w, _ = NewRequest("POST", "/contact/cards", &msg) + SetBearerAuth(r, access[1]) + AddCard(w, r) + var card Card + assert.NoError(t, ReadResponse(w, &card)) - PrintMsg(msg) - PrintMsg(guid) - PrintMsg(messageType) - PrintMsg(ts) - PrintMsg(err) - PrintMsg(identity) +PrintMsg(card) + + // A request B // set B card in A