satellite/console: support public id as generated api parameter
On generated console api endpoints allow either the project ID or the public ID to be used as the ID parameter. github issue: https://github.com/storj/storj/issues/5412 Change-Id: Ic9901ed273931a50ae12f20142a3c4938dfcc8c0
This commit is contained in:
parent
ed910b6087
commit
5da2544e62
@ -6,6 +6,7 @@ package console
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
@ -1612,7 +1613,7 @@ func (s *Service) DeleteProject(ctx context.Context, projectID uuid.UUID) (err e
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = s.isProjectOwner(ctx, user.ID, projectID)
|
||||
_, _, err = s.isProjectOwner(ctx, user.ID, projectID)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -1643,14 +1644,20 @@ func (s *Service) GenDeleteProject(ctx context.Context, projectID uuid.UUID) (ht
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.isProjectOwner(ctx, user.ID, projectID)
|
||||
_, p, err := s.isProjectOwner(ctx, user.ID, projectID)
|
||||
if err != nil {
|
||||
status := http.StatusInternalServerError
|
||||
if ErrUnauthorized.Has(err) {
|
||||
status = http.StatusUnauthorized
|
||||
}
|
||||
return api.HTTPError{
|
||||
Status: http.StatusUnauthorized,
|
||||
Status: status,
|
||||
Err: Error.Wrap(err),
|
||||
}
|
||||
}
|
||||
|
||||
projectID = p.ID
|
||||
|
||||
err = s.checkProjectCanBeDeleted(ctx, user, projectID)
|
||||
if err != nil {
|
||||
return api.HTTPError{
|
||||
@ -1766,7 +1773,6 @@ func (s *Service) GenUpdateProject(ctx context.Context, projectID uuid.UUID, pro
|
||||
Err: Error.Wrap(err),
|
||||
}
|
||||
}
|
||||
|
||||
err = ValidateNameAndDescription(projectInfo.Name, projectInfo.Description)
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
@ -1915,10 +1921,13 @@ func (s *Service) DeleteProjectMembers(ctx context.Context, projectID uuid.UUID,
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
if _, err = s.isProjectMember(ctx, user.ID, projectID); err != nil {
|
||||
var isMember isProjectMember
|
||||
if isMember, err = s.isProjectMember(ctx, user.ID, projectID); err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
projectID = isMember.project.ID
|
||||
|
||||
var userIDs []uuid.UUID
|
||||
var userErr errs.Group
|
||||
|
||||
@ -1930,7 +1939,7 @@ func (s *Service) DeleteProjectMembers(ctx context.Context, projectID uuid.UUID,
|
||||
continue
|
||||
}
|
||||
|
||||
isOwner, err := s.isProjectOwner(ctx, user.ID, projectID)
|
||||
isOwner, _, err := s.isProjectOwner(ctx, user.ID, projectID)
|
||||
if isOwner {
|
||||
return ErrValidation.New(projectOwnerDeletionForbiddenErrMsg, user.Email)
|
||||
}
|
||||
@ -2045,7 +2054,7 @@ func (s *Service) GenCreateAPIKey(ctx context.Context, requestInfo CreateAPIKeyR
|
||||
}
|
||||
}
|
||||
|
||||
projectID, err := uuid.FromString(requestInfo.ProjectID)
|
||||
reqProjectID, err := uuid.FromString(requestInfo.ProjectID)
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
Status: http.StatusBadRequest,
|
||||
@ -2053,7 +2062,7 @@ func (s *Service) GenCreateAPIKey(ctx context.Context, requestInfo CreateAPIKeyR
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.isProjectMember(ctx, user.ID, projectID)
|
||||
isMember, err := s.isProjectMember(ctx, user.ID, reqProjectID)
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
Status: http.StatusUnauthorized,
|
||||
@ -2061,6 +2070,8 @@ func (s *Service) GenCreateAPIKey(ctx context.Context, requestInfo CreateAPIKeyR
|
||||
}
|
||||
}
|
||||
|
||||
projectID := isMember.project.ID
|
||||
|
||||
_, err = s.store.APIKeys().GetByNameAndProjectID(ctx, requestInfo.Name, projectID)
|
||||
if err == nil {
|
||||
return nil, api.HTTPError{
|
||||
@ -2101,6 +2112,9 @@ func (s *Service) GenCreateAPIKey(ctx context.Context, requestInfo CreateAPIKeyR
|
||||
}
|
||||
}
|
||||
|
||||
// in case the project ID from the request is the public ID, replace projectID with reqProjectID
|
||||
info.ProjectID = reqProjectID
|
||||
|
||||
return &CreateAPIKeyResponse{
|
||||
Key: key.Serialize(),
|
||||
KeyInfo: info,
|
||||
@ -2279,19 +2293,21 @@ func (s *Service) DeleteAPIKeyByNameAndProjectID(ctx context.Context, name strin
|
||||
}
|
||||
|
||||
// GetAPIKeys returns paged api key list for given Project.
|
||||
func (s *Service) GetAPIKeys(ctx context.Context, projectID uuid.UUID, cursor APIKeyCursor) (page *APIKeyPage, err error) {
|
||||
func (s *Service) GetAPIKeys(ctx context.Context, reqProjectID uuid.UUID, cursor APIKeyCursor) (page *APIKeyPage, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
user, err := s.getUserAndAuditLog(ctx, "get api keys", zap.String("projectID", projectID.String()))
|
||||
user, err := s.getUserAndAuditLog(ctx, "get api keys", zap.String("projectID", reqProjectID.String()))
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = s.isProjectMember(ctx, user.ID, projectID)
|
||||
isMember, err := s.isProjectMember(ctx, user.ID, reqProjectID)
|
||||
if err != nil {
|
||||
return nil, ErrUnauthorized.Wrap(err)
|
||||
}
|
||||
|
||||
projectID := isMember.project.ID
|
||||
|
||||
if cursor.Limit > maxLimit {
|
||||
cursor.Limit = maxLimit
|
||||
}
|
||||
@ -2301,7 +2317,14 @@ func (s *Service) GetAPIKeys(ctx context.Context, projectID uuid.UUID, cursor AP
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
return
|
||||
// if project ID from request is public ID, replace api key's project IDs with public ID
|
||||
if projectID != reqProjectID {
|
||||
for i := range page.APIKeys {
|
||||
page.APIKeys[i].ProjectID = reqProjectID
|
||||
}
|
||||
}
|
||||
|
||||
return page, err
|
||||
}
|
||||
|
||||
// CreateRESTKey creates a satellite rest key.
|
||||
@ -2438,11 +2461,11 @@ func (s *Service) GetBucketUsageRollups(ctx context.Context, projectID uuid.UUID
|
||||
}
|
||||
|
||||
// GenGetBucketUsageRollups retrieves summed usage rollups for every bucket of particular project for a given period for generated api.
|
||||
func (s *Service) GenGetBucketUsageRollups(ctx context.Context, projectID uuid.UUID, since, before time.Time) (rollups []accounting.BucketUsageRollup, httpError api.HTTPError) {
|
||||
func (s *Service) GenGetBucketUsageRollups(ctx context.Context, reqProjectID uuid.UUID, since, before time.Time) (rollups []accounting.BucketUsageRollup, httpError api.HTTPError) {
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
user, err := s.getUserAndAuditLog(ctx, "get bucket usage rollups", zap.String("projectID", projectID.String()))
|
||||
user, err := s.getUserAndAuditLog(ctx, "get bucket usage rollups", zap.String("projectID", reqProjectID.String()))
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
Status: http.StatusUnauthorized,
|
||||
@ -2450,7 +2473,7 @@ func (s *Service) GenGetBucketUsageRollups(ctx context.Context, projectID uuid.U
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.isProjectMember(ctx, user.ID, projectID)
|
||||
isMember, err := s.isProjectMember(ctx, user.ID, reqProjectID)
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
Status: http.StatusUnauthorized,
|
||||
@ -2458,6 +2481,8 @@ func (s *Service) GenGetBucketUsageRollups(ctx context.Context, projectID uuid.U
|
||||
}
|
||||
}
|
||||
|
||||
projectID := isMember.project.ID
|
||||
|
||||
rollups, err = s.projectAccounting.GetBucketUsageRollups(ctx, projectID, since, before)
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
@ -2466,15 +2491,22 @@ func (s *Service) GenGetBucketUsageRollups(ctx context.Context, projectID uuid.U
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
// if project ID from request is public ID, replace rollup's project ID with public ID
|
||||
if reqProjectID != projectID {
|
||||
for i := range rollups {
|
||||
rollups[i].ProjectID = reqProjectID
|
||||
}
|
||||
}
|
||||
|
||||
return rollups, httpError
|
||||
}
|
||||
|
||||
// GenGetSingleBucketUsageRollup retrieves usage rollup for single bucket of particular project for a given period for generated api.
|
||||
func (s *Service) GenGetSingleBucketUsageRollup(ctx context.Context, projectID uuid.UUID, bucket string, since, before time.Time) (rollup *accounting.BucketUsageRollup, httpError api.HTTPError) {
|
||||
func (s *Service) GenGetSingleBucketUsageRollup(ctx context.Context, reqProjectID uuid.UUID, bucket string, since, before time.Time) (rollup *accounting.BucketUsageRollup, httpError api.HTTPError) {
|
||||
var err error
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
user, err := s.getUserAndAuditLog(ctx, "get single bucket usage rollup", zap.String("projectID", projectID.String()))
|
||||
user, err := s.getUserAndAuditLog(ctx, "get single bucket usage rollup", zap.String("projectID", reqProjectID.String()))
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
Status: http.StatusUnauthorized,
|
||||
@ -2482,7 +2514,7 @@ func (s *Service) GenGetSingleBucketUsageRollup(ctx context.Context, projectID u
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.isProjectMember(ctx, user.ID, projectID)
|
||||
isMember, err := s.isProjectMember(ctx, user.ID, reqProjectID)
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
Status: http.StatusUnauthorized,
|
||||
@ -2490,6 +2522,8 @@ func (s *Service) GenGetSingleBucketUsageRollup(ctx context.Context, projectID u
|
||||
}
|
||||
}
|
||||
|
||||
projectID := isMember.project.ID
|
||||
|
||||
rollup, err = s.projectAccounting.GetSingleBucketUsageRollup(ctx, projectID, bucket, since, before)
|
||||
if err != nil {
|
||||
return nil, api.HTTPError{
|
||||
@ -2498,7 +2532,10 @@ func (s *Service) GenGetSingleBucketUsageRollup(ctx context.Context, projectID u
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
// make sure to replace rollup project ID with reqProjectID in case it is the public ID
|
||||
rollup.ProjectID = reqProjectID
|
||||
|
||||
return rollup, httpError
|
||||
}
|
||||
|
||||
// GetDailyProjectUsage returns daily usage by project ID.
|
||||
@ -2791,18 +2828,26 @@ type isProjectMember struct {
|
||||
}
|
||||
|
||||
// isProjectOwner checks if the user is an owner of a project.
|
||||
func (s *Service) isProjectOwner(ctx context.Context, userID uuid.UUID, projectID uuid.UUID) (isOwner bool, err error) {
|
||||
func (s *Service) isProjectOwner(ctx context.Context, userID uuid.UUID, projectID uuid.UUID) (isOwner bool, project *Project, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
project, err := s.store.Projects().Get(ctx, projectID)
|
||||
|
||||
project, err = s.store.Projects().GetByPublicID(ctx, projectID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
project, err = s.store.Projects().Get(ctx, projectID)
|
||||
if err != nil {
|
||||
return false, nil, Error.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
return false, nil, Error.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
if project.OwnerID != userID {
|
||||
return false, ErrUnauthorized.New(unauthorizedErrMsg)
|
||||
return false, nil, ErrUnauthorized.New(unauthorizedErrMsg)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return true, project, nil
|
||||
}
|
||||
|
||||
// isProjectMember checks if the user is a member of given project.
|
||||
|
@ -1188,3 +1188,116 @@ func TestPaymentsWalletPayments(t *testing.T) {
|
||||
require.Equal(t, expected, walletPayments.Payments)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServiceGenMethods(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 2,
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
s := sat.API.Console.Service
|
||||
u0 := planet.Uplinks[0]
|
||||
u1 := planet.Uplinks[1]
|
||||
user0Ctx, err := sat.UserContext(ctx, u0.Projects[0].Owner.ID)
|
||||
require.NoError(t, err)
|
||||
user1Ctx, err := sat.UserContext(ctx, u1.Projects[0].Owner.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
p0ID := u0.Projects[0].ID
|
||||
p, err := s.GetProject(user1Ctx, u1.Projects[0].ID)
|
||||
require.NoError(t, err)
|
||||
p1PublicID := p.PublicID
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
ID uuid.UUID
|
||||
ctx context.Context
|
||||
uplink *testplanet.Uplink
|
||||
}{
|
||||
{"projectID", p0ID, user0Ctx, u0},
|
||||
{"publicID", p1PublicID, user1Ctx, u1},
|
||||
} {
|
||||
|
||||
t.Run("GenUpdateProject with "+tt.name, func(t *testing.T) {
|
||||
updatedName := "name " + tt.name
|
||||
updatedDescription := "desc " + tt.name
|
||||
updatedStorageLimit := memory.Size(100)
|
||||
updatedBandwidthLimit := memory.Size(100)
|
||||
|
||||
info := console.ProjectInfo{
|
||||
Name: updatedName,
|
||||
Description: updatedDescription,
|
||||
StorageLimit: updatedStorageLimit,
|
||||
BandwidthLimit: updatedBandwidthLimit,
|
||||
}
|
||||
updatedProject, err := s.GenUpdateProject(tt.ctx, tt.ID, info)
|
||||
require.NoError(t, err.Err)
|
||||
if tt.name == "projectID" {
|
||||
require.Equal(t, tt.ID, updatedProject.ID)
|
||||
} else {
|
||||
require.Equal(t, tt.ID, updatedProject.PublicID)
|
||||
}
|
||||
require.Equal(t, info.Name, updatedProject.Name)
|
||||
require.Equal(t, info.Description, updatedProject.Description)
|
||||
})
|
||||
t.Run("GenCreateAPIKey with "+tt.name, func(t *testing.T) {
|
||||
request := console.CreateAPIKeyRequest{
|
||||
ProjectID: tt.ID.String(),
|
||||
Name: tt.name + " Key",
|
||||
}
|
||||
apiKey, err := s.GenCreateAPIKey(tt.ctx, request)
|
||||
require.NoError(t, err.Err)
|
||||
require.Equal(t, tt.ID, apiKey.KeyInfo.ProjectID)
|
||||
require.Equal(t, request.Name, apiKey.KeyInfo.Name)
|
||||
})
|
||||
t.Run("GenGetAPIKeys with "+tt.name, func(t *testing.T) {
|
||||
apiKeys, err := s.GenGetAPIKeys(tt.ctx, tt.ID, "", 10, 1, 0, 0)
|
||||
require.NoError(t, err.Err)
|
||||
require.NotEmpty(t, apiKeys)
|
||||
for _, key := range apiKeys.APIKeys {
|
||||
require.Equal(t, tt.ID, key.ProjectID)
|
||||
}
|
||||
})
|
||||
|
||||
bucket := "testbucket"
|
||||
require.NoError(t, tt.uplink.CreateBucket(tt.ctx, sat, bucket))
|
||||
require.NoError(t, tt.uplink.Upload(tt.ctx, sat, bucket, "helloworld.txt", []byte("hello world")))
|
||||
sat.Accounting.Tally.Loop.TriggerWait()
|
||||
|
||||
t.Run("GenGetSingleBucketUsageRollup with "+tt.name, func(t *testing.T) {
|
||||
rollup, err := s.GenGetSingleBucketUsageRollup(tt.ctx, tt.ID, bucket, time.Now().Add(-24*time.Hour), time.Now())
|
||||
require.NoError(t, err.Err)
|
||||
require.NotNil(t, rollup)
|
||||
require.Equal(t, tt.ID, rollup.ProjectID)
|
||||
})
|
||||
t.Run("GenGetBucketUsageRollups with "+tt.name, func(t *testing.T) {
|
||||
rollups, err := s.GenGetBucketUsageRollups(tt.ctx, tt.ID, time.Now().Add(-24*time.Hour), time.Now())
|
||||
require.NoError(t, err.Err)
|
||||
require.NotEmpty(t, rollups)
|
||||
for _, r := range rollups {
|
||||
require.Equal(t, tt.ID, r.ProjectID)
|
||||
}
|
||||
})
|
||||
|
||||
// create empty project for easy deletion
|
||||
p, err := s.CreateProject(tt.ctx, console.ProjectInfo{
|
||||
Name: "foo",
|
||||
Description: "bar",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("GenDeleteProject with "+tt.name, func(t *testing.T) {
|
||||
var id uuid.UUID
|
||||
if tt.name == "projectID" {
|
||||
id = p.ID
|
||||
} else {
|
||||
id = p.PublicID
|
||||
}
|
||||
httpErr := s.GenDeleteProject(tt.ctx, id)
|
||||
require.NoError(t, httpErr.Err)
|
||||
p, err := s.GetProject(ctx, id)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, p)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user