minio/cmd/web-handlers.go

792 lines
23 KiB
Go
Raw Normal View History

/*
* Minio Cloud Storage, (C) 2016 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 cmd
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
2016-02-04 01:46:45 -05:00
"os"
2016-02-22 01:38:38 -05:00
"path"
2016-02-04 01:46:45 -05:00
"runtime"
"strconv"
"strings"
"time"
2016-01-25 01:26:53 -05:00
jwtgo "github.com/dgrijalva/jwt-go"
jwtreq "github.com/dgrijalva/jwt-go/request"
2016-02-04 01:46:45 -05:00
"github.com/dustin/go-humanize"
"github.com/gorilla/mux"
"github.com/gorilla/rpc/v2/json2"
"github.com/minio/minio-go/pkg/policy"
"github.com/minio/miniobrowser"
)
// isJWTReqAuthenticated validates if any incoming request to be a
// valid JWT authenticated request.
func isJWTReqAuthenticated(req *http.Request) bool {
jwt, err := newJWT(defaultJWTExpiry)
if err != nil {
errorIf(err, "unable to initialize a new JWT")
return false
}
var reqCallback jwtgo.Keyfunc
reqCallback = func(token *jwtgo.Token) (interface{}, error) {
if _, ok := token.Method.(*jwtgo.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(jwt.SecretAccessKey), nil
}
token, err := jwtreq.ParseFromRequest(req, jwtreq.AuthorizationHeaderExtractor, reqCallback)
if err != nil {
errorIf(err, "token parsing failed")
return false
}
return token.Valid
}
2016-04-08 02:44:08 -04:00
// WebGenericArgs - empty struct for calls that don't accept arguments
// for ex. ServerInfo, GenerateAuth
2016-04-08 02:44:08 -04:00
type WebGenericArgs struct{}
2016-04-08 02:44:08 -04:00
// WebGenericRep - reply structure for calls for which reply is success/failure
// for ex. RemoveObject MakeBucket
2016-04-08 02:44:08 -04:00
type WebGenericRep struct {
UIVersion string `json:"uiVersion"`
}
// ServerInfoRep - server info reply.
type ServerInfoRep struct {
MinioVersion string
MinioMemory string
MinioPlatform string
MinioRuntime string
MinioEnvVars []string
UIVersion string `json:"uiVersion"`
}
2016-02-04 01:46:45 -05:00
// ServerInfo - get server info.
func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, reply *ServerInfoRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
2016-02-04 01:46:45 -05:00
}
host, err := os.Hostname()
if err != nil {
host = ""
}
memstats := &runtime.MemStats{}
runtime.ReadMemStats(memstats)
mem := fmt.Sprintf("Used: %s | Allocated: %s | Used-Heap: %s | Allocated-Heap: %s",
humanize.Bytes(memstats.Alloc),
humanize.Bytes(memstats.TotalAlloc),
humanize.Bytes(memstats.HeapAlloc),
humanize.Bytes(memstats.HeapSys))
platform := fmt.Sprintf("Host: %s | OS: %s | Arch: %s",
host,
runtime.GOOS,
runtime.GOARCH)
goruntime := fmt.Sprintf("Version: %s | CPUs: %s", runtime.Version(), strconv.Itoa(runtime.NumCPU()))
reply.MinioEnvVars = os.Environ()
reply.MinioVersion = Version
reply.MinioMemory = mem
reply.MinioPlatform = platform
reply.MinioRuntime = goruntime
reply.UIVersion = miniobrowser.UIVersion
2016-02-04 01:46:45 -05:00
return nil
}
// StorageInfoRep - contains storage usage statistics.
type StorageInfoRep struct {
StorageInfo StorageInfo `json:"storageInfo"`
UIVersion string `json:"uiVersion"`
}
// StorageInfo - web call to gather storage usage statistics.
func (web *webAPIHandlers) StorageInfo(r *http.Request, args *GenericArgs, reply *StorageInfoRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
objectAPI := web.ObjectAPI()
if objectAPI == nil {
return &json2.Error{Message: "Server not initialized"}
}
reply.StorageInfo = objectAPI.StorageInfo()
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// MakeBucketArgs - make bucket args.
type MakeBucketArgs struct {
BucketName string `json:"bucketName"`
}
2016-01-25 01:26:53 -05:00
// MakeBucket - make a bucket.
func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *WebGenericRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
2016-01-25 01:26:53 -05:00
}
objectAPI := web.ObjectAPI()
if objectAPI == nil {
return &json2.Error{Message: "Server not initialized"}
}
if err := objectAPI.MakeBucket(args.BucketName); err != nil {
return &json2.Error{Message: err.Error()}
}
reply.UIVersion = miniobrowser.UIVersion
return nil
2016-01-25 01:26:53 -05:00
}
// ListBucketsRep - list buckets response
type ListBucketsRep struct {
Buckets []WebBucketInfo `json:"buckets"`
UIVersion string `json:"uiVersion"`
}
// WebBucketInfo container for list buckets metadata.
type WebBucketInfo struct {
// The name of the bucket.
Name string `json:"name"`
// Date the bucket was created.
CreationDate time.Time `json:"creationDate"`
}
// ListBuckets - list buckets api.
func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, reply *ListBucketsRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
objectAPI := web.ObjectAPI()
if objectAPI == nil {
return &json2.Error{Message: "Server not initialized"}
}
buckets, err := objectAPI.ListBuckets()
if err != nil {
return &json2.Error{Message: err.Error()}
}
2016-01-25 01:26:53 -05:00
for _, bucket := range buckets {
// List all buckets which are not private.
if bucket.Name != path.Base(reservedBucket) {
reply.Buckets = append(reply.Buckets, WebBucketInfo{
Name: bucket.Name,
CreationDate: bucket.Created,
})
}
}
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// ListObjectsArgs - list object args.
type ListObjectsArgs struct {
BucketName string `json:"bucketName"`
Prefix string `json:"prefix"`
}
// ListObjectsRep - list objects response.
type ListObjectsRep struct {
Objects []WebObjectInfo `json:"objects"`
UIVersion string `json:"uiVersion"`
}
// WebObjectInfo container for list objects metadata.
type WebObjectInfo struct {
// Name of the object
Key string `json:"name"`
// Date and time the object was last modified.
LastModified time.Time `json:"lastModified"`
// Size in bytes of the object.
Size int64 `json:"size"`
// ContentType is mime type of the object.
ContentType string `json:"contentType"`
}
// ListObjects - list objects api.
func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error {
marker := ""
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
for {
objectAPI := web.ObjectAPI()
if objectAPI == nil {
return &json2.Error{Message: "Server not initialized"}
}
lo, err := objectAPI.ListObjects(args.BucketName, args.Prefix, marker, "/", 1000)
if err != nil {
return &json2.Error{Message: err.Error()}
}
marker = lo.NextMarker
for _, obj := range lo.Objects {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: obj.Name,
LastModified: obj.ModTime,
Size: obj.Size,
})
}
for _, prefix := range lo.Prefixes {
reply.Objects = append(reply.Objects, WebObjectInfo{
Key: prefix,
})
}
if !lo.IsTruncated {
break
}
}
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// RemoveObjectArgs - args to remove an object
type RemoveObjectArgs struct {
TargetHost string `json:"targetHost"`
BucketName string `json:"bucketName"`
ObjectName string `json:"objectName"`
}
// RemoveObject - removes an object.
func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply *WebGenericRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
reply.UIVersion = miniobrowser.UIVersion
objectAPI := web.ObjectAPI()
if objectAPI == nil {
return &json2.Error{Message: "Server not initialized"}
}
if err := objectAPI.DeleteObject(args.BucketName, args.ObjectName); err != nil {
return &json2.Error{Message: err.Error()}
}
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// LoginArgs - login arguments.
type LoginArgs struct {
Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"`
}
// LoginRep - login reply.
type LoginRep struct {
Token string `json:"token"`
UIVersion string `json:"uiVersion"`
}
// Login - user login handler.
func (web *webAPIHandlers) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error {
jwt, err := newJWT(defaultJWTExpiry)
if err != nil {
return &json2.Error{Message: err.Error()}
}
if err = jwt.Authenticate(args.Username, args.Password); err != nil {
return &json2.Error{Message: err.Error()}
}
token, err := jwt.GenerateToken(args.Username)
if err != nil {
return &json2.Error{Message: err.Error()}
}
reply.Token = token
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// GenerateAuthReply - reply for GenerateAuth
type GenerateAuthReply struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
UIVersion string `json:"uiVersion"`
}
func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, reply *GenerateAuthReply) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
cred := mustGenAccessKeys()
reply.AccessKey = cred.AccessKeyID
reply.SecretKey = cred.SecretAccessKey
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// SetAuthArgs - argument for SetAuth
type SetAuthArgs struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
}
// SetAuthReply - reply for SetAuth
type SetAuthReply struct {
Token string `json:"token"`
UIVersion string `json:"uiVersion"`
PeerErrMsgs map[string]string `json:"peerErrMsgs"`
}
// SetAuth - Set accessKey and secretKey credentials.
func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
if !isValidAccessKey.MatchString(args.AccessKey) {
return &json2.Error{Message: "Invalid Access Key"}
}
if !isValidSecretKey.MatchString(args.SecretKey) {
return &json2.Error{Message: "Invalid Secret Key"}
}
cred := credential{args.AccessKey, args.SecretKey}
unexpErrsMsg := "ALERT: Unexpected error(s) happened - please check the server logs."
gaveUpMsg := func(errMsg error, moreErrors bool) *json2.Error {
msg := fmt.Sprintf(
"ALERT: We gave up due to: '%s', but there were more errors. Please check the server logs.",
errMsg.Error(),
)
if moreErrors {
return &json2.Error{Message: msg}
}
return &json2.Error{Message: errMsg.Error()}
}
// Notify all other Minio peers to update credentials
errsMap := updateCredsOnPeers(cred)
// Update local credentials
serverConfig.SetCredential(cred)
if err := serverConfig.Save(); err != nil {
errsMap[globalMinioAddr] = err
}
// Log all the peer related error messages, and populate the
// PeerErrMsgs map.
reply.PeerErrMsgs = make(map[string]string)
for svr, errVal := range errsMap {
tErr := fmt.Errorf("Unable to change credentials on %s: %v", svr, errVal)
errorIf(tErr, "Credentials change could not be propagated successfully!")
reply.PeerErrMsgs[svr] = errVal.Error()
}
// If we were unable to update locally, we return an error to
// the user/browser.
if errsMap[globalMinioAddr] != nil {
// Since the error message may be very long to display
// on the browser, we tell the user to check the
// server logs.
return &json2.Error{Message: unexpErrsMsg}
}
// Did we have peer errors?
var moreErrors bool
if len(errsMap) > 0 {
moreErrors = true
}
// If we were able to update locally, we try to generate a new
// token and complete the request.
jwt, err := newJWT(defaultJWTExpiry) // JWT Expiry set to 24Hrs.
if err != nil {
return gaveUpMsg(err, moreErrors)
}
if err = jwt.Authenticate(args.AccessKey, args.SecretKey); err != nil {
return gaveUpMsg(err, moreErrors)
}
token, err := jwt.GenerateToken(args.AccessKey)
if err != nil {
return gaveUpMsg(err, moreErrors)
}
reply.Token = token
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// GetAuthReply - Reply current credentials.
type GetAuthReply struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
UIVersion string `json:"uiVersion"`
}
// GetAuth - return accessKey and secretKey credentials.
func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply *GetAuthReply) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
creds := serverConfig.GetCredential()
reply.AccessKey = creds.AccessKeyID
reply.SecretKey = creds.SecretAccessKey
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// Upload - file upload handler.
func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
if !isJWTReqAuthenticated(r) {
writeWebErrorResponse(w, errInvalidToken)
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
object := vars["object"]
// Extract incoming metadata if any.
metadata := extractMetadataFromHeader(r.Header)
objectAPI := web.ObjectAPI()
if objectAPI == nil {
writeWebErrorResponse(w, errors.New("Server not initialized"))
return
}
sha256sum := ""
if _, err := objectAPI.PutObject(bucket, object, -1, r.Body, metadata, sha256sum); err != nil {
writeWebErrorResponse(w, err)
return
}
// Fetch object info for notifications.
objInfo, err := objectAPI.GetObjectInfo(bucket, object)
if err != nil {
errorIf(err, "Unable to fetch object info for \"%s\"", path.Join(bucket, object))
return
}
// Notify object created event.
eventNotify(eventData{
Type: ObjectCreatedPut,
Bucket: bucket,
ObjInfo: objInfo,
ReqParams: map[string]string{
"sourceIPAddress": r.RemoteAddr,
},
})
}
// Download - file download handler.
func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
object := vars["object"]
tokenStr := r.URL.Query().Get("token")
jwt, err := newJWT(defaultJWTExpiry) // Expiry set to 24Hrs.
if err != nil {
errorIf(err, "error in getting new JWT")
return
}
token, e := jwtgo.Parse(tokenStr, func(token *jwtgo.Token) (interface{}, error) {
if _, ok := token.Method.(*jwtgo.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(jwt.SecretAccessKey), nil
})
if e != nil || !token.Valid {
writeWebErrorResponse(w, errInvalidToken)
return
}
2016-04-08 02:44:08 -04:00
// Add content disposition.
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(object)))
objectAPI := web.ObjectAPI()
if objectAPI == nil {
writeWebErrorResponse(w, errors.New("Server not initialized"))
return
}
objInfo, err := objectAPI.GetObjectInfo(bucket, object)
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
if err != nil {
writeWebErrorResponse(w, err)
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
return
}
offset := int64(0)
err = objectAPI.GetObject(bucket, object, offset, objInfo.Size, w)
if err != nil {
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
/// No need to print error, response writer already written to.
return
}
}
// writeWebErrorResponse - set HTTP status code and write error description to the body.
func writeWebErrorResponse(w http.ResponseWriter, err error) {
2016-04-08 02:44:08 -04:00
// Handle invalid token as a special case.
if err == errInvalidToken {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(err.Error()))
return
}
2016-04-08 02:44:08 -04:00
// Convert error type to api error code.
var apiErrCode APIErrorCode
switch err.(type) {
case StorageFull:
apiErrCode = ErrStorageFull
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
case BucketNotFound:
2016-04-08 02:44:08 -04:00
apiErrCode = ErrNoSuchBucket
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
case BucketNameInvalid:
2016-04-08 02:44:08 -04:00
apiErrCode = ErrInvalidBucketName
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
case BadDigest:
2016-04-08 02:44:08 -04:00
apiErrCode = ErrBadDigest
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
case IncompleteBody:
2016-04-08 02:44:08 -04:00
apiErrCode = ErrIncompleteBody
case ObjectExistsAsDirectory:
apiErrCode = ErrObjectExistsAsDirectory
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
case ObjectNotFound:
2016-04-08 02:44:08 -04:00
apiErrCode = ErrNoSuchKey
fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
2016-03-30 19:15:28 -04:00
case ObjectNameInvalid:
2016-04-08 02:44:08 -04:00
apiErrCode = ErrNoSuchKey
case InsufficientWriteQuorum:
apiErrCode = ErrWriteQuorum
case InsufficientReadQuorum:
apiErrCode = ErrReadQuorum
default:
2016-04-08 02:44:08 -04:00
apiErrCode = ErrInternalError
}
2016-04-08 02:44:08 -04:00
apiErr := getAPIError(apiErrCode)
w.WriteHeader(apiErr.HTTPStatusCode)
w.Write([]byte(apiErr.Description))
}
// GetBucketPolicyArgs - get bucket policy args.
type GetBucketPolicyArgs struct {
BucketName string `json:"bucketName"`
Prefix string `json:"prefix"`
}
// GetBucketPolicyRep - get bucket policy reply.
type GetBucketPolicyRep struct {
UIVersion string `json:"uiVersion"`
Policy policy.BucketPolicy `json:"policy"`
}
func readBucketAccessPolicy(objAPI ObjectLayer, bucketName string) (policy.BucketAccessPolicy, error) {
bucketPolicyReader, err := readBucketPolicyJSON(bucketName, objAPI)
if err != nil {
if _, ok := err.(BucketPolicyNotFound); ok {
return policy.BucketAccessPolicy{Version: "2012-10-17"}, nil
}
return policy.BucketAccessPolicy{}, err
}
bucketPolicyBuf, err := ioutil.ReadAll(bucketPolicyReader)
if err != nil {
return policy.BucketAccessPolicy{}, err
}
policyInfo := policy.BucketAccessPolicy{}
err = json.Unmarshal(bucketPolicyBuf, &policyInfo)
if err != nil {
return policy.BucketAccessPolicy{}, err
}
return policyInfo, nil
}
// GetBucketPolicy - get bucket policy.
func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolicyArgs, reply *GetBucketPolicyRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
objectAPI := web.ObjectAPI()
if objectAPI == nil {
return &json2.Error{Message: "Server not initialized"}
}
policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName)
if err != nil {
return &json2.Error{Message: err.Error()}
}
reply.UIVersion = miniobrowser.UIVersion
reply.Policy = policy.GetPolicy(policyInfo.Statements, args.BucketName, args.Prefix)
return nil
}
// ListAllBucketPoliciesArgs - get all bucket policies.
type ListAllBucketPoliciesArgs struct {
BucketName string `json:"bucketName"`
}
// Collection of canned bucket policy at a given prefix.
type bucketAccessPolicy struct {
Prefix string `json:"prefix"`
Policy policy.BucketPolicy `json:"policy"`
}
// ListAllBucketPoliciesRep - get all bucket policy reply.
type ListAllBucketPoliciesRep struct {
UIVersion string `json:"uiVersion"`
Policies []bucketAccessPolicy `json:"policies"`
}
// GetllBucketPolicy - get all bucket policy.
func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllBucketPoliciesArgs, reply *ListAllBucketPoliciesRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
objectAPI := web.ObjectAPI()
if objectAPI == nil {
return &json2.Error{Message: "Server not initialized"}
}
policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName)
if err != nil {
return &json2.Error{Message: err.Error()}
}
reply.UIVersion = miniobrowser.UIVersion
for prefix, policy := range policy.GetPolicies(policyInfo.Statements, args.BucketName) {
reply.Policies = append(reply.Policies, bucketAccessPolicy{
Prefix: prefix,
Policy: policy,
})
}
return nil
}
// SetBucketPolicyArgs - set bucket policy args.
type SetBucketPolicyArgs struct {
BucketName string `json:"bucketName"`
Prefix string `json:"prefix"`
Policy string `json:"policy"`
}
// SetBucketPolicy - set bucket policy.
func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolicyArgs, reply *WebGenericRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
objectAPI := web.ObjectAPI()
if objectAPI == nil {
return &json2.Error{Message: "Server not initialized"}
}
bucketP := policy.BucketPolicy(args.Policy)
if !bucketP.IsValidBucketPolicy() {
return &json2.Error{Message: "Invalid policy " + args.Policy}
}
policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName)
if err != nil {
return &json2.Error{Message: err.Error()}
}
policyInfo.Statements = policy.SetPolicy(policyInfo.Statements, bucketP, args.BucketName, args.Prefix)
if len(policyInfo.Statements) == 0 {
if err = persistAndNotifyBucketPolicyChange(args.BucketName, policyChange{true, nil}, objectAPI); err != nil {
return &json2.Error{Message: err.Error()}
}
reply.UIVersion = miniobrowser.UIVersion
return nil
}
data, err := json.Marshal(policyInfo)
if err != nil {
return &json2.Error{Message: err.Error()}
}
// Parse bucket policy.
var policy = &bucketPolicy{}
err = parseBucketPolicy(bytes.NewReader(data), policy)
if err != nil {
errorIf(err, "Unable to parse bucket policy.")
return &json2.Error{Message: err.Error()}
}
// Parse check bucket policy.
if s3Error := checkBucketPolicyResources(args.BucketName, policy); s3Error != ErrNone {
return &json2.Error{Message: getAPIError(s3Error).Description}
}
// TODO: update policy statements according to bucket name,
// prefix and policy arguments.
if err := persistAndNotifyBucketPolicyChange(args.BucketName, policyChange{false, policy}, objectAPI); err != nil {
return &json2.Error{Message: err.Error()}
}
reply.UIVersion = miniobrowser.UIVersion
return nil
}
// PresignedGetArgs - presigned-get API args.
type PresignedGetArgs struct {
// Host header required for signed headers.
HostName string `json:"host"`
// Bucket name of the object to be presigned.
BucketName string `json:"bucket"`
// Object name to be presigned.
ObjectName string `json:"object"`
}
// PresignedGetRep - presigned-get URL reply.
type PresignedGetRep struct {
UIVersion string `json:"uiVersion"`
// Presigned URL of the object.
URL string `json:"url"`
}
// PresignedGET - returns presigned-Get url.
func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error {
if !isJWTReqAuthenticated(r) {
return &json2.Error{Message: "Unauthorized request"}
}
if args.BucketName == "" || args.ObjectName == "" {
return &json2.Error{Message: "Required arguments: Host, Bucket, Object"}
}
reply.UIVersion = miniobrowser.UIVersion
reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName)
return nil
}
// Returns presigned url for GET method.
func presignedGet(host, bucket, object string) string {
cred := serverConfig.GetCredential()
region := serverConfig.GetRegion()
accessKey := cred.AccessKeyID
secretKey := cred.SecretAccessKey
date := time.Now().UTC()
dateStr := date.Format("20060102T150405Z")
credential := fmt.Sprintf("%s/%s", accessKey, getScope(date, region))
query := strings.Join([]string{
"X-Amz-Algorithm=" + signV4Algorithm,
"X-Amz-Credential=" + strings.Replace(credential, "/", "%2F", -1),
"X-Amz-Date=" + dateStr,
"X-Amz-Expires=" + "604800", // Default set to be expire in 7days.
"X-Amz-SignedHeaders=host",
}, "&")
path := "/" + path.Join(bucket, object)
// Headers are empty, since "host" is the only header required to be signed for Presigned URLs.
var extractedSignedHeaders http.Header
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, query, path, "GET", host)
stringToSign := getStringToSign(canonicalRequest, date, region)
signingKey := getSigningKey(secretKey, date, region)
signature := getSignature(signingKey, stringToSign)
// Construct the final presigned URL.
return host + path + "?" + query + "&" + "X-Amz-Signature=" + signature
}