mirror of
https://github.com/netbirdio/netbird.git
synced 2025-07-15 22:05:17 +02:00
1267 lines
32 KiB
Go
1267 lines
32 KiB
Go
//go:build component
|
|
|
|
package http
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/netbirdio/netbird/management/server"
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
|
"github.com/netbirdio/netbird/management/server/geolocation"
|
|
"github.com/netbirdio/netbird/management/server/http/api"
|
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
|
)
|
|
|
|
const (
|
|
testAccountId = "testAccountId"
|
|
testPeerId = "testPeerId"
|
|
testGroupId = "testGroupId"
|
|
testKeyId = "testKeyId"
|
|
|
|
testUserId = "testUserId"
|
|
testAdminId = "testAdminId"
|
|
testOwnerId = "testOwnerId"
|
|
testServiceUserId = "testServiceUserId"
|
|
testServiceAdminId = "testServiceAdminId"
|
|
blockedUserId = "blockedUserId"
|
|
otherUserId = "otherUserId"
|
|
invalidToken = "invalidToken"
|
|
|
|
newKeyName = "newKey"
|
|
newGroupId = "newGroupId"
|
|
expiresIn = 3600
|
|
revokedKeyId = "revokedKeyId"
|
|
expiredKeyId = "expiredKeyId"
|
|
|
|
existingKeyName = "existingKey"
|
|
)
|
|
|
|
func Test_SetupKeys_Create(t *testing.T) {
|
|
truePointer := true
|
|
|
|
users := []struct {
|
|
name string
|
|
userId string
|
|
expectResponse bool
|
|
}{
|
|
{
|
|
name: "Regular user",
|
|
userId: testUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin user",
|
|
userId: testAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Owner user",
|
|
userId: testOwnerId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Regular service user",
|
|
userId: testServiceUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin service user",
|
|
userId: testServiceAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Blocked user",
|
|
userId: blockedUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Other user",
|
|
userId: otherUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Invalid token",
|
|
userId: invalidToken,
|
|
expectResponse: false,
|
|
},
|
|
}
|
|
|
|
tt := []struct {
|
|
name string
|
|
expectedStatus int
|
|
expectedResponse *api.SetupKey
|
|
requestBody *api.CreateSetupKeyRequest
|
|
requestType string
|
|
requestPath string
|
|
userId string
|
|
}{
|
|
{
|
|
name: "Create Setup Key",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/setup-keys",
|
|
requestBody: &api.CreateSetupKeyRequest{
|
|
AutoGroups: nil,
|
|
ExpiresIn: expiresIn,
|
|
Name: newKeyName,
|
|
Type: "reusable",
|
|
UsageLimit: 0,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: newKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 0,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Create Setup Key with already existing name",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/setup-keys",
|
|
requestBody: &api.CreateSetupKeyRequest{
|
|
AutoGroups: nil,
|
|
ExpiresIn: expiresIn,
|
|
Name: existingKeyName,
|
|
Type: "one-off",
|
|
UsageLimit: 0,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "one-off",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Create Setup Key as on-off with more than one usage",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/setup-keys",
|
|
requestBody: &api.CreateSetupKeyRequest{
|
|
AutoGroups: nil,
|
|
ExpiresIn: expiresIn,
|
|
Name: newKeyName,
|
|
Type: "one-off",
|
|
UsageLimit: 3,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: newKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "one-off",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Create Setup Key with expiration in the past",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/setup-keys",
|
|
requestBody: &api.CreateSetupKeyRequest{
|
|
AutoGroups: nil,
|
|
ExpiresIn: -expiresIn,
|
|
Name: newKeyName,
|
|
Type: "one-off",
|
|
UsageLimit: 0,
|
|
},
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
|
expectedResponse: nil,
|
|
},
|
|
{
|
|
name: "Create Setup Key with AutoGroups that do exist",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/setup-keys",
|
|
requestBody: &api.CreateSetupKeyRequest{
|
|
AutoGroups: []string{testGroupId},
|
|
ExpiresIn: expiresIn,
|
|
Name: newKeyName,
|
|
Type: "reusable",
|
|
UsageLimit: 1,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: newKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Create Setup Key for ephemeral peers",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/setup-keys",
|
|
requestBody: &api.CreateSetupKeyRequest{
|
|
AutoGroups: []string{},
|
|
ExpiresIn: expiresIn,
|
|
Name: newKeyName,
|
|
Type: "reusable",
|
|
Ephemeral: &truePointer,
|
|
UsageLimit: 1,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{},
|
|
Ephemeral: true,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: newKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Create Setup Key with AutoGroups that do not exist",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/setup-keys",
|
|
requestBody: &api.CreateSetupKeyRequest{
|
|
AutoGroups: []string{"someGroupID"},
|
|
ExpiresIn: expiresIn,
|
|
Name: newKeyName,
|
|
Type: "reusable",
|
|
UsageLimit: 0,
|
|
},
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
|
expectedResponse: nil,
|
|
},
|
|
{
|
|
name: "Create Setup Key",
|
|
requestType: http.MethodPost,
|
|
requestPath: "/api/setup-keys",
|
|
requestBody: &api.CreateSetupKeyRequest{
|
|
AutoGroups: nil,
|
|
ExpiresIn: expiresIn,
|
|
Name: newKeyName,
|
|
Type: "reusable",
|
|
UsageLimit: 0,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: newKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 0,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
for _, user := range users {
|
|
t.Run(user.name+" - "+tc.name, func(t *testing.T) {
|
|
apiHandler, am, done := buildApiBlackBoxWithDBState(t, "testdata/setup_keys.sql", nil)
|
|
|
|
body, err := json.Marshal(tc.requestBody)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal request body: %v", err)
|
|
}
|
|
req := buildRequest(t, body, tc.requestType, tc.requestPath, user.userId)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
apiHandler.ServeHTTP(recorder, req)
|
|
|
|
content, expectResponse := readResponse(t, recorder, tc.expectedStatus, user.expectResponse)
|
|
if !expectResponse {
|
|
return
|
|
}
|
|
got := &api.SetupKey{}
|
|
if err := json.Unmarshal(content, &got); err != nil {
|
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
|
}
|
|
|
|
validateCreatedKey(t, tc.expectedResponse, got)
|
|
|
|
key, err := am.GetSetupKey(context.Background(), testAccountId, testUserId, got.Id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
validateCreatedKey(t, tc.expectedResponse, toResponseBody(key))
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SetupKeys_Update(t *testing.T) {
|
|
users := []struct {
|
|
name string
|
|
userId string
|
|
expectResponse bool
|
|
}{
|
|
{
|
|
name: "Regular user",
|
|
userId: testUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin user",
|
|
userId: testAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Owner user",
|
|
userId: testOwnerId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Regular service user",
|
|
userId: testServiceUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin service user",
|
|
userId: testServiceAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Blocked user",
|
|
userId: blockedUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Other user",
|
|
userId: otherUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Invalid token",
|
|
userId: invalidToken,
|
|
expectResponse: false,
|
|
},
|
|
}
|
|
|
|
tt := []struct {
|
|
name string
|
|
expectedStatus int
|
|
expectedResponse *api.SetupKey
|
|
requestBody *api.SetupKeyRequest
|
|
requestType string
|
|
requestPath string
|
|
requestId string
|
|
}{
|
|
{
|
|
name: "Add existing Group to existing Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: testKeyId,
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{testGroupId, newGroupId},
|
|
Revoked: false,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId, newGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "one-off",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Add non-existing Group to existing Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: testKeyId,
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{testGroupId, "someGroupId"},
|
|
Revoked: false,
|
|
},
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
|
expectedResponse: nil,
|
|
},
|
|
{
|
|
name: "Add existing Group to non-existing Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: "someId",
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{testGroupId, newGroupId},
|
|
Revoked: false,
|
|
},
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedResponse: nil,
|
|
},
|
|
{
|
|
name: "Remove existing Group from existing Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: testKeyId,
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{},
|
|
Revoked: false,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "one-off",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Remove existing Group to non-existing Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: "someID",
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{},
|
|
Revoked: false,
|
|
},
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedResponse: nil,
|
|
},
|
|
{
|
|
name: "Revoke existing valid Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: testKeyId,
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{testGroupId},
|
|
Revoked: true,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: true,
|
|
State: "revoked",
|
|
Type: "one-off",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Revoke existing revoked Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: revokedKeyId,
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{testGroupId},
|
|
Revoked: true,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: true,
|
|
State: "revoked",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 3,
|
|
UsedTimes: 0,
|
|
Valid: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Un-Revoke existing revoked Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: revokedKeyId,
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{testGroupId},
|
|
Revoked: false,
|
|
},
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
|
expectedResponse: nil,
|
|
},
|
|
{
|
|
name: "Revoke existing expired Setup Key",
|
|
requestType: http.MethodPut,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: expiredKeyId,
|
|
requestBody: &api.SetupKeyRequest{
|
|
AutoGroups: []string{testGroupId},
|
|
Revoked: true,
|
|
},
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: true,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: true,
|
|
State: "expired",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Now(),
|
|
UsageLimit: 5,
|
|
UsedTimes: 1,
|
|
Valid: false,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
for _, user := range users {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
apiHandler, am, done := buildApiBlackBoxWithDBState(t, "testdata/setup_keys.sql", nil)
|
|
|
|
body, err := json.Marshal(tc.requestBody)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal request body: %v", err)
|
|
}
|
|
|
|
req := buildRequest(t, body, tc.requestType, strings.Replace(tc.requestPath, "{id}", tc.requestId, 1), user.userId)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
apiHandler.ServeHTTP(recorder, req)
|
|
|
|
content, expectResponse := readResponse(t, recorder, tc.expectedStatus, user.expectResponse)
|
|
if !expectResponse {
|
|
return
|
|
}
|
|
got := &api.SetupKey{}
|
|
if err := json.Unmarshal(content, &got); err != nil {
|
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
|
}
|
|
|
|
validateCreatedKey(t, tc.expectedResponse, got)
|
|
|
|
key, err := am.GetSetupKey(context.Background(), testAccountId, testUserId, got.Id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
validateCreatedKey(t, tc.expectedResponse, toResponseBody(key))
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SetupKeys_Get(t *testing.T) {
|
|
users := []struct {
|
|
name string
|
|
userId string
|
|
expectResponse bool
|
|
}{
|
|
{
|
|
name: "Regular user",
|
|
userId: testUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin user",
|
|
userId: testAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Owner user",
|
|
userId: testOwnerId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Regular service user",
|
|
userId: testServiceUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin service user",
|
|
userId: testServiceAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Blocked user",
|
|
userId: blockedUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Other user",
|
|
userId: otherUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Invalid token",
|
|
userId: invalidToken,
|
|
expectResponse: false,
|
|
},
|
|
}
|
|
|
|
tt := []struct {
|
|
name string
|
|
expectedStatus int
|
|
expectedResponse *api.SetupKey
|
|
requestType string
|
|
requestPath string
|
|
requestId string
|
|
}{
|
|
{
|
|
name: "Get existing valid Setup Key",
|
|
requestType: http.MethodGet,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: testKeyId,
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "one-off",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Get existing expired Setup Key",
|
|
requestType: http.MethodGet,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: expiredKeyId,
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: true,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "expired",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 5,
|
|
UsedTimes: 1,
|
|
Valid: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Get existing revoked Setup Key",
|
|
requestType: http.MethodGet,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: revokedKeyId,
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: true,
|
|
State: "revoked",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 3,
|
|
UsedTimes: 0,
|
|
Valid: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Get non-existing Setup Key",
|
|
requestType: http.MethodGet,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: "someId",
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedResponse: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
for _, user := range users {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
apiHandler, am, done := buildApiBlackBoxWithDBState(t, "testdata/setup_keys.sql", nil)
|
|
|
|
req := buildRequest(t, []byte{}, tc.requestType, strings.Replace(tc.requestPath, "{id}", tc.requestId, 1), user.userId)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
apiHandler.ServeHTTP(recorder, req)
|
|
|
|
content, noResponseExpected := readResponse(t, recorder, tc.expectedStatus, user.expectResponse)
|
|
if noResponseExpected {
|
|
return
|
|
}
|
|
got := &api.SetupKey{}
|
|
if err := json.Unmarshal(content, &got); err != nil {
|
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
|
}
|
|
|
|
validateCreatedKey(t, tc.expectedResponse, got)
|
|
|
|
key, err := am.GetSetupKey(context.Background(), testAccountId, testUserId, got.Id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
validateCreatedKey(t, tc.expectedResponse, toResponseBody(key))
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SetupKeys_GetAll(t *testing.T) {
|
|
users := []struct {
|
|
name string
|
|
userId string
|
|
expectResponse bool
|
|
}{
|
|
{
|
|
name: "Regular user",
|
|
userId: testUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin user",
|
|
userId: testAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Owner user",
|
|
userId: testOwnerId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Regular service user",
|
|
userId: testServiceUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin service user",
|
|
userId: testServiceAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Blocked user",
|
|
userId: blockedUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Other user",
|
|
userId: otherUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Invalid token",
|
|
userId: invalidToken,
|
|
expectResponse: false,
|
|
},
|
|
}
|
|
|
|
tt := []struct {
|
|
name string
|
|
expectedStatus int
|
|
expectedResponse []*api.SetupKey
|
|
requestType string
|
|
requestPath string
|
|
}{
|
|
{
|
|
name: "Get all Setup Keys",
|
|
requestType: http.MethodGet,
|
|
requestPath: "/api/setup-keys",
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: []*api.SetupKey{
|
|
{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "one-off",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: true,
|
|
State: "revoked",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 3,
|
|
UsedTimes: 0,
|
|
Valid: false,
|
|
},
|
|
{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: true,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "expired",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 5,
|
|
UsedTimes: 1,
|
|
Valid: false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
for _, user := range users {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
apiHandler, am, done := buildApiBlackBoxWithDBState(t, "testdata/setup_keys.sql", nil)
|
|
|
|
req := buildRequest(t, []byte{}, tc.requestType, tc.requestPath, user.userId)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
apiHandler.ServeHTTP(recorder, req)
|
|
|
|
content, expectResponse := readResponse(t, recorder, tc.expectedStatus, user.expectResponse)
|
|
if !expectResponse {
|
|
return
|
|
}
|
|
got := []api.SetupKey{}
|
|
if err := json.Unmarshal(content, &got); err != nil {
|
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
|
}
|
|
|
|
sort.Slice(got, func(i, j int) bool {
|
|
return got[i].UsageLimit < got[j].UsageLimit
|
|
})
|
|
|
|
sort.Slice(tc.expectedResponse, func(i, j int) bool {
|
|
return tc.expectedResponse[i].UsageLimit < tc.expectedResponse[j].UsageLimit
|
|
})
|
|
|
|
for i, _ := range tc.expectedResponse {
|
|
validateCreatedKey(t, tc.expectedResponse[i], &got[i])
|
|
|
|
key, err := am.GetSetupKey(context.Background(), testAccountId, testUserId, got[i].Id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
validateCreatedKey(t, tc.expectedResponse[i], toResponseBody(key))
|
|
}
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_SetupKeys_Delete(t *testing.T) {
|
|
users := []struct {
|
|
name string
|
|
userId string
|
|
expectResponse bool
|
|
}{
|
|
{
|
|
name: "Regular user",
|
|
userId: testUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin user",
|
|
userId: testAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Owner user",
|
|
userId: testOwnerId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Regular service user",
|
|
userId: testServiceUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Admin service user",
|
|
userId: testServiceAdminId,
|
|
expectResponse: true,
|
|
},
|
|
{
|
|
name: "Blocked user",
|
|
userId: blockedUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Other user",
|
|
userId: otherUserId,
|
|
expectResponse: false,
|
|
},
|
|
{
|
|
name: "Invalid token",
|
|
userId: invalidToken,
|
|
expectResponse: false,
|
|
},
|
|
}
|
|
|
|
tt := []struct {
|
|
name string
|
|
expectedStatus int
|
|
expectedResponse *api.SetupKey
|
|
requestType string
|
|
requestPath string
|
|
requestId string
|
|
}{
|
|
{
|
|
name: "Delete existing valid Setup Key",
|
|
requestType: http.MethodDelete,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: testKeyId,
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "valid",
|
|
Type: "one-off",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 1,
|
|
UsedTimes: 0,
|
|
Valid: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Delete existing expired Setup Key",
|
|
requestType: http.MethodDelete,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: expiredKeyId,
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: true,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: false,
|
|
State: "expired",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 5,
|
|
UsedTimes: 1,
|
|
Valid: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Delete existing revoked Setup Key",
|
|
requestType: http.MethodDelete,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: revokedKeyId,
|
|
expectedStatus: http.StatusOK,
|
|
expectedResponse: &api.SetupKey{
|
|
AutoGroups: []string{testGroupId},
|
|
Ephemeral: false,
|
|
Expires: time.Time{},
|
|
Id: "",
|
|
Key: "",
|
|
LastUsed: time.Time{},
|
|
Name: existingKeyName,
|
|
Revoked: true,
|
|
State: "revoked",
|
|
Type: "reusable",
|
|
UpdatedAt: time.Date(2021, time.August, 19, 20, 46, 20, 5936822, time.Local),
|
|
UsageLimit: 3,
|
|
UsedTimes: 0,
|
|
Valid: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Delete non-existing Setup Key",
|
|
requestType: http.MethodDelete,
|
|
requestPath: "/api/setup-keys/{id}",
|
|
requestId: "someId",
|
|
expectedStatus: http.StatusNotFound,
|
|
expectedResponse: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
for _, user := range users {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
apiHandler, am, done := buildApiBlackBoxWithDBState(t, "testdata/setup_keys.sql", nil)
|
|
|
|
req := buildRequest(t, []byte{}, tc.requestType, strings.Replace(tc.requestPath, "{id}", tc.requestId, 1), user.userId)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
apiHandler.ServeHTTP(recorder, req)
|
|
|
|
content, expectResponse := readResponse(t, recorder, tc.expectedStatus, user.expectResponse)
|
|
if !expectResponse {
|
|
return
|
|
}
|
|
got := &api.SetupKey{}
|
|
if err := json.Unmarshal(content, &got); err != nil {
|
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
|
}
|
|
|
|
_, err := am.GetSetupKey(context.Background(), testAccountId, testUserId, got.Id)
|
|
assert.Errorf(t, err, "Expected error when trying to get deleted key")
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func buildApiBlackBoxWithDBState(t *testing.T, sqlFile string, expectedPeerUpdate *server.UpdateMessage) (http.Handler, server.AccountManager, chan struct{}) {
|
|
store, cleanup, err := server.NewTestStoreFromSQL(context.Background(), sqlFile, t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Failed to create test store: %v", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
|
|
|
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
|
updMsg := peersUpdateManager.CreateChannel(context.Background(), testPeerId)
|
|
done := make(chan struct{})
|
|
go func() {
|
|
if expectedPeerUpdate != nil {
|
|
peerShouldReceiveUpdate(t, updMsg, expectedPeerUpdate)
|
|
} else {
|
|
peerShouldNotReceiveUpdate(t, updMsg)
|
|
}
|
|
close(done)
|
|
}()
|
|
|
|
geoMock := &geolocation.GeolocationMock{}
|
|
validatorMock := server.MocIntegratedValidator{}
|
|
am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create manager: %v", err)
|
|
}
|
|
|
|
apiHandler, err := APIHandler(context.Background(), am, geoMock, &jwtclaims.JwtValidatorMock{}, metrics, AuthCfg{}, validatorMock)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create API handler: %v", err)
|
|
}
|
|
|
|
return apiHandler, am, done
|
|
}
|
|
|
|
func buildRequest(t *testing.T, requestBody []byte, requestType, requestPath, user string) *http.Request {
|
|
t.Helper()
|
|
|
|
req := httptest.NewRequest(requestType, requestPath, bytes.NewBuffer(requestBody))
|
|
req.Header.Set("Authorization", "Bearer "+user)
|
|
|
|
return req
|
|
}
|
|
|
|
func readResponse(t *testing.T, recorder *httptest.ResponseRecorder, expectedStatus int, expectResponse bool) ([]byte, bool) {
|
|
t.Helper()
|
|
|
|
res := recorder.Result()
|
|
defer res.Body.Close()
|
|
|
|
content, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read response body: %v", err)
|
|
}
|
|
|
|
if !expectResponse {
|
|
return nil, false
|
|
}
|
|
|
|
if status := recorder.Code; status != expectedStatus {
|
|
t.Fatalf("handler returned wrong status code: got %v want %v, content: %s",
|
|
status, expectedStatus, string(content))
|
|
}
|
|
|
|
return content, expectedStatus == http.StatusOK
|
|
}
|
|
|
|
func validateCreatedKey(t *testing.T, expectedKey *api.SetupKey, got *api.SetupKey) {
|
|
t.Helper()
|
|
|
|
if got.Expires.After(time.Now().Add(-1*time.Minute)) && got.Expires.Before(time.Now().Add(expiresIn*time.Second)) ||
|
|
got.Expires.After(time.Date(2300, 01, 01, 0, 0, 0, 0, time.Local)) ||
|
|
got.Expires.Before(time.Date(1950, 01, 01, 0, 0, 0, 0, time.Local)) {
|
|
got.Expires = time.Time{}
|
|
expectedKey.Expires = time.Time{}
|
|
}
|
|
|
|
if got.Id == "" {
|
|
t.Fatalf("Expected key to have an ID")
|
|
}
|
|
got.Id = ""
|
|
|
|
if got.Key == "" {
|
|
t.Fatalf("Expected key to have a key")
|
|
}
|
|
got.Key = ""
|
|
|
|
if got.UpdatedAt.After(time.Now().Add(-1*time.Minute)) && got.UpdatedAt.Before(time.Now().Add(+1*time.Minute)) {
|
|
got.UpdatedAt = time.Time{}
|
|
expectedKey.UpdatedAt = time.Time{}
|
|
}
|
|
|
|
assert.Equal(t, expectedKey, got)
|
|
}
|
|
|
|
func peerShouldNotReceiveUpdate(t *testing.T, updateMessage <-chan *server.UpdateMessage) {
|
|
t.Helper()
|
|
select {
|
|
case msg := <-updateMessage:
|
|
t.Errorf("Unexpected message received: %+v", msg)
|
|
case <-time.After(500 * time.Millisecond):
|
|
return
|
|
}
|
|
}
|
|
|
|
func peerShouldReceiveUpdate(t *testing.T, updateMessage <-chan *server.UpdateMessage, expected *server.UpdateMessage) {
|
|
t.Helper()
|
|
|
|
select {
|
|
case msg := <-updateMessage:
|
|
if msg == nil {
|
|
t.Errorf("Received nil update message, expected valid message")
|
|
}
|
|
assert.Equal(t, expected, msg)
|
|
case <-time.After(500 * time.Millisecond):
|
|
t.Error("Timed out waiting for update message")
|
|
}
|
|
}
|