satellite/console: create project salt endpoint on satellite web server

Introduces a new endpoint on the satellite web server to get the
project's salt. The endpoint utilizes a new console service method
GetSalt which in turn calls the project DB GetSalt method if the
user is authorized. It returns the project salt bytes as a base64
encoded string in the response.

Change-Id: Ia13b5a4b8580e7bdad0dbb98014a276b1c74b46d
This commit is contained in:
Cameron 2022-09-13 08:59:15 -04:00 committed by Cameron
parent fa4af92392
commit d8fb082f89
5 changed files with 122 additions and 0 deletions

View File

@ -0,0 +1,69 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package consoleapi
import (
"encoding/base64"
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/common/uuid"
"storj.io/storj/satellite/console"
)
// Projects is an api controller that exposes projects related functionality.
type Projects struct {
log *zap.Logger
service *console.Service
}
// NewProjects is a constructor for api analytics controller.
func NewProjects(log *zap.Logger, service *console.Service) *Projects {
return &Projects{
log: log,
service: service,
}
}
// GetSalt returns the project's salt.
func (p *Projects) GetSalt(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
w.Header().Set("Content-Type", "application/json")
idParam, ok := mux.Vars(r)["id"]
if !ok {
p.serveJSONError(w, http.StatusBadRequest, errs.New("missing id route param"))
return
}
id, err := uuid.FromString(idParam)
if err != nil {
p.serveJSONError(w, http.StatusBadRequest, err)
}
salt, err := p.service.GetSalt(ctx, id)
if err != nil {
p.serveJSONError(w, http.StatusUnauthorized, err)
return
}
b64SaltString := base64.StdEncoding.EncodeToString(salt)
err = json.NewEncoder(w).Encode(b64SaltString)
if err != nil {
p.serveJSONError(w, http.StatusInternalServerError, err)
}
}
// serveJSONError writes JSON error to response output stream.
func (p *Projects) serveJSONError(w http.ResponseWriter, status int, err error) {
ServeJSONError(p.log, w, status, err)
}

View File

@ -4,7 +4,9 @@
package consoleweb_test package consoleweb_test
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -17,6 +19,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"storj.io/common/testcontext" "storj.io/common/testcontext"
"storj.io/common/uuid"
"storj.io/storj/private/testplanet" "storj.io/storj/private/testplanet"
"storj.io/storj/satellite/payments/storjscan/blockchaintest" "storj.io/storj/satellite/payments/storjscan/blockchaintest"
) )
@ -362,6 +365,23 @@ func TestProjects(t *testing.T) {
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
} }
{ // Get_Salt
projectID := test.defaultProjectID()
id, err := uuid.FromString(projectID)
require.NoError(t, err)
// get salt from endpoint
var b64Salt string
resp, body := test.request(http.MethodGet, fmt.Sprintf("/projects/%s/salt", test.defaultProjectID()), nil)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(t, json.Unmarshal([]byte(body), &b64Salt))
// get salt from db and base64 encode it
salt, err := planet.Satellites[0].DB.Console().Projects().GetSalt(ctx, id)
require.NoError(t, err)
require.Equal(t, b64Salt, base64.StdEncoding.EncodeToString(salt))
}
{ // Get_ProjectInfo { // Get_ProjectInfo
resp, body := test.request(http.MethodPost, "/graphql", resp, body := test.request(http.MethodPost, "/graphql",
test.toJSON(map[string]interface{}{ test.toJSON(map[string]interface{}{

View File

@ -248,6 +248,12 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
consoleapi.NewUserManagement(logger, mon, server.service, router, &apiAuth{&server}) consoleapi.NewUserManagement(logger, mon, server.service, router, &apiAuth{&server})
} }
projectsController := consoleapi.NewProjects(logger, service)
router.Handle(
"/api/v0/projects/{id}/salt",
server.withAuth(http.HandlerFunc(projectsController.GetSalt)),
).Methods(http.MethodGet)
router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler) router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler)
router.HandleFunc("/robots.txt", server.seoHandler) router.HandleFunc("/robots.txt", server.seoHandler)

View File

@ -1406,6 +1406,21 @@ func (s *Service) GetProject(ctx context.Context, projectID uuid.UUID) (p *Proje
return return
} }
// GetSalt is a method for querying project salt by id.
func (s *Service) GetSalt(ctx context.Context, projectID uuid.UUID) (salt []byte, err error) {
defer mon.Task()(&ctx)(&err)
user, err := s.getUserAndAuditLog(ctx, "get project salt", zap.String("projectID", projectID.String()))
if err != nil {
return nil, Error.Wrap(err)
}
if _, err = s.isProjectMember(ctx, user.ID, projectID); err != nil {
return nil, Error.Wrap(err)
}
return s.store.Projects().GetSalt(ctx, projectID)
}
// GetUsersProjects is a method for querying all projects. // GetUsersProjects is a method for querying all projects.
func (s *Service) GetUsersProjects(ctx context.Context) (ps []Project, err error) { func (s *Service) GetUsersProjects(ctx context.Context) (ps []Project, err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)

View File

@ -71,6 +71,18 @@ func TestService(t *testing.T) {
require.Nil(t, project) require.Nil(t, project)
}) })
t.Run("GetSalt", func(t *testing.T) {
// Getting project salt as a member should work
salt, err := service.GetSalt(userCtx1, up1Pro1.ID)
require.NoError(t, err)
require.NotNil(t, salt)
// Getting project salt as a non-member should not work
salt, err = service.GetSalt(userCtx1, up2Pro1.ID)
require.Error(t, err)
require.Nil(t, salt)
})
t.Run("UpdateProject", func(t *testing.T) { t.Run("UpdateProject", func(t *testing.T) {
updatedName := "newName" updatedName := "newName"
updatedDescription := "newDescription" updatedDescription := "newDescription"