From 686588bc1a26658fdf529c9091f7c3b6295bf1b3 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Thu, 7 Jul 2022 12:02:05 +0300 Subject: [PATCH] [#15] Accept list of tokens to sign Signed-off-by: Denis Kirillov --- cmd/neofs-rest-gw/integration_test.go | 117 +++++++++++----- gen/models/bearer.go | 3 + gen/models/token_response.go | 3 + gen/restapi/embedded_spec.go | 58 ++++---- gen/restapi/operations/auth_parameters.go | 72 ++-------- gen/restapi/operations/auth_responses.go | 19 +-- handlers/auth.go | 130 +++++++++++++----- handlers/auth_test.go | 20 ++- handlers/util.go | 16 +++ internal/util/transformers.go | 12 +- .../wallet-connect/wallet_connect_test.go | 20 ++- spec/rest.yaml | 22 +-- 12 files changed, 289 insertions(+), 203 deletions(-) diff --git a/cmd/neofs-rest-gw/integration_test.go b/cmd/neofs-rest-gw/integration_test.go index 2de6e15..665db4d 100644 --- a/cmd/neofs-rest-gw/integration_test.go +++ b/cmd/neofs-rest-gw/integration_test.go @@ -57,9 +57,6 @@ const ( XBearerSignatureKey = "X-Bearer-Signature-Key" // XBearerOwnerID header contains owner id (wallet address) that corresponds the signature of the token body. XBearerOwnerID = "X-Bearer-Owner-Id" - // XBearerScope header contains operation scope for auth (bearer) token. - // It corresponds to 'object' or 'container' services in neofs. - XBearerScope = "X-Bearer-Scope" // tests configuration. useWalletConnect = false @@ -119,6 +116,8 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s cnrID := createContainer(ctx, t, clientPool, containerName) restrictByEACL(ctx, t, clientPool, cnrID) + t.Run("rest auth several tokens "+version, func(t *testing.T) { authTokens(ctx, t) }) + 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 delete object "+version, func(t *testing.T) { restObjectDelete(ctx, t, clientPool, cnrID) }) @@ -231,6 +230,44 @@ func formRestrictRecord(op models.Operation) *models.Record { }}} } +func authTokens(ctx context.Context, t *testing.T) { + 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), + }, + }, + { + Name: "delete-container", + Container: &models.Rule{ + Verb: models.NewVerb(models.VerbDELETE), + }, + }, + } + + httpClient := defaultHTTPClient() + makeAuthTokenRequest(ctx, t, bearers, httpClient) +} + func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) { bearer := &models.Bearer{ Object: []*models.Record{{ @@ -246,7 +283,8 @@ func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnr bearer.Object = append(bearer.Object, getRestrictBearerRecords()...) httpClient := defaultHTTPClient() - bearerToken := makeAuthObjectTokenRequest(ctx, t, bearer, httpClient) + bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient) + bearerToken := bearerTokens[0] content := "content of file" attrKey, attrValue := "User-Attribute", "user value" @@ -338,7 +376,8 @@ func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.I bearer.Object = append(bearer.Object, getRestrictBearerRecords()...) httpClient := defaultHTTPClient() - bearerToken := makeAuthObjectTokenRequest(ctx, t, bearer, httpClient) + bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient) + bearerToken := bearerTokens[0] query := make(url.Values) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) @@ -415,7 +454,8 @@ func restObjectDelete(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *ci bearer.Object = append(bearer.Object, getRestrictBearerRecords()...) httpClient := defaultHTTPClient() - bearerToken := makeAuthObjectTokenRequest(ctx, t, bearer, httpClient) + bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient) + bearerToken := bearerTokens[0] query := make(url.Values) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) @@ -473,7 +513,8 @@ func restObjectsSearch(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *c bearer.Object = append(bearer.Object, getRestrictBearerRecords()...) httpClient := defaultHTTPClient() - bearerToken := makeAuthObjectTokenRequest(ctx, t, bearer, httpClient) + bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient) + bearerToken := bearerTokens[0] search := &models.SearchFilters{ Filters: []*models.SearchFilter{ @@ -553,7 +594,8 @@ func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Poo } httpClient := defaultHTTPClient() - bearerToken := makeAuthContainerTokenRequest(ctx, t, bearer, httpClient) + bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient) + bearerToken := bearerTokens[0] query := make(url.Values) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) @@ -581,7 +623,8 @@ func restContainerEACLPut(ctx context.Context, t *testing.T, clientPool *pool.Po Verb: models.NewVerb(models.VerbSETEACL), }, } - bearerToken := makeAuthContainerTokenRequest(ctx, t, bearer, httpClient) + bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient) + bearerToken := bearerTokens[0] req := models.Eacl{ Records: []*models.Record{{ @@ -674,28 +717,19 @@ func restContainerList(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *c require.Contains(t, list.Containers, expected) } -func makeAuthContainerTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bearer, httpClient *http.Client) *handlers.BearerToken { - return makeAuthTokenRequest(ctx, t, bearer, httpClient, models.TokenTypeContainer) -} - -func makeAuthObjectTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bearer, httpClient *http.Client) *handlers.BearerToken { - return makeAuthTokenRequest(ctx, t, bearer, httpClient, models.TokenTypeObject) -} - -func makeAuthTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bearer, httpClient *http.Client, tokenType models.TokenType) *handlers.BearerToken { +func makeAuthTokenRequest(ctx context.Context, t *testing.T, bearers []*models.Bearer, httpClient *http.Client) []*handlers.BearerToken { key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey) require.NoError(t, err) ownerID := owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key.PublicKey())) - data, err := json.Marshal(bearer) + data, err := json.Marshal(bearers) require.NoError(t, err) request, err := http.NewRequest(http.MethodPost, testHost+"/v1/auth", bytes.NewReader(data)) require.NoError(t, err) request = request.WithContext(ctx) request.Header.Add("Content-Type", "application/json") - request.Header.Add(XBearerScope, string(tokenType)) request.Header.Add(XBearerOwnerID, ownerID.String()) resp, err := httpClient.Do(request) @@ -713,24 +747,40 @@ func makeAuthTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bear } require.Equal(t, http.StatusOK, resp.StatusCode) - stokenResp := &models.TokenResponse{} - err = json.Unmarshal(rr, stokenResp) + var stokenResp []*models.TokenResponse + err = json.Unmarshal(rr, &stokenResp) require.NoError(t, err) - require.Equal(t, *stokenResp.Type, tokenType) + fmt.Println("resp tokens:") - binaryData, err := base64.StdEncoding.DecodeString(*stokenResp.Token) - require.NoError(t, err) + respTokens := make([]*handlers.BearerToken, len(stokenResp)) + for i, tok := range stokenResp { + isObject, err := handlers.IsObjectToken(bearers[i]) + require.NoError(t, err) - var bt *handlers.BearerToken - if useWalletConnect { - bt = signTokenWalletConnect(t, key, binaryData) - } else { - bt = signToken(t, key, binaryData) + require.Equal(t, bearers[i].Name, tok.Name) + + if isObject { + require.Equal(t, models.TokenTypeObject, *tok.Type) + } else { + require.Equal(t, models.TokenTypeContainer, *tok.Type) + } + + binaryData, err := base64.StdEncoding.DecodeString(*tok.Token) + require.NoError(t, err) + + var bt *handlers.BearerToken + if useWalletConnect { + bt = signTokenWalletConnect(t, key, binaryData) + } else { + bt = signToken(t, key, binaryData) + } + + respTokens[i] = bt + fmt.Printf("%+v\n", bt) } - fmt.Printf("container token:\n%+v\n", bt) - return bt + return respTokens } func signToken(t *testing.T, key *keys.PrivateKey, data []byte) *handlers.BearerToken { @@ -765,7 +815,8 @@ func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool) } httpClient := &http.Client{Timeout: 30 * time.Second} - bearerToken := makeAuthContainerTokenRequest(ctx, t, bearer, httpClient) + bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient) + bearerToken := bearerTokens[0] attrKey, attrValue := "User-Attribute", "user value" userAttributes := map[string]string{ diff --git a/gen/models/bearer.go b/gen/models/bearer.go index 492e4ba..23e4e60 100644 --- a/gen/models/bearer.go +++ b/gen/models/bearer.go @@ -22,6 +22,9 @@ type Bearer struct { // container Container *Rule `json:"container,omitempty"` + // name + Name string `json:"name,omitempty"` + // object Object []*Record `json:"object"` } diff --git a/gen/models/token_response.go b/gen/models/token_response.go index caa15c3..6982f3c 100644 --- a/gen/models/token_response.go +++ b/gen/models/token_response.go @@ -20,6 +20,9 @@ import ( // swagger:model TokenResponse type TokenResponse struct { + // name + Name string `json:"name,omitempty"` + // token // Required: true Token *string `json:"token"` diff --git a/gen/restapi/embedded_spec.go b/gen/restapi/embedded_spec.go index e52e143..c713a56 100644 --- a/gen/restapi/embedded_spec.go +++ b/gen/restapi/embedded_spec.go @@ -49,17 +49,6 @@ func init() { "in": "header", "required": true }, - { - "enum": [ - "object", - "container" - ], - "type": "string", - "description": "Supported operation scope for token", - "name": "X-Bearer-Scope", - "in": "header", - "required": true - }, { "type": "integer", "default": 100, @@ -69,11 +58,14 @@ func init() { }, { "description": "Bearer token", - "name": "token", + "name": "tokens", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/Bearer" + "type": "array", + "items": { + "$ref": "#/definitions/Bearer" + } } } ], @@ -81,7 +73,10 @@ func init() { "200": { "description": "Base64 encoded stable binary marshaled bearer token", "schema": { - "$ref": "#/definitions/TokenResponse" + "type": "array", + "items": { + "$ref": "#/definitions/TokenResponse" + } } }, "400": { @@ -555,6 +550,9 @@ func init() { "container": { "$ref": "#/definitions/Rule" }, + "name": { + "type": "string" + }, "object": { "type": "array", "items": { @@ -983,6 +981,9 @@ func init() { "token" ], "properties": { + "name": { + "type": "string" + }, "token": { "type": "string" }, @@ -1100,17 +1101,6 @@ func init() { "in": "header", "required": true }, - { - "enum": [ - "object", - "container" - ], - "type": "string", - "description": "Supported operation scope for token", - "name": "X-Bearer-Scope", - "in": "header", - "required": true - }, { "type": "integer", "default": 100, @@ -1120,11 +1110,14 @@ func init() { }, { "description": "Bearer token", - "name": "token", + "name": "tokens", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/Bearer" + "type": "array", + "items": { + "$ref": "#/definitions/Bearer" + } } } ], @@ -1132,7 +1125,10 @@ func init() { "200": { "description": "Base64 encoded stable binary marshaled bearer token", "schema": { - "$ref": "#/definitions/TokenResponse" + "type": "array", + "items": { + "$ref": "#/definitions/TokenResponse" + } } }, "400": { @@ -1702,6 +1698,9 @@ func init() { "container": { "$ref": "#/definitions/Rule" }, + "name": { + "type": "string" + }, "object": { "type": "array", "items": { @@ -2130,6 +2129,9 @@ func init() { "token" ], "properties": { + "name": { + "type": "string" + }, "token": { "type": "string" }, diff --git a/gen/restapi/operations/auth_parameters.go b/gen/restapi/operations/auth_parameters.go index b5fde96..a380581 100644 --- a/gen/restapi/operations/auth_parameters.go +++ b/gen/restapi/operations/auth_parameters.go @@ -6,7 +6,6 @@ package operations // Editing this file might prove futile when you re-run the swagger generate command import ( - "context" "io" "net/http" @@ -54,16 +53,11 @@ type AuthParams struct { In: header */ XBearerOwnerID string - /*Supported operation scope for token - Required: true - In: header - */ - XBearerScope string /*Bearer token Required: true In: body */ - Token *models.Bearer + Tokens []*models.Bearer } // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface @@ -83,36 +77,34 @@ func (o *AuthParams) BindRequest(r *http.Request, route *middleware.MatchedRoute res = append(res, err) } - if err := o.bindXBearerScope(r.Header[http.CanonicalHeaderKey("X-Bearer-Scope")], true, route.Formats); err != nil { - res = append(res, err) - } - if runtime.HasBody(r) { defer r.Body.Close() - var body models.Bearer + var body []*models.Bearer if err := route.Consumer.Consume(r.Body, &body); err != nil { if err == io.EOF { - res = append(res, errors.Required("token", "body", "")) + res = append(res, errors.Required("tokens", "body", "")) } else { - res = append(res, errors.NewParseError("token", "body", "", err)) + res = append(res, errors.NewParseError("tokens", "body", "", err)) } } else { - // validate body object - if err := body.Validate(route.Formats); err != nil { - res = append(res, err) - } - ctx := validate.WithOperationRequest(context.Background()) - if err := body.ContextValidate(ctx, route.Formats); err != nil { - res = append(res, err) + // validate array of body objects + for i := range body { + if body[i] == nil { + continue + } + if err := body[i].Validate(route.Formats); err != nil { + res = append(res, err) + break + } } if len(res) == 0 { - o.Token = &body + o.Tokens = body } } } else { - res = append(res, errors.Required("token", "body", "")) + res = append(res, errors.Required("tokens", "body", "")) } if len(res) > 0 { return errors.CompositeValidationError(res...) @@ -162,37 +154,3 @@ func (o *AuthParams) bindXBearerOwnerID(rawData []string, hasKey bool, formats s return nil } - -// bindXBearerScope binds and validates parameter XBearerScope from header. -func (o *AuthParams) bindXBearerScope(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("X-Bearer-Scope", "header", rawData) - } - var raw string - if len(rawData) > 0 { - raw = rawData[len(rawData)-1] - } - - // Required: true - - if err := validate.RequiredString("X-Bearer-Scope", "header", raw); err != nil { - return err - } - o.XBearerScope = raw - - if err := o.validateXBearerScope(formats); err != nil { - return err - } - - return nil -} - -// validateXBearerScope carries on validations for parameter XBearerScope -func (o *AuthParams) validateXBearerScope(formats strfmt.Registry) error { - - if err := validate.EnumCase("X-Bearer-Scope", "header", o.XBearerScope, []interface{}{"object", "container"}, true); err != nil { - return err - } - - return nil -} diff --git a/gen/restapi/operations/auth_responses.go b/gen/restapi/operations/auth_responses.go index 2fd3a76..f7f3406 100644 --- a/gen/restapi/operations/auth_responses.go +++ b/gen/restapi/operations/auth_responses.go @@ -25,7 +25,7 @@ type AuthOK struct { /* In: Body */ - Payload *models.TokenResponse `json:"body,omitempty"` + Payload []*models.TokenResponse `json:"body,omitempty"` } // NewAuthOK creates AuthOK with default headers values @@ -35,13 +35,13 @@ func NewAuthOK() *AuthOK { } // WithPayload adds the payload to the auth o k response -func (o *AuthOK) WithPayload(payload *models.TokenResponse) *AuthOK { +func (o *AuthOK) WithPayload(payload []*models.TokenResponse) *AuthOK { o.Payload = payload return o } // SetPayload sets the payload to the auth o k response -func (o *AuthOK) SetPayload(payload *models.TokenResponse) { +func (o *AuthOK) SetPayload(payload []*models.TokenResponse) { o.Payload = payload } @@ -49,11 +49,14 @@ func (o *AuthOK) SetPayload(payload *models.TokenResponse) { func (o *AuthOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { rw.WriteHeader(200) - if o.Payload != nil { - payload := o.Payload - if err := producer.Produce(rw, payload); err != nil { - panic(err) // let the recovery middleware deal with this - } + payload := o.Payload + if payload == nil { + // return empty array + payload = make([]*models.TokenResponse, 0, 50) + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this } } diff --git a/handlers/auth.go b/handlers/auth.go index 6afec2a..cf29057 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -17,29 +17,86 @@ import ( const defaultTokenExpDuration = 100 // in epoch -// PostAuth handler that forms bearer token to sign. -func (a *API) PostAuth(params operations.AuthParams) middleware.Responder { - var ( - err error - resp *models.TokenResponse - ) - - if params.XBearerScope == "object" { - resp, err = prepareObjectToken(params, a.pool) - } else { - resp, err = prepareContainerTokens(params, a.pool, a.key.PublicKey()) - } - if err != nil { - return operations.NewAuthBadRequest().WithPayload(models.Error(err.Error())) - } - - return operations.NewAuthOK().WithPayload(resp) +type headersParams struct { + XBearerLifetime uint64 + XBearerOwnerID string } -func prepareObjectToken(params operations.AuthParams, pool *pool.Pool) (*models.TokenResponse, error) { - ctx := params.HTTPRequest.Context() +type objectTokenParams struct { + headersParams + Records []*models.Record + Name string +} - btoken, err := util.ToNativeObjectToken(params.Token) +type containerTokenParams struct { + headersParams + Rule *models.Rule + Name string +} + +func newHeaderParams(params operations.AuthParams) headersParams { + prm := headersParams{ + XBearerOwnerID: params.XBearerOwnerID, + } + + if params.XBearerLifetime != nil && *params.XBearerLifetime > 0 { + prm.XBearerLifetime = uint64(*params.XBearerLifetime) + } + + return prm +} + +func newObjectParams(common headersParams, token *models.Bearer) objectTokenParams { + return objectTokenParams{ + headersParams: common, + Records: token.Object, + Name: token.Name, + } +} + +func newContainerParams(common headersParams, token *models.Bearer) containerTokenParams { + return containerTokenParams{ + headersParams: common, + Rule: token.Container, + Name: token.Name, + } +} + +// PostAuth handler that forms bearer token to sign. +func (a *API) PostAuth(params operations.AuthParams) middleware.Responder { + ctx := params.HTTPRequest.Context() + commonPrm := newHeaderParams(params) + + tokenNames := make(map[string]struct{}) + response := make([]*models.TokenResponse, len(params.Tokens)) + for i, token := range params.Tokens { + if _, ok := tokenNames[token.Name]; ok { + return operations.NewAuthBadRequest().WithPayload(models.Error(fmt.Sprintf("duplicated token name '%s'", token.Name))) + } + tokenNames[token.Name] = struct{}{} + + isObject, err := IsObjectToken(token) + if err != nil { + return operations.NewAuthBadRequest().WithPayload(models.Error(err.Error())) + } + + if isObject { + prm := newObjectParams(commonPrm, token) + response[i], err = prepareObjectToken(ctx, prm, a.pool) + } else { + prm := newContainerParams(commonPrm, token) + response[i], err = prepareContainerTokens(ctx, prm, a.pool, a.key.PublicKey()) + } + if err != nil { + return operations.NewAuthBadRequest().WithPayload(models.Error(err.Error())) + } + } + + return operations.NewAuthOK().WithPayload(response) +} + +func prepareObjectToken(ctx context.Context, params objectTokenParams, pool *pool.Pool) (*models.TokenResponse, error) { + btoken, err := util.ToNativeObjectToken(params.Records) if err != nil { return nil, fmt.Errorf("couldn't transform token to native: %w", err) } @@ -56,16 +113,14 @@ func prepareObjectToken(params operations.AuthParams, pool *pool.Pool) (*models. return nil, fmt.Errorf("couldn't marshal bearer token: %w", err) } - var resp models.TokenResponse - resp.Type = models.NewTokenType(models.TokenTypeObject) - resp.Token = util.NewString(base64.StdEncoding.EncodeToString(binaryBearer)) - - return &resp, nil + return &models.TokenResponse{ + Name: params.Name, + Type: models.NewTokenType(models.TokenTypeObject), + Token: util.NewString(base64.StdEncoding.EncodeToString(binaryBearer)), + }, nil } -func prepareContainerTokens(params operations.AuthParams, pool *pool.Pool, key *keys.PublicKey) (*models.TokenResponse, error) { - ctx := params.HTTPRequest.Context() - +func prepareContainerTokens(ctx context.Context, params containerTokenParams, pool *pool.Pool, key *keys.PublicKey) (*models.TokenResponse, error) { iat, exp, err := getTokenLifetime(ctx, pool, params.XBearerLifetime) if err != nil { return nil, fmt.Errorf("couldn't get lifetime: %w", err) @@ -76,10 +131,7 @@ func prepareContainerTokens(params operations.AuthParams, pool *pool.Pool, key * return nil, fmt.Errorf("invalid bearer owner: %w", err) } - var resp models.TokenResponse - resp.Type = models.NewTokenType(models.TokenTypeContainer) - - stoken, err := util.ToNativeContainerToken(params.Token) + stoken, err := util.ToNativeContainerToken(params.Rule) if err != nil { return nil, fmt.Errorf("couldn't transform rule to native session token: %w", err) } @@ -101,9 +153,11 @@ func prepareContainerTokens(params operations.AuthParams, pool *pool.Pool, key * return nil, fmt.Errorf("couldn't marshal session token: %w", err) } - resp.Token = util.NewString(base64.StdEncoding.EncodeToString(binaryToken)) - - return &resp, nil + return &models.TokenResponse{ + Name: params.Name, + Type: models.NewTokenType(models.TokenTypeContainer), + Token: util.NewString(base64.StdEncoding.EncodeToString(binaryToken)), + }, nil } func getCurrentEpoch(ctx context.Context, p *pool.Pool) (uint64, error) { @@ -115,15 +169,15 @@ func getCurrentEpoch(ctx context.Context, p *pool.Pool) (uint64, error) { return netInfo.CurrentEpoch(), nil } -func getTokenLifetime(ctx context.Context, p *pool.Pool, expDuration *int64) (uint64, uint64, error) { +func getTokenLifetime(ctx context.Context, p *pool.Pool, expDuration uint64) (uint64, uint64, error) { currEpoch, err := getCurrentEpoch(ctx, p) if err != nil { return 0, 0, err } var lifetimeDuration uint64 = defaultTokenExpDuration - if expDuration != nil && *expDuration > 0 { - lifetimeDuration = uint64(*expDuration) + if expDuration != 0 { + lifetimeDuration = expDuration } return currEpoch, currEpoch + lifetimeDuration, nil diff --git a/handlers/auth_test.go b/handlers/auth_test.go index eae1b2a..8fe37da 100644 --- a/handlers/auth_test.go +++ b/handlers/auth_test.go @@ -24,19 +24,17 @@ func TestSign(t *testing.T) { pubKeyHex := hex.EncodeToString(key.PublicKey().Bytes()) - b := &models.Bearer{ - 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{}, - }}, + records := []*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{}, }}, - } + }} - btoken, err := util.ToNativeObjectToken(b) + btoken, err := util.ToNativeObjectToken(records) require.NoError(t, err) ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex) diff --git a/handlers/util.go b/handlers/util.go index a6b168f..1a6dc97 100644 --- a/handlers/util.go +++ b/handlers/util.go @@ -216,3 +216,19 @@ func updateExpirationHeader(headers map[string]string, durations *epochDurations numEpoch := expDuration.Milliseconds() / epochDuration headers[objectv2.SysAttributeExpEpoch] = strconv.FormatInt(int64(durations.currentEpoch)+numEpoch, 10) } + +// IsObjectToken check that provided token is for object. +func IsObjectToken(token *models.Bearer) (bool, error) { + isObject := len(token.Object) != 0 + isContainer := token.Container != nil + + if !isObject && !isContainer { + return false, fmt.Errorf("token '%s': rules must not be empty", token.Name) + } + + if isObject && isContainer { + return false, fmt.Errorf("token '%s': only one type rules can be provided: object or container, not both", token.Name) + } + + return isObject, nil +} diff --git a/internal/util/transformers.go b/internal/util/transformers.go index 251b26d..8258dd8 100644 --- a/internal/util/transformers.go +++ b/internal/util/transformers.go @@ -222,9 +222,9 @@ func ToNativeRule(r *models.Rule) (*session.ContainerContext, error) { return &ctx, nil } -// ToNativeContainerToken converts models.Bearer to appropriate session.Token. -func ToNativeContainerToken(b *models.Bearer) (*session.Token, error) { - sctx, err := ToNativeRule(b.Container) +// ToNativeContainerToken converts models.Rule to appropriate session.Token. +func ToNativeContainerToken(tokenRule *models.Rule) (*session.Token, error) { + sctx, err := ToNativeRule(tokenRule) if err != nil { return nil, fmt.Errorf("couldn't transform rule to native: %w", err) } @@ -365,9 +365,9 @@ func FromNativeTarget(t eacl.Target) (*models.Target, error) { return &target, nil } -// ToNativeObjectToken converts Bearer to appropriate token.BearerToken. -func ToNativeObjectToken(b *models.Bearer) (*token.BearerToken, error) { - table, err := ToNativeTable(b.Object) +// ToNativeObjectToken converts []*models.Record to appropriate token.BearerToken. +func ToNativeObjectToken(tokenRecords []*models.Record) (*token.BearerToken, error) { + table, err := ToNativeTable(tokenRecords) if err != nil { return nil, err } diff --git a/internal/wallet-connect/wallet_connect_test.go b/internal/wallet-connect/wallet_connect_test.go index d0af5fd..2aee923 100644 --- a/internal/wallet-connect/wallet_connect_test.go +++ b/internal/wallet-connect/wallet_connect_test.go @@ -20,19 +20,17 @@ func TestSign(t *testing.T) { pubKeyHex := hex.EncodeToString(key.PublicKey().Bytes()) - b := &models.Bearer{ - 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{}, - }}, + records := []*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{}, }}, - } + }} - btoken, err := util.ToNativeObjectToken(b) + btoken, err := util.ToNativeObjectToken(records) require.NoError(t, err) ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex) diff --git a/spec/rest.yaml b/spec/rest.yaml index 0d72c0d..541a945 100644 --- a/spec/rest.yaml +++ b/spec/rest.yaml @@ -64,25 +64,19 @@ paths: description: Owner Id (wallet address) that will sign the token type: string required: true - - in: header - description: Supported operation scope for token - name: X-Bearer-Scope - type: string - enum: - - object - - container - required: true - in: header description: Token lifetime in epoch name: X-Bearer-Lifetime type: integer default: 100 - in: body - name: token + name: tokens required: true description: Bearer token schema: - $ref: '#/definitions/Bearer' + type: array + items: + $ref: '#/definitions/Bearer' consumes: - application/json produces: @@ -91,7 +85,9 @@ paths: 200: description: Base64 encoded stable binary marshaled bearer token schema: - $ref: '#/definitions/TokenResponse' + type: array + items: + $ref: '#/definitions/TokenResponse' 400: description: Bad request schema: @@ -363,6 +359,8 @@ definitions: Bearer: type: object properties: + name: + type: string object: type: array items: @@ -483,6 +481,8 @@ definitions: TokenResponse: type: object properties: + name: + type: string type: $ref: '#/definitions/TokenType' token: