mirror of
https://github.com/netbirdio/netbird.git
synced 2025-08-19 03:16:58 +02:00
Merge branch 'main' into groups-get-account-refactoring
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -1484,6 +1485,17 @@ func (e *Engine) stopDNSServer() {
|
|||||||
|
|
||||||
// isChecksEqual checks if two slices of checks are equal.
|
// isChecksEqual checks if two slices of checks are equal.
|
||||||
func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool {
|
func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool {
|
||||||
|
for _, check := range checks {
|
||||||
|
sort.Slice(check.Files, func(i, j int) bool {
|
||||||
|
return check.Files[i] < check.Files[j]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, oCheck := range oChecks {
|
||||||
|
sort.Slice(oCheck.Files, func(i, j int) bool {
|
||||||
|
return oCheck.Files[i] < oCheck.Files[j]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return slices.EqualFunc(checks, oChecks, func(checks, oChecks *mgmProto.Checks) bool {
|
return slices.EqualFunc(checks, oChecks, func(checks, oChecks *mgmProto.Checks) bool {
|
||||||
return slices.Equal(checks.Files, oChecks.Files)
|
return slices.Equal(checks.Files, oChecks.Files)
|
||||||
})
|
})
|
||||||
|
@@ -1006,6 +1006,99 @@ func Test_ParseNATExternalIPMappings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_CheckFilesEqual(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
inputChecks1 []*mgmtProto.Checks
|
||||||
|
inputChecks2 []*mgmtProto.Checks
|
||||||
|
expectedBool bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Equal Files In Equal Order Should Return True",
|
||||||
|
inputChecks1: []*mgmtProto.Checks{
|
||||||
|
{
|
||||||
|
Files: []string{
|
||||||
|
"testfile1",
|
||||||
|
"testfile2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputChecks2: []*mgmtProto.Checks{
|
||||||
|
{
|
||||||
|
Files: []string{
|
||||||
|
"testfile1",
|
||||||
|
"testfile2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedBool: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Equal Files In Reverse Order Should Return True",
|
||||||
|
inputChecks1: []*mgmtProto.Checks{
|
||||||
|
{
|
||||||
|
Files: []string{
|
||||||
|
"testfile1",
|
||||||
|
"testfile2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputChecks2: []*mgmtProto.Checks{
|
||||||
|
{
|
||||||
|
Files: []string{
|
||||||
|
"testfile2",
|
||||||
|
"testfile1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedBool: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unequal Files Should Return False",
|
||||||
|
inputChecks1: []*mgmtProto.Checks{
|
||||||
|
{
|
||||||
|
Files: []string{
|
||||||
|
"testfile1",
|
||||||
|
"testfile2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputChecks2: []*mgmtProto.Checks{
|
||||||
|
{
|
||||||
|
Files: []string{
|
||||||
|
"testfile1",
|
||||||
|
"testfile3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedBool: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Compared With Empty Should Return False",
|
||||||
|
inputChecks1: []*mgmtProto.Checks{
|
||||||
|
{
|
||||||
|
Files: []string{
|
||||||
|
"testfile1",
|
||||||
|
"testfile2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputChecks2: []*mgmtProto.Checks{
|
||||||
|
{
|
||||||
|
Files: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedBool: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
result := isChecksEqual(testCase.inputChecks1, testCase.inputChecks2)
|
||||||
|
assert.Equal(t, testCase.expectedBool, result, "result should match expected bool")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey string, i int, mgmtAddr string, signalAddr string) (*Engine, error) {
|
func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey string, i int, mgmtAddr string, signalAddr string) (*Engine, error) {
|
||||||
key, err := wgtypes.GeneratePrivateKey()
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -1186,20 +1186,25 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) handleInactivityExpirationSettings(ctx context.Context, account *Account, oldSettings, newSettings *Settings, userID, accountID string) error {
|
func (am *DefaultAccountManager) handleInactivityExpirationSettings(ctx context.Context, account *Account, oldSettings, newSettings *Settings, userID, accountID string) error {
|
||||||
if oldSettings.PeerInactivityExpirationEnabled != newSettings.PeerInactivityExpirationEnabled {
|
|
||||||
event := activity.AccountPeerInactivityExpirationEnabled
|
if newSettings.PeerInactivityExpirationEnabled {
|
||||||
if !newSettings.PeerInactivityExpirationEnabled {
|
if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration {
|
||||||
event = activity.AccountPeerInactivityExpirationDisabled
|
oldSettings.PeerInactivityExpiration = newSettings.PeerInactivityExpiration
|
||||||
am.peerInactivityExpiry.Cancel(ctx, []string{accountID})
|
|
||||||
} else {
|
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountPeerInactivityExpirationDurationUpdated, nil)
|
||||||
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
|
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
|
||||||
}
|
}
|
||||||
am.StoreEvent(ctx, userID, accountID, accountID, event, nil)
|
} else {
|
||||||
}
|
if oldSettings.PeerInactivityExpirationEnabled != newSettings.PeerInactivityExpirationEnabled {
|
||||||
|
event := activity.AccountPeerInactivityExpirationEnabled
|
||||||
if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration {
|
if !newSettings.PeerInactivityExpirationEnabled {
|
||||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountPeerInactivityExpirationDurationUpdated, nil)
|
event = activity.AccountPeerInactivityExpirationDisabled
|
||||||
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
|
am.peerInactivityExpiry.Cancel(ctx, []string{accountID})
|
||||||
|
} else {
|
||||||
|
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
|
||||||
|
}
|
||||||
|
am.StoreEvent(ctx, userID, accountID, accountID, event, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -2323,7 +2328,7 @@ func (am *DefaultAccountManager) OnPeerDisconnected(ctx context.Context, account
|
|||||||
|
|
||||||
err = am.MarkPeerConnected(ctx, peerPubKey, false, nil, account)
|
err = am.MarkPeerConnected(ctx, peerPubKey, false, nil, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Warnf("failed marking peer as connected %s %v", peerPubKey, err)
|
log.WithContext(ctx).Warnf("failed marking peer as disconnected %s %v", peerPubKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -2339,6 +2344,9 @@ func (am *DefaultAccountManager) SyncPeerMeta(ctx context.Context, peerPubKey st
|
|||||||
unlock := am.Store.AcquireReadLockByUID(ctx, accountID)
|
unlock := am.Store.AcquireReadLockByUID(ctx, accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
|
unlockPeer := am.Store.AcquireWriteLockByUID(ctx, peerPubKey)
|
||||||
|
defer unlockPeer()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(ctx, accountID)
|
account, err := am.Store.GetAccount(ctx, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -521,19 +521,6 @@ components:
|
|||||||
SetupKeyRequest:
|
SetupKeyRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
name:
|
|
||||||
description: Setup Key name
|
|
||||||
type: string
|
|
||||||
example: Default key
|
|
||||||
type:
|
|
||||||
description: Setup key type, one-off for single time usage and reusable
|
|
||||||
type: string
|
|
||||||
example: reusable
|
|
||||||
expires_in:
|
|
||||||
description: Expiration time in seconds, 0 will mean the key never expires
|
|
||||||
type: integer
|
|
||||||
minimum: 0
|
|
||||||
example: 86400
|
|
||||||
revoked:
|
revoked:
|
||||||
description: Setup key revocation status
|
description: Setup key revocation status
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -544,21 +531,9 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
example: "ch8i4ug6lnn4g9hqv7m0"
|
example: "ch8i4ug6lnn4g9hqv7m0"
|
||||||
usage_limit:
|
|
||||||
description: A number of times this key can be used. The value of 0 indicates the unlimited usage.
|
|
||||||
type: integer
|
|
||||||
example: 0
|
|
||||||
ephemeral:
|
|
||||||
description: Indicate that the peer will be ephemeral or not
|
|
||||||
type: boolean
|
|
||||||
example: true
|
|
||||||
required:
|
required:
|
||||||
- name
|
|
||||||
- type
|
|
||||||
- expires_in
|
|
||||||
- revoked
|
- revoked
|
||||||
- auto_groups
|
- auto_groups
|
||||||
- usage_limit
|
|
||||||
CreateSetupKeyRequest:
|
CreateSetupKeyRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@@ -1098,23 +1098,8 @@ type SetupKeyRequest struct {
|
|||||||
// AutoGroups List of group IDs to auto-assign to peers registered with this key
|
// AutoGroups List of group IDs to auto-assign to peers registered with this key
|
||||||
AutoGroups []string `json:"auto_groups"`
|
AutoGroups []string `json:"auto_groups"`
|
||||||
|
|
||||||
// Ephemeral Indicate that the peer will be ephemeral or not
|
|
||||||
Ephemeral *bool `json:"ephemeral,omitempty"`
|
|
||||||
|
|
||||||
// ExpiresIn Expiration time in seconds, 0 will mean the key never expires
|
|
||||||
ExpiresIn int `json:"expires_in"`
|
|
||||||
|
|
||||||
// Name Setup Key name
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// Revoked Setup key revocation status
|
// Revoked Setup key revocation status
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
|
|
||||||
// Type Setup key type, one-off for single time usage and reusable
|
|
||||||
Type string `json:"type"`
|
|
||||||
|
|
||||||
// UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage.
|
|
||||||
UsageLimit int `json:"usage_limit"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// User defines model for User.
|
// User defines model for User.
|
||||||
|
@@ -137,11 +137,6 @@ func (h *SetupKeysHandler) UpdateSetupKey(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Name == "" {
|
|
||||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "setup key name field is invalid: %s", req.Name), w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.AutoGroups == nil {
|
if req.AutoGroups == nil {
|
||||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "setup key AutoGroups field is invalid"), w)
|
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "setup key AutoGroups field is invalid"), w)
|
||||||
return
|
return
|
||||||
@@ -150,7 +145,6 @@ func (h *SetupKeysHandler) UpdateSetupKey(w http.ResponseWriter, r *http.Request
|
|||||||
newKey := &server.SetupKey{}
|
newKey := &server.SetupKey{}
|
||||||
newKey.AutoGroups = req.AutoGroups
|
newKey.AutoGroups = req.AutoGroups
|
||||||
newKey.Revoked = req.Revoked
|
newKey.Revoked = req.Revoked
|
||||||
newKey.Name = req.Name
|
|
||||||
newKey.Id = keyID
|
newKey.Id = keyID
|
||||||
|
|
||||||
newKey, err = h.accountManager.SaveSetupKey(r.Context(), accountID, newKey, userID)
|
newKey, err = h.accountManager.SaveSetupKey(r.Context(), accountID, newKey, userID)
|
||||||
|
@@ -168,6 +168,8 @@ func (am *DefaultAccountManager) updatePeerStatusAndLocation(ctx context.Context
|
|||||||
|
|
||||||
account.UpdatePeer(peer)
|
account.UpdatePeer(peer)
|
||||||
|
|
||||||
|
log.WithContext(ctx).Tracef("saving peer status for peer %s is connected: %t", peer.ID, connected)
|
||||||
|
|
||||||
err := am.Store.SavePeerStatus(account.Id, peer.ID, *newStatus)
|
err := am.Store.SavePeerStatus(account.Id, peer.ID, *newStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to save peer status: %w", err)
|
return false, fmt.Errorf("failed to save peer status: %w", err)
|
||||||
@@ -665,6 +667,7 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync PeerSync, ac
|
|||||||
|
|
||||||
updated := peer.UpdateMetaIfNew(sync.Meta)
|
updated := peer.UpdateMetaIfNew(sync.Meta)
|
||||||
if updated {
|
if updated {
|
||||||
|
log.WithContext(ctx).Tracef("peer %s metadata updated", peer.ID)
|
||||||
err = am.Store.SavePeer(ctx, account.Id, peer)
|
err = am.Store.SavePeer(ctx, account.Id, peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, fmt.Errorf("failed to save peer: %w", err)
|
return nil, nil, nil, fmt.Errorf("failed to save peer: %w", err)
|
||||||
|
@@ -12,9 +12,10 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -276,7 +277,7 @@ func (am *DefaultAccountManager) CreateSetupKey(ctx context.Context, accountID s
|
|||||||
// SaveSetupKey saves the provided SetupKey to the database overriding the existing one.
|
// 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
|
// Due to the unique nature of a SetupKey certain properties must not be overwritten
|
||||||
// (e.g. the key itself, creation date, ID, etc).
|
// (e.g. the key itself, creation date, ID, etc).
|
||||||
// These properties are overwritten: Name, AutoGroups, Revoked. The rest is copied from the existing key.
|
// These properties are overwritten: AutoGroups, Revoked (only from false to true), and the UpdatedAt. The rest is copied from the existing key.
|
||||||
func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID string, keyToSave *SetupKey, userID string) (*SetupKey, error) {
|
func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID string, keyToSave *SetupKey, userID string) (*SetupKey, error) {
|
||||||
if keyToSave == nil {
|
if keyToSave == nil {
|
||||||
return nil, status.Errorf(status.InvalidArgument, "provided setup key to update is nil")
|
return nil, status.Errorf(status.InvalidArgument, "provided setup key to update is nil")
|
||||||
@@ -312,9 +313,12 @@ func (am *DefaultAccountManager) SaveSetupKey(ctx context.Context, accountID str
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// only auto groups, revoked status, and name can be updated for now
|
if oldKey.Revoked && !keyToSave.Revoked {
|
||||||
|
return status.Errorf(status.InvalidArgument, "can't un-revoke a revoked setup key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// only auto groups, revoked status (from false to true) can be updated
|
||||||
newKey = oldKey.Copy()
|
newKey = oldKey.Copy()
|
||||||
newKey.Name = keyToSave.Name
|
|
||||||
newKey.AutoGroups = keyToSave.AutoGroups
|
newKey.AutoGroups = keyToSave.AutoGroups
|
||||||
newKey.Revoked = keyToSave.Revoked
|
newKey.Revoked = keyToSave.Revoked
|
||||||
newKey.UpdatedAt = time.Now().UTC()
|
newKey.UpdatedAt = time.Now().UTC()
|
||||||
|
@@ -56,11 +56,9 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
autoGroups := []string{"group_1", "group_2"}
|
autoGroups := []string{"group_1", "group_2"}
|
||||||
newKeyName := "my-new-test-key"
|
|
||||||
revoked := true
|
revoked := true
|
||||||
newKey, err := manager.SaveSetupKey(context.Background(), account.Id, &SetupKey{
|
newKey, err := manager.SaveSetupKey(context.Background(), account.Id, &SetupKey{
|
||||||
Id: key.Id,
|
Id: key.Id,
|
||||||
Name: newKeyName,
|
|
||||||
Revoked: revoked,
|
Revoked: revoked,
|
||||||
AutoGroups: autoGroups,
|
AutoGroups: autoGroups,
|
||||||
}, userID)
|
}, userID)
|
||||||
@@ -68,7 +66,7 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertKey(t, newKey, newKeyName, revoked, "reusable", 0, key.CreatedAt, key.ExpiresAt,
|
assertKey(t, newKey, keyName, revoked, "reusable", 0, key.CreatedAt, key.ExpiresAt,
|
||||||
key.Id, time.Now().UTC(), autoGroups, true)
|
key.Id, time.Now().UTC(), autoGroups, true)
|
||||||
|
|
||||||
// check the corresponding events that should have been generated
|
// check the corresponding events that should have been generated
|
||||||
@@ -76,7 +74,7 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
|||||||
|
|
||||||
assert.NotNil(t, ev)
|
assert.NotNil(t, ev)
|
||||||
assert.Equal(t, account.Id, ev.AccountID)
|
assert.Equal(t, account.Id, ev.AccountID)
|
||||||
assert.Equal(t, newKeyName, ev.Meta["name"])
|
assert.Equal(t, keyName, ev.Meta["name"])
|
||||||
assert.Equal(t, fmt.Sprint(key.Type), fmt.Sprint(ev.Meta["type"]))
|
assert.Equal(t, fmt.Sprint(key.Type), fmt.Sprint(ev.Meta["type"]))
|
||||||
assert.NotEmpty(t, ev.Meta["key"])
|
assert.NotEmpty(t, ev.Meta["key"])
|
||||||
assert.Equal(t, userID, ev.InitiatorID)
|
assert.Equal(t, userID, ev.InitiatorID)
|
||||||
@@ -89,7 +87,6 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
|||||||
autoGroups = append(autoGroups, groupAll.ID)
|
autoGroups = append(autoGroups, groupAll.ID)
|
||||||
_, err = manager.SaveSetupKey(context.Background(), account.Id, &SetupKey{
|
_, err = manager.SaveSetupKey(context.Background(), account.Id, &SetupKey{
|
||||||
Id: key.Id,
|
Id: key.Id,
|
||||||
Name: newKeyName,
|
|
||||||
Revoked: revoked,
|
Revoked: revoked,
|
||||||
AutoGroups: autoGroups,
|
AutoGroups: autoGroups,
|
||||||
}, userID)
|
}, userID)
|
||||||
@@ -449,3 +446,31 @@ func TestSetupKeyAccountPeersUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountManager_CreateSetupKey_ShouldNotAllowToUpdateRevokedKey(t *testing.T) {
|
||||||
|
manager, err := createManager(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := "testingUser"
|
||||||
|
account, err := manager.GetOrCreateAccountByUser(context.Background(), userID, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := manager.CreateSetupKey(context.Background(), account.Id, "testName", SetupKeyReusable, time.Hour, nil, SetupKeyUnlimitedUsage, userID, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// revoke the key
|
||||||
|
updateKey := key.Copy()
|
||||||
|
updateKey.Revoked = true
|
||||||
|
_, err = manager.SaveSetupKey(context.Background(), account.Id, updateKey, userID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// re-activate revoked key
|
||||||
|
updateKey.Revoked = false
|
||||||
|
_, err = manager.SaveSetupKey(context.Background(), account.Id, updateKey, userID)
|
||||||
|
assert.Error(t, err, "should not allow to update revoked key")
|
||||||
|
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user