Skip to content

Commit

Permalink
feature: add update setup key endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
braginini committed Aug 20, 2021
1 parent 617f79e commit 2e9fc20
Show file tree
Hide file tree
Showing 12 changed files with 315 additions and 32 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.2.0
github.com/gorilla/mux v1.8.0
github.com/kardianos/service v1.2.0
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.13.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
Expand Down
100 changes: 93 additions & 7 deletions management/server/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net"
"strings"
"sync"
"time"
)

type AccountManager struct {
Expand Down Expand Up @@ -77,6 +78,79 @@ func (manager *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error)
return res, nil
}

//AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
func (manager *AccountManager) AddSetupKey(accountId string, keyName string, keyType SetupKeyType, expiresIn time.Duration) (*SetupKey, error) {
manager.mux.Lock()
defer manager.mux.Unlock()

account, err := manager.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}

setupKey := GenerateSetupKey(keyName, keyType, expiresIn)
account.SetupKeys[setupKey.Key] = setupKey

err = manager.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed adding account key")
}

return setupKey, nil
}

//RevokeSetupKey marks SetupKey as revoked - becomes not valid anymore
func (manager *AccountManager) RevokeSetupKey(accountId string, keyId string) (*SetupKey, error) {
manager.mux.Lock()
defer manager.mux.Unlock()

account, err := manager.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}

setupKey := getAccountSetupKeyById(account, keyId)
if setupKey == nil {
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", keyId)
}

keyCopy := setupKey.Copy()
keyCopy.Revoked = true
account.SetupKeys[keyCopy.Key] = keyCopy
err = manager.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed adding account key")
}

return keyCopy, nil
}

//RenameSetupKey renames existing setup key of the specified account.
func (manager *AccountManager) RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error) {
manager.mux.Lock()
defer manager.mux.Unlock()

account, err := manager.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}

setupKey := getAccountSetupKeyById(account, keyId)
if setupKey == nil {
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", keyId)
}

keyCopy := setupKey.Copy()
keyCopy.Name = newName
account.SetupKeys[keyCopy.Key] = keyCopy
err = manager.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed adding account key")
}

return keyCopy, nil
}

//GetAccount returns an existing account or error (NotFound) if doesn't exist
func (manager *AccountManager) GetAccount(accountId string) (*Account, error) {
manager.mux.Lock()
Expand Down Expand Up @@ -177,13 +251,7 @@ func (manager *AccountManager) AddPeer(setupKey string, peerKey string) (*Peer,
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
}

for _, key := range account.SetupKeys {
if upperKey == key.Key {
sk = key
break
}
}

sk = getAccountSetupKeyByKey(account, setupKey)
if sk == nil {
// shouldn't happen actually
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
Expand Down Expand Up @@ -242,3 +310,21 @@ func newAccount() (*Account, *SetupKey) {
accountId := uuid.New().String()
return newAccountWithId(accountId)
}

func getAccountSetupKeyById(acc *Account, keyId string) *SetupKey {
for _, k := range acc.SetupKeys {
if keyId == k.Id {
return k
}
}
return nil
}

func getAccountSetupKeyByKey(acc *Account, key string) *SetupKey {
for _, k := range acc.SetupKeys {
if key == k.Key {
return k
}
}
return nil
}
4 changes: 2 additions & 2 deletions management/server/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestAccountManager_AddAccount(t *testing.T) {
}

if account.Id != expectedId {
t.Errorf("expected account to have ID = %s, got %s", expectedId, account.Id)
t.Errorf("expected account to have Id = %s, got %s", expectedId, account.Id)
}

if len(account.Peers) != expectedPeersSize {
Expand Down Expand Up @@ -130,7 +130,7 @@ func TestAccountManager_GetAccount(t *testing.T) {
}

if account.Id != getAccount.Id {
t.Errorf("expected account.ID %s, got %s", account.Id, getAccount.Id)
t.Errorf("expected account.Id %s, got %s", account.Id, getAccount.Id)
}

for _, peer := range account.Peers {
Expand Down
2 changes: 1 addition & 1 deletion management/server/http/handler/peers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func NewPeers(accountManager *server.AccountManager) *Peers {
}
}

func (h *Peers) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
accountId := extractAccountIdFromRequestContext(r)
Expand Down
130 changes: 121 additions & 9 deletions management/server/http/handler/setupkeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package handler

import (
"encoding/json"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/wiretrustee/wiretrustee/management/server"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net/http"
"time"
)
Expand All @@ -15,11 +18,21 @@ type SetupKeys struct {

// SetupKeyResponse is a response sent to the client
type SetupKeyResponse struct {
Id string
Key string
Name string
Expires time.Time
Type string
Type server.SetupKeyType
Valid bool
Revoked bool
}

// SetupKeyRequest is a request sent by client. This object contains fields that can be modified
type SetupKeyRequest struct {
Name string
Type server.SetupKeyType
ExpiresIn Duration
Revoked bool
}

func NewSetupKeysHandler(accountManager *server.AccountManager) *SetupKeys {
Expand All @@ -28,7 +41,90 @@ func NewSetupKeysHandler(accountManager *server.AccountManager) *SetupKeys {
}
}

func (h *SetupKeys) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (h *SetupKeys) CreateKey(w http.ResponseWriter, r *http.Request) {
accountId := extractAccountIdFromRequestContext(r)
req := &SetupKeyRequest{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, req.Type, req.ExpiresIn.Duration)
if err != nil {
errStatus, ok := status.FromError(err)
if ok && errStatus.Code() == codes.NotFound {
http.Error(w, "account not found", http.StatusNotFound)
return
}
http.Error(w, "failed adding setup key", http.StatusInternalServerError)
return
}

writeSuccess(w, setupKey)
}

func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
accountId := extractAccountIdFromRequestContext(r)
vars := mux.Vars(r)
keyId := vars["id"]
if len(keyId) == 0 {
http.Error(w, "invalid key Id", http.StatusBadRequest)
return
}

switch r.Method {
case http.MethodPost:
req := &SetupKeyRequest{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

var key *server.SetupKey
if req.Revoked {
//handle only if being revoked, don't allow to enable key again for now
key, err = h.accountManager.RevokeSetupKey(accountId, keyId)
if err != nil {
http.Error(w, "failed revoking key", http.StatusInternalServerError)
return
}
}
if len(req.Name) != 0 {
key, err = h.accountManager.RenameSetupKey(accountId, keyId, req.Name)
if err != nil {
http.Error(w, "failed renaming key", http.StatusInternalServerError)
return
}
}

if key != nil {
writeSuccess(w, key)
}

return

case http.MethodGet:
account, err := h.accountManager.GetAccount(accountId)
if err != nil {
http.Error(w, "account doesn't exist", http.StatusInternalServerError)
return
}
for _, key := range account.SetupKeys {
if key.Id == keyId {
writeSuccess(w, key)
return
}
}
http.Error(w, "setup key not found", http.StatusNotFound)
return
default:
http.Error(w, "", http.StatusNotFound)
}
}

func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
accountId := extractAccountIdFromRequestContext(r)
Expand All @@ -44,13 +140,7 @@ func (h *SetupKeys) ServeHTTP(w http.ResponseWriter, r *http.Request) {

respBody := []*SetupKeyResponse{}
for _, key := range account.SetupKeys {
respBody = append(respBody, &SetupKeyResponse{
Key: key.Key,
Name: key.Name,
Expires: key.ExpiresAt,
Type: string(key.Type),
Valid: key.IsValid(),
})
respBody = append(respBody, toResponseBody(key))
}

err = json.NewEncoder(w).Encode(respBody)
Expand All @@ -63,3 +153,25 @@ func (h *SetupKeys) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "", http.StatusNotFound)
}
}

func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(toResponseBody(key))
if err != nil {
http.Error(w, "failed handling request", http.StatusInternalServerError)
return
}
}

func toResponseBody(key *server.SetupKey) *SetupKeyResponse {
return &SetupKeyResponse{
Id: key.Id,
Key: key.Key,
Name: key.Name,
Expires: key.ExpiresAt,
Type: key.Type,
Valid: key.IsValid(),
Revoked: key.Revoked,
}
}
33 changes: 33 additions & 0 deletions management/server/http/handler/util.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package handler

import (
"encoding/json"
"errors"
"github.com/golang-jwt/jwt"
"net/http"
"time"
)

// extractAccountIdFromRequestContext extracts accountId from the request context previously filled by the JWT token (after auth)
Expand All @@ -13,3 +16,33 @@ func extractAccountIdFromRequestContext(r *http.Request) string {
//actually a user id but for now we have a 1 to 1 mapping.
return claims["sub"].(string)
}

//Duration is used strictly for JSON requests/responses due to duration marshalling issues
type Duration struct {
time.Duration
}

func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}

func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return errors.New("invalid duration")
}
}
Loading

0 comments on commit 2e9fc20

Please sign in to comment.