[#507] Return not implemented by default in bucket router
All checks were successful
/ DCO (pull_request) Successful in 1m0s
/ Vulncheck (pull_request) Successful in 1m27s
/ Builds (pull_request) Successful in 1m12s
/ Lint (pull_request) Successful in 1m58s
/ Tests (pull_request) Successful in 1m29s

Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
Marina Biryukova 2024-10-01 17:43:55 +03:00
parent 582e6ac642
commit c1828e1654
5 changed files with 116 additions and 16 deletions

View file

@ -25,11 +25,15 @@ import (
) )
const ( const (
QueryVersionID = "versionId" QueryVersionID = "versionId"
QueryPrefix = "prefix" QueryPrefix = "prefix"
QueryDelimiter = "delimiter" QueryDelimiter = "delimiter"
QueryMaxKeys = "max-keys" QueryMaxKeys = "max-keys"
amzTagging = "x-amz-tagging" QueryMarker = "marker"
QueryEncodingType = "encoding-type"
amzTagging = "x-amz-tagging"
unmatchedBucketOperation = "UnmatchedBucketOperation"
) )
// In these operations we don't check resource tags because // In these operations we don't check resource tags because
@ -269,8 +273,17 @@ func determineBucketOperation(r *http.Request) string {
return ListObjectsV2MOperation return ListObjectsV2MOperation
case query.Get(ListTypeQuery) == "2": case query.Get(ListTypeQuery) == "2":
return ListObjectsV2Operation return ListObjectsV2Operation
default: case len(query) == 0 || func() bool {
for key := range query {
if key != QueryDelimiter && key != QueryMaxKeys && key != QueryPrefix && key != QueryMarker && key != QueryEncodingType {
return false
}
}
return true
}():
return ListObjectsV1Operation return ListObjectsV1Operation
default:
return unmatchedBucketOperation
} }
case http.MethodPut: case http.MethodPut:
switch { switch {
@ -292,8 +305,10 @@ func determineBucketOperation(r *http.Request) string {
return PutBucketVersioningOperation return PutBucketVersioningOperation
case query.Has(NotificationQuery): case query.Has(NotificationQuery):
return PutBucketNotificationOperation return PutBucketNotificationOperation
default: case len(query) == 0:
return CreateBucketOperation return CreateBucketOperation
default:
return unmatchedBucketOperation
} }
case http.MethodPost: case http.MethodPost:
switch { switch {
@ -316,12 +331,14 @@ func determineBucketOperation(r *http.Request) string {
return DeleteBucketLifecycleOperation return DeleteBucketLifecycleOperation
case query.Has(EncryptionQuery): case query.Has(EncryptionQuery):
return DeleteBucketEncryptionOperation return DeleteBucketEncryptionOperation
default: case len(query) == 0:
return DeleteBucketOperation return DeleteBucketOperation
default:
return unmatchedBucketOperation
} }
} }
return "UnmatchedBucketOperation" return unmatchedBucketOperation
} }
func determineObjectOperation(r *http.Request) string { func determineObjectOperation(r *http.Request) string {

View file

@ -152,6 +152,12 @@ func TestDetermineBucketOperation(t *testing.T) {
method: http.MethodGet, method: http.MethodGet,
expected: ListObjectsV1Operation, expected: ListObjectsV1Operation,
}, },
{
name: "UnmatchedBucketOperation GET",
method: http.MethodGet,
queryParam: map[string]string{"query": ""},
expected: unmatchedBucketOperation,
},
{ {
name: "PutBucketCorsOperation", name: "PutBucketCorsOperation",
method: http.MethodPut, method: http.MethodPut,
@ -211,6 +217,12 @@ func TestDetermineBucketOperation(t *testing.T) {
method: http.MethodPut, method: http.MethodPut,
expected: CreateBucketOperation, expected: CreateBucketOperation,
}, },
{
name: "UnmatchedBucketOperation PUT",
method: http.MethodPut,
queryParam: map[string]string{"query": ""},
expected: unmatchedBucketOperation,
},
{ {
name: "DeleteMultipleObjectsOperation", name: "DeleteMultipleObjectsOperation",
method: http.MethodPost, method: http.MethodPost,
@ -263,10 +275,16 @@ func TestDetermineBucketOperation(t *testing.T) {
method: http.MethodDelete, method: http.MethodDelete,
expected: DeleteBucketOperation, expected: DeleteBucketOperation,
}, },
{
name: "UnmatchedBucketOperation DELETE",
method: http.MethodDelete,
queryParam: map[string]string{"query": ""},
expected: unmatchedBucketOperation,
},
{ {
name: "UnmatchedBucketOperation", name: "UnmatchedBucketOperation",
method: "invalid-method", method: "invalid-method",
expected: "UnmatchedBucketOperation", expected: unmatchedBucketOperation,
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {

View file

@ -226,6 +226,28 @@ func errorResponseHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
func notSupportedHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
reqInfo := s3middleware.GetReqInfo(ctx)
_, wrErr := s3middleware.WriteErrorResponse(w, reqInfo, errors.GetAPIError(errors.ErrNotSupported))
if log := s3middleware.GetReqLog(ctx); log != nil {
fields := []zap.Field{
zap.String("http method", r.Method),
zap.String("url", r.RequestURI),
}
if wrErr != nil {
fields = append(fields, zap.NamedError("write_response_error", wrErr))
}
log.Error(logs.NotSupported, fields...)
}
}
}
// attachErrorHandler set NotFoundHandler and MethodNotAllowedHandler for chi.Router. // attachErrorHandler set NotFoundHandler and MethodNotAllowedHandler for chi.Router.
func attachErrorHandler(api *chi.Mux) { func attachErrorHandler(api *chi.Mux) {
errorHandler := http.HandlerFunc(errorResponseHandler) errorHandler := http.HandlerFunc(errorResponseHandler)
@ -313,7 +335,14 @@ func bucketRouter(h Handler) chi.Router {
Add(NewFilter(). Add(NewFilter().
Queries(s3middleware.VersionsQuery). Queries(s3middleware.VersionsQuery).
Handler(named(s3middleware.ListBucketObjectVersionsOperation, h.ListBucketObjectVersionsHandler))). Handler(named(s3middleware.ListBucketObjectVersionsOperation, h.ListBucketObjectVersionsHandler))).
DefaultHandler(listWrapper(h))) Add(NewFilter().
AllowedQueries(s3middleware.QueryDelimiter, s3middleware.QueryMaxKeys, s3middleware.QueryPrefix,
s3middleware.QueryMarker, s3middleware.QueryEncodingType).
Handler(named(s3middleware.ListObjectsV1Operation, h.ListObjectsV1Handler))).
Add(NewFilter().
NoQueries().
Handler(listWrapper(h))).
DefaultHandler(notSupportedHandler()))
}) })
// PUT method handlers // PUT method handlers
@ -346,7 +375,10 @@ func bucketRouter(h Handler) chi.Router {
Add(NewFilter(). Add(NewFilter().
Queries(s3middleware.NotificationQuery). Queries(s3middleware.NotificationQuery).
Handler(named(s3middleware.PutBucketNotificationOperation, h.PutBucketNotificationHandler))). Handler(named(s3middleware.PutBucketNotificationOperation, h.PutBucketNotificationHandler))).
DefaultHandler(named(s3middleware.CreateBucketOperation, h.CreateBucketHandler))) Add(NewFilter().
NoQueries().
Handler(named(s3middleware.CreateBucketOperation, h.CreateBucketHandler))).
DefaultHandler(notSupportedHandler()))
}) })
// POST method handlers // POST method handlers
@ -380,7 +412,10 @@ func bucketRouter(h Handler) chi.Router {
Add(NewFilter(). Add(NewFilter().
Queries(s3middleware.EncryptionQuery). Queries(s3middleware.EncryptionQuery).
Handler(named(s3middleware.DeleteBucketEncryptionOperation, h.DeleteBucketEncryptionHandler))). Handler(named(s3middleware.DeleteBucketEncryptionOperation, h.DeleteBucketEncryptionHandler))).
DefaultHandler(named(s3middleware.DeleteBucketOperation, h.DeleteBucketHandler))) Add(NewFilter().
NoQueries().
Handler(named(s3middleware.DeleteBucketOperation, h.DeleteBucketHandler))).
DefaultHandler(notSupportedHandler()))
}) })
attachErrorHandler(bktRouter) attachErrorHandler(bktRouter)

View file

@ -11,9 +11,11 @@ type HandlerFilters struct {
} }
type Filter struct { type Filter struct {
queries []Pair queries []Pair
headers []Pair headers []Pair
h http.Handler allowedQueries map[string]struct{}
noQueries bool
h http.Handler
} }
type Pair struct { type Pair struct {
@ -105,6 +107,22 @@ func (f *Filter) Queries(queries ...string) *Filter {
return f return f
} }
// NoQueries sets flag indicating that request shouldn't have query parameters.
func (f *Filter) NoQueries() *Filter {
f.noQueries = true
return f
}
// AllowedQueries adds query parameter keys that may be present in request.
func (f *Filter) AllowedQueries(queries ...string) *Filter {
f.allowedQueries = make(map[string]struct{}, len(queries))
for _, query := range queries {
f.allowedQueries[query] = struct{}{}
}
return f
}
func (hf *HandlerFilters) DefaultHandler(handler http.HandlerFunc) *HandlerFilters { func (hf *HandlerFilters) DefaultHandler(handler http.HandlerFunc) *HandlerFilters {
hf.defaultHandler = handler hf.defaultHandler = handler
return hf return hf
@ -122,6 +140,17 @@ func (hf *HandlerFilters) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (hf *HandlerFilters) match(r *http.Request) http.Handler { func (hf *HandlerFilters) match(r *http.Request) http.Handler {
LOOP: LOOP:
for _, filter := range hf.filters { for _, filter := range hf.filters {
if filter.noQueries && len(r.URL.Query()) > 0 {
continue LOOP
}
if len(filter.allowedQueries) > 0 {
queries := r.URL.Query()
for key := range queries {
if _, ok := filter.allowedQueries[key]; !ok {
continue LOOP
}
}
}
for _, header := range filter.headers { for _, header := range filter.headers {
hdrVals := r.Header.Values(header.Key) hdrVals := r.Header.Values(header.Key)
if len(hdrVals) == 0 || header.Value != "" && header.Value != hdrVals[0] { if len(hdrVals) == 0 || header.Value != "" && header.Value != hdrVals[0] {

View file

@ -170,4 +170,5 @@ const (
WarnDomainContainsInvalidPlaceholder = "the domain contains an invalid placeholder, domain skipped" WarnDomainContainsInvalidPlaceholder = "the domain contains an invalid placeholder, domain skipped"
FailedToRemoveOldPartNode = "failed to remove old part node" FailedToRemoveOldPartNode = "failed to remove old part node"
CouldntCacheNetworkInfo = "couldn't cache network info" CouldntCacheNetworkInfo = "couldn't cache network info"
NotSupported = "not supported"
) )