[#15] Accept list of tokens to sign

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-07-07 12:02:05 +03:00 committed by Alex Vanin
parent 0e4e213352
commit 686588bc1a
12 changed files with 289 additions and 203 deletions

View file

@ -57,9 +57,6 @@ const (
XBearerSignatureKey = "X-Bearer-Signature-Key" XBearerSignatureKey = "X-Bearer-Signature-Key"
// XBearerOwnerID header contains owner id (wallet address) that corresponds the signature of the token body. // XBearerOwnerID header contains owner id (wallet address) that corresponds the signature of the token body.
XBearerOwnerID = "X-Bearer-Owner-Id" 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. // tests configuration.
useWalletConnect = false 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) cnrID := createContainer(ctx, t, clientPool, containerName)
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 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) })
t.Run("rest delete object "+version, func(t *testing.T) { restObjectDelete(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) { 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{{
@ -246,7 +283,8 @@ func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnr
bearer.Object = append(bearer.Object, getRestrictBearerRecords()...) bearer.Object = append(bearer.Object, getRestrictBearerRecords()...)
httpClient := defaultHTTPClient() httpClient := defaultHTTPClient()
bearerToken := makeAuthObjectTokenRequest(ctx, t, bearer, httpClient) bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient)
bearerToken := bearerTokens[0]
content := "content of file" content := "content of file"
attrKey, attrValue := "User-Attribute", "user value" 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()...) bearer.Object = append(bearer.Object, getRestrictBearerRecords()...)
httpClient := defaultHTTPClient() httpClient := defaultHTTPClient()
bearerToken := makeAuthObjectTokenRequest(ctx, t, bearer, httpClient) bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient)
bearerToken := bearerTokens[0]
query := make(url.Values) query := make(url.Values)
query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) 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()...) bearer.Object = append(bearer.Object, getRestrictBearerRecords()...)
httpClient := defaultHTTPClient() httpClient := defaultHTTPClient()
bearerToken := makeAuthObjectTokenRequest(ctx, t, bearer, httpClient) bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient)
bearerToken := bearerTokens[0]
query := make(url.Values) query := make(url.Values)
query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) 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()...) bearer.Object = append(bearer.Object, getRestrictBearerRecords()...)
httpClient := defaultHTTPClient() httpClient := defaultHTTPClient()
bearerToken := makeAuthObjectTokenRequest(ctx, t, bearer, httpClient) bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient)
bearerToken := bearerTokens[0]
search := &models.SearchFilters{ search := &models.SearchFilters{
Filters: []*models.SearchFilter{ Filters: []*models.SearchFilter{
@ -553,7 +594,8 @@ func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Poo
} }
httpClient := defaultHTTPClient() httpClient := defaultHTTPClient()
bearerToken := makeAuthContainerTokenRequest(ctx, t, bearer, httpClient) bearerTokens := makeAuthTokenRequest(ctx, t, []*models.Bearer{bearer}, httpClient)
bearerToken := bearerTokens[0]
query := make(url.Values) query := make(url.Values)
query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) 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), 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{ req := models.Eacl{
Records: []*models.Record{{ 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) require.Contains(t, list.Containers, expected)
} }
func makeAuthContainerTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bearer, httpClient *http.Client) *handlers.BearerToken { func makeAuthTokenRequest(ctx context.Context, t *testing.T, bearers []*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 {
key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey) key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey)
require.NoError(t, err) require.NoError(t, err)
ownerID := owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key.PublicKey())) ownerID := owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key.PublicKey()))
data, err := json.Marshal(bearer) data, err := json.Marshal(bearers)
require.NoError(t, err) require.NoError(t, err)
request, err := http.NewRequest(http.MethodPost, testHost+"/v1/auth", bytes.NewReader(data)) request, err := http.NewRequest(http.MethodPost, testHost+"/v1/auth", bytes.NewReader(data))
require.NoError(t, err) require.NoError(t, err)
request = request.WithContext(ctx) request = request.WithContext(ctx)
request.Header.Add("Content-Type", "application/json") request.Header.Add("Content-Type", "application/json")
request.Header.Add(XBearerScope, string(tokenType))
request.Header.Add(XBearerOwnerID, ownerID.String()) request.Header.Add(XBearerOwnerID, ownerID.String())
resp, err := httpClient.Do(request) resp, err := httpClient.Do(request)
@ -713,13 +747,26 @@ func makeAuthTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bear
} }
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
stokenResp := &models.TokenResponse{} var stokenResp []*models.TokenResponse
err = json.Unmarshal(rr, stokenResp) err = json.Unmarshal(rr, &stokenResp)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, *stokenResp.Type, tokenType) fmt.Println("resp tokens:")
binaryData, err := base64.StdEncoding.DecodeString(*stokenResp.Token) respTokens := make([]*handlers.BearerToken, len(stokenResp))
for i, tok := range stokenResp {
isObject, err := handlers.IsObjectToken(bearers[i])
require.NoError(t, err)
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) require.NoError(t, err)
var bt *handlers.BearerToken var bt *handlers.BearerToken
@ -729,8 +776,11 @@ func makeAuthTokenRequest(ctx context.Context, t *testing.T, bearer *models.Bear
bt = signToken(t, key, binaryData) bt = signToken(t, key, binaryData)
} }
fmt.Printf("container token:\n%+v\n", bt) respTokens[i] = bt
return bt fmt.Printf("%+v\n", bt)
}
return respTokens
} }
func signToken(t *testing.T, key *keys.PrivateKey, data []byte) *handlers.BearerToken { 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} 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" attrKey, attrValue := "User-Attribute", "user value"
userAttributes := map[string]string{ userAttributes := map[string]string{

View file

@ -22,6 +22,9 @@ type Bearer struct {
// container // container
Container *Rule `json:"container,omitempty"` Container *Rule `json:"container,omitempty"`
// name
Name string `json:"name,omitempty"`
// object // object
Object []*Record `json:"object"` Object []*Record `json:"object"`
} }

View file

@ -20,6 +20,9 @@ import (
// swagger:model TokenResponse // swagger:model TokenResponse
type TokenResponse struct { type TokenResponse struct {
// name
Name string `json:"name,omitempty"`
// token // token
// Required: true // Required: true
Token *string `json:"token"` Token *string `json:"token"`

View file

@ -49,17 +49,6 @@ func init() {
"in": "header", "in": "header",
"required": true "required": true
}, },
{
"enum": [
"object",
"container"
],
"type": "string",
"description": "Supported operation scope for token",
"name": "X-Bearer-Scope",
"in": "header",
"required": true
},
{ {
"type": "integer", "type": "integer",
"default": 100, "default": 100,
@ -69,20 +58,26 @@ func init() {
}, },
{ {
"description": "Bearer token", "description": "Bearer token",
"name": "token", "name": "tokens",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Bearer" "$ref": "#/definitions/Bearer"
} }
} }
}
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Base64 encoded stable binary marshaled bearer token", "description": "Base64 encoded stable binary marshaled bearer token",
"schema": { "schema": {
"type": "array",
"items": {
"$ref": "#/definitions/TokenResponse" "$ref": "#/definitions/TokenResponse"
} }
}
}, },
"400": { "400": {
"description": "Bad request", "description": "Bad request",
@ -555,6 +550,9 @@ func init() {
"container": { "container": {
"$ref": "#/definitions/Rule" "$ref": "#/definitions/Rule"
}, },
"name": {
"type": "string"
},
"object": { "object": {
"type": "array", "type": "array",
"items": { "items": {
@ -983,6 +981,9 @@ func init() {
"token" "token"
], ],
"properties": { "properties": {
"name": {
"type": "string"
},
"token": { "token": {
"type": "string" "type": "string"
}, },
@ -1100,17 +1101,6 @@ func init() {
"in": "header", "in": "header",
"required": true "required": true
}, },
{
"enum": [
"object",
"container"
],
"type": "string",
"description": "Supported operation scope for token",
"name": "X-Bearer-Scope",
"in": "header",
"required": true
},
{ {
"type": "integer", "type": "integer",
"default": 100, "default": 100,
@ -1120,20 +1110,26 @@ func init() {
}, },
{ {
"description": "Bearer token", "description": "Bearer token",
"name": "token", "name": "tokens",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Bearer" "$ref": "#/definitions/Bearer"
} }
} }
}
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Base64 encoded stable binary marshaled bearer token", "description": "Base64 encoded stable binary marshaled bearer token",
"schema": { "schema": {
"type": "array",
"items": {
"$ref": "#/definitions/TokenResponse" "$ref": "#/definitions/TokenResponse"
} }
}
}, },
"400": { "400": {
"description": "Bad request", "description": "Bad request",
@ -1702,6 +1698,9 @@ func init() {
"container": { "container": {
"$ref": "#/definitions/Rule" "$ref": "#/definitions/Rule"
}, },
"name": {
"type": "string"
},
"object": { "object": {
"type": "array", "type": "array",
"items": { "items": {
@ -2130,6 +2129,9 @@ func init() {
"token" "token"
], ],
"properties": { "properties": {
"name": {
"type": "string"
},
"token": { "token": {
"type": "string" "type": "string"
}, },

View file

@ -6,7 +6,6 @@ package operations
// Editing this file might prove futile when you re-run the swagger generate command // Editing this file might prove futile when you re-run the swagger generate command
import ( import (
"context"
"io" "io"
"net/http" "net/http"
@ -54,16 +53,11 @@ type AuthParams struct {
In: header In: header
*/ */
XBearerOwnerID string XBearerOwnerID string
/*Supported operation scope for token
Required: true
In: header
*/
XBearerScope string
/*Bearer token /*Bearer token
Required: true Required: true
In: body 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 // 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) 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) { if runtime.HasBody(r) {
defer r.Body.Close() defer r.Body.Close()
var body models.Bearer var body []*models.Bearer
if err := route.Consumer.Consume(r.Body, &body); err != nil { if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF { if err == io.EOF {
res = append(res, errors.Required("token", "body", "")) res = append(res, errors.Required("tokens", "body", ""))
} else { } else {
res = append(res, errors.NewParseError("token", "body", "", err)) res = append(res, errors.NewParseError("tokens", "body", "", err))
} }
} else { } else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
ctx := validate.WithOperationRequest(context.Background()) // validate array of body objects
if err := body.ContextValidate(ctx, route.Formats); err != nil { for i := range body {
if body[i] == nil {
continue
}
if err := body[i].Validate(route.Formats); err != nil {
res = append(res, err) res = append(res, err)
break
}
} }
if len(res) == 0 { if len(res) == 0 {
o.Token = &body o.Tokens = body
} }
} }
} else { } else {
res = append(res, errors.Required("token", "body", "")) res = append(res, errors.Required("tokens", "body", ""))
} }
if len(res) > 0 { if len(res) > 0 {
return errors.CompositeValidationError(res...) return errors.CompositeValidationError(res...)
@ -162,37 +154,3 @@ func (o *AuthParams) bindXBearerOwnerID(rawData []string, hasKey bool, formats s
return nil 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
}

View file

@ -25,7 +25,7 @@ type AuthOK struct {
/* /*
In: Body In: Body
*/ */
Payload *models.TokenResponse `json:"body,omitempty"` Payload []*models.TokenResponse `json:"body,omitempty"`
} }
// NewAuthOK creates AuthOK with default headers values // NewAuthOK creates AuthOK with default headers values
@ -35,13 +35,13 @@ func NewAuthOK() *AuthOK {
} }
// WithPayload adds the payload to the auth o k response // 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 o.Payload = payload
return o return o
} }
// SetPayload sets the payload to the auth o k response // 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 o.Payload = payload
} }
@ -49,12 +49,15 @@ func (o *AuthOK) SetPayload(payload *models.TokenResponse) {
func (o *AuthOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { func (o *AuthOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200) rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload payload := o.Payload
if payload == nil {
// return empty array
payload = make([]*models.TokenResponse, 0, 50)
}
if err := producer.Produce(rw, payload); err != nil { if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this panic(err) // let the recovery middleware deal with this
} }
}
} }
// AuthBadRequestCode is the HTTP code returned for type AuthBadRequest // AuthBadRequestCode is the HTTP code returned for type AuthBadRequest

View file

@ -17,29 +17,86 @@ import (
const defaultTokenExpDuration = 100 // in epoch const defaultTokenExpDuration = 100 // in epoch
type headersParams struct {
XBearerLifetime uint64
XBearerOwnerID string
}
type objectTokenParams struct {
headersParams
Records []*models.Record
Name string
}
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. // PostAuth handler that forms bearer token to sign.
func (a *API) PostAuth(params operations.AuthParams) middleware.Responder { func (a *API) PostAuth(params operations.AuthParams) middleware.Responder {
var ( ctx := params.HTTPRequest.Context()
err error commonPrm := newHeaderParams(params)
resp *models.TokenResponse
)
if params.XBearerScope == "object" { tokenNames := make(map[string]struct{})
resp, err = prepareObjectToken(params, a.pool) response := make([]*models.TokenResponse, len(params.Tokens))
} else { for i, token := range params.Tokens {
resp, err = prepareContainerTokens(params, a.pool, a.key.PublicKey()) 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 { if err != nil {
return operations.NewAuthBadRequest().WithPayload(models.Error(err.Error())) return operations.NewAuthBadRequest().WithPayload(models.Error(err.Error()))
} }
return operations.NewAuthOK().WithPayload(resp) 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(params operations.AuthParams, pool *pool.Pool) (*models.TokenResponse, error) { func prepareObjectToken(ctx context.Context, params objectTokenParams, pool *pool.Pool) (*models.TokenResponse, error) {
ctx := params.HTTPRequest.Context() btoken, err := util.ToNativeObjectToken(params.Records)
btoken, err := util.ToNativeObjectToken(params.Token)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't transform token to native: %w", err) 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) return nil, fmt.Errorf("couldn't marshal bearer token: %w", err)
} }
var resp models.TokenResponse return &models.TokenResponse{
resp.Type = models.NewTokenType(models.TokenTypeObject) Name: params.Name,
resp.Token = util.NewString(base64.StdEncoding.EncodeToString(binaryBearer)) Type: models.NewTokenType(models.TokenTypeObject),
Token: util.NewString(base64.StdEncoding.EncodeToString(binaryBearer)),
return &resp, nil }, nil
} }
func prepareContainerTokens(params operations.AuthParams, pool *pool.Pool, key *keys.PublicKey) (*models.TokenResponse, error) { func prepareContainerTokens(ctx context.Context, params containerTokenParams, pool *pool.Pool, key *keys.PublicKey) (*models.TokenResponse, error) {
ctx := params.HTTPRequest.Context()
iat, exp, err := getTokenLifetime(ctx, pool, params.XBearerLifetime) iat, exp, err := getTokenLifetime(ctx, pool, params.XBearerLifetime)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't get lifetime: %w", err) 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) return nil, fmt.Errorf("invalid bearer owner: %w", err)
} }
var resp models.TokenResponse stoken, err := util.ToNativeContainerToken(params.Rule)
resp.Type = models.NewTokenType(models.TokenTypeContainer)
stoken, err := util.ToNativeContainerToken(params.Token)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't transform rule to native session token: %w", err) 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) return nil, fmt.Errorf("couldn't marshal session token: %w", err)
} }
resp.Token = util.NewString(base64.StdEncoding.EncodeToString(binaryToken)) return &models.TokenResponse{
Name: params.Name,
return &resp, nil Type: models.NewTokenType(models.TokenTypeContainer),
Token: util.NewString(base64.StdEncoding.EncodeToString(binaryToken)),
}, nil
} }
func getCurrentEpoch(ctx context.Context, p *pool.Pool) (uint64, error) { 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 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) currEpoch, err := getCurrentEpoch(ctx, p)
if err != nil { if err != nil {
return 0, 0, err return 0, 0, err
} }
var lifetimeDuration uint64 = defaultTokenExpDuration var lifetimeDuration uint64 = defaultTokenExpDuration
if expDuration != nil && *expDuration > 0 { if expDuration != 0 {
lifetimeDuration = uint64(*expDuration) lifetimeDuration = expDuration
} }
return currEpoch, currEpoch + lifetimeDuration, nil return currEpoch, currEpoch + lifetimeDuration, nil

View file

@ -24,8 +24,7 @@ func TestSign(t *testing.T) {
pubKeyHex := hex.EncodeToString(key.PublicKey().Bytes()) pubKeyHex := hex.EncodeToString(key.PublicKey().Bytes())
b := &models.Bearer{ records := []*models.Record{{
Object: []*models.Record{{
Operation: models.NewOperation(models.OperationPUT), Operation: models.NewOperation(models.OperationPUT),
Action: models.NewAction(models.ActionALLOW), Action: models.NewAction(models.ActionALLOW),
Filters: []*models.Filter{}, Filters: []*models.Filter{},
@ -33,10 +32,9 @@ func TestSign(t *testing.T) {
Role: models.NewRole(models.RoleOTHERS), Role: models.NewRole(models.RoleOTHERS),
Keys: []string{}, Keys: []string{},
}}, }},
}}, }}
}
btoken, err := util.ToNativeObjectToken(b) btoken, err := util.ToNativeObjectToken(records)
require.NoError(t, err) require.NoError(t, err)
ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex) ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex)

View file

@ -216,3 +216,19 @@ func updateExpirationHeader(headers map[string]string, durations *epochDurations
numEpoch := expDuration.Milliseconds() / epochDuration numEpoch := expDuration.Milliseconds() / epochDuration
headers[objectv2.SysAttributeExpEpoch] = strconv.FormatInt(int64(durations.currentEpoch)+numEpoch, 10) 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
}

View file

@ -222,9 +222,9 @@ func ToNativeRule(r *models.Rule) (*session.ContainerContext, error) {
return &ctx, nil return &ctx, nil
} }
// ToNativeContainerToken converts models.Bearer to appropriate session.Token. // ToNativeContainerToken converts models.Rule to appropriate session.Token.
func ToNativeContainerToken(b *models.Bearer) (*session.Token, error) { func ToNativeContainerToken(tokenRule *models.Rule) (*session.Token, error) {
sctx, err := ToNativeRule(b.Container) sctx, err := ToNativeRule(tokenRule)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't transform rule to native: %w", err) 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 return &target, nil
} }
// ToNativeObjectToken converts Bearer to appropriate token.BearerToken. // ToNativeObjectToken converts []*models.Record to appropriate token.BearerToken.
func ToNativeObjectToken(b *models.Bearer) (*token.BearerToken, error) { func ToNativeObjectToken(tokenRecords []*models.Record) (*token.BearerToken, error) {
table, err := ToNativeTable(b.Object) table, err := ToNativeTable(tokenRecords)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -20,8 +20,7 @@ func TestSign(t *testing.T) {
pubKeyHex := hex.EncodeToString(key.PublicKey().Bytes()) pubKeyHex := hex.EncodeToString(key.PublicKey().Bytes())
b := &models.Bearer{ records := []*models.Record{{
Object: []*models.Record{{
Operation: models.NewOperation(models.OperationPUT), Operation: models.NewOperation(models.OperationPUT),
Action: models.NewAction(models.ActionALLOW), Action: models.NewAction(models.ActionALLOW),
Filters: []*models.Filter{}, Filters: []*models.Filter{},
@ -29,10 +28,9 @@ func TestSign(t *testing.T) {
Role: models.NewRole(models.RoleOTHERS), Role: models.NewRole(models.RoleOTHERS),
Keys: []string{}, Keys: []string{},
}}, }},
}}, }}
}
btoken, err := util.ToNativeObjectToken(b) btoken, err := util.ToNativeObjectToken(records)
require.NoError(t, err) require.NoError(t, err)
ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex) ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex)

View file

@ -64,24 +64,18 @@ paths:
description: Owner Id (wallet address) that will sign the token description: Owner Id (wallet address) that will sign the token
type: string type: string
required: true required: true
- in: header
description: Supported operation scope for token
name: X-Bearer-Scope
type: string
enum:
- object
- container
required: true
- in: header - in: header
description: Token lifetime in epoch description: Token lifetime in epoch
name: X-Bearer-Lifetime name: X-Bearer-Lifetime
type: integer type: integer
default: 100 default: 100
- in: body - in: body
name: token name: tokens
required: true required: true
description: Bearer token description: Bearer token
schema: schema:
type: array
items:
$ref: '#/definitions/Bearer' $ref: '#/definitions/Bearer'
consumes: consumes:
- application/json - application/json
@ -91,6 +85,8 @@ paths:
200: 200:
description: Base64 encoded stable binary marshaled bearer token description: Base64 encoded stable binary marshaled bearer token
schema: schema:
type: array
items:
$ref: '#/definitions/TokenResponse' $ref: '#/definitions/TokenResponse'
400: 400:
description: Bad request description: Bad request
@ -363,6 +359,8 @@ definitions:
Bearer: Bearer:
type: object type: object
properties: properties:
name:
type: string
object: object:
type: array type: array
items: items:
@ -483,6 +481,8 @@ definitions:
TokenResponse: TokenResponse:
type: object type: object
properties: properties:
name:
type: string
type: type:
$ref: '#/definitions/TokenType' $ref: '#/definitions/TokenType'
token: token: