forked from TrueCloudLab/frostfs-rest-gw
[#15] Accept list of tokens to sign
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
0e4e213352
commit
686588bc1a
12 changed files with 289 additions and 203 deletions
|
@ -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{
|
||||
|
|
|
@ -22,6 +22,9 @@ type Bearer struct {
|
|||
// container
|
||||
Container *Rule `json:"container,omitempty"`
|
||||
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// object
|
||||
Object []*Record `json:"object"`
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ import (
|
|||
// swagger:model TokenResponse
|
||||
type TokenResponse struct {
|
||||
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// token
|
||||
// Required: true
|
||||
Token *string `json:"token"`
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
130
handlers/auth.go
130
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue