storj/satellite/admin/apikeys_test.go
Sean Harvey b1523f82c8
satellite/admin: add owner full name on /api/apikeys/{apikey} endpoint
Updates storj/gateway-mt#321

Change-Id: I6759ec5dbba49261bb183e42d8cb333c326cb9e8
2023-06-28 16:01:15 +12:00

350 lines
12 KiB
Go

// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package admin_test
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"storj.io/common/macaroon"
"storj.io/common/testcontext"
"storj.io/common/uuid"
"storj.io/storj/private/testplanet"
"storj.io/storj/satellite"
"storj.io/storj/satellite/console"
)
func TestApiKeyAdd(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
StorageNodeCount: 0,
UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
projectID := planet.Uplinks[0].Projects[0].ID
keys, err := planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{Page: 1, Limit: 10})
require.NoError(t, err)
require.Len(t, keys.APIKeys, 1)
body := strings.NewReader(`{"name":"Default"}`)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("http://"+address.String()+"/api/projects/%s/apikeys", projectID.String()), body)
require.NoError(t, err)
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
response, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
require.Equal(t, "application/json", response.Header.Get("Content-Type"))
responseBody, err := io.ReadAll(response.Body)
require.NoError(t, err)
require.NoError(t, response.Body.Close())
var output struct {
APIKey string `json:"apikey"`
}
err = json.Unmarshal(responseBody, &output)
require.NoError(t, err)
apikey, err := macaroon.ParseAPIKey(output.APIKey)
require.NoError(t, err)
require.NotNil(t, apikey)
keys, err = planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{Page: 1, Limit: 10})
require.NoError(t, err)
require.Len(t, keys.APIKeys, 2)
key, err := planet.Satellites[0].DB.Console().APIKeys().GetByHead(ctx, apikey.Head())
require.NoError(t, err)
require.Equal(t, "Default", key.Name)
})
}
func TestApiKeyDelete(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
StorageNodeCount: 0,
UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
projectID := planet.Uplinks[0].Projects[0].ID
keys, err := planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{Page: 1, Limit: 10})
require.NoError(t, err)
require.Len(t, keys.APIKeys, 1)
apikey := planet.Uplinks[0].APIKey[planet.Satellites[0].ID()].Serialize()
link := fmt.Sprintf("http://"+address.String()+"/api/apikeys/%s", apikey)
body := assertReq(ctx, t, link, http.MethodDelete, "", http.StatusOK, "", planet.Satellites[0].Config.Console.AuthToken)
require.Len(t, body, 0)
keys, err = planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{Page: 1, Limit: 10})
require.NoError(t, err)
require.Len(t, keys.APIKeys, 0)
// Delete a deleted key returns Not Found.
body = assertReq(ctx, t, link, http.MethodDelete, "", http.StatusNotFound, "", planet.Satellites[0].Config.Console.AuthToken)
require.Contains(t, string(body), "does not exist")
})
}
func TestApiKeyDelete_ByName(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
StorageNodeCount: 0,
UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
projectID := planet.Uplinks[0].Projects[0].ID
keys, err := planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{Page: 1, Limit: 10})
require.NoError(t, err)
require.Len(t, keys.APIKeys, 1)
link := fmt.Sprintf("http://"+address.String()+"/api/projects/%s/apikeys/%s", projectID.String(), keys.APIKeys[0].Name)
body := assertReq(ctx, t, link, http.MethodDelete, "", http.StatusOK, "", planet.Satellites[0].Config.Console.AuthToken)
require.Len(t, body, 0)
keys, err = planet.Satellites[0].DB.Console().APIKeys().GetPagedByProjectID(ctx, projectID, console.APIKeyCursor{Page: 1, Limit: 10})
require.NoError(t, err)
require.Len(t, keys.APIKeys, 0)
// Delete a deleted key returns Not Found.
body = assertReq(ctx, t, link, http.MethodDelete, "", http.StatusNotFound, "", planet.Satellites[0].Config.Console.AuthToken)
require.Contains(t, string(body), "does not exist")
})
}
func TestApiKeysList(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
StorageNodeCount: 0,
UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
var (
sat = planet.Satellites[0]
authToken = planet.Satellites[0].Config.Console.AuthToken
address = sat.Admin.Admin.Listener.Addr()
)
project, err := sat.DB.Console().Projects().Get(ctx, planet.Uplinks[0].Projects[0].ID)
require.NoError(t, err)
link := "http://" + address.String() + "/api/projects/" + project.ID.String() + "/apikeys"
{ // Delete initial API Keys to run this test
page, err := sat.DB.Console().APIKeys().GetPagedByProjectID(
ctx, project.ID, console.APIKeyCursor{
Limit: 50, Page: 1, Order: console.KeyName, OrderDirection: console.Ascending,
},
)
require.NoError(t, err)
// Ensure that we are getting all the initial keys with one single page.
require.Len(t, page.APIKeys, int(page.TotalCount))
for _, ak := range page.APIKeys {
require.NoError(t, sat.DB.Console().APIKeys().Delete(ctx, ak.ID))
}
}
// Check get initial list of API keys.
assertGet(ctx, t, link, "[]", authToken)
{ // Create 2 new API Key.
body := assertReq(ctx, t, link, http.MethodPost, `{"name": "first"}`, http.StatusOK, "", authToken)
apiKey := struct {
Apikey string `json:"apikey"`
}{}
require.NoError(t, json.Unmarshal(body, &apiKey))
require.NotEmpty(t, apiKey.Apikey)
body = assertReq(ctx, t, link, http.MethodPost, `{"name": "second"}`, http.StatusOK, "", authToken)
require.NoError(t, json.Unmarshal(body, &apiKey))
require.NotEmpty(t, apiKey.Apikey)
// TODO: figure out how to create an API Key associated to a partner.
// sat.DB.Console().APIKeys.Update only allows to update the API Key name
}
// Check get list of API keys.
body := assertReq(ctx, t, link, http.MethodGet, "", http.StatusOK, "", authToken)
var apiKeys []struct {
ID string `json:"id"`
ProjectID string `json:"projectId"`
Name string `json:"name"`
CreatedAt string `json:"createdAt"`
}
require.NoError(t, json.Unmarshal(body, &apiKeys))
require.Len(t, apiKeys, 2)
{ // Assert API keys info.
a := apiKeys[0]
assert.NotEmpty(t, a.ID, "API key ID")
assert.Equal(t, "first", a.Name, "API key name")
assert.Equal(t, project.ID.String(), a.ProjectID, "API key project ID")
assert.NotEmpty(t, a.CreatedAt, "API key created at")
a = apiKeys[1]
assert.NotEmpty(t, a.ID, "API key ID")
assert.Equal(t, "second", a.Name, "API key name")
assert.Equal(t, project.ID.String(), a.ProjectID, "API key project ID")
assert.NotEmpty(t, a.CreatedAt, "API key created at")
}
{ // Delete one API key to check that the endpoint just returns one.
id, err := uuid.FromString(apiKeys[1].ID)
require.NoError(t, err)
require.NoError(t, sat.DB.Console().APIKeys().Delete(ctx, id))
}
body = assertReq(ctx, t, link, http.MethodGet, "", http.StatusOK, "", authToken)
require.NoError(t, json.Unmarshal(body, &apiKeys))
require.Len(t, apiKeys, 1)
{ // Assert API keys info.
a := apiKeys[0]
assert.NotEmpty(t, a.ID, "API key ID")
assert.Equal(t, "first", a.Name, "API key name")
assert.Equal(t, project.ID.String(), a.ProjectID, "API key project ID")
assert.NotEmpty(t, a.CreatedAt, "API key created at")
}
{ // Delete the one API key that last to check that the endpoint just returns none.
id, err := uuid.FromString(apiKeys[0].ID)
require.NoError(t, err)
require.NoError(t, sat.DB.Console().APIKeys().Delete(ctx, id))
}
// Check get initial list of API keys.
assertGet(ctx, t, link, "[]", authToken)
})
}
func TestAPIKeyManagementGet(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
StorageNodeCount: 0,
UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
user, err := planet.Satellites[0].AddUser(ctx, console.CreateUser{
FullName: "testuser123",
Email: "test@email.com",
}, 1)
require.NoError(t, err)
project, err := planet.Satellites[0].AddProject(ctx, user.ID, "testproject")
require.NoError(t, err)
secret, err := macaroon.NewSecret()
require.NoError(t, err)
apiKey, err := macaroon.NewAPIKey(secret)
require.NoError(t, err)
apiKeyInfo, err := planet.Satellites[0].DB.Console().APIKeys().Create(ctx, apiKey.Head(), console.APIKeyInfo{
Name: "testkey",
ProjectID: project.ID,
Secret: secret,
})
require.NoError(t, err)
userCtx, err := planet.Satellites[0].UserContext(ctx, user.ID)
require.NoError(t, err)
_, err = planet.Satellites[0].API.Console.Service.Payments().AddCreditCard(userCtx, "test")
require.NoError(t, err)
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
link := fmt.Sprintf("http://"+address.String()+"/api/apikeys/%s", apiKey.Serialize())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, link, nil)
require.NoError(t, err)
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
resp, err := http.DefaultClient.Do(req) //nolint:bodyclose
require.NoError(t, err)
defer ctx.Check(resp.Body.Close)
require.Equal(t, http.StatusOK, resp.StatusCode)
type apiKeyData struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
}
type projectData struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
}
type ownerData struct {
ID uuid.UUID `json:"id"`
FullName string `json:"fullName"`
Email string `json:"email"`
PaidTier bool `json:"paidTier"`
}
type response struct {
APIKey apiKeyData `json:"api_key"`
Project projectData `json:"project"`
Owner ownerData `json:"owner"`
}
var apiResp response
require.NoError(t, json.NewDecoder(resp.Body).Decode(&apiResp))
require.Equal(t, response{
APIKey: apiKeyData{
ID: apiKeyInfo.ID,
Name: "testkey",
CreatedAt: apiKeyInfo.CreatedAt.UTC(),
},
Project: projectData{
ID: project.ID,
Name: "testproject",
},
Owner: ownerData{
ID: user.ID,
FullName: "testuser123",
Email: "test@email.com",
PaidTier: true,
},
}, apiResp)
})
}