Implement x-amz-acl handling

master
Harshavardhana 10 years ago
parent 28785421cd
commit 107e077ec0
  1. 72
      pkg/api/acl.go
  2. 47
      pkg/api/api_bucket_handlers.go
  3. 11
      pkg/api/api_generic_handlers.go
  4. 30
      pkg/api/api_object_handlers.go
  5. 12
      pkg/api/api_response.go
  6. 12
      pkg/api/contenttype.go
  7. 13
      pkg/api/errors.go
  8. 5
      pkg/api/headers.go
  9. 14
      pkg/api/utils.go

@ -0,0 +1,72 @@
/*
* Minimalist Object Storage, (C) 2015 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package api
import (
"net/http"
"strings"
)
// Please read for more information - http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
//
// Here We are only supporting 'acl's through request headers not through their request body
// http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#setting-acls
// Minio only supports three types for now i.e 'private, public-read, public-read-write'
type ACLType int
const (
unsupportedACLType ACLType = iota
privateACLType
publicReadACLType
publicReadWriteACLType
)
// Get acl type requested from 'x-amz-acl' header
func getACLType(req *http.Request) ACLType {
aclHeader := req.Header.Get("x-amz-acl")
switch {
case strings.HasPrefix(aclHeader, "private"):
return privateACLType
case strings.HasPrefix(aclHeader, "public-read"):
return publicReadACLType
case strings.HasPrefix(aclHeader, "public-read-write"):
return publicReadWriteACLType
default:
return unsupportedACLType
}
}
// ACL type to human readable string
func getACLTypeString(acl ACLType) string {
switch acl {
case privateACLType:
{
return "private"
}
case publicReadACLType:
{
return "public-read"
}
case publicReadWriteACLType:
{
return "public-read-write"
}
default:
return ""
}
}

@ -32,14 +32,19 @@ import (
// criteria to return a subset of the objects in a bucket. // criteria to return a subset of the objects in a bucket.
// //
func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req) acceptsContentType := getContentType(req)
bucket := vars["bucket"] if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
resources := getBucketResources(req.URL.Query()) resources := getBucketResources(req.URL.Query())
if resources.Maxkeys == 0 { if resources.Maxkeys == 0 {
resources.Maxkeys = maxObjectList resources.Maxkeys = maxObjectList
} }
acceptsContentType := getContentType(req)
vars := mux.Vars(req)
bucket := vars["bucket"]
objects, resources, err := server.driver.ListObjects(bucket, resources) objects, resources, err := server.driver.ListObjects(bucket, resources)
switch err.(type) { switch err.(type) {
case nil: // success case nil: // success
@ -49,8 +54,8 @@ func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Requ
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
// write body // write body
response := generateObjectsListResult(bucket, objects, resources) response := generateObjectsListResult(bucket, objects, resources)
encodedResponse := encodeResponse(response, acceptsContentType) encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
w.Write(encodedResponse) w.Write(encodedSuccessResponse)
} }
case drivers.BucketNotFound: case drivers.BucketNotFound:
{ {
@ -78,6 +83,11 @@ func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Requ
// owned by the authenticated sender of the request. // owned by the authenticated sender of the request.
func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req) acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
buckets, err := server.driver.ListBuckets() buckets, err := server.driver.ListBuckets()
// cannot fallthrough in (type) switch :( // cannot fallthrough in (type) switch :(
switch err := err.(type) { switch err := err.(type) {
@ -88,8 +98,8 @@ func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Requ
setCommonHeaders(w, getContentTypeString(acceptsContentType)) setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
// write response // write response
encodedResponse := encodeResponse(response, acceptsContentType) encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
w.Write(encodedResponse) w.Write(encodedSuccessResponse)
} }
default: default:
{ {
@ -103,11 +113,22 @@ func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Requ
// ---------- // ----------
// This implementation of the PUT operation creates a new bucket for authenticated request // This implementation of the PUT operation creates a new bucket for authenticated request
func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
// read from 'x-amz-acl'
aclType := getACLType(req)
if aclType == unsupportedACLType {
writeErrorResponse(w, req, NotImplemented, acceptsContentType, req.URL.Path)
return
}
vars := mux.Vars(req) vars := mux.Vars(req)
bucket := vars["bucket"] bucket := vars["bucket"]
err := server.driver.CreateBucket(bucket) err := server.driver.CreateBucket(bucket)
acceptsContentType := getContentType(req)
switch err.(type) { switch err.(type) {
case nil: case nil:
{ {
@ -138,10 +159,14 @@ func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Reques
// have permission to access it. Otherwise, the operation might // have permission to access it. Otherwise, the operation might
// return responses such as 404 Not Found and 403 Forbidden. // return responses such as 404 Not Found and 403 Forbidden.
func (server *minioAPI) headBucketHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) headBucketHandler(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
bucket := vars["bucket"]
acceptsContentType := getContentType(req) acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
vars := mux.Vars(req)
bucket := vars["bucket"]
_, err := server.driver.GetBucketMetadata(bucket) _, err := server.driver.GetBucketMetadata(bucket)
switch err.(type) { switch err.(type) {
case nil: case nil:

@ -142,14 +142,3 @@ func ignoreUnImplementedObjectResources(req *http.Request) bool {
} }
return false return false
} }
func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorType int, acceptsContentType contentType, resource string) {
error := getErrorCode(errorType)
errorResponse := getErrorResponse(error, resource)
// set headers
setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(error.HTTPStatusCode)
// write body
encodedErrorResponse := encodeErrorResponse(errorResponse, acceptsContentType)
w.Write(encodedErrorResponse)
}

@ -30,12 +30,16 @@ import (
// This implementation of the GET operation retrieves object. To use GET, // This implementation of the GET operation retrieves object. To use GET,
// you must have READ access to the object. // you must have READ access to the object.
func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Request) {
var object, bucket string
acceptsContentType := getContentType(req) acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
var object, bucket string
vars := mux.Vars(req) vars := mux.Vars(req)
bucket = vars["bucket"] bucket = vars["bucket"]
object = vars["object"] object = vars["object"]
metadata, err := server.driver.GetObjectMetadata(bucket, object, "") metadata, err := server.driver.GetObjectMetadata(bucket, object, "")
switch err := err.(type) { switch err := err.(type) {
case nil: // success case nil: // success
@ -95,12 +99,16 @@ func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Reques
// ----------- // -----------
// The HEAD operation retrieves metadata from an object without returning the object itself. // The HEAD operation retrieves metadata from an object without returning the object itself.
func (server *minioAPI) headObjectHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) headObjectHandler(w http.ResponseWriter, req *http.Request) {
var object, bucket string
acceptsContentType := getContentType(req) acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
var object, bucket string
vars := mux.Vars(req) vars := mux.Vars(req)
bucket = vars["bucket"] bucket = vars["bucket"]
object = vars["object"] object = vars["object"]
metadata, err := server.driver.GetObjectMetadata(bucket, object, "") metadata, err := server.driver.GetObjectMetadata(bucket, object, "")
switch err := err.(type) { switch err := err.(type) {
case nil: case nil:
@ -128,14 +136,22 @@ func (server *minioAPI) headObjectHandler(w http.ResponseWriter, req *http.Reque
// ---------- // ----------
// This implementation of the PUT operation adds an object to a bucket. // This implementation of the PUT operation adds an object to a bucket.
func (server *minioAPI) putObjectHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) putObjectHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
var object, bucket string var object, bucket string
vars := mux.Vars(req) vars := mux.Vars(req)
acceptsContentType := getContentType(req)
bucket = vars["bucket"] bucket = vars["bucket"]
object = vars["object"] object = vars["object"]
// get Content-MD5 sent by client and verify if valid
// get Content-MD5 sent by client
md5 := req.Header.Get("Content-MD5") md5 := req.Header.Get("Content-MD5")
if !isValidMD5(md5) {
writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path)
return
}
err := server.driver.CreateObject(bucket, object, "", md5, req.Body) err := server.driver.CreateObject(bucket, object, "", md5, req.Body)
switch err := err.(type) { switch err := err.(type) {
case nil: case nil:

@ -17,6 +17,7 @@
package api package api
import ( import (
"net/http"
"sort" "sort"
"github.com/minio-io/minio/pkg/storage/drivers" "github.com/minio-io/minio/pkg/storage/drivers"
@ -107,3 +108,14 @@ func generateObjectsListResult(bucket string, objects []drivers.ObjectMetadata,
data.CommonPrefixes = prefixes data.CommonPrefixes = prefixes
return data return data
} }
func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorType int, acceptsContentType contentType, resource string) {
error := getErrorCode(errorType)
errorResponse := getErrorResponse(error, resource)
// set headers
setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(error.HTTPStatusCode)
// write body
encodedErrorResponse := encodeErrorResponse(errorResponse, acceptsContentType)
w.Write(encodedErrorResponse)
}

@ -24,7 +24,8 @@ import (
type contentType int type contentType int
const ( const (
xmlContentType contentType = iota unknownContentType contentType = iota
xmlContentType
jsonContentType jsonContentType
) )
@ -34,8 +35,10 @@ func getContentType(req *http.Request) contentType {
switch { switch {
case strings.HasPrefix(acceptHeader, "application/json"): case strings.HasPrefix(acceptHeader, "application/json"):
return jsonContentType return jsonContentType
default: case strings.HasPrefix(acceptHeader, "application/xml"):
return xmlContentType return xmlContentType
default:
return unknownContentType
} }
} }
@ -44,9 +47,12 @@ func getContentTypeString(content contentType) string {
switch content { switch content {
case jsonContentType: case jsonContentType:
{ {
return "application/json" return "application/json"
} }
case xmlContentType:
{
return "application/xml"
}
default: default:
{ {
return "application/xml" return "application/xml"

@ -38,7 +38,7 @@ type ErrorResponse struct {
HostID string HostID string
} }
// Error codes, non exhaustive list // Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
const ( const (
AccessDenied = iota AccessDenied = iota
BadDigest BadDigest
@ -63,6 +63,11 @@ const (
TooManyBuckets TooManyBuckets
) )
// Error codes, non exhaustive list - standard HTTP errors
const (
NotAcceptable = iota + 21
)
// Error code to Error structure map // Error code to Error structure map
var errorCodeResponse = map[int]Error{ var errorCodeResponse = map[int]Error{
AccessDenied: { AccessDenied: {
@ -170,6 +175,12 @@ var errorCodeResponse = map[int]Error{
Description: "You have attempted to create more buckets than allowed.", Description: "You have attempted to create more buckets than allowed.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
NotAcceptable: {
Code: "NotAcceptable",
Description: `The requested resource is only capable of generating content
not acceptable according to the Accept headers sent in the request.`,
HTTPStatusCode: http.StatusNotAcceptable,
},
} }
// errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown // errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown

@ -52,6 +52,9 @@ func encodeErrorResponse(response interface{}, acceptsType contentType) []byte {
encoder = xml.NewEncoder(&bytesBuffer) encoder = xml.NewEncoder(&bytesBuffer)
case jsonContentType: case jsonContentType:
encoder = json.NewEncoder(&bytesBuffer) encoder = json.NewEncoder(&bytesBuffer)
// by default even if unknown Accept header received handle it by sending XML contenttype response
default:
encoder = xml.NewEncoder(&bytesBuffer)
} }
encoder.Encode(response) encoder.Encode(response)
return bytesBuffer.Bytes() return bytesBuffer.Bytes()
@ -77,7 +80,7 @@ func setRangeObjectHeaders(w http.ResponseWriter, metadata drivers.ObjectMetadat
w.Header().Set("Content-Range", contentRange.getContentRange()) w.Header().Set("Content-Range", contentRange.getContentRange())
} }
func encodeResponse(response interface{}, acceptsType contentType) []byte { func encodeSuccessResponse(response interface{}, acceptsType contentType) []byte {
var encoder encoder var encoder encoder
var bytesBuffer bytes.Buffer var bytesBuffer bytes.Buffer
switch acceptsType { switch acceptsType {

@ -0,0 +1,14 @@
package api
import (
"encoding/base64"
"strings"
)
func isValidMD5(md5 string) bool {
_, err := base64.StdEncoding.DecodeString(strings.TrimSpace(md5))
if err != nil {
return false
}
return true
}
Loading…
Cancel
Save