Merge branch 'feature/add_rest_endpoints_for_pat' into feature/add_pat_middleware

# Conflicts:
#	management/server/mock_server/account_mock.go
This commit is contained in:
Pascal Fischer 2023-03-30 14:02:58 +02:00
commit 9b000b89d5
6 changed files with 322 additions and 106 deletions

View File

@ -68,8 +68,10 @@ type AccountManager interface {
GetNetworkMap(peerID string) (*NetworkMap, error)
GetPeerNetwork(peerID string) (*Network, error)
AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error)
AddPATToUser(accountID string, userID string, pat *PersonalAccessToken) error
DeletePAT(accountID string, userID string, tokenID string) error
CreatePAT(accountID string, executingUserID string, targetUserId string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error)
DeletePAT(accountID string, executingUserID string, targetUserId string, tokenID string) error
GetPAT(accountID string, executingUserID string, targetUserId string, tokenID string) (*PersonalAccessToken, error)
GetAllPATs(accountID string, executingUserID string, targetUserId string) ([]*PersonalAccessToken, error)
UpdatePeerSSHKey(peerID string, sshKey string) error
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
GetGroup(accountId, groupID string) (*Group, error)

View File

@ -46,17 +46,19 @@ func (h *PATHandler) GetAllTokens(w http.ResponseWriter, r *http.Request) {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
return
}
if userID != user.Id {
util.WriteErrorResponse("User not authorized to get tokens", http.StatusUnauthorized, w)
pats, err := h.accountManager.GetAllPATs(account.Id, user.Id, userID)
if err != nil {
util.WriteError(err, w)
return
}
var pats []*api.PersonalAccessToken
for _, pat := range account.Users[userID].PATs {
pats = append(pats, toPATResponse(pat))
var patResponse []*api.PersonalAccessToken
for _, pat := range pats {
patResponse = append(patResponse, toPATResponse(pat))
}
util.WriteJSONObject(w, pats)
util.WriteJSONObject(w, patResponse)
}
// GetToken is HTTP GET handler that returns a personal access token for the given user
@ -69,15 +71,11 @@ func (h *PATHandler) GetToken(w http.ResponseWriter, r *http.Request) {
}
vars := mux.Vars(r)
userID := vars["userId"]
if len(userID) == 0 {
targetUserID := vars["userId"]
if len(targetUserID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
return
}
if userID != user.Id {
util.WriteErrorResponse("User not authorized to get token", http.StatusUnauthorized, w)
return
}
tokenID := vars["tokenId"]
if len(tokenID) == 0 {
@ -85,15 +83,9 @@ func (h *PATHandler) GetToken(w http.ResponseWriter, r *http.Request) {
return
}
user = account.Users[userID]
if user == nil {
util.WriteError(status.Errorf(status.NotFound, "user not found"), w)
return
}
pat := user.PATs[tokenID]
if pat == nil {
util.WriteError(status.Errorf(status.NotFound, "PAT not found"), w)
pat, err := h.accountManager.GetPAT(account.Id, user.Id, targetUserID, tokenID)
if err != nil {
util.WriteError(err, w)
return
}
@ -110,15 +102,11 @@ func (h *PATHandler) CreateToken(w http.ResponseWriter, r *http.Request) {
}
vars := mux.Vars(r)
userID := vars["userId"]
if len(userID) == 0 {
targetUserID := vars["userId"]
if len(targetUserID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
return
}
if userID != user.Id {
util.WriteErrorResponse("User not authorized to create token", http.StatusUnauthorized, w)
return
}
var req api.PostApiUsersUserIdTokensJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
@ -127,23 +115,7 @@ func (h *PATHandler) CreateToken(w http.ResponseWriter, r *http.Request) {
return
}
if req.Name == "" {
util.WriteErrorResponse("name can't be empty", http.StatusBadRequest, w)
return
}
if req.ExpiresIn < 1 || req.ExpiresIn > 365 {
util.WriteErrorResponse("expiration has to be between 1 and 365", http.StatusBadRequest, w)
return
}
pat, err := server.CreateNewPAT(req.Name, req.ExpiresIn, user.Id)
if err != nil {
util.WriteError(err, w)
return
}
err = h.accountManager.AddPATToUser(account.Id, userID, &pat.PersonalAccessToken)
pat, err := h.accountManager.CreatePAT(account.Id, user.Id, targetUserID, req.Name, req.ExpiresIn)
if err != nil {
util.WriteError(err, w)
return
@ -162,15 +134,11 @@ func (h *PATHandler) DeleteToken(w http.ResponseWriter, r *http.Request) {
}
vars := mux.Vars(r)
userID := vars["userId"]
if len(userID) == 0 {
targetUserID := vars["userId"]
if len(targetUserID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid user ID"), w)
return
}
if userID != user.Id {
util.WriteErrorResponse("User not authorized to delete token", http.StatusUnauthorized, w)
return
}
tokenID := vars["tokenId"]
if len(tokenID) == 0 {
@ -178,7 +146,7 @@ func (h *PATHandler) DeleteToken(w http.ResponseWriter, r *http.Request) {
return
}
err = h.accountManager.DeletePAT(account.Id, userID, tokenID)
err = h.accountManager.DeletePAT(account.Id, user.Id, targetUserID, tokenID)
if err != nil {
util.WriteError(err, w)
return

View File

@ -63,31 +63,55 @@ var testAccount = &server.Account{
func initPATTestData() *PATHandler {
return &PATHandler{
accountManager: &mock_server.MockAccountManager{
AddPATToUserFunc: func(accountID string, userID string, pat *server.PersonalAccessToken) error {
CreatePATFunc: func(accountID string, executingUserID string, targetUserID string, tokenName string, expiresIn int) (*server.PersonalAccessTokenGenerated, error) {
if accountID != existingAccountID {
return status.Errorf(status.NotFound, "account with ID %s not found", accountID)
return nil, status.Errorf(status.NotFound, "account with ID %s not found", accountID)
}
if userID != existingUserID {
return status.Errorf(status.NotFound, "user with ID %s not found", userID)
if targetUserID != existingUserID {
return nil, status.Errorf(status.NotFound, "user with ID %s not found", targetUserID)
}
return nil
return &server.PersonalAccessTokenGenerated{
PlainToken: "nbp_z1pvsg2wP3EzmEou4S679KyTNhov632eyrXe",
PersonalAccessToken: server.PersonalAccessToken{},
}, nil
},
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
return testAccount, testAccount.Users[existingUserID], nil
},
DeletePATFunc: func(accountID string, userID string, tokenID string) error {
DeletePATFunc: func(accountID string, executingUserID string, targetUserID string, tokenID string) error {
if accountID != existingAccountID {
return status.Errorf(status.NotFound, "account with ID %s not found", accountID)
}
if userID != existingUserID {
return status.Errorf(status.NotFound, "user with ID %s not found", userID)
if targetUserID != existingUserID {
return status.Errorf(status.NotFound, "user with ID %s not found", targetUserID)
}
if tokenID != existingTokenID {
return status.Errorf(status.NotFound, "token with ID %s not found", tokenID)
}
return nil
},
GetPATFunc: func(accountID string, executingUserID string, targetUserID string, tokenID string) (*server.PersonalAccessToken, error) {
if accountID != existingAccountID {
return nil, status.Errorf(status.NotFound, "account with ID %s not found", accountID)
}
if targetUserID != existingUserID {
return nil, status.Errorf(status.NotFound, "user with ID %s not found", targetUserID)
}
if tokenID != existingTokenID {
return nil, status.Errorf(status.NotFound, "token with ID %s not found", tokenID)
}
return testAccount.Users[existingUserID].PATs[existingTokenID], nil
},
GetAllPATsFunc: func(accountID string, executingUserID string, targetUserID string) ([]*server.PersonalAccessToken, error) {
if accountID != existingAccountID {
return nil, status.Errorf(status.NotFound, "account with ID %s not found", accountID)
}
if targetUserID != existingUserID {
return nil, status.Errorf(status.NotFound, "user with ID %s not found", targetUserID)
}
return []*server.PersonalAccessToken{testAccount.Users[existingUserID].PATs[existingTokenID], testAccount.Users[existingUserID].PATs["token2"]}, nil
},
},
claimsExtractor: jwtclaims.NewClaimsExtractor(
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {

View File

@ -61,8 +61,10 @@ type MockAccountManager struct {
SaveSetupKeyFunc func(accountID string, key *server.SetupKey, userID string) (*server.SetupKey, error)
ListSetupKeysFunc func(accountID, userID string) ([]*server.SetupKey, error)
SaveUserFunc func(accountID, userID string, user *server.User) (*server.UserInfo, error)
AddPATToUserFunc func(accountID string, userID string, pat *server.PersonalAccessToken) error
DeletePATFunc func(accountID string, userID string, tokenID string) error
CreatePATFunc func(accountID string, executingUserID string, targetUserId string, tokenName string, expiresIn int) (*server.PersonalAccessTokenGenerated, error)
DeletePATFunc func(accountID string, executingUserID string, targetUserId string, tokenID string) error
GetPATFunc func(accountID string, executingUserID string, targetUserId string, tokenID string) (*server.PersonalAccessToken, error)
GetAllPATsFunc func(accountID string, executingUserID string, targetUserId string) ([]*server.PersonalAccessToken, error)
GetNameServerGroupFunc func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
CreateNameServerGroupFunc func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string) (*nbdns.NameServerGroup, error)
SaveNameServerGroupFunc func(accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
@ -195,22 +197,38 @@ func (am *MockAccountManager) MarkPATUsed(pat string) error {
return status.Errorf(codes.Unimplemented, "method MarkPATUsed is not implemented")
}
// AddPATToUser mock implementation of AddPATToUser from server.AccountManager interface
func (am *MockAccountManager) AddPATToUser(accountID string, userID string, pat *server.PersonalAccessToken) error {
if am.AddPATToUserFunc != nil {
return am.AddPATToUserFunc(accountID, userID, pat)
// CreatePAT mock implementation of GetPAT from server.AccountManager interface
func (am *MockAccountManager) CreatePAT(accountID string, executingUserID string, targetUserID string, name string, expiresIn int) (*server.PersonalAccessTokenGenerated, error) {
if am.CreatePATFunc != nil {
return am.CreatePATFunc(accountID, executingUserID, targetUserID, name, expiresIn)
}
return status.Errorf(codes.Unimplemented, "method AddPATToUser is not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CreatePAT is not implemented")
}
// DeletePAT mock implementation of DeletePAT from server.AccountManager interface
func (am *MockAccountManager) DeletePAT(accountID string, userID string, tokenID string) error {
func (am *MockAccountManager) DeletePAT(accountID string, executingUserID string, targetUserID string, tokenID string) error {
if am.DeletePATFunc != nil {
return am.DeletePATFunc(accountID, userID, tokenID)
return am.DeletePATFunc(accountID, executingUserID, targetUserID, tokenID)
}
return status.Errorf(codes.Unimplemented, "method DeletePAT is not implemented")
}
// GetPAT mock implementation of GetPAT from server.AccountManager interface
func (am *MockAccountManager) GetPAT(accountID string, executingUserID string, targetUserID string, tokenID string) (*server.PersonalAccessToken, error) {
if am.GetPATFunc != nil {
return am.GetPATFunc(accountID, executingUserID, targetUserID, tokenID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetPAT is not implemented")
}
// GetAllPATs mock implementation of GetAllPATs from server.AccountManager interface
func (am *MockAccountManager) GetAllPATs(accountID string, executingUserID string, targetUserID string) ([]*server.PersonalAccessToken, error) {
if am.GetAllPATsFunc != nil {
return am.GetAllPATsFunc(accountID, executingUserID, targetUserID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetAllPATs is not implemented")
}
// GetNetworkMap mock implementation of GetNetworkMap from server.AccountManager interface
func (am *MockAccountManager) GetNetworkMap(peerKey string) (*server.NetworkMap, error) {
if am.GetNetworkMapFunc != nil {

View File

@ -193,37 +193,63 @@ func (am *DefaultAccountManager) CreateUser(accountID, userID string, invite *Us
}
// AddPATToUser takes the userID and the accountID the user belongs to and assigns a provided PersonalAccessToken to that user
func (am *DefaultAccountManager) AddPATToUser(accountID string, userID string, pat *PersonalAccessToken) error {
// CreatePAT creates a new PAT for the given user
func (am *DefaultAccountManager) CreatePAT(accountID string, executingUserID string, targetUserId string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
if tokenName == "" {
return nil, status.Errorf(status.InvalidArgument, "token name can't be empty")
}
if expiresIn < 1 || expiresIn > 365 {
return nil, status.Errorf(status.InvalidArgument, "expiration has to be between 1 and 365")
}
if executingUserID != targetUserId {
return nil, status.Errorf(status.PermissionDenied, "no permission to create PAT for this user")
}
account, err := am.Store.GetAccount(accountID)
if err != nil {
return err
return nil, err
}
user := account.Users[userID]
if user == nil {
return status.Errorf(status.NotFound, "user not found")
targetUser := account.Users[targetUserId]
if targetUser == nil {
return nil, status.Errorf(status.NotFound, "targetUser not found")
}
user.PATs[pat.ID] = pat
pat, err := CreateNewPAT(tokenName, expiresIn, targetUser.Id)
if err != nil {
return nil, status.Errorf(status.Internal, "failed to create PAT: %v", err)
}
return am.Store.SaveAccount(account)
targetUser.PATs[pat.ID] = &pat.PersonalAccessToken
err = am.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(status.Internal, "failed to save account: %v", err)
}
return pat, nil
}
// DeletePAT deletes a specific PAT from a user
func (am *DefaultAccountManager) DeletePAT(accountID string, userID string, tokenID string) error {
func (am *DefaultAccountManager) DeletePAT(accountID string, executingUserID string, targetUserID string, tokenID string) error {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return err
if executingUserID != targetUserID {
return status.Errorf(status.PermissionDenied, "no permission to delete PAT for this user")
}
user := account.Users[userID]
account, err := am.Store.GetAccount(accountID)
if err != nil {
return status.Errorf(status.NotFound, "account not found: %s", err)
}
user := account.Users[targetUserID]
if user == nil {
return status.Errorf(status.NotFound, "user not found")
}
@ -235,15 +261,73 @@ func (am *DefaultAccountManager) DeletePAT(accountID string, userID string, toke
err = am.Store.DeleteTokenID2UserIDIndex(pat.ID)
if err != nil {
return err
return status.Errorf(status.Internal, "Failed to delete token id index: %s", err)
}
err = am.Store.DeleteHashedPAT2TokenIDIndex(pat.HashedToken)
if err != nil {
return err
return status.Errorf(status.Internal, "Failed to delete hashed token index: %s", err)
}
delete(user.PATs, tokenID)
return am.Store.SaveAccount(account)
err = am.Store.SaveAccount(account)
if err != nil {
return status.Errorf(status.Internal, "Failed to save account: %s", err)
}
return nil
}
// GetPAT returns a specific PAT from a user
func (am *DefaultAccountManager) GetPAT(accountID string, executingUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
if executingUserID != targetUserID {
return nil, status.Errorf(status.PermissionDenied, "no permission to get PAT for this user")
}
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, status.Errorf(status.NotFound, "account not found: %s", err)
}
user := account.Users[targetUserID]
if user == nil {
return nil, status.Errorf(status.NotFound, "user not found")
}
pat := user.PATs[tokenID]
if pat == nil {
return nil, status.Errorf(status.NotFound, "PAT not found")
}
return pat, nil
}
// GetAllPATs returns all PATs for a user
func (am *DefaultAccountManager) GetAllPATs(accountID string, executingUserID string, targetUserID string) ([]*PersonalAccessToken, error) {
unlock := am.Store.AcquireAccountLock(accountID)
defer unlock()
if executingUserID != targetUserID {
return nil, status.Errorf(status.PermissionDenied, "no permission to get PAT for this user")
}
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, status.Errorf(status.NotFound, "account not found: %s", err)
}
user := account.Users[targetUserID]
if user == nil {
return nil, status.Errorf(status.NotFound, "user not found")
}
var pats []*PersonalAccessToken
for _, pat := range user.PATs {
pats = append(pats, pat)
}
return pats, nil
}
// SaveUser saves updates a given user. If the user doesn't exit it will throw status.NotFound error.

View File

@ -9,11 +9,18 @@ import (
const (
mockAccountID = "accountID"
mockUserID = "userID"
mockTokenID = "tokenID"
mockToken = "SoMeHaShEdToKeN"
mockTargetUserId = "targetUserID"
mockTokenID1 = "tokenID1"
mockToken1 = "SoMeHaShEdToKeN1"
mockTokenID2 = "tokenID2"
mockToken2 = "SoMeHaShEdToKeN2"
mockTokenName = "tokenName"
mockEmptyTokenName = ""
mockExpiresIn = 7
mockWrongExpiresIn = 4506
)
func TestUser_AddPATToUser(t *testing.T) {
func TestUser_CreatePAT_ForSameUser(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")
@ -26,24 +33,19 @@ func TestUser_AddPATToUser(t *testing.T) {
Store: store,
}
pat := PersonalAccessToken{
ID: mockTokenID,
HashedToken: mockToken,
}
err = am.AddPATToUser(mockAccountID, mockUserID, &pat)
pat, err := am.CreatePAT(mockAccountID, mockUserID, mockUserID, mockTokenName, mockExpiresIn)
if err != nil {
t.Fatalf("Error when adding PAT to user: %s", err)
}
fileStore := am.Store.(*FileStore)
tokenID := fileStore.HashedPAT2TokenID[mockToken]
tokenID := fileStore.HashedPAT2TokenID[pat.HashedToken]
if tokenID == "" {
t.Fatal("GetTokenIDByHashedToken failed after adding PAT")
}
assert.Equal(t, mockTokenID, tokenID)
assert.Equal(t, pat.ID, tokenID)
userID := fileStore.TokenID2UserID[tokenID]
if userID == "" {
@ -52,15 +54,66 @@ func TestUser_AddPATToUser(t *testing.T) {
assert.Equal(t, mockUserID, userID)
}
func TestUser_CreatePAT_ForDifferentUser(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")
err := store.SaveAccount(account)
if err != nil {
t.Fatalf("Error when saving account: %s", err)
}
am := DefaultAccountManager{
Store: store,
}
_, err = am.CreatePAT(mockAccountID, mockUserID, mockTargetUserId, mockTokenName, mockExpiresIn)
assert.Errorf(t, err, "Creating PAT for different user should thorw error")
}
func TestUser_CreatePAT_WithWrongExpiration(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")
err := store.SaveAccount(account)
if err != nil {
t.Fatalf("Error when saving account: %s", err)
}
am := DefaultAccountManager{
Store: store,
}
_, err = am.CreatePAT(mockAccountID, mockUserID, mockUserID, mockTokenName, mockWrongExpiresIn)
assert.Errorf(t, err, "Wrong expiration should thorw error")
}
func TestUser_CreatePAT_WithEmptyName(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")
err := store.SaveAccount(account)
if err != nil {
t.Fatalf("Error when saving account: %s", err)
}
am := DefaultAccountManager{
Store: store,
}
_, err = am.CreatePAT(mockAccountID, mockUserID, mockUserID, mockEmptyTokenName, mockExpiresIn)
assert.Errorf(t, err, "Wrong expiration should thorw error")
}
func TestUser_DeletePAT(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")
account.Users[mockUserID] = &User{
Id: mockUserID,
PATs: map[string]*PersonalAccessToken{
mockTokenID: {
ID: mockTokenID,
HashedToken: mockToken,
mockTokenID1: {
ID: mockTokenID1,
HashedToken: mockToken1,
},
},
}
@ -73,12 +126,79 @@ func TestUser_DeletePAT(t *testing.T) {
Store: store,
}
err = am.DeletePAT(mockAccountID, mockUserID, mockTokenID)
err = am.DeletePAT(mockAccountID, mockUserID, mockUserID, mockTokenID1)
if err != nil {
t.Fatalf("Error when adding PAT to user: %s", err)
}
assert.Nil(t, store.Accounts[mockAccountID].Users[mockUserID].PATs[mockTokenID])
assert.Empty(t, store.HashedPAT2TokenID[mockToken])
assert.Empty(t, store.TokenID2UserID[mockTokenID])
assert.Nil(t, store.Accounts[mockAccountID].Users[mockUserID].PATs[mockTokenID1])
assert.Empty(t, store.HashedPAT2TokenID[mockToken1])
assert.Empty(t, store.TokenID2UserID[mockTokenID1])
}
func TestUser_GetPAT(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")
account.Users[mockUserID] = &User{
Id: mockUserID,
PATs: map[string]*PersonalAccessToken{
mockTokenID1: {
ID: mockTokenID1,
HashedToken: mockToken1,
},
},
}
err := store.SaveAccount(account)
if err != nil {
t.Fatalf("Error when saving account: %s", err)
}
am := DefaultAccountManager{
Store: store,
}
pat, err := am.GetPAT(mockAccountID, mockUserID, mockUserID, mockTokenID1)
if err != nil {
t.Fatalf("Error when adding PAT to user: %s", err)
}
assert.Equal(t, mockTokenID1, pat.ID)
assert.Equal(t, mockToken1, pat.HashedToken)
}
func TestUser_GetAllPATs(t *testing.T) {
store := newStore(t)
account := newAccountWithId(mockAccountID, mockUserID, "")
account.Users[mockUserID] = &User{
Id: mockUserID,
PATs: map[string]*PersonalAccessToken{
mockTokenID1: {
ID: mockTokenID1,
HashedToken: mockToken1,
},
mockTokenID2: {
ID: mockTokenID2,
HashedToken: mockToken2,
},
},
}
err := store.SaveAccount(account)
if err != nil {
t.Fatalf("Error when saving account: %s", err)
}
am := DefaultAccountManager{
Store: store,
}
pats, err := am.GetAllPATs(mockAccountID, mockUserID, mockUserID)
if err != nil {
t.Fatalf("Error when adding PAT to user: %s", err)
}
assert.Equal(t, 2, len(pats))
assert.Equal(t, mockTokenID1, pats[0].ID)
assert.Equal(t, mockToken1, pats[0].HashedToken)
assert.Equal(t, mockTokenID2, pats[1].ID)
assert.Equal(t, mockToken2, pats[1].HashedToken)
}