forked from TrueCloudLab/frostfs-rest-gw
[#15] Add check verbs and token type
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
eab43cea3b
commit
5c122a4325
3 changed files with 153 additions and 23 deletions
|
@ -117,6 +117,7 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s
|
||||||
restrictByEACL(ctx, t, clientPool, cnrID)
|
restrictByEACL(ctx, t, clientPool, cnrID)
|
||||||
|
|
||||||
t.Run("rest auth several tokens "+version, func(t *testing.T) { authTokens(ctx, t) })
|
t.Run("rest auth several tokens "+version, func(t *testing.T) { authTokens(ctx, t) })
|
||||||
|
t.Run("rest check mix tokens up "+version, func(t *testing.T) { mixTokens(ctx, t, cnrID) })
|
||||||
|
|
||||||
t.Run("rest put object "+version, func(t *testing.T) { restObjectPut(ctx, t, clientPool, cnrID) })
|
t.Run("rest put object "+version, func(t *testing.T) { restObjectPut(ctx, t, clientPool, cnrID) })
|
||||||
t.Run("rest get object "+version, func(t *testing.T) { restObjectGet(ctx, t, clientPool, cnrID) })
|
t.Run("rest get object "+version, func(t *testing.T) { restObjectGet(ctx, t, clientPool, cnrID) })
|
||||||
|
@ -269,6 +270,113 @@ func authTokens(ctx context.Context, t *testing.T) {
|
||||||
makeAuthTokenRequest(ctx, t, bearers, httpClient)
|
makeAuthTokenRequest(ctx, t, bearers, httpClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mixTokens(ctx context.Context, t *testing.T, cnrID *cid.ID) {
|
||||||
|
bearers := []*models.Bearer{
|
||||||
|
{
|
||||||
|
Name: "all-object",
|
||||||
|
Object: []*models.Record{{
|
||||||
|
Operation: models.NewOperation(models.OperationPUT),
|
||||||
|
Action: models.NewAction(models.ActionALLOW),
|
||||||
|
Filters: []*models.Filter{},
|
||||||
|
Targets: []*models.Target{{
|
||||||
|
Role: models.NewRole(models.RoleOTHERS),
|
||||||
|
Keys: []string{},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "put-container",
|
||||||
|
Container: &models.Rule{
|
||||||
|
Verb: models.NewVerb(models.VerbPUT),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "seteacl-container",
|
||||||
|
Container: &models.Rule{
|
||||||
|
Verb: models.NewVerb(models.VerbSETEACL),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := defaultHTTPClient()
|
||||||
|
tokens := makeAuthTokenRequest(ctx, t, bearers, httpClient)
|
||||||
|
objectToken := tokens[0]
|
||||||
|
containerPutToken := tokens[1]
|
||||||
|
containerSetEACLToken := tokens[2]
|
||||||
|
|
||||||
|
// check reject object token when container tokens is required
|
||||||
|
checkPutContainerWithError(t, httpClient, objectToken)
|
||||||
|
|
||||||
|
// check reject wrong verb container token
|
||||||
|
checkPutContainerWithError(t, httpClient, containerSetEACLToken)
|
||||||
|
|
||||||
|
// check reject wrong verb container token
|
||||||
|
checkDeleteContainerWithError(t, httpClient, cnrID, containerSetEACLToken)
|
||||||
|
|
||||||
|
// check reject wrong verb container token
|
||||||
|
checkSetEACLContainerWithError(t, httpClient, cnrID, containerPutToken)
|
||||||
|
|
||||||
|
// check reject container token when object tokens is required
|
||||||
|
checkPutObjectWithError(t, httpClient, cnrID, containerSetEACLToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPutContainerWithError(t *testing.T, httpClient *http.Client, token *handlers.BearerToken) {
|
||||||
|
reqURL, err := url.Parse(testHost + "/v1/containers")
|
||||||
|
require.NoError(t, err)
|
||||||
|
body, err := json.Marshal(&operations.PutContainerBody{ContainerName: "container"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
request, err := http.NewRequest(http.MethodPut, reqURL.String(), bytes.NewReader(body))
|
||||||
|
require.NoError(t, err)
|
||||||
|
prepareCommonHeaders(request.Header, token)
|
||||||
|
|
||||||
|
checkGWErrorResponse(t, httpClient, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDeleteContainerWithError(t *testing.T, httpClient *http.Client, cnrID *cid.ID, token *handlers.BearerToken) {
|
||||||
|
reqURL, err := url.Parse(testHost + "/v1/containers/" + cnrID.String())
|
||||||
|
require.NoError(t, err)
|
||||||
|
request, err := http.NewRequest(http.MethodDelete, reqURL.String(), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
prepareCommonHeaders(request.Header, token)
|
||||||
|
|
||||||
|
checkGWErrorResponse(t, httpClient, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSetEACLContainerWithError(t *testing.T, httpClient *http.Client, cnrID *cid.ID, token *handlers.BearerToken) {
|
||||||
|
req := models.Eacl{Records: []*models.Record{}}
|
||||||
|
body, err := json.Marshal(&req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
request, err := http.NewRequest(http.MethodPut, testHost+"/v1/containers/"+cnrID.String()+"/eacl", bytes.NewReader(body))
|
||||||
|
require.NoError(t, err)
|
||||||
|
prepareCommonHeaders(request.Header, token)
|
||||||
|
|
||||||
|
checkGWErrorResponse(t, httpClient, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPutObjectWithError(t *testing.T, httpClient *http.Client, cnrID *cid.ID, token *handlers.BearerToken) {
|
||||||
|
req := &models.ObjectUpload{
|
||||||
|
ContainerID: util.NewString(cnrID.String()),
|
||||||
|
FileName: util.NewString("newFile.txt"),
|
||||||
|
Payload: base64.StdEncoding.EncodeToString([]byte("content")),
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
request, err := http.NewRequest(http.MethodPut, testHost+"/v1/objects?", bytes.NewReader(body))
|
||||||
|
require.NoError(t, err)
|
||||||
|
prepareCommonHeaders(request.Header, token)
|
||||||
|
|
||||||
|
checkGWErrorResponse(t, httpClient, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkGWErrorResponse(t *testing.T, httpClient *http.Client, request *http.Request) {
|
||||||
|
resp := &models.ErrorResponse{}
|
||||||
|
doRequest(t, httpClient, request, http.StatusBadRequest, resp)
|
||||||
|
require.Equal(t, int64(0), resp.Code)
|
||||||
|
require.Equal(t, models.ErrorTypeGW, *resp.Type)
|
||||||
|
}
|
||||||
|
|
||||||
func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) {
|
func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) {
|
||||||
bearer := &models.Bearer{
|
bearer := &models.Bearer{
|
||||||
Object: []*models.Record{{
|
Object: []*models.Record{{
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/go-openapi/errors"
|
"github.com/go-openapi/errors"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
|
"github.com/nspcc-dev/neofs-rest-gw/gen/models"
|
||||||
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
|
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
|
||||||
"github.com/nspcc-dev/neofs-rest-gw/internal/util"
|
"github.com/nspcc-dev/neofs-rest-gw/internal/util"
|
||||||
|
@ -38,6 +39,11 @@ type BearerToken struct {
|
||||||
Key string
|
Key string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionToken struct {
|
||||||
|
BearerToken
|
||||||
|
Verb sessionv2.ContainerSessionVerb
|
||||||
|
}
|
||||||
|
|
||||||
// ContextKey is used for context.Context value. The value requires a key that is not primitive type.
|
// ContextKey is used for context.Context value. The value requires a key that is not primitive type.
|
||||||
type ContextKey string
|
type ContextKey string
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -36,12 +37,15 @@ const (
|
||||||
|
|
||||||
// PutContainers handler that creates container in NeoFS.
|
// PutContainers handler that creates container in NeoFS.
|
||||||
func (a *API) PutContainers(params operations.PutContainerParams, principal *models.Principal) middleware.Responder {
|
func (a *API) PutContainers(params operations.PutContainerParams, principal *models.Principal) middleware.Responder {
|
||||||
bt := &BearerToken{
|
st := &SessionToken{
|
||||||
Token: string(*principal),
|
BearerToken: BearerToken{
|
||||||
Signature: params.XBearerSignature,
|
Token: string(*principal),
|
||||||
Key: params.XBearerSignatureKey,
|
Signature: params.XBearerSignature,
|
||||||
|
Key: params.XBearerSignatureKey,
|
||||||
|
},
|
||||||
|
Verb: sessionv2.ContainerVerbPut,
|
||||||
}
|
}
|
||||||
stoken, err := prepareSessionToken(bt, *params.WalletConnect)
|
stoken, err := prepareSessionToken(st, *params.WalletConnect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp := a.logAndGetErrorResponse("invalid session token", err)
|
resp := a.logAndGetErrorResponse("invalid session token", err)
|
||||||
return operations.NewPutContainerBadRequest().WithPayload(resp)
|
return operations.NewPutContainerBadRequest().WithPayload(resp)
|
||||||
|
@ -97,12 +101,15 @@ func (a *API) PutContainerEACL(params operations.PutContainerEACLParams, princip
|
||||||
return operations.NewPutContainerEACLBadRequest().WithPayload(resp)
|
return operations.NewPutContainerEACLBadRequest().WithPayload(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
bt := &BearerToken{
|
st := &SessionToken{
|
||||||
Token: string(*principal),
|
BearerToken: BearerToken{
|
||||||
Signature: params.XBearerSignature,
|
Token: string(*principal),
|
||||||
Key: params.XBearerSignatureKey,
|
Signature: params.XBearerSignature,
|
||||||
|
Key: params.XBearerSignatureKey,
|
||||||
|
},
|
||||||
|
Verb: sessionv2.ContainerVerbSetEACL,
|
||||||
}
|
}
|
||||||
stoken, err := prepareSessionToken(bt, *params.WalletConnect)
|
stoken, err := prepareSessionToken(st, *params.WalletConnect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp := a.logAndGetErrorResponse("invalid session token", err)
|
resp := a.logAndGetErrorResponse("invalid session token", err)
|
||||||
return operations.NewPutContainerEACLBadRequest().WithPayload(resp)
|
return operations.NewPutContainerEACLBadRequest().WithPayload(resp)
|
||||||
|
@ -186,12 +193,15 @@ func (a *API) ListContainer(params operations.ListContainersParams) middleware.R
|
||||||
|
|
||||||
// DeleteContainer handler that returns container info.
|
// DeleteContainer handler that returns container info.
|
||||||
func (a *API) DeleteContainer(params operations.DeleteContainerParams, principal *models.Principal) middleware.Responder {
|
func (a *API) DeleteContainer(params operations.DeleteContainerParams, principal *models.Principal) middleware.Responder {
|
||||||
bt := &BearerToken{
|
st := &SessionToken{
|
||||||
Token: string(*principal),
|
BearerToken: BearerToken{
|
||||||
Signature: params.XBearerSignature,
|
Token: string(*principal),
|
||||||
Key: params.XBearerSignatureKey,
|
Signature: params.XBearerSignature,
|
||||||
|
Key: params.XBearerSignatureKey,
|
||||||
|
},
|
||||||
|
Verb: sessionv2.ContainerVerbDelete,
|
||||||
}
|
}
|
||||||
stoken, err := prepareSessionToken(bt, *params.WalletConnect)
|
stoken, err := prepareSessionToken(st, *params.WalletConnect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp := a.logAndGetErrorResponse("invalid session token", err)
|
resp := a.logAndGetErrorResponse("invalid session token", err)
|
||||||
return operations.NewDeleteContainerBadRequest().WithPayload(resp)
|
return operations.NewDeleteContainerBadRequest().WithPayload(resp)
|
||||||
|
@ -389,25 +399,31 @@ func isAlNum(c uint8) bool {
|
||||||
return c >= 'a' && c <= 'z' || c >= '0' && c <= '9'
|
return c >= 'a' && c <= 'z' || c >= '0' && c <= '9'
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareSessionToken(bt *BearerToken, isWalletConnect bool) (*session.Token, error) {
|
func prepareSessionToken(st *SessionToken, isWalletConnect bool) (*session.Token, error) {
|
||||||
data, err := base64.StdEncoding.DecodeString(bt.Token)
|
data, err := base64.StdEncoding.DecodeString(st.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't base64-decode bearer token: %w", err)
|
return nil, fmt.Errorf("can't base64-decode session token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signature, err := hex.DecodeString(bt.Signature)
|
signature, err := hex.DecodeString(st.Signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't decode bearer signature: %w", err)
|
return nil, fmt.Errorf("couldn't decode signature: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ownerKey, err := keys.NewPublicKeyFromString(bt.Key)
|
ownerKey, err := keys.NewPublicKeyFromString(st.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't fetch bearer token owner key: %w", err)
|
return nil, fmt.Errorf("couldn't fetch session token owner key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := new(sessionv2.TokenBody)
|
body := new(sessionv2.TokenBody)
|
||||||
if err = body.Unmarshal(data); err != nil {
|
if err = body.Unmarshal(data); err != nil {
|
||||||
return nil, fmt.Errorf("can't unmarshal bearer token: %w", err)
|
return nil, fmt.Errorf("can't unmarshal session token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionContext, ok := body.GetContext().(*sessionv2.ContainerSessionContext); !ok {
|
||||||
|
return nil, errors.New("expected container session context but got something different")
|
||||||
|
} else if sessionContext.Verb() != st.Verb {
|
||||||
|
return nil, fmt.Errorf("invalid container session verb '%s', expected: '%s'", sessionContext.Verb().String(), st.Verb.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
stoken := new(session.Token)
|
stoken := new(session.Token)
|
||||||
|
|
Loading…
Reference in a new issue