forked from TrueCloudLab/frostfs-rest-gw
[#2] Allow unauthenticated requests to GET and SEARCH
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
ac5750670f
commit
3f05207530
6 changed files with 151 additions and 88 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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, signature, key *string, isWalletConnect, isFullToken bool) (*bearer.Token, error) {
|
||||
if token == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getBearerToken(token *models.Principal, hdr *BearerTokenHeaders) (bearer.Token, error) {
|
||||
bt := &BearerToken{
|
||||
Token: string(*token),
|
||||
Signature: hdr.Signature,
|
||||
Key: hdr.Key,
|
||||
bt := &BearerToken{Token: string(*token)}
|
||||
|
||||
if !isFullToken {
|
||||
if signature == nil || key == nil {
|
||||
return nil, errors.New("missed signature or key header")
|
||||
}
|
||||
|
||||
return prepareBearerToken(bt, hdr.IsWalletConnect, hdr.IsFullToken)
|
||||
bt.Signature = *signature
|
||||
bt.Key = *key
|
||||
}
|
||||
|
||||
func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (bearer.Token, error) {
|
||||
return prepareBearerToken(bt, isWalletConnect, isFullToken)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue