[#2] Allow unauthenticated requests to GET and SEARCH

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-10-26 12:40:17 +03:00 committed by Alex Vanin
parent ac5750670f
commit 3f05207530
6 changed files with 151 additions and 88 deletions

View file

@ -123,6 +123,7 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s
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, &owner, cnrID) })
t.Run("rest get object unauthenticated "+version, func(t *testing.T) { restObjectGetUnauthenticated(ctx, t, clientPool, &owner, cnrID) })
t.Run("rest get object full bearer "+version, func(t *testing.T) { restObjectGetFullBearer(ctx, t, clientPool, &owner, cnrID) })
t.Run("rest delete object "+version, func(t *testing.T) { restObjectDelete(ctx, t, clientPool, &owner, cnrID) })
t.Run("rest search objects "+version, func(t *testing.T) { restObjectsSearch(ctx, t, clientPool, &owner, cnrID) })
@ -630,6 +631,53 @@ func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, ownerID *use
require.Equal(t, int64(0), *objInfo2.ObjectSize)
}
func restObjectGetUnauthenticated(ctx context.Context, t *testing.T, p *pool.Pool, ownerID *user.ID, cnrID cid.ID) {
content := []byte("some content")
attributes := map[string]string{
object.AttributeFileName: "get-obj-unauth-name",
"user-attribute": "user value",
}
objID := createObject(ctx, t, p, ownerID, cnrID, attributes, content)
httpClient := defaultHTTPClient()
request, err := http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/"+objID.EncodeToString(), nil)
require.NoError(t, err)
request.Header.Add("Content-Type", "application/json")
resp := &models.ErrorResponse{}
doRequest(t, httpClient, request, http.StatusBadRequest, resp)
require.Equal(t, int64(2048), resp.Code)
require.Equal(t, models.ErrorTypeAPI, *resp.Type)
// set empty eacl table to be able to do unauthenticated request
allowByEACL(ctx, t, p, cnrID)
request, err = http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/"+objID.EncodeToString(), nil)
require.NoError(t, err)
objInfo := &models.ObjectInfo{}
doRequest(t, httpClient, request, http.StatusOK, objInfo)
require.Equal(t, cnrID.EncodeToString(), *objInfo.ContainerID)
require.Equal(t, objID.EncodeToString(), *objInfo.ObjectID)
require.Equal(t, ownerID.EncodeToString(), *objInfo.OwnerID)
require.Equal(t, len(attributes), len(objInfo.Attributes))
require.Equal(t, int64(len(content)), *objInfo.ObjectSize)
contentData, err := base64.StdEncoding.DecodeString(objInfo.Payload)
require.NoError(t, err)
require.Equal(t, content, contentData)
for _, attr := range objInfo.Attributes {
require.Equal(t, attributes[*attr.Key], *attr.Value)
}
// set eacl the same as was before test started
restrictByEACL(ctx, t, p, cnrID)
}
func restObjectGetFullBearer(ctx context.Context, t *testing.T, p *pool.Pool, ownerID *user.ID, cnrID cid.ID) {
content := []byte("some content")
attributes := map[string]string{
@ -1298,3 +1346,21 @@ func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cn
return table
}
func allowByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID cid.ID) *eacl.Table {
table := eacl.NewTable()
table.SetCID(cnrID)
var waitPrm pool.WaitParams
waitPrm.SetPollInterval(3 * time.Second)
waitPrm.SetTimeout(15 * time.Second)
var prm pool.PrmContainerSetEACL
prm.SetTable(*table)
prm.SetWaitParams(waitPrm)
err := clientPool.SetEACL(ctx, prm)
require.NoError(t, err)
return table
}

View file

@ -599,6 +599,12 @@ func init() {
},
"/objects/{containerId}/search": {
"post": {
"security": [
{},
{
"BearerAuth": []
}
],
"summary": "Search objects by filters",
"operationId": "searchObjects",
"parameters": [
@ -685,6 +691,12 @@ func init() {
},
"/objects/{containerId}/{objectId}": {
"get": {
"security": [
{},
{
"BearerAuth": []
}
],
"summary": "Get object info by address",
"operationId": "getObjectInfo",
"parameters": [
@ -2298,6 +2310,12 @@ func init() {
},
"/objects/{containerId}/search": {
"post": {
"security": [
{},
{
"BearerAuth": []
}
],
"summary": "Search objects by filters",
"operationId": "searchObjects",
"parameters": [
@ -2403,6 +2421,12 @@ func init() {
},
"/objects/{containerId}/{objectId}": {
"get": {
"security": [
{},
{
"BearerAuth": []
}
],
"summary": "Get object info by address",
"operationId": "getObjectInfo",
"parameters": [

View file

@ -106,13 +106,7 @@ func (a *API) PostAuth(params operations.AuthParams) middleware.Responder {
// FormBinaryBearer handler that forms binary bearer token using headers with body and signature.
func (a *API) FormBinaryBearer(params operations.FormBinaryBearerParams, principal *models.Principal) middleware.Responder {
bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, false)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer headers", err)
return operations.NewFormBinaryBearerBadRequest().WithPayload(resp)
}
btoken, err := getBearerToken(principal, bearerHeaders)
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, false)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer token", err)
return operations.NewFormBinaryBearerBadRequest().WithPayload(resp)

View file

@ -33,13 +33,7 @@ func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Pr
errorResponse := operations.NewPutObjectBadRequest()
ctx := params.HTTPRequest.Context()
bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer headers", err)
return errorResponse.WithPayload(resp)
}
btoken, err := getBearerToken(principal, bearerHeaders)
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer token", err)
return errorResponse.WithPayload(resp)
@ -67,17 +61,15 @@ func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Pr
return errorResponse.WithPayload(resp)
}
owner := bearer.ResolveIssuer(btoken)
obj := object.New()
obj.SetContainerID(cnrID)
obj.SetOwnerID(&owner)
attachOwner(obj, btoken)
obj.SetPayload(payload)
obj.SetAttributes(attributes...)
var prmPut pool.PrmObjectPut
prmPut.SetHeader(*obj)
prmPut.UseBearer(btoken)
attachBearer(&prmPut, btoken)
objID, err := a.pool.PutObject(ctx, prmPut)
if err != nil {
@ -105,13 +97,7 @@ func (a *API) GetObjectInfo(params operations.GetObjectInfoParams, principal *mo
return errorResponse.WithPayload(resp)
}
bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer headers", err)
return errorResponse.WithPayload(resp)
}
btoken, err := getBearerToken(principal, bearerHeaders)
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("get bearer token", err)
return errorResponse.WithPayload(resp)
@ -119,7 +105,7 @@ func (a *API) GetObjectInfo(params operations.GetObjectInfoParams, principal *mo
var prm pool.PrmObjectHead
prm.SetAddress(addr)
prm.UseBearer(btoken)
attachBearer(&prm, btoken)
objInfo, err := a.pool.HeadObject(ctx, prm)
if err != nil {
@ -158,7 +144,7 @@ func (a *API) GetObjectInfo(params operations.GetObjectInfoParams, principal *mo
var prmRange pool.PrmObjectRange
prmRange.SetAddress(addr)
prmRange.UseBearer(btoken)
attachBearer(&prmRange, btoken)
prmRange.SetOffset(offset)
prmRange.SetLength(length)
@ -205,13 +191,7 @@ func (a *API) DeleteObject(params operations.DeleteObjectParams, principal *mode
return errorResponse.WithPayload(resp)
}
bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer headers", err)
return errorResponse.WithPayload(resp)
}
btoken, err := getBearerToken(principal, bearerHeaders)
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("failed to get bearer token", err)
return errorResponse.WithPayload(resp)
@ -219,7 +199,7 @@ func (a *API) DeleteObject(params operations.DeleteObjectParams, principal *mode
var prm pool.PrmObjectDelete
prm.SetAddress(addr)
prm.UseBearer(btoken)
attachBearer(&prm, btoken)
if err = a.pool.DeleteObject(ctx, prm); err != nil {
resp := a.logAndGetErrorResponse("failed to delete object", err)
@ -242,13 +222,7 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo
return errorResponse.WithPayload(resp)
}
bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("invalid bearer headers", err)
return errorResponse.WithPayload(resp)
}
btoken, err := getBearerToken(principal, bearerHeaders)
btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer)
if err != nil {
resp := a.logAndGetErrorResponse("failed to get bearer token", err)
return errorResponse.WithPayload(resp)
@ -262,7 +236,7 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo
var prm pool.PrmObjectSearch
prm.SetContainerID(cnrID)
prm.UseBearer(btoken)
attachBearer(&prm, btoken)
prm.SetFilters(filters)
resSearch, err := a.pool.SearchObjects(ctx, prm)
@ -311,14 +285,14 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo
WithAccessControlAllowOrigin("*")
}
func headObjectBaseInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID, objID oid.ID, btoken bearer.Token) (*models.ObjectBaseInfo, error) {
func headObjectBaseInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID, objID oid.ID, btoken *bearer.Token) (*models.ObjectBaseInfo, error) {
var addr oid.Address
addr.SetContainer(cnrID)
addr.SetObject(objID)
var prm pool.PrmObjectHead
prm.SetAddress(addr)
prm.UseBearer(btoken)
attachBearer(&prm, btoken)
objInfo, err := p.HeadObject(ctx, prm)
if err != nil {
@ -361,54 +335,56 @@ func parseAddress(containerID, objectID string) (oid.Address, error) {
return addr, nil
}
type BearerTokenHeaders struct {
Signature string
Key string
IsWalletConnect bool
IsFullToken bool
}
func getBearerToken(token *models.Principal, hdr *BearerTokenHeaders) (bearer.Token, error) {
bt := &BearerToken{
Token: string(*token),
Signature: hdr.Signature,
Key: hdr.Key,
func getBearerToken(token *models.Principal, signature, key *string, isWalletConnect, isFullToken bool) (*bearer.Token, error) {
if token == nil {
return nil, nil
}
return prepareBearerToken(bt, hdr.IsWalletConnect, hdr.IsFullToken)
bt := &BearerToken{Token: string(*token)}
if !isFullToken {
if signature == nil || key == nil {
return nil, errors.New("missed signature or key header")
}
bt.Signature = *signature
bt.Key = *key
}
return prepareBearerToken(bt, isWalletConnect, isFullToken)
}
func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (bearer.Token, error) {
func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (*bearer.Token, error) {
data, err := base64.StdEncoding.DecodeString(bt.Token)
if err != nil {
return bearer.Token{}, fmt.Errorf("can't base64-decode bearer token: %w", err)
return nil, fmt.Errorf("can't base64-decode bearer token: %w", err)
}
if isFullToken {
var btoken bearer.Token
if err = btoken.Unmarshal(data); err != nil {
return bearer.Token{}, fmt.Errorf("couldn't unmarshall bearer token: %w", err)
return nil, fmt.Errorf("couldn't unmarshall bearer token: %w", err)
}
if !btoken.VerifySignature() {
return bearer.Token{}, fmt.Errorf("invalid signature")
return nil, fmt.Errorf("invalid signature")
}
return btoken, nil
return &btoken, nil
}
signature, err := hex.DecodeString(bt.Signature)
if err != nil {
return bearer.Token{}, fmt.Errorf("couldn't decode bearer signature: %w", err)
return nil, fmt.Errorf("couldn't decode bearer signature: %w", err)
}
ownerKey, err := keys.NewPublicKeyFromString(bt.Key)
if err != nil {
return bearer.Token{}, fmt.Errorf("couldn't fetch bearer token owner key: %w", err)
return nil, fmt.Errorf("couldn't fetch bearer token owner key: %w", err)
}
body := new(acl.BearerTokenBody)
if err = body.Unmarshal(data); err != nil {
return bearer.Token{}, fmt.Errorf("can't unmarshal bearer token body: %w", err)
return nil, fmt.Errorf("can't unmarshal bearer token body: %w", err)
}
v2signature := new(refs.Signature)
@ -425,14 +401,14 @@ func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (bea
var btoken bearer.Token
if err = btoken.ReadFromV2(v2btoken); err != nil {
return bearer.Token{}, fmt.Errorf("read from v2 token: %w", err)
return nil, fmt.Errorf("read from v2 token: %w", err)
}
if !btoken.VerifySignature() {
return bearer.Token{}, fmt.Errorf("invalid signature")
return nil, fmt.Errorf("invalid signature")
}
return btoken, nil
return &btoken, nil
}
func prepareOffsetLength(params operations.GetObjectInfoParams, objSize uint64) (uint64, uint64, error) {
@ -457,3 +433,19 @@ func prepareOffsetLength(params operations.GetObjectInfoParams, objSize uint64)
return offset, length, nil
}
type prmWithBearer interface {
UseBearer(token bearer.Token)
}
func attachBearer(prm prmWithBearer, btoken *bearer.Token) {
if btoken != nil {
prm.UseBearer(*btoken)
}
}
func attachOwner(obj *object.Object, btoken *bearer.Token) {
if btoken != nil {
owner := bearer.ResolveIssuer(*btoken)
obj.SetOwnerID(&owner)
}
}

View file

@ -188,25 +188,6 @@ func IsObjectToken(token *models.Bearer) (bool, error) {
return isObject, nil
}
func prepareBearerTokenHeaders(signature, key *string, isWalletConnect, isFullToken bool) (*BearerTokenHeaders, error) {
bearerHeaders := &BearerTokenHeaders{
IsWalletConnect: isWalletConnect,
IsFullToken: isFullToken,
}
if isFullToken {
return bearerHeaders, nil
}
if signature == nil || key == nil {
return nil, errors.New("missed signature or key header")
}
bearerHeaders.Signature = *signature
bearerHeaders.Key = *key
return bearerHeaders, nil
}
func formSessionTokenFromHeaders(principal *models.Principal, signature, key *string, verb sessionv2.ContainerSessionVerb) (*SessionToken, error) {
if signature == nil || key == nil {
return nil, errors.New("missed signature or key header")

View file

@ -240,6 +240,9 @@ paths:
post:
operationId: searchObjects
summary: Search objects by filters
security:
- {}
- BearerAuth: [ ]
parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'
@ -297,6 +300,9 @@ paths:
get:
operationId: getObjectInfo
summary: Get object info by address
security:
- {}
- BearerAuth: [ ]
parameters:
- $ref: '#/parameters/signatureParam'
- $ref: '#/parameters/signatureKeyParam'