From 5c122a43250f81d8b883507e22cc987c340f482e Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Thu, 7 Jul 2022 17:24:41 +0300 Subject: [PATCH] [#15] Add check verbs and token type Signed-off-by: Denis Kirillov --- cmd/neofs-rest-gw/integration_test.go | 108 ++++++++++++++++++++++++++ handlers/api.go | 6 ++ handlers/containers.go | 62 +++++++++------ 3 files changed, 153 insertions(+), 23 deletions(-) diff --git a/cmd/neofs-rest-gw/integration_test.go b/cmd/neofs-rest-gw/integration_test.go index c09c1c0..b2c2831 100644 --- a/cmd/neofs-rest-gw/integration_test.go +++ b/cmd/neofs-rest-gw/integration_test.go @@ -117,6 +117,7 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s restrictByEACL(ctx, t, clientPool, cnrID) 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 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) } +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) { bearer := &models.Bearer{ Object: []*models.Record{{ diff --git a/handlers/api.go b/handlers/api.go index 8d8e372..94f8dde 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -9,6 +9,7 @@ import ( "github.com/go-openapi/errors" "github.com/google/uuid" "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/restapi/operations" "github.com/nspcc-dev/neofs-rest-gw/internal/util" @@ -38,6 +39,11 @@ type BearerToken struct { 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. type ContextKey string diff --git a/handlers/containers.go b/handlers/containers.go index ba10e03..0047a92 100644 --- a/handlers/containers.go +++ b/handlers/containers.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "encoding/base64" "encoding/hex" + "errors" "fmt" "net/http" "strconv" @@ -36,12 +37,15 @@ const ( // PutContainers handler that creates container in NeoFS. func (a *API) PutContainers(params operations.PutContainerParams, principal *models.Principal) middleware.Responder { - bt := &BearerToken{ - Token: string(*principal), - Signature: params.XBearerSignature, - Key: params.XBearerSignatureKey, + st := &SessionToken{ + BearerToken: BearerToken{ + Token: string(*principal), + Signature: params.XBearerSignature, + Key: params.XBearerSignatureKey, + }, + Verb: sessionv2.ContainerVerbPut, } - stoken, err := prepareSessionToken(bt, *params.WalletConnect) + stoken, err := prepareSessionToken(st, *params.WalletConnect) if err != nil { resp := a.logAndGetErrorResponse("invalid session token", err) return operations.NewPutContainerBadRequest().WithPayload(resp) @@ -97,12 +101,15 @@ func (a *API) PutContainerEACL(params operations.PutContainerEACLParams, princip return operations.NewPutContainerEACLBadRequest().WithPayload(resp) } - bt := &BearerToken{ - Token: string(*principal), - Signature: params.XBearerSignature, - Key: params.XBearerSignatureKey, + st := &SessionToken{ + BearerToken: BearerToken{ + Token: string(*principal), + Signature: params.XBearerSignature, + Key: params.XBearerSignatureKey, + }, + Verb: sessionv2.ContainerVerbSetEACL, } - stoken, err := prepareSessionToken(bt, *params.WalletConnect) + stoken, err := prepareSessionToken(st, *params.WalletConnect) if err != nil { resp := a.logAndGetErrorResponse("invalid session token", err) return operations.NewPutContainerEACLBadRequest().WithPayload(resp) @@ -186,12 +193,15 @@ func (a *API) ListContainer(params operations.ListContainersParams) middleware.R // DeleteContainer handler that returns container info. func (a *API) DeleteContainer(params operations.DeleteContainerParams, principal *models.Principal) middleware.Responder { - bt := &BearerToken{ - Token: string(*principal), - Signature: params.XBearerSignature, - Key: params.XBearerSignatureKey, + st := &SessionToken{ + BearerToken: BearerToken{ + Token: string(*principal), + Signature: params.XBearerSignature, + Key: params.XBearerSignatureKey, + }, + Verb: sessionv2.ContainerVerbDelete, } - stoken, err := prepareSessionToken(bt, *params.WalletConnect) + stoken, err := prepareSessionToken(st, *params.WalletConnect) if err != nil { resp := a.logAndGetErrorResponse("invalid session token", err) return operations.NewDeleteContainerBadRequest().WithPayload(resp) @@ -389,25 +399,31 @@ func isAlNum(c uint8) bool { return c >= 'a' && c <= 'z' || c >= '0' && c <= '9' } -func prepareSessionToken(bt *BearerToken, isWalletConnect bool) (*session.Token, error) { - data, err := base64.StdEncoding.DecodeString(bt.Token) +func prepareSessionToken(st *SessionToken, isWalletConnect bool) (*session.Token, error) { + data, err := base64.StdEncoding.DecodeString(st.Token) 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 { - 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 { - 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) 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)