mirror of
https://github.com/netbirdio/netbird.git
synced 2025-05-08 18:24:42 +02:00
Add Account HTTP API (#691)
Extend HTTP API with Account endpoints to configure global peer login expiration. GET /api/accounts PUT /api/account/{id}/ The GET endpoint returns an array of accounts with always one account in the list. No exceptions. The PUT endpoint updates account settings: PeerLoginExpiration and PeerLoginExpirationEnabled. PeerLoginExpiration is a duration in seconds after which peers' logins will expire.
This commit is contained in:
parent
d31219ba89
commit
fe63a64b6e
@ -97,6 +97,7 @@ type AccountManager interface {
|
|||||||
SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error
|
SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error
|
||||||
GetPeer(accountID, peerID, userID string) (*Peer, error)
|
GetPeer(accountID, peerID, userID string) (*Peer, error)
|
||||||
UpdatePeerLastLogin(peerID string) error
|
UpdatePeerLastLogin(peerID string) error
|
||||||
|
UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultAccountManager struct {
|
type DefaultAccountManager struct {
|
||||||
@ -315,6 +316,12 @@ func (a *Account) GetPeers() []*Peer {
|
|||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSettings saves new account settings
|
||||||
|
func (a *Account) UpdateSettings(update *Settings) *Account {
|
||||||
|
a.Settings = update.Copy()
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
// UpdatePeer saves new or replaces existing peer
|
// UpdatePeer saves new or replaces existing peer
|
||||||
func (a *Account) UpdatePeer(update *Peer) {
|
func (a *Account) UpdatePeer(update *Peer) {
|
||||||
a.Peers[update.ID] = update
|
a.Peers[update.ID] = update
|
||||||
@ -596,6 +603,61 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
|
|||||||
return am, nil
|
return am, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAccountSettings updates Account settings.
|
||||||
|
// Only users with role UserRoleAdmin can update the account.
|
||||||
|
// User that performs the update has to belong to the account.
|
||||||
|
// Returns an updated Account
|
||||||
|
func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error) {
|
||||||
|
|
||||||
|
halfYearLimit := 180 * 24 * time.Hour
|
||||||
|
if newSettings.PeerLoginExpiration > halfYearLimit {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be larger than 180 days")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newSettings.PeerLoginExpiration < time.Hour {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be smaller than one hour")
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccountByUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := account.FindUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.IsAdmin() {
|
||||||
|
return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account")
|
||||||
|
}
|
||||||
|
|
||||||
|
oldSettings := account.Settings
|
||||||
|
if oldSettings.PeerLoginExpirationEnabled != newSettings.PeerLoginExpirationEnabled {
|
||||||
|
event := activity.AccountPeerLoginExpirationEnabled
|
||||||
|
if !newSettings.PeerLoginExpirationEnabled {
|
||||||
|
event = activity.AccountPeerLoginExpirationDisabled
|
||||||
|
}
|
||||||
|
am.storeEvent(userID, accountID, accountID, event, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldSettings.PeerLoginExpiration != newSettings.PeerLoginExpiration {
|
||||||
|
am.storeEvent(userID, accountID, accountID, activity.AccountPeerLoginExpirationDurationUpdated, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAccount := account.UpdateSettings(newSettings)
|
||||||
|
|
||||||
|
err = am.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedAccount, nil
|
||||||
|
}
|
||||||
|
|
||||||
// newAccount creates a new Account with a generated ID and generated default setup keys.
|
// newAccount creates a new Account with a generated ID and generated default setup keys.
|
||||||
// If ID is already in use (due to collision) we try one more time before returning error
|
// If ID is already in use (due to collision) we try one more time before returning error
|
||||||
func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, error) {
|
func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, error) {
|
||||||
|
@ -1283,6 +1283,48 @@ func hasNilField(x interface{}) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func TestDefaultAccountManager_DefaultAccountSettings(t *testing.T) {
|
||||||
|
manager, err := createManager(t)
|
||||||
|
require.NoError(t, err, "unable to create account manager")
|
||||||
|
|
||||||
|
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||||
|
require.NoError(t, err, "unable to create an account")
|
||||||
|
|
||||||
|
assert.NotNil(t, account.Settings)
|
||||||
|
assert.Equal(t, account.Settings.PeerLoginExpirationEnabled, true)
|
||||||
|
assert.Equal(t, account.Settings.PeerLoginExpiration, 24*time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) {
|
||||||
|
manager, err := createManager(t)
|
||||||
|
require.NoError(t, err, "unable to create account manager")
|
||||||
|
|
||||||
|
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||||
|
require.NoError(t, err, "unable to create an account")
|
||||||
|
|
||||||
|
updated, err := manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||||
|
PeerLoginExpiration: time.Hour,
|
||||||
|
PeerLoginExpirationEnabled: false})
|
||||||
|
require.NoError(t, err, "expecting to update account settings successfully but got error")
|
||||||
|
assert.False(t, updated.Settings.PeerLoginExpirationEnabled)
|
||||||
|
assert.Equal(t, updated.Settings.PeerLoginExpiration, time.Hour)
|
||||||
|
|
||||||
|
account, err = manager.GetAccountByUserOrAccountID("", account.Id, "")
|
||||||
|
require.NoError(t, err, "unable to get account by ID")
|
||||||
|
|
||||||
|
assert.False(t, account.Settings.PeerLoginExpirationEnabled)
|
||||||
|
assert.Equal(t, account.Settings.PeerLoginExpiration, time.Hour)
|
||||||
|
|
||||||
|
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||||
|
PeerLoginExpiration: time.Second,
|
||||||
|
PeerLoginExpirationEnabled: false})
|
||||||
|
require.Error(t, err, "expecting to fail when providing PeerLoginExpiration less than one hour")
|
||||||
|
|
||||||
|
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||||
|
PeerLoginExpiration: time.Hour * 24 * 181,
|
||||||
|
PeerLoginExpirationEnabled: false})
|
||||||
|
require.Error(t, err, "expecting to fail when providing PeerLoginExpiration more than 180 days")
|
||||||
|
}
|
||||||
|
|
||||||
func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||||
store, err := createStore(t)
|
store, err := createStore(t)
|
||||||
|
@ -71,6 +71,12 @@ const (
|
|||||||
NameserverGroupDeleted
|
NameserverGroupDeleted
|
||||||
// NameserverGroupUpdated indicates that a user updated a nameservers group
|
// NameserverGroupUpdated indicates that a user updated a nameservers group
|
||||||
NameserverGroupUpdated
|
NameserverGroupUpdated
|
||||||
|
// AccountPeerLoginExpirationEnabled indicates that a user enabled peer login expiration for the account
|
||||||
|
AccountPeerLoginExpirationEnabled
|
||||||
|
// AccountPeerLoginExpirationDisabled indicates that a user disabled peer login expiration for the account
|
||||||
|
AccountPeerLoginExpirationDisabled
|
||||||
|
// AccountPeerLoginExpirationDurationUpdated indicates that a user updated peer login expiration duration for the account
|
||||||
|
AccountPeerLoginExpirationDurationUpdated
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -144,6 +150,12 @@ const (
|
|||||||
NameserverGroupDeletedMessage string = "Nameserver group deleted"
|
NameserverGroupDeletedMessage string = "Nameserver group deleted"
|
||||||
// NameserverGroupUpdatedMessage is a human-readable text message of the NameserverGroupUpdated activity
|
// NameserverGroupUpdatedMessage is a human-readable text message of the NameserverGroupUpdated activity
|
||||||
NameserverGroupUpdatedMessage string = "Nameserver group updated"
|
NameserverGroupUpdatedMessage string = "Nameserver group updated"
|
||||||
|
// AccountPeerLoginExpirationEnabledMessage is a human-readable text message of the AccountPeerLoginExpirationEnabled activity
|
||||||
|
AccountPeerLoginExpirationEnabledMessage string = "Peer login expiration enabled for the account"
|
||||||
|
// AccountPeerLoginExpirationDisabledMessage is a human-readable text message of the AccountPeerLoginExpirationDisabled activity
|
||||||
|
AccountPeerLoginExpirationDisabledMessage string = "Peer login expiration disabled for the account"
|
||||||
|
// AccountPeerLoginExpirationDurationUpdatedMessage is a human-readable text message of the AccountPeerLoginExpirationDurationUpdated activity
|
||||||
|
AccountPeerLoginExpirationDurationUpdatedMessage string = "Peer login expiration duration updated"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Activity that triggered an Event
|
// Activity that triggered an Event
|
||||||
@ -222,6 +234,12 @@ func (a Activity) Message() string {
|
|||||||
return NameserverGroupDeletedMessage
|
return NameserverGroupDeletedMessage
|
||||||
case NameserverGroupUpdated:
|
case NameserverGroupUpdated:
|
||||||
return NameserverGroupUpdatedMessage
|
return NameserverGroupUpdatedMessage
|
||||||
|
case AccountPeerLoginExpirationEnabled:
|
||||||
|
return AccountPeerLoginExpirationEnabledMessage
|
||||||
|
case AccountPeerLoginExpirationDisabled:
|
||||||
|
return AccountPeerLoginExpirationDisabledMessage
|
||||||
|
case AccountPeerLoginExpirationDurationUpdated:
|
||||||
|
return AccountPeerLoginExpirationDurationUpdatedMessage
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN_ACTIVITY"
|
return "UNKNOWN_ACTIVITY"
|
||||||
}
|
}
|
||||||
@ -300,6 +318,12 @@ func (a Activity) StringCode() string {
|
|||||||
return "nameserver.group.delete"
|
return "nameserver.group.delete"
|
||||||
case NameserverGroupUpdated:
|
case NameserverGroupUpdated:
|
||||||
return "nameserver.group.update"
|
return "nameserver.group.update"
|
||||||
|
case AccountPeerLoginExpirationDurationUpdated:
|
||||||
|
return "account.settings.peer.login.expiration.update"
|
||||||
|
case AccountPeerLoginExpirationEnabled:
|
||||||
|
return "account.setting.peer.login.expiration.enable"
|
||||||
|
case AccountPeerLoginExpirationDisabled:
|
||||||
|
return "account.setting.peer.login.expiration.disable"
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN_ACTIVITY"
|
return "UNKNOWN_ACTIVITY"
|
||||||
}
|
}
|
||||||
|
96
management/server/http/accounts.go
Normal file
96
management/server/http/accounts.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/util"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Accounts is a handler that handles the server.Account HTTP endpoints
|
||||||
|
type Accounts struct {
|
||||||
|
accountManager server.AccountManager
|
||||||
|
claimsExtractor *jwtclaims.ClaimsExtractor
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccounts creates a new Accounts HTTP handler
|
||||||
|
func NewAccounts(accountManager server.AccountManager, authCfg AuthCfg) *Accounts {
|
||||||
|
return &Accounts{
|
||||||
|
accountManager: accountManager,
|
||||||
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||||
|
jwtclaims.WithAudience(authCfg.Audience),
|
||||||
|
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountsHandler is HTTP GET handler that returns a list of accounts. Effectively returns just a single account.
|
||||||
|
func (h *Accounts) GetAccountsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.IsAdmin() {
|
||||||
|
util.WriteError(status.Errorf(status.PermissionDenied, "the user has no permission to access account data"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toAccountResponse(account)
|
||||||
|
util.WriteJSONObject(w, []*api.Account{resp})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccountHandler is HTTP PUT handler that updates the provided account. Updates only account settings (server.Settings)
|
||||||
|
func (h *Accounts) UpdateAccountHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims := h.claimsExtractor.FromRequestContext(r)
|
||||||
|
_, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
accountID := vars["id"]
|
||||||
|
if len(accountID) == 0 {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "invalid accountID ID"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PutApiAccountsIdJSONBody
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAccount, err := h.accountManager.UpdateAccountSettings(accountID, user.Id, &server.Settings{
|
||||||
|
PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled,
|
||||||
|
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toAccountResponse(updatedAccount)
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toAccountResponse(account *server.Account) *api.Account {
|
||||||
|
return &api.Account{
|
||||||
|
Id: account.Id,
|
||||||
|
Settings: api.AccountSettings{
|
||||||
|
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
|
||||||
|
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
181
management/server/http/accounts_test.go
Normal file
181
management/server/http/accounts_test.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initAccountsTestData(account *server.Account, admin *server.User) *Accounts {
|
||||||
|
return &Accounts{
|
||||||
|
accountManager: &mock_server.MockAccountManager{
|
||||||
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
|
return account, admin, nil
|
||||||
|
},
|
||||||
|
UpdateAccountSettingsFunc: func(accountID, userID string, newSettings *server.Settings) (*server.Account, error) {
|
||||||
|
halfYearLimit := 180 * 24 * time.Hour
|
||||||
|
if newSettings.PeerLoginExpiration > halfYearLimit {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be larger than 180 days")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newSettings.PeerLoginExpiration < time.Hour {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be smaller than one hour")
|
||||||
|
}
|
||||||
|
|
||||||
|
accCopy := account.Copy()
|
||||||
|
accCopy.UpdateSettings(newSettings)
|
||||||
|
return accCopy, nil
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||||
|
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
|
||||||
|
return jwtclaims.AuthorizationClaims{
|
||||||
|
UserId: "test_user",
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
AccountId: "test_account",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccounts_AccountsHandler(t *testing.T) {
|
||||||
|
|
||||||
|
accountID := "test_account"
|
||||||
|
adminUser := server.NewAdminUser("test_user")
|
||||||
|
|
||||||
|
handler := initAccountsTestData(&server.Account{
|
||||||
|
Id: accountID,
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
Network: server.NewNetwork(),
|
||||||
|
Users: map[string]*server.User{
|
||||||
|
adminUser.Id: adminUser,
|
||||||
|
},
|
||||||
|
Settings: &server.Settings{
|
||||||
|
PeerLoginExpirationEnabled: false,
|
||||||
|
PeerLoginExpiration: time.Hour,
|
||||||
|
},
|
||||||
|
}, adminUser)
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
expectedStatus int
|
||||||
|
expectedBody bool
|
||||||
|
expectedID string
|
||||||
|
expectedArray bool
|
||||||
|
expectedSettings api.AccountSettings
|
||||||
|
requestType string
|
||||||
|
requestPath string
|
||||||
|
requestBody io.Reader
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "GetAccounts OK",
|
||||||
|
expectedBody: true,
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/accounts",
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedSettings: api.AccountSettings{
|
||||||
|
PeerLoginExpiration: int(time.Hour.Seconds()),
|
||||||
|
PeerLoginExpirationEnabled: false,
|
||||||
|
},
|
||||||
|
expectedArray: true,
|
||||||
|
expectedID: accountID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PutAccount OK",
|
||||||
|
expectedBody: true,
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/accounts/" + accountID,
|
||||||
|
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": true}}"),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedSettings: api.AccountSettings{
|
||||||
|
PeerLoginExpiration: 15552000,
|
||||||
|
PeerLoginExpirationEnabled: true,
|
||||||
|
},
|
||||||
|
expectedArray: false,
|
||||||
|
expectedID: accountID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Update account failure with high peer_login_expiration more than 180 days",
|
||||||
|
expectedBody: true,
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/accounts/" + accountID,
|
||||||
|
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552001,\"peer_login_expiration_enabled\": true}}"),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedArray: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Update account failure with peer_login_expiration less than an hour",
|
||||||
|
expectedBody: true,
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/accounts/" + accountID,
|
||||||
|
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 3599,\"peer_login_expiration_enabled\": true}}"),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedArray: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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/accounts", handler.GetAccountsHandler).Methods("GET")
|
||||||
|
router.HandleFunc("/api/accounts/{id}", handler.UpdateAccountHandler).Methods("PUT")
|
||||||
|
router.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
res := recorder.Result()
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if status := recorder.Code; status != tc.expectedStatus {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, tc.expectedStatus)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedStatus != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectedBody {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("I don't know what I expected; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual *api.Account
|
||||||
|
if tc.expectedArray {
|
||||||
|
var got []*api.Account
|
||||||
|
if err = json.Unmarshal(content, &got); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, got, 1)
|
||||||
|
actual = got[0]
|
||||||
|
} else {
|
||||||
|
if err = json.Unmarshal(content, &actual); err != nil {
|
||||||
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectedID, actual.Id)
|
||||||
|
assert.Equal(t, tc.expectedSettings, actual.Settings)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -20,8 +20,31 @@ tags:
|
|||||||
description: Interact with and view information about DNS configuration.
|
description: Interact with and view information about DNS configuration.
|
||||||
- name: Events
|
- name: Events
|
||||||
description: View information about the account and network events.
|
description: View information about the account and network events.
|
||||||
|
- name: Accounts
|
||||||
|
description: View information about the accounts.
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
Account:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: Account ID
|
||||||
|
type: string
|
||||||
|
settings:
|
||||||
|
$ref: '#/components/schemas/AccountSettings'
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- settings
|
||||||
|
AccountSettings:
|
||||||
|
properties:
|
||||||
|
peer_login_expiration_enabled:
|
||||||
|
description: Enables or disables peer login expiration globally. After peer's login has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login).
|
||||||
|
type: boolean
|
||||||
|
peer_login_expiration:
|
||||||
|
description: Period of time after which peer login expires (seconds).
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- peer_login_expiration_enabled
|
||||||
|
- peer_login_expiration
|
||||||
User:
|
User:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -606,6 +629,68 @@ components:
|
|||||||
security:
|
security:
|
||||||
- BearerAuth: [ ]
|
- BearerAuth: [ ]
|
||||||
paths:
|
paths:
|
||||||
|
/api/accounts:
|
||||||
|
get:
|
||||||
|
summary: Returns a list of accounts of a user. Always returns a list of one account. Only available for admin users.
|
||||||
|
tags: [ Accounts ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A JSON array of accounts
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Account'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
/api/accounts/{id}:
|
||||||
|
put:
|
||||||
|
summary: Update information about an account
|
||||||
|
tags: [ Accounts ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Account ID
|
||||||
|
requestBody:
|
||||||
|
description: update an account
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
settings:
|
||||||
|
$ref: '#/components/schemas/AccountSettings'
|
||||||
|
required:
|
||||||
|
- settings
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: An Account object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Account'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
/api/users:
|
/api/users:
|
||||||
get:
|
get:
|
||||||
summary: Returns a list of all users
|
summary: Returns a list of all users
|
||||||
|
@ -134,6 +134,22 @@ const (
|
|||||||
UserStatusInvited UserStatus = "invited"
|
UserStatusInvited UserStatus = "invited"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Account defines model for Account.
|
||||||
|
type Account struct {
|
||||||
|
// Id Account ID
|
||||||
|
Id string `json:"id"`
|
||||||
|
Settings AccountSettings `json:"settings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountSettings defines model for AccountSettings.
|
||||||
|
type AccountSettings struct {
|
||||||
|
// PeerLoginExpiration Period of time after which peer login expires (seconds).
|
||||||
|
PeerLoginExpiration int `json:"peer_login_expiration"`
|
||||||
|
|
||||||
|
// PeerLoginExpirationEnabled Enables or disables peer login expiration globally. After peer's login has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login).
|
||||||
|
PeerLoginExpirationEnabled bool `json:"peer_login_expiration_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
// DNSSettings defines model for DNSSettings.
|
// DNSSettings defines model for DNSSettings.
|
||||||
type DNSSettings struct {
|
type DNSSettings struct {
|
||||||
// DisabledManagementGroups Groups whose DNS management is disabled
|
// DisabledManagementGroups Groups whose DNS management is disabled
|
||||||
@ -617,6 +633,11 @@ type UserRequest struct {
|
|||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutApiAccountsIdJSONBody defines parameters for PutApiAccountsId.
|
||||||
|
type PutApiAccountsIdJSONBody struct {
|
||||||
|
Settings AccountSettings `json:"settings"`
|
||||||
|
}
|
||||||
|
|
||||||
// PatchApiDnsNameserversIdJSONBody defines parameters for PatchApiDnsNameserversId.
|
// PatchApiDnsNameserversIdJSONBody defines parameters for PatchApiDnsNameserversId.
|
||||||
type PatchApiDnsNameserversIdJSONBody = []NameserverGroupPatchOperation
|
type PatchApiDnsNameserversIdJSONBody = []NameserverGroupPatchOperation
|
||||||
|
|
||||||
@ -682,6 +703,9 @@ type PutApiRulesIdJSONBody struct {
|
|||||||
Sources *[]string `json:"sources,omitempty"`
|
Sources *[]string `json:"sources,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutApiAccountsIdJSONRequestBody defines body for PutApiAccountsId for application/json ContentType.
|
||||||
|
type PutApiAccountsIdJSONRequestBody PutApiAccountsIdJSONBody
|
||||||
|
|
||||||
// PostApiDnsNameserversJSONRequestBody defines body for PostApiDnsNameservers for application/json ContentType.
|
// PostApiDnsNameserversJSONRequestBody defines body for PostApiDnsNameservers for application/json ContentType.
|
||||||
type PostApiDnsNameserversJSONRequestBody = NameserverGroupRequest
|
type PostApiDnsNameserversJSONRequestBody = NameserverGroupRequest
|
||||||
|
|
||||||
|
@ -50,6 +50,10 @@ func APIHandler(accountManager s.AccountManager, appMetrics telemetry.AppMetrics
|
|||||||
nameserversHandler := NewNameservers(accountManager, authCfg)
|
nameserversHandler := NewNameservers(accountManager, authCfg)
|
||||||
eventsHandler := NewEvents(accountManager, authCfg)
|
eventsHandler := NewEvents(accountManager, authCfg)
|
||||||
dnsSettingsHandler := NewDNSSettings(accountManager, authCfg)
|
dnsSettingsHandler := NewDNSSettings(accountManager, authCfg)
|
||||||
|
accountsHandler := NewAccounts(accountManager, authCfg)
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/accounts/{id}", accountsHandler.UpdateAccountHandler).Methods("PUT", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/accounts", accountsHandler.GetAccountsHandler).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
apiHandler.HandleFunc("/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/peers/{id}", peersHandler.HandlePeer).
|
apiHandler.HandleFunc("/peers/{id}", peersHandler.HandlePeer).
|
||||||
|
@ -70,6 +70,7 @@ type MockAccountManager struct {
|
|||||||
GetPeerFunc func(accountID, peerID, userID string) (*server.Peer, error)
|
GetPeerFunc func(accountID, peerID, userID string) (*server.Peer, error)
|
||||||
GetAccountByPeerIDFunc func(peerID string) (*server.Account, error)
|
GetAccountByPeerIDFunc func(peerID string) (*server.Account, error)
|
||||||
UpdatePeerLastLoginFunc func(peerID string) error
|
UpdatePeerLastLoginFunc func(peerID string) error
|
||||||
|
UpdateAccountSettingsFunc func(accountID, userID string, newSettings *server.Settings) (*server.Account, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||||
@ -553,3 +554,11 @@ func (am *MockAccountManager) UpdatePeerLastLogin(peerID string) error {
|
|||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method UpdatePeerLastLogin is not implemented")
|
return status.Errorf(codes.Unimplemented, "method UpdatePeerLastLogin is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAccountSettings mocks UpdateAccountSettings of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) UpdateAccountSettings(accountID, userID string, newSettings *server.Settings) (*server.Account, error) {
|
||||||
|
if am.UpdateAccountSettingsFunc != nil {
|
||||||
|
return am.UpdateAccountSettingsFunc(accountID, userID, newSettings)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method UpdateAccountSettings is not implemented")
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user