mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-23 19:21:23 +02:00
Add SetupKey auto-groups property (#460)
This commit is contained in:
parent
ed1872560f
commit
be7d829858
@ -31,14 +31,15 @@ const (
|
|||||||
type AccountManager interface {
|
type AccountManager interface {
|
||||||
GetOrCreateAccountByUser(userId, domain string) (*Account, error)
|
GetOrCreateAccountByUser(userId, domain string) (*Account, error)
|
||||||
GetAccountByUser(userId string) (*Account, error)
|
GetAccountByUser(userId string) (*Account, error)
|
||||||
AddSetupKey(
|
CreateSetupKey(
|
||||||
accountId string,
|
accountId string,
|
||||||
keyName string,
|
keyName string,
|
||||||
keyType SetupKeyType,
|
keyType SetupKeyType,
|
||||||
expiresIn time.Duration,
|
expiresIn time.Duration,
|
||||||
|
autoGroups []string,
|
||||||
) (*SetupKey, error)
|
) (*SetupKey, error)
|
||||||
RevokeSetupKey(accountId string, keyId string) (*SetupKey, error)
|
SaveSetupKey(accountID string, key *SetupKey) (*SetupKey, error)
|
||||||
RenameSetupKey(accountId string, keyId string, newName string) (*SetupKey, error)
|
GetSetupKey(accountID, keyID string) (*SetupKey, error)
|
||||||
GetAccountById(accountId string) (*Account, error)
|
GetAccountById(accountId string) (*Account, error)
|
||||||
GetAccountByUserOrAccountId(userId, accountId, domain string) (*Account, error)
|
GetAccountByUserOrAccountId(userId, accountId, domain string) (*Account, error)
|
||||||
GetAccountWithAuthorizationClaims(claims jwtclaims.AuthorizationClaims) (*Account, error)
|
GetAccountWithAuthorizationClaims(claims jwtclaims.AuthorizationClaims) (*Account, error)
|
||||||
@ -75,6 +76,7 @@ type AccountManager interface {
|
|||||||
UpdateRoute(accountID string, routeID string, operations []RouteUpdateOperation) (*route.Route, error)
|
UpdateRoute(accountID string, routeID string, operations []RouteUpdateOperation) (*route.Route, error)
|
||||||
DeleteRoute(accountID, routeID string) error
|
DeleteRoute(accountID, routeID string) error
|
||||||
ListRoutes(accountID string) ([]*route.Route, error)
|
ListRoutes(accountID string) ([]*route.Route, error)
|
||||||
|
ListSetupKeys(accountID string) ([]*SetupKey, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultAccountManager struct {
|
type DefaultAccountManager struct {
|
||||||
@ -244,93 +246,6 @@ func (am *DefaultAccountManager) warmupIDPCache() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSetupKey generates a new setup key with a given name and type, and adds it to the specified account
|
|
||||||
func (am *DefaultAccountManager) AddSetupKey(
|
|
||||||
accountId string,
|
|
||||||
keyName string,
|
|
||||||
keyType SetupKeyType,
|
|
||||||
expiresIn time.Duration,
|
|
||||||
) (*SetupKey, error) {
|
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
|
||||||
|
|
||||||
keyDuration := DefaultSetupKeyDuration
|
|
||||||
if expiresIn != 0 {
|
|
||||||
keyDuration = expiresIn
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.NotFound, "account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupKey := GenerateSetupKey(keyName, keyType, keyDuration)
|
|
||||||
account.SetupKeys[setupKey.Key] = setupKey
|
|
||||||
|
|
||||||
err = am.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 (am *DefaultAccountManager) RevokeSetupKey(accountId string, keyId string) (*SetupKey, error) {
|
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
|
||||||
|
|
||||||
account, err := am.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 = am.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 (am *DefaultAccountManager) RenameSetupKey(
|
|
||||||
accountId string,
|
|
||||||
keyId string,
|
|
||||||
newName string,
|
|
||||||
) (*SetupKey, error) {
|
|
||||||
am.mux.Lock()
|
|
||||||
defer am.mux.Unlock()
|
|
||||||
|
|
||||||
account, err := am.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 = am.Store.SaveAccount(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(codes.Internal, "failed adding account key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyCopy, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountById returns an existing account using its ID or error (NotFound) if doesn't exist
|
// GetAccountById returns an existing account using its ID or error (NotFound) if doesn't exist
|
||||||
func (am *DefaultAccountManager) GetAccountById(accountId string) (*Account, error) {
|
func (am *DefaultAccountManager) GetAccountById(accountId string) (*Account, error) {
|
||||||
am.mux.Lock()
|
am.mux.Lock()
|
||||||
@ -504,7 +419,6 @@ func (am *DefaultAccountManager) updateAccountDomainAttributes(
|
|||||||
|
|
||||||
// handleExistingUserAccount handles existing User accounts and update its domain attributes.
|
// handleExistingUserAccount handles existing User accounts and update its domain attributes.
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// If there is no primary domain account yet, we set the account as primary for the domain. Otherwise,
|
// If there is no primary domain account yet, we set the account as primary for the domain. Otherwise,
|
||||||
// we compare the account's ID with the domain account ID, and if they don't match, we set the account as
|
// we compare the account's ID with the domain account ID, and if they don't match, we set the account as
|
||||||
// non-primary account for the domain. We don't merge accounts at this stage, because of cases when a domain
|
// non-primary account for the domain. We don't merge accounts at this stage, because of cases when a domain
|
||||||
@ -688,7 +602,7 @@ func newAccountWithId(accountId, userId, domain string) *Account {
|
|||||||
|
|
||||||
setupKeys := make(map[string]*SetupKey)
|
setupKeys := make(map[string]*SetupKey)
|
||||||
defaultKey := GenerateDefaultSetupKey()
|
defaultKey := GenerateDefaultSetupKey()
|
||||||
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration)
|
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration, []string{})
|
||||||
setupKeys[defaultKey.Key] = defaultKey
|
setupKeys[defaultKey.Key] = defaultKey
|
||||||
setupKeys[oneOffKey.Key] = oneOffKey
|
setupKeys[oneOffKey.Key] = oneOffKey
|
||||||
network := NewNetwork()
|
network := NewNetwork()
|
||||||
@ -713,15 +627,6 @@ func newAccountWithId(accountId, userId, domain string) *Account {
|
|||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func getAccountSetupKeyByKey(acc *Account, key string) *SetupKey {
|
||||||
for _, k := range acc.SetupKeys {
|
for _, k := range acc.SetupKeys {
|
||||||
if key == k.Key {
|
if key == k.Key {
|
||||||
|
@ -134,6 +134,15 @@ components:
|
|||||||
state:
|
state:
|
||||||
description: Setup key status, "valid", "overused","expired" or "revoked"
|
description: Setup key status, "valid", "overused","expired" or "revoked"
|
||||||
type: string
|
type: string
|
||||||
|
auto_groups:
|
||||||
|
description: Setup key groups to auto-assign to peers registered with this key
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
description: Setup key last update date
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- key
|
- key
|
||||||
@ -145,6 +154,8 @@ components:
|
|||||||
- used_times
|
- used_times
|
||||||
- last_used
|
- last_used
|
||||||
- state
|
- state
|
||||||
|
- auto_groups
|
||||||
|
- updated_at
|
||||||
SetupKeyRequest:
|
SetupKeyRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -160,11 +171,17 @@ components:
|
|||||||
revoked:
|
revoked:
|
||||||
description: Setup key revocation status
|
description: Setup key revocation status
|
||||||
type: boolean
|
type: boolean
|
||||||
|
auto_groups:
|
||||||
|
description: Setup key groups to auto-assign to peers registered with this key
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- type
|
- type
|
||||||
- expires_in
|
- expires_in
|
||||||
- revoked
|
- revoked
|
||||||
|
- auto_groups
|
||||||
GroupMinimum:
|
GroupMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -299,6 +299,9 @@ type RulePatchOperationPath string
|
|||||||
|
|
||||||
// SetupKey defines model for SetupKey.
|
// SetupKey defines model for SetupKey.
|
||||||
type SetupKey struct {
|
type SetupKey struct {
|
||||||
|
// Setup key groups to auto-assign to peers registered with this key
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
// Setup Key expiration date
|
// Setup Key expiration date
|
||||||
Expires time.Time `json:"expires"`
|
Expires time.Time `json:"expires"`
|
||||||
|
|
||||||
@ -323,6 +326,9 @@ type SetupKey struct {
|
|||||||
// Setup key type, one-off for single time usage and reusable
|
// Setup key type, one-off for single time usage and reusable
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Setup key last update date
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// Usage count of setup key
|
// Usage count of setup key
|
||||||
UsedTimes int `json:"used_times"`
|
UsedTimes int `json:"used_times"`
|
||||||
|
|
||||||
@ -332,6 +338,9 @@ type SetupKey struct {
|
|||||||
|
|
||||||
// SetupKeyRequest defines model for SetupKeyRequest.
|
// SetupKeyRequest defines model for SetupKeyRequest.
|
||||||
type SetupKeyRequest struct {
|
type SetupKeyRequest struct {
|
||||||
|
// Setup key groups to auto-assign to peers registered with this key
|
||||||
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
// Expiration time in seconds
|
// Expiration time in seconds
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
|
||||||
|
@ -39,12 +39,11 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
|
|||||||
apiHandler.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
|
apiHandler.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
|
||||||
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/api/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("GET", "POST", "OPTIONS")
|
|
||||||
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "PUT", "OPTIONS")
|
|
||||||
|
|
||||||
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("POST", "OPTIONS")
|
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetAllSetupKeysHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).
|
apiHandler.HandleFunc("/api/setup-keys", keysHandler.CreateSetupKeyHandler).Methods("POST", "OPTIONS")
|
||||||
Methods("GET", "PUT", "DELETE", "OPTIONS")
|
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.GetSetupKeyHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.UpdateSetupKeyHandler).Methods("PUT", "OPTIONS")
|
||||||
|
|
||||||
apiHandler.HandleFunc("/api/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/api/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
|
apiHandler.HandleFunc("/api/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
|
||||||
|
@ -348,6 +348,11 @@ func (h *Routes) DeleteRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
err = h.accountManager.DeleteRoute(account.Id, routeID)
|
err = h.accountManager.DeleteRoute(account.Id, routeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errStatus, ok := status.FromError(err)
|
||||||
|
if ok && errStatus.Code() == codes.NotFound {
|
||||||
|
http.Error(w, fmt.Sprintf("route %s not found under account %s", routeID, account.Id), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
log.Errorf("failed delete route %s under account %s %v", routeID, account.Id, err)
|
log.Errorf("failed delete route %s under account %s %v", routeID, account.Id, err)
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -78,7 +78,10 @@ func initRoutesTestData() *Routes {
|
|||||||
SaveRouteFunc: func(_ string, _ *route.Route) error {
|
SaveRouteFunc: func(_ string, _ *route.Route) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
DeleteRouteFunc: func(_ string, _ string) error {
|
DeleteRouteFunc: func(_ string, peerIP string) error {
|
||||||
|
if peerIP != existingRouteID {
|
||||||
|
return status.Errorf(codes.NotFound, "Peer with ID %s not found", peerIP)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
||||||
@ -155,7 +158,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Get Not Existing Route",
|
name: "Get Not Existing Route",
|
||||||
requestType: http.MethodGet,
|
requestType: http.MethodGet,
|
||||||
requestPath: "/api/rules/" + notFoundRouteID,
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
expectedStatus: http.StatusNotFound,
|
expectedStatus: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -168,7 +171,7 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Delete Not Existing Route",
|
name: "Delete Not Existing Route",
|
||||||
requestType: http.MethodDelete,
|
requestType: http.MethodDelete,
|
||||||
requestPath: "/api/rules/" + notFoundRouteID,
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
expectedStatus: http.StatusNotFound,
|
expectedStatus: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
@ -28,54 +29,17 @@ func NewSetupKeysHandler(accountManager server.AccountManager, authAudience stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) updateKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
// CreateSetupKeyHandler is a POST requests that creates a new SetupKey
|
||||||
req := &api.PutApiSetupKeysIdJSONRequestBody{}
|
func (h *SetupKeys) CreateSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
return
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) getKey(accountId string, keyId string, w http.ResponseWriter, r *http.Request) {
|
|
||||||
account, err := h.accountManager.GetAccountById(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.Request) {
|
|
||||||
req := &api.PostApiSetupKeysJSONRequestBody{}
|
req := &api.PostApiSetupKeysJSONRequestBody{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@ -95,7 +59,13 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
expiresIn := time.Duration(req.ExpiresIn) * time.Second
|
expiresIn := time.Duration(req.ExpiresIn) * time.Second
|
||||||
|
|
||||||
setupKey, err := h.accountManager.AddSetupKey(accountId, req.Name, server.SetupKeyType(req.Type), expiresIn)
|
if req.AutoGroups == nil {
|
||||||
|
req.AutoGroups = []string{}
|
||||||
|
}
|
||||||
|
// newExpiresIn := time.Duration(req.ExpiresIn) * time.Second
|
||||||
|
// newKey.ExpiresAt = time.Now().Add(newExpiresIn)
|
||||||
|
setupKey, err := h.accountManager.CreateSetupKey(account.Id, req.Name, server.SetupKeyType(req.Type), expiresIn,
|
||||||
|
req.AutoGroups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStatus, ok := status.FromError(err)
|
errStatus, ok := status.FromError(err)
|
||||||
if ok && errStatus.Code() == codes.NotFound {
|
if ok && errStatus.Code() == codes.NotFound {
|
||||||
@ -109,7 +79,8 @@ func (h *SetupKeys) createKey(accountId string, w http.ResponseWriter, r *http.R
|
|||||||
writeSuccess(w, setupKey)
|
writeSuccess(w, setupKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
// GetSetupKeyHandler is a GET request to get a SetupKey by ID
|
||||||
|
func (h *SetupKeys) GetSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
@ -118,25 +89,84 @@ func (h *SetupKeys) HandleKey(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
keyId := vars["id"]
|
keyID := vars["id"]
|
||||||
if len(keyId) == 0 {
|
if len(keyID) == 0 {
|
||||||
http.Error(w, "invalid key Id", http.StatusBadRequest)
|
http.Error(w, "invalid key Id", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.Method {
|
key, err := h.accountManager.GetSetupKey(account.Id, keyID)
|
||||||
case http.MethodPut:
|
if err != nil {
|
||||||
h.updateKey(account.Id, keyId, w, r)
|
errStatus, ok := status.FromError(err)
|
||||||
|
if ok && errStatus.Code() == codes.NotFound {
|
||||||
|
http.Error(w, fmt.Sprintf("setup key %s not found under account %s", keyID, account.Id), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Errorf("failed getting setup key %s under account %s %v", keyID, account.Id, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
case http.MethodGet:
|
|
||||||
h.getKey(account.Id, keyId, w, r)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
http.Error(w, "", http.StatusNotFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeSuccess(w, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
// UpdateSetupKeyHandler is a PUT request to update server.SetupKey
|
||||||
|
func (h *SetupKeys) UpdateSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
keyID := vars["id"]
|
||||||
|
if len(keyID) == 0 {
|
||||||
|
http.Error(w, "invalid key Id", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &api.PutApiSetupKeysIdJSONRequestBody{}
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name == "" {
|
||||||
|
http.Error(w, fmt.Sprintf("setup key name field is invalid: %s", req.Name), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.AutoGroups == nil {
|
||||||
|
http.Error(w, fmt.Sprintf("setup key AutoGroups field is invalid: %s", req.AutoGroups), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newKey := &server.SetupKey{}
|
||||||
|
newKey.AutoGroups = req.AutoGroups
|
||||||
|
newKey.Revoked = req.Revoked
|
||||||
|
newKey.Name = req.Name
|
||||||
|
newKey.Id = keyID
|
||||||
|
|
||||||
|
newKey, err = h.accountManager.SaveSetupKey(account.Id, newKey)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := status.FromError(err); ok {
|
||||||
|
switch e.Code() {
|
||||||
|
case codes.NotFound:
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't find setup key for ID %s", keyID), http.StatusNotFound)
|
||||||
|
default:
|
||||||
|
http.Error(w, "failed updating setup key", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccess(w, newKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllSetupKeysHandler is a GET request that returns a list of SetupKey
|
||||||
|
func (h *SetupKeys) GetAllSetupKeysHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -145,28 +175,18 @@ func (h *SetupKeys) GetKeys(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.Method {
|
setupKeys, err := h.accountManager.ListSetupKeys(account.Id)
|
||||||
case http.MethodPost:
|
if err != nil {
|
||||||
h.createKey(account.Id, w, r)
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
case http.MethodGet:
|
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
respBody := []*api.SetupKey{}
|
|
||||||
for _, key := range account.SetupKeys {
|
|
||||||
respBody = append(respBody, toResponseBody(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(respBody)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed encoding account peers %s: %v", account.Id, err)
|
|
||||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
http.Error(w, "", http.StatusNotFound)
|
|
||||||
}
|
}
|
||||||
|
apiSetupKeys := make([]*api.SetupKey, 0)
|
||||||
|
for _, key := range setupKeys {
|
||||||
|
apiSetupKeys = append(apiSetupKeys, toResponseBody(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, apiSetupKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
|
func writeSuccess(w http.ResponseWriter, key *server.SetupKey) {
|
||||||
@ -190,16 +210,19 @@ func toResponseBody(key *server.SetupKey) *api.SetupKey {
|
|||||||
} else {
|
} else {
|
||||||
state = "valid"
|
state = "valid"
|
||||||
}
|
}
|
||||||
|
|
||||||
return &api.SetupKey{
|
return &api.SetupKey{
|
||||||
Id: key.Id,
|
Id: key.Id,
|
||||||
Key: key.Key,
|
Key: key.Key,
|
||||||
Name: key.Name,
|
Name: key.Name,
|
||||||
Expires: key.ExpiresAt,
|
Expires: key.ExpiresAt,
|
||||||
Type: string(key.Type),
|
Type: string(key.Type),
|
||||||
Valid: key.IsValid(),
|
Valid: key.IsValid(),
|
||||||
Revoked: key.Revoked,
|
Revoked: key.Revoked,
|
||||||
UsedTimes: key.UsedTimes,
|
UsedTimes: key.UsedTimes,
|
||||||
LastUsed: key.LastUsed,
|
LastUsed: key.LastUsed,
|
||||||
State: state,
|
State: state,
|
||||||
|
AutoGroups: key.AutoGroups,
|
||||||
|
UpdatedAt: key.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
222
management/server/http/setupkeys_test.go
Normal file
222
management/server/http/setupkeys_test.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
existingSetupKeyID = "existingSetupKeyID"
|
||||||
|
newSetupKeyName = "New Setup Key"
|
||||||
|
updatedSetupKeyName = "KKKey"
|
||||||
|
notFoundSetupKeyID = "notFoundSetupKeyID"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initSetupKeysTestMetaData(defaultKey *server.SetupKey, newKey *server.SetupKey, updatedSetupKey *server.SetupKey) *SetupKeys {
|
||||||
|
return &SetupKeys{
|
||||||
|
accountManager: &mock_server.MockAccountManager{
|
||||||
|
GetAccountWithAuthorizationClaimsFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||||
|
return &server.Account{
|
||||||
|
Id: testAccountID,
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
SetupKeys: map[string]*server.SetupKey{
|
||||||
|
defaultKey.Key: defaultKey,
|
||||||
|
},
|
||||||
|
Groups: map[string]*server.Group{
|
||||||
|
"group-1": {ID: "group-1", Peers: []string{"A", "B"}},
|
||||||
|
"id-all": {ID: "id-all", Name: "All"}},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
CreateSetupKeyFunc: func(_ string, keyName string, typ server.SetupKeyType, _ time.Duration, _ []string) (*server.SetupKey, error) {
|
||||||
|
if keyName == newKey.Name || typ != newKey.Type {
|
||||||
|
return newKey, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed creating setup key")
|
||||||
|
},
|
||||||
|
GetSetupKeyFunc: func(accountID string, keyID string) (*server.SetupKey, error) {
|
||||||
|
switch keyID {
|
||||||
|
case defaultKey.Id:
|
||||||
|
return defaultKey, nil
|
||||||
|
case newKey.Id:
|
||||||
|
return newKey, nil
|
||||||
|
default:
|
||||||
|
return nil, status.Errorf(codes.NotFound, "key %s not found", keyID)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
SaveSetupKeyFunc: func(accountID string, key *server.SetupKey) (*server.SetupKey, error) {
|
||||||
|
if key.Id == updatedSetupKey.Id {
|
||||||
|
return updatedSetupKey, nil
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.NotFound, "key %s not found", key.Id)
|
||||||
|
},
|
||||||
|
|
||||||
|
ListSetupKeysFunc: func(accountID string) ([]*server.SetupKey, error) {
|
||||||
|
return []*server.SetupKey{defaultKey}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authAudience: "",
|
||||||
|
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||||
|
ExtractClaimsFromRequestContext: func(r *http.Request, authAudience string) jwtclaims.AuthorizationClaims {
|
||||||
|
return jwtclaims.AuthorizationClaims{
|
||||||
|
UserId: "test_user",
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
AccountId: testAccountID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupKeysHandlers(t *testing.T) {
|
||||||
|
defaultSetupKey := server.GenerateDefaultSetupKey()
|
||||||
|
defaultSetupKey.Id = existingSetupKeyID
|
||||||
|
|
||||||
|
newSetupKey := server.GenerateSetupKey(newSetupKeyName, server.SetupKeyReusable, 0, []string{"group-1"})
|
||||||
|
updatedDefaultSetupKey := defaultSetupKey.Copy()
|
||||||
|
updatedDefaultSetupKey.AutoGroups = []string{"group-1"}
|
||||||
|
updatedDefaultSetupKey.Name = updatedSetupKeyName
|
||||||
|
updatedDefaultSetupKey.Revoked = true
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
requestType string
|
||||||
|
requestPath string
|
||||||
|
requestBody io.Reader
|
||||||
|
expectedStatus int
|
||||||
|
expectedBody bool
|
||||||
|
expectedSetupKey *api.SetupKey
|
||||||
|
expectedSetupKeys []*api.SetupKey
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get Setup Keys",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/setup-keys",
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedSetupKeys: []*api.SetupKey{toResponseBody(defaultSetupKey)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Existing Setup Key",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/setup-keys/" + existingSetupKeyID,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedSetupKey: toResponseBody(defaultSetupKey),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Not Existing Setup Key",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/setup-keys/" + notFoundSetupKeyID,
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Create Setup Key",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/setup-keys",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"name\":\"%s\",\"type\":\"%s\"}", newSetupKey.Name, newSetupKey.Type))),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedSetupKey: toResponseBody(newSetupKey),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Update Setup Key",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/setup-keys/" + defaultSetupKey.Id,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"name\":\"%s\",\"auto_groups\":[\"%s\"], \"revoked\":%v}",
|
||||||
|
updatedDefaultSetupKey.Type,
|
||||||
|
updatedDefaultSetupKey.AutoGroups[0],
|
||||||
|
updatedDefaultSetupKey.Revoked,
|
||||||
|
))),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedSetupKey: toResponseBody(updatedDefaultSetupKey),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := initSetupKeysTestMetaData(defaultSetupKey, newSetupKey, updatedDefaultSetupKey)
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/api/setup-keys", handler.GetAllSetupKeysHandler).Methods("GET", "OPTIONS")
|
||||||
|
router.HandleFunc("/api/setup-keys", handler.CreateSetupKeyHandler).Methods("POST", "OPTIONS")
|
||||||
|
router.HandleFunc("/api/setup-keys/{id}", handler.GetSetupKeyHandler).Methods("GET", "OPTIONS")
|
||||||
|
router.HandleFunc("/api/setup-keys/{id}", handler.UpdateSetupKeyHandler).Methods("PUT", "OPTIONS")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
res := recorder.Result()
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("I don't know what I expected; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := recorder.Code; status != tc.expectedStatus {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
|
||||||
|
status, tc.expectedStatus, string(content))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectedBody {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedSetupKey != nil {
|
||||||
|
got := &api.SetupKey{}
|
||||||
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
assertKeys(t, got, tc.expectedSetupKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tc.expectedSetupKeys) > 0 {
|
||||||
|
var got []*api.SetupKey
|
||||||
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
assertKeys(t, got[0], tc.expectedSetupKeys[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertKeys(t *testing.T, got *api.SetupKey, expected *api.SetupKey) {
|
||||||
|
// this comparison is done manually because when converting to JSON dates formatted differently
|
||||||
|
// assert.Equal(t, got.UpdatedAt, tc.expectedSetupKey.UpdatedAt) //doesn't work
|
||||||
|
assert.WithinDurationf(t, got.UpdatedAt, expected.UpdatedAt, 0, "")
|
||||||
|
assert.WithinDurationf(t, got.Expires, expected.Expires, 0, "")
|
||||||
|
assert.Equal(t, got.Name, expected.Name)
|
||||||
|
assert.Equal(t, got.Id, expected.Id)
|
||||||
|
assert.Equal(t, got.Key, expected.Key)
|
||||||
|
assert.Equal(t, got.Type, expected.Type)
|
||||||
|
assert.Equal(t, got.UsedTimes, expected.UsedTimes)
|
||||||
|
assert.Equal(t, got.Revoked, expected.Revoked)
|
||||||
|
assert.ElementsMatch(t, got.AutoGroups, expected.AutoGroups)
|
||||||
|
}
|
@ -12,9 +12,8 @@ import (
|
|||||||
type MockAccountManager struct {
|
type MockAccountManager struct {
|
||||||
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
|
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
|
||||||
GetAccountByUserFunc func(userId string) (*server.Account, error)
|
GetAccountByUserFunc func(userId string) (*server.Account, error)
|
||||||
AddSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn time.Duration) (*server.SetupKey, error)
|
CreateSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn time.Duration, autoGroups []string) (*server.SetupKey, error)
|
||||||
RevokeSetupKeyFunc func(accountId string, keyId string) (*server.SetupKey, error)
|
GetSetupKeyFunc func(accountID string, keyID string) (*server.SetupKey, error)
|
||||||
RenameSetupKeyFunc func(accountId string, keyId string, newName string) (*server.SetupKey, error)
|
|
||||||
GetAccountByIdFunc func(accountId string) (*server.Account, error)
|
GetAccountByIdFunc func(accountId string) (*server.Account, error)
|
||||||
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
|
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
|
||||||
GetAccountWithAuthorizationClaimsFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, error)
|
GetAccountWithAuthorizationClaimsFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, error)
|
||||||
@ -51,6 +50,8 @@ type MockAccountManager struct {
|
|||||||
UpdateRouteFunc func(accountID string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error)
|
UpdateRouteFunc func(accountID string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error)
|
||||||
DeleteRouteFunc func(accountID, routeID string) error
|
DeleteRouteFunc func(accountID, routeID string) error
|
||||||
ListRoutesFunc func(accountID string) ([]*route.Route, error)
|
ListRoutesFunc func(accountID string) ([]*route.Route, error)
|
||||||
|
SaveSetupKeyFunc func(accountID string, key *server.SetupKey) (*server.SetupKey, error)
|
||||||
|
ListSetupKeysFunc func(accountID string) ([]*server.SetupKey, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||||
@ -82,40 +83,18 @@ func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account,
|
|||||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSetupKey mock implementation of AddSetupKey from server.AccountManager interface
|
// CreateSetupKey mock implementation of CreateSetupKey from server.AccountManager interface
|
||||||
func (am *MockAccountManager) AddSetupKey(
|
func (am *MockAccountManager) CreateSetupKey(
|
||||||
accountId string,
|
accountId string,
|
||||||
keyName string,
|
keyName string,
|
||||||
keyType server.SetupKeyType,
|
keyType server.SetupKeyType,
|
||||||
expiresIn time.Duration,
|
expiresIn time.Duration,
|
||||||
|
autoGroups []string,
|
||||||
) (*server.SetupKey, error) {
|
) (*server.SetupKey, error) {
|
||||||
if am.AddSetupKeyFunc != nil {
|
if am.CreateSetupKeyFunc != nil {
|
||||||
return am.AddSetupKeyFunc(accountId, keyName, keyType, expiresIn)
|
return am.CreateSetupKeyFunc(accountId, keyName, keyType, expiresIn, autoGroups)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CreateSetupKey is not implemented")
|
||||||
}
|
|
||||||
|
|
||||||
// RevokeSetupKey mock implementation of RevokeSetupKey from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) RevokeSetupKey(
|
|
||||||
accountId string,
|
|
||||||
keyId string,
|
|
||||||
) (*server.SetupKey, error) {
|
|
||||||
if am.RevokeSetupKeyFunc != nil {
|
|
||||||
return am.RevokeSetupKeyFunc(accountId, keyId)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenameSetupKey mock implementation of RenameSetupKey from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) RenameSetupKey(
|
|
||||||
accountId string,
|
|
||||||
keyId string,
|
|
||||||
newName string,
|
|
||||||
) (*server.SetupKey, error) {
|
|
||||||
if am.RenameSetupKeyFunc != nil {
|
|
||||||
return am.RenameSetupKeyFunc(accountId, keyId, newName)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey is not implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountById mock implementation of GetAccountById from server.AccountManager interface
|
// GetAccountById mock implementation of GetAccountById from server.AccountManager interface
|
||||||
@ -415,3 +394,30 @@ func (am *MockAccountManager) ListRoutes(accountID string) ([]*route.Route, erro
|
|||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListRoutes is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListRoutes is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveSetupKey mocks SaveSetupKey of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) SaveSetupKey(accountID string, key *server.SetupKey) (*server.SetupKey, error) {
|
||||||
|
if am.SaveSetupKeyFunc != nil {
|
||||||
|
return am.SaveSetupKeyFunc(accountID, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method SaveSetupKey is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSetupKey mocks GetSetupKey of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) GetSetupKey(accountID, keyID string) (*server.SetupKey, error) {
|
||||||
|
if am.GetSetupKeyFunc != nil {
|
||||||
|
return am.GetSetupKeyFunc(accountID, keyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetSetupKey is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSetupKeys mocks ListSetupKeys of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) ListSetupKeys(accountID string) ([]*server.SetupKey, error) {
|
||||||
|
if am.ListSetupKeysFunc != nil {
|
||||||
|
return am.ListSetupKeysFunc(accountID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListSetupKeys is not implemented")
|
||||||
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -18,8 +21,41 @@ const (
|
|||||||
DefaultSetupKeyDuration = 24 * 30 * time.Hour
|
DefaultSetupKeyDuration = 24 * 30 * time.Hour
|
||||||
// DefaultSetupKeyName is a default name of the default setup key
|
// DefaultSetupKeyName is a default name of the default setup key
|
||||||
DefaultSetupKeyName = "Default key"
|
DefaultSetupKeyName = "Default key"
|
||||||
|
|
||||||
|
// UpdateSetupKeyName indicates a setup key name update operation
|
||||||
|
UpdateSetupKeyName SetupKeyUpdateOperationType = iota
|
||||||
|
// UpdateSetupKeyRevoked indicates a setup key revoked filed update operation
|
||||||
|
UpdateSetupKeyRevoked
|
||||||
|
// UpdateSetupKeyAutoGroups indicates a setup key auto-assign groups update operation
|
||||||
|
UpdateSetupKeyAutoGroups
|
||||||
|
// UpdateSetupKeyExpiresAt indicates a setup key expiration time update operation
|
||||||
|
UpdateSetupKeyExpiresAt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SetupKeyUpdateOperationType operation type
|
||||||
|
type SetupKeyUpdateOperationType int
|
||||||
|
|
||||||
|
func (t SetupKeyUpdateOperationType) String() string {
|
||||||
|
switch t {
|
||||||
|
case UpdateSetupKeyName:
|
||||||
|
return "UpdateSetupKeyName"
|
||||||
|
case UpdateSetupKeyRevoked:
|
||||||
|
return "UpdateSetupKeyRevoked"
|
||||||
|
case UpdateSetupKeyAutoGroups:
|
||||||
|
return "UpdateSetupKeyAutoGroups"
|
||||||
|
case UpdateSetupKeyExpiresAt:
|
||||||
|
return "UpdateSetupKeyExpiresAt"
|
||||||
|
default:
|
||||||
|
return "InvalidOperation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupKeyUpdateOperation operation object with type and values to be applied
|
||||||
|
type SetupKeyUpdateOperation struct {
|
||||||
|
Type SetupKeyUpdateOperationType
|
||||||
|
Values []string
|
||||||
|
}
|
||||||
|
|
||||||
// SetupKeyType is the type of setup key
|
// SetupKeyType is the type of setup key
|
||||||
type SetupKeyType string
|
type SetupKeyType string
|
||||||
|
|
||||||
@ -31,30 +67,38 @@ type SetupKey struct {
|
|||||||
Type SetupKeyType
|
Type SetupKeyType
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
ExpiresAt time.Time
|
ExpiresAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
// Revoked indicates whether the key was revoked or not (we don't remove them for tracking purposes)
|
// Revoked indicates whether the key was revoked or not (we don't remove them for tracking purposes)
|
||||||
Revoked bool
|
Revoked bool
|
||||||
// UsedTimes indicates how many times the key was used
|
// UsedTimes indicates how many times the key was used
|
||||||
UsedTimes int
|
UsedTimes int
|
||||||
// LastUsed last time the key was used for peer registration
|
// LastUsed last time the key was used for peer registration
|
||||||
LastUsed time.Time
|
LastUsed time.Time
|
||||||
|
// AutoGroups is a list of Group IDs that are auto assigned to a Peer when it uses this key to register
|
||||||
|
AutoGroups []string
|
||||||
}
|
}
|
||||||
|
|
||||||
//Copy copies SetupKey to a new object
|
// Copy copies SetupKey to a new object
|
||||||
func (key *SetupKey) Copy() *SetupKey {
|
func (key *SetupKey) Copy() *SetupKey {
|
||||||
|
if key.UpdatedAt.IsZero() {
|
||||||
|
key.UpdatedAt = key.CreatedAt
|
||||||
|
}
|
||||||
return &SetupKey{
|
return &SetupKey{
|
||||||
Id: key.Id,
|
Id: key.Id,
|
||||||
Key: key.Key,
|
Key: key.Key,
|
||||||
Name: key.Name,
|
Name: key.Name,
|
||||||
Type: key.Type,
|
Type: key.Type,
|
||||||
CreatedAt: key.CreatedAt,
|
CreatedAt: key.CreatedAt,
|
||||||
ExpiresAt: key.ExpiresAt,
|
ExpiresAt: key.ExpiresAt,
|
||||||
Revoked: key.Revoked,
|
UpdatedAt: key.UpdatedAt,
|
||||||
UsedTimes: key.UsedTimes,
|
Revoked: key.Revoked,
|
||||||
LastUsed: key.LastUsed,
|
UsedTimes: key.UsedTimes,
|
||||||
|
LastUsed: key.LastUsed,
|
||||||
|
AutoGroups: key.AutoGroups,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//IncrementUsage makes a copy of a key, increments the UsedTimes by 1 and sets LastUsed to now
|
// IncrementUsage makes a copy of a key, increments the UsedTimes by 1 and sets LastUsed to now
|
||||||
func (key *SetupKey) IncrementUsage() *SetupKey {
|
func (key *SetupKey) IncrementUsage() *SetupKey {
|
||||||
c := key.Copy()
|
c := key.Copy()
|
||||||
c.UsedTimes = c.UsedTimes + 1
|
c.UsedTimes = c.UsedTimes + 1
|
||||||
@ -83,24 +127,25 @@ func (key *SetupKey) IsOverUsed() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateSetupKey generates a new setup key
|
// GenerateSetupKey generates a new setup key
|
||||||
func GenerateSetupKey(name string, t SetupKeyType, validFor time.Duration) *SetupKey {
|
func GenerateSetupKey(name string, t SetupKeyType, validFor time.Duration, autoGroups []string) *SetupKey {
|
||||||
key := strings.ToUpper(uuid.New().String())
|
key := strings.ToUpper(uuid.New().String())
|
||||||
createdAt := time.Now()
|
|
||||||
return &SetupKey{
|
return &SetupKey{
|
||||||
Id: strconv.Itoa(int(Hash(key))),
|
Id: strconv.Itoa(int(Hash(key))),
|
||||||
Key: key,
|
Key: key,
|
||||||
Name: name,
|
Name: name,
|
||||||
Type: t,
|
Type: t,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: time.Now(),
|
||||||
ExpiresAt: createdAt.Add(validFor),
|
ExpiresAt: time.Now().Add(validFor),
|
||||||
Revoked: false,
|
UpdatedAt: time.Now(),
|
||||||
UsedTimes: 0,
|
Revoked: false,
|
||||||
|
UsedTimes: 0,
|
||||||
|
AutoGroups: autoGroups,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateDefaultSetupKey generates a default setup key
|
// GenerateDefaultSetupKey generates a default setup key
|
||||||
func GenerateDefaultSetupKey() *SetupKey {
|
func GenerateDefaultSetupKey() *SetupKey {
|
||||||
return GenerateSetupKey(DefaultSetupKeyName, SetupKeyReusable, DefaultSetupKeyDuration)
|
return GenerateSetupKey(DefaultSetupKeyName, SetupKeyReusable, DefaultSetupKeyDuration, []string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Hash(s string) uint32 {
|
func Hash(s string) uint32 {
|
||||||
@ -111,3 +156,127 @@ func Hash(s string) uint32 {
|
|||||||
}
|
}
|
||||||
return h.Sum32()
|
return h.Sum32()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSetupKey generates a new setup key with a given name, type, list of groups IDs to auto-assign to peers registered with this key,
|
||||||
|
// and adds it to the specified account. A list of autoGroups IDs can be empty.
|
||||||
|
func (am *DefaultAccountManager) CreateSetupKey(accountID string, keyName string, keyType SetupKeyType,
|
||||||
|
expiresIn time.Duration, autoGroups []string) (*SetupKey, error) {
|
||||||
|
am.mux.Lock()
|
||||||
|
defer am.mux.Unlock()
|
||||||
|
|
||||||
|
keyDuration := DefaultSetupKeyDuration
|
||||||
|
if expiresIn != 0 {
|
||||||
|
keyDuration = expiresIn
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range autoGroups {
|
||||||
|
if _, ok := account.Groups[group]; !ok {
|
||||||
|
return nil, fmt.Errorf("group %s doesn't exist", group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupKey := GenerateSetupKey(keyName, keyType, keyDuration, autoGroups)
|
||||||
|
account.SetupKeys[setupKey.Key] = setupKey
|
||||||
|
|
||||||
|
err = am.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed adding account key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return setupKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveSetupKey saves the provided SetupKey to the database overriding the existing one.
|
||||||
|
// Due to the unique nature of a SetupKey certain properties must not be overwritten
|
||||||
|
// (e.g. the key itself, creation date, ID, etc).
|
||||||
|
// These properties are overwritten: Name, AutoGroups, Revoked. The rest is copied from the existing key.
|
||||||
|
func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *SetupKey) (*SetupKey, error) {
|
||||||
|
am.mux.Lock()
|
||||||
|
defer am.mux.Unlock()
|
||||||
|
|
||||||
|
if keyToSave == nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "provided setup key to update is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldKey *SetupKey
|
||||||
|
for _, key := range account.SetupKeys {
|
||||||
|
if key.Id == keyToSave.Id {
|
||||||
|
oldKey = key.Copy()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if oldKey == nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "setup key not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// only auto groups, revoked status, and name can be updated for now
|
||||||
|
newKey := oldKey.Copy()
|
||||||
|
newKey.Name = keyToSave.Name
|
||||||
|
newKey.AutoGroups = keyToSave.AutoGroups
|
||||||
|
newKey.Revoked = keyToSave.Revoked
|
||||||
|
newKey.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
account.SetupKeys[newKey.Key] = newKey
|
||||||
|
|
||||||
|
if err = am.Store.SaveAccount(account); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newKey, am.updateAccountPeers(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSetupKeys returns a list of all setup keys of the account
|
||||||
|
func (am *DefaultAccountManager) ListSetupKeys(accountID string) ([]*SetupKey, error) {
|
||||||
|
am.mux.Lock()
|
||||||
|
defer am.mux.Unlock()
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]*SetupKey, 0, len(account.SetupKeys))
|
||||||
|
for _, key := range account.SetupKeys {
|
||||||
|
keys = append(keys, key.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSetupKey looks up a SetupKey by KeyID, returns NotFound error if not found.
|
||||||
|
func (am *DefaultAccountManager) GetSetupKey(accountID, keyID string) (*SetupKey, error) {
|
||||||
|
am.mux.Lock()
|
||||||
|
defer am.mux.Unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "account not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundKey *SetupKey
|
||||||
|
for _, key := range account.SetupKeys {
|
||||||
|
if key.Id == keyID {
|
||||||
|
foundKey = key.Copy()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundKey == nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "setup key not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the UpdatedAt field was introduced later, so there might be that some keys have a Zero value (e.g, null in the store file)
|
||||||
|
if foundKey.UpdatedAt.IsZero() {
|
||||||
|
foundKey.UpdatedAt = foundKey.CreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundKey, nil
|
||||||
|
}
|
||||||
|
@ -2,23 +2,159 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
||||||
|
manager, err := createManager(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := "test_user"
|
||||||
|
account, err := manager.GetOrCreateAccountByUser(userID, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = manager.SaveGroup(account.Id, &Group{
|
||||||
|
ID: "group_1",
|
||||||
|
Name: "group_name_1",
|
||||||
|
Peers: []string{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expiresIn := time.Hour
|
||||||
|
keyName := "my-test-key"
|
||||||
|
|
||||||
|
key, err := manager.CreateSetupKey(account.Id, keyName, SetupKeyReusable, expiresIn, []string{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
autoGroups := []string{"group_1", "group_2"}
|
||||||
|
newKeyName := "my-new-test-key"
|
||||||
|
revoked := true
|
||||||
|
newKey, err := manager.SaveSetupKey(account.Id, &SetupKey{
|
||||||
|
Id: key.Id,
|
||||||
|
Name: newKeyName,
|
||||||
|
Revoked: revoked,
|
||||||
|
AutoGroups: autoGroups,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertKey(t, newKey, newKeyName, revoked, "reusable", 0, key.CreatedAt, key.ExpiresAt,
|
||||||
|
key.Id, time.Now(), autoGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
|
||||||
|
manager, err := createManager(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := "test_user"
|
||||||
|
account, err := manager.GetOrCreateAccountByUser(userID, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = manager.SaveGroup(account.Id, &Group{
|
||||||
|
ID: "group_1",
|
||||||
|
Name: "group_name_1",
|
||||||
|
Peers: []string{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = manager.SaveGroup(account.Id, &Group{
|
||||||
|
ID: "group_2",
|
||||||
|
Name: "group_name_2",
|
||||||
|
Peers: []string{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
expectedKeyName string
|
||||||
|
expectedUsedTimes int
|
||||||
|
expectedType string
|
||||||
|
expectedGroups []string
|
||||||
|
expectedCreatedAt time.Time
|
||||||
|
expectedUpdatedAt time.Time
|
||||||
|
expectedExpiresAt time.Time
|
||||||
|
expectedFailure bool //indicates whether key creation should fail
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
expiresIn := time.Hour
|
||||||
|
testCase1 := testCase{
|
||||||
|
name: "Should Create Setup Key successfully",
|
||||||
|
expectedKeyName: "my-test-key",
|
||||||
|
expectedUsedTimes: 0,
|
||||||
|
expectedType: "reusable",
|
||||||
|
expectedGroups: []string{"group_1", "group_2"},
|
||||||
|
expectedCreatedAt: now,
|
||||||
|
expectedUpdatedAt: now,
|
||||||
|
expectedExpiresAt: now.Add(expiresIn),
|
||||||
|
expectedFailure: false,
|
||||||
|
}
|
||||||
|
testCase2 := testCase{
|
||||||
|
name: "Create Setup Key should fail because of unexistent group",
|
||||||
|
expectedKeyName: "my-test-key",
|
||||||
|
expectedGroups: []string{"FAKE"},
|
||||||
|
expectedFailure: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tCase := range []testCase{testCase1, testCase2} {
|
||||||
|
t.Run(tCase.name, func(t *testing.T) {
|
||||||
|
key, err := manager.CreateSetupKey(account.Id, tCase.expectedKeyName, SetupKeyReusable, expiresIn,
|
||||||
|
tCase.expectedGroups)
|
||||||
|
|
||||||
|
if tCase.expectedFailure {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected to fail")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertKey(t, key, tCase.expectedKeyName, false, tCase.expectedType, tCase.expectedUsedTimes,
|
||||||
|
tCase.expectedCreatedAt, tCase.expectedExpiresAt, strconv.Itoa(int(Hash(key.Key))),
|
||||||
|
tCase.expectedUpdatedAt, tCase.expectedGroups)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateDefaultSetupKey(t *testing.T) {
|
func TestGenerateDefaultSetupKey(t *testing.T) {
|
||||||
expectedName := "Default key"
|
expectedName := "Default key"
|
||||||
expectedRevoke := false
|
expectedRevoke := false
|
||||||
expectedType := "reusable"
|
expectedType := "reusable"
|
||||||
expectedUsedTimes := 0
|
expectedUsedTimes := 0
|
||||||
expectedCreatedAt := time.Now()
|
expectedCreatedAt := time.Now()
|
||||||
|
expectedUpdatedAt := time.Now()
|
||||||
expectedExpiresAt := time.Now().Add(24 * 30 * time.Hour)
|
expectedExpiresAt := time.Now().Add(24 * 30 * time.Hour)
|
||||||
|
var expectedAutoGroups []string
|
||||||
|
|
||||||
key := GenerateDefaultSetupKey()
|
key := GenerateDefaultSetupKey()
|
||||||
|
|
||||||
assertKey(t, key, expectedName, expectedRevoke, expectedType, expectedUsedTimes, expectedCreatedAt,
|
assertKey(t, key, expectedName, expectedRevoke, expectedType, expectedUsedTimes, expectedCreatedAt,
|
||||||
expectedExpiresAt, strconv.Itoa(int(Hash(key.Key))))
|
expectedExpiresAt, strconv.Itoa(int(Hash(key.Key))), expectedUpdatedAt, expectedAutoGroups)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,41 +165,44 @@ func TestGenerateSetupKey(t *testing.T) {
|
|||||||
expectedUsedTimes := 0
|
expectedUsedTimes := 0
|
||||||
expectedCreatedAt := time.Now()
|
expectedCreatedAt := time.Now()
|
||||||
expectedExpiresAt := time.Now().Add(time.Hour)
|
expectedExpiresAt := time.Now().Add(time.Hour)
|
||||||
|
expectedUpdatedAt := time.Now()
|
||||||
|
var expectedAutoGroups []string
|
||||||
|
|
||||||
key := GenerateSetupKey(expectedName, SetupKeyOneOff, time.Hour)
|
key := GenerateSetupKey(expectedName, SetupKeyOneOff, time.Hour, []string{})
|
||||||
|
|
||||||
assertKey(t, key, expectedName, expectedRevoke, expectedType, expectedUsedTimes, expectedCreatedAt, expectedExpiresAt, strconv.Itoa(int(Hash(key.Key))))
|
assertKey(t, key, expectedName, expectedRevoke, expectedType, expectedUsedTimes, expectedCreatedAt,
|
||||||
|
expectedExpiresAt, strconv.Itoa(int(Hash(key.Key))), expectedUpdatedAt, expectedAutoGroups)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetupKey_IsValid(t *testing.T) {
|
func TestSetupKey_IsValid(t *testing.T) {
|
||||||
validKey := GenerateSetupKey("valid key", SetupKeyOneOff, time.Hour)
|
validKey := GenerateSetupKey("valid key", SetupKeyOneOff, time.Hour, []string{})
|
||||||
if !validKey.IsValid() {
|
if !validKey.IsValid() {
|
||||||
t.Errorf("expected key to be valid, got invalid %v", validKey)
|
t.Errorf("expected key to be valid, got invalid %v", validKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// expired
|
// expired
|
||||||
expiredKey := GenerateSetupKey("invalid key", SetupKeyOneOff, -time.Hour)
|
expiredKey := GenerateSetupKey("invalid key", SetupKeyOneOff, -time.Hour, []string{})
|
||||||
if expiredKey.IsValid() {
|
if expiredKey.IsValid() {
|
||||||
t.Errorf("expected key to be invalid due to expiration, got valid %v", expiredKey)
|
t.Errorf("expected key to be invalid due to expiration, got valid %v", expiredKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// revoked
|
// revoked
|
||||||
revokedKey := GenerateSetupKey("invalid key", SetupKeyOneOff, time.Hour)
|
revokedKey := GenerateSetupKey("invalid key", SetupKeyOneOff, time.Hour, []string{})
|
||||||
revokedKey.Revoked = true
|
revokedKey.Revoked = true
|
||||||
if revokedKey.IsValid() {
|
if revokedKey.IsValid() {
|
||||||
t.Errorf("expected revoked key to be invalid, got valid %v", revokedKey)
|
t.Errorf("expected revoked key to be invalid, got valid %v", revokedKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overused
|
// overused
|
||||||
overUsedKey := GenerateSetupKey("invalid key", SetupKeyOneOff, time.Hour)
|
overUsedKey := GenerateSetupKey("invalid key", SetupKeyOneOff, time.Hour, []string{})
|
||||||
overUsedKey.UsedTimes = 1
|
overUsedKey.UsedTimes = 1
|
||||||
if overUsedKey.IsValid() {
|
if overUsedKey.IsValid() {
|
||||||
t.Errorf("expected overused key to be invalid, got valid %v", overUsedKey)
|
t.Errorf("expected overused key to be invalid, got valid %v", overUsedKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overused
|
// overused
|
||||||
reusableKey := GenerateSetupKey("valid key", SetupKeyReusable, time.Hour)
|
reusableKey := GenerateSetupKey("valid key", SetupKeyReusable, time.Hour, []string{})
|
||||||
reusableKey.UsedTimes = 99
|
reusableKey.UsedTimes = 99
|
||||||
if !reusableKey.IsValid() {
|
if !reusableKey.IsValid() {
|
||||||
t.Errorf("expected reusable key to be valid when used many times, got valid %v", reusableKey)
|
t.Errorf("expected reusable key to be valid when used many times, got valid %v", reusableKey)
|
||||||
@ -71,7 +210,8 @@ func TestSetupKey_IsValid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func assertKey(t *testing.T, key *SetupKey, expectedName string, expectedRevoke bool, expectedType string,
|
func assertKey(t *testing.T, key *SetupKey, expectedName string, expectedRevoke bool, expectedType string,
|
||||||
expectedUsedTimes int, expectedCreatedAt time.Time, expectedExpiresAt time.Time, expectedID string) {
|
expectedUsedTimes int, expectedCreatedAt time.Time, expectedExpiresAt time.Time, expectedID string,
|
||||||
|
expectedUpdatedAt time.Time, expectedAutoGroups []string) {
|
||||||
if key.Name != expectedName {
|
if key.Name != expectedName {
|
||||||
t.Errorf("expected setup key to have Name %v, got %v", expectedName, key.Name)
|
t.Errorf("expected setup key to have Name %v, got %v", expectedName, key.Name)
|
||||||
}
|
}
|
||||||
@ -92,6 +232,10 @@ func assertKey(t *testing.T, key *SetupKey, expectedName string, expectedRevoke
|
|||||||
t.Errorf("expected setup key to have ExpiresAt ~ %v, got %v", expectedExpiresAt, key.ExpiresAt)
|
t.Errorf("expected setup key to have ExpiresAt ~ %v, got %v", expectedExpiresAt, key.ExpiresAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key.UpdatedAt.Sub(expectedUpdatedAt).Round(time.Hour) != 0 {
|
||||||
|
t.Errorf("expected setup key to have UpdatedAt ~ %v, got %v", expectedUpdatedAt, key.UpdatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
if key.CreatedAt.Sub(expectedCreatedAt).Round(time.Hour) != 0 {
|
if key.CreatedAt.Sub(expectedCreatedAt).Round(time.Hour) != 0 {
|
||||||
t.Errorf("expected setup key to have CreatedAt ~ %v, got %v", expectedCreatedAt, key.CreatedAt)
|
t.Errorf("expected setup key to have CreatedAt ~ %v, got %v", expectedCreatedAt, key.CreatedAt)
|
||||||
}
|
}
|
||||||
@ -104,13 +248,19 @@ func assertKey(t *testing.T, key *SetupKey, expectedName string, expectedRevoke
|
|||||||
if key.Id != strconv.Itoa(int(Hash(key.Key))) {
|
if key.Id != strconv.Itoa(int(Hash(key.Key))) {
|
||||||
t.Errorf("expected key Id t= %v, got %v", expectedID, key.Id)
|
t.Errorf("expected key Id t= %v, got %v", expectedID, key.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(key.AutoGroups) != len(expectedAutoGroups) {
|
||||||
|
t.Errorf("expected key AutoGroups size=%d, got %d", len(expectedAutoGroups), len(key.AutoGroups))
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, key.AutoGroups, expectedAutoGroups, "expected key AutoGroups to be equal")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetupKey_Copy(t *testing.T) {
|
func TestSetupKey_Copy(t *testing.T) {
|
||||||
|
|
||||||
key := GenerateSetupKey("key name", SetupKeyOneOff, time.Hour)
|
key := GenerateSetupKey("key name", SetupKeyOneOff, time.Hour, []string{})
|
||||||
keyCopy := key.Copy()
|
keyCopy := key.Copy()
|
||||||
|
|
||||||
assertKey(t, keyCopy, key.Name, key.Revoked, string(key.Type), key.UsedTimes, key.CreatedAt, key.ExpiresAt, key.Id)
|
assertKey(t, keyCopy, key.Name, key.Revoked, string(key.Type), key.UsedTimes, key.CreatedAt, key.ExpiresAt, key.Id,
|
||||||
|
key.UpdatedAt, key.AutoGroups)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user