Implement x-amz-acl handling

This commit is contained in:
Harshavardhana 2015-04-22 16:28:13 -07:00
parent 28785421cd
commit 107e077ec0
9 changed files with 182 additions and 34 deletions

72
pkg/api/acl.go Normal file
View file

@ -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 ""
}
}

View file

@ -32,14 +32,19 @@ import (
// criteria to return a subset of the objects in a bucket.
//
func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
bucket := vars["bucket"]
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
resources := getBucketResources(req.URL.Query())
if resources.Maxkeys == 0 {
resources.Maxkeys = maxObjectList
}
acceptsContentType := getContentType(req)
vars := mux.Vars(req)
bucket := vars["bucket"]
objects, resources, err := server.driver.ListObjects(bucket, resources)
switch err.(type) {
case nil: // success
@ -49,8 +54,8 @@ func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Requ
w.WriteHeader(http.StatusOK)
// write body
response := generateObjectsListResult(bucket, objects, resources)
encodedResponse := encodeResponse(response, acceptsContentType)
w.Write(encodedResponse)
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
w.Write(encodedSuccessResponse)
}
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.
func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
buckets, err := server.driver.ListBuckets()
// cannot fallthrough in (type) switch :(
switch err := err.(type) {
@ -88,8 +98,8 @@ func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Requ
setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(http.StatusOK)
// write response
encodedResponse := encodeResponse(response, acceptsContentType)
w.Write(encodedResponse)
encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
w.Write(encodedSuccessResponse)
}
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
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)
bucket := vars["bucket"]
err := server.driver.CreateBucket(bucket)
acceptsContentType := getContentType(req)
switch err.(type) {
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
// return responses such as 404 Not Found and 403 Forbidden.
func (server *minioAPI) headBucketHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
vars := mux.Vars(req)
bucket := vars["bucket"]
acceptsContentType := getContentType(req)
_, err := server.driver.GetBucketMetadata(bucket)
switch err.(type) {
case nil:

View file

@ -142,14 +142,3 @@ func ignoreUnImplementedObjectResources(req *http.Request) bool {
}
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)
}

View file

@ -30,12 +30,16 @@ import (
// This implementation of the GET operation retrieves object. To use GET,
// you must have READ access to the object.
func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Request) {
var object, bucket string
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
var object, bucket string
vars := mux.Vars(req)
bucket = vars["bucket"]
object = vars["object"]
metadata, err := server.driver.GetObjectMetadata(bucket, object, "")
switch err := err.(type) {
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.
func (server *minioAPI) headObjectHandler(w http.ResponseWriter, req *http.Request) {
var object, bucket string
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
var object, bucket string
vars := mux.Vars(req)
bucket = vars["bucket"]
object = vars["object"]
metadata, err := server.driver.GetObjectMetadata(bucket, object, "")
switch err := err.(type) {
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.
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
vars := mux.Vars(req)
acceptsContentType := getContentType(req)
bucket = vars["bucket"]
object = vars["object"]
// get Content-MD5 sent by client
// get Content-MD5 sent by client and verify if valid
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)
switch err := err.(type) {
case nil:

View file

@ -17,6 +17,7 @@
package api
import (
"net/http"
"sort"
"github.com/minio-io/minio/pkg/storage/drivers"
@ -107,3 +108,14 @@ func generateObjectsListResult(bucket string, objects []drivers.ObjectMetadata,
data.CommonPrefixes = prefixes
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)
}

View file

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

View file

@ -38,7 +38,7 @@ type ErrorResponse struct {
HostID string
}
// Error codes, non exhaustive list
// Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
const (
AccessDenied = iota
BadDigest
@ -63,6 +63,11 @@ const (
TooManyBuckets
)
// Error codes, non exhaustive list - standard HTTP errors
const (
NotAcceptable = iota + 21
)
// Error code to Error structure map
var errorCodeResponse = map[int]Error{
AccessDenied: {
@ -170,6 +175,12 @@ var errorCodeResponse = map[int]Error{
Description: "You have attempted to create more buckets than allowed.",
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

View file

@ -52,6 +52,9 @@ func encodeErrorResponse(response interface{}, acceptsType contentType) []byte {
encoder = xml.NewEncoder(&bytesBuffer)
case jsonContentType:
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)
return bytesBuffer.Bytes()
@ -77,7 +80,7 @@ func setRangeObjectHeaders(w http.ResponseWriter, metadata drivers.ObjectMetadat
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 bytesBuffer bytes.Buffer
switch acceptsType {

14
pkg/api/utils.go Normal file
View file

@ -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
}