2016-01-26 02:29:20 +01:00
/ *
* 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 .
* /
2016-08-19 01:23:42 +02:00
package cmd
2016-01-24 04:44:32 +01:00
import (
2016-08-30 19:04:50 +02:00
"encoding/json"
2016-11-22 20:12:38 +01:00
"errors"
2016-01-24 04:44:32 +01:00
"fmt"
2016-08-30 19:04:50 +02:00
"io/ioutil"
2016-01-24 04:44:32 +01:00
"net/http"
2016-02-04 07:46:45 +01:00
"os"
2016-02-22 07:38:38 +01:00
"path"
2016-02-04 07:46:45 +01:00
"runtime"
"strconv"
2016-09-23 10:24:49 +02:00
"strings"
2016-01-24 04:44:32 +01:00
"time"
2016-02-04 07:46:45 +01:00
"github.com/dustin/go-humanize"
2016-03-31 15:57:29 +02:00
"github.com/gorilla/mux"
2016-02-13 03:55:17 +01:00
"github.com/gorilla/rpc/v2/json2"
2016-08-30 19:04:50 +02:00
"github.com/minio/minio-go/pkg/policy"
2017-01-24 03:07:22 +01:00
"github.com/minio/minio/browser"
2016-01-24 04:44:32 +01:00
)
2016-04-08 08:44:08 +02:00
// WebGenericArgs - empty struct for calls that don't accept arguments
2016-03-21 19:15:08 +01:00
// for ex. ServerInfo, GenerateAuth
2016-04-08 08:44:08 +02:00
type WebGenericArgs struct { }
2016-03-21 19:15:08 +01:00
2016-04-08 08:44:08 +02:00
// WebGenericRep - reply structure for calls for which reply is success/failure
2016-02-19 09:00:32 +01:00
// for ex. RemoveObject MakeBucket
2016-04-08 08:44:08 +02:00
type WebGenericRep struct {
2016-02-19 09:00:32 +01:00
UIVersion string ` json:"uiVersion" `
}
// ServerInfoRep - server info reply.
type ServerInfoRep struct {
MinioVersion string
MinioMemory string
MinioPlatform string
MinioRuntime string
2016-08-03 22:47:03 +02:00
MinioEnvVars [ ] string
2016-02-19 09:00:32 +01:00
UIVersion string ` json:"uiVersion" `
}
2016-02-04 07:46:45 +01:00
// ServerInfo - get server info.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) ServerInfo ( r * http . Request , args * WebGenericArgs , reply * ServerInfoRep ) error {
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-02-04 07:46:45 +01: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 ( ) ) )
2016-08-03 22:47:03 +02:00
reply . MinioEnvVars = os . Environ ( )
2016-08-19 01:23:42 +02:00
reply . MinioVersion = Version
2016-02-08 21:40:22 +01:00
reply . MinioMemory = mem
reply . MinioPlatform = platform
reply . MinioRuntime = goruntime
2016-02-23 22:05:47 +01:00
reply . UIVersion = miniobrowser . UIVersion
2016-02-04 07:46:45 +01:00
return nil
}
2016-05-26 23:13:10 +02:00
// StorageInfoRep - contains storage usage statistics.
type StorageInfoRep struct {
StorageInfo StorageInfo ` json:"storageInfo" `
UIVersion string ` json:"uiVersion" `
}
// StorageInfo - web call to gather storage usage statistics.
2016-12-23 16:12:19 +01:00
func ( web * webAPIHandlers ) StorageInfo ( r * http . Request , args * AuthRPCArgs , reply * StorageInfoRep ) error {
2016-07-31 23:11:14 +02:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( errServerNotInitialized )
2016-11-02 22:45:11 +01:00
}
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-07-31 23:11:14 +02:00
}
reply . StorageInfo = objectAPI . StorageInfo ( )
2016-10-23 21:32:35 +02:00
reply . UIVersion = miniobrowser . UIVersion
2016-05-26 23:13:10 +02:00
return nil
}
2016-02-19 09:00:32 +01:00
// MakeBucketArgs - make bucket args.
type MakeBucketArgs struct {
BucketName string ` json:"bucketName" `
}
2016-01-25 07:26:53 +01:00
// MakeBucket - make a bucket.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) MakeBucket ( r * http . Request , args * MakeBucketArgs , reply * WebGenericRep ) error {
2016-07-31 23:11:14 +02:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( errServerNotInitialized )
2016-11-02 22:45:11 +01:00
}
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-07-31 23:11:14 +02:00
}
2016-12-11 01:15:12 +01:00
bucketLock := globalNSMutex . NewNSLock ( args . BucketName , "" )
bucketLock . Lock ( )
defer bucketLock . Unlock ( )
2016-07-31 23:11:14 +02:00
if err := objectAPI . MakeBucket ( args . BucketName ) ; err != nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( err , args . BucketName )
2016-02-13 03:55:17 +01:00
}
2016-10-23 21:32:35 +02:00
reply . UIVersion = miniobrowser . UIVersion
2016-02-13 03:55:17 +01:00
return nil
2016-01-25 07:26:53 +01:00
}
2016-02-19 09:00:32 +01:00
// ListBucketsRep - list buckets response
type ListBucketsRep struct {
objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 10:34:20 +02:00
Buckets [ ] WebBucketInfo ` json:"buckets" `
UIVersion string ` json:"uiVersion" `
2016-02-19 09:00:32 +01:00
}
objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 10:34:20 +02:00
// WebBucketInfo container for list buckets metadata.
type WebBucketInfo struct {
2016-02-19 09:00:32 +01:00
// The name of the bucket.
Name string ` json:"name" `
// Date the bucket was created.
CreationDate time . Time ` json:"creationDate" `
}
2016-01-24 04:44:32 +01:00
// ListBuckets - list buckets api.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) ListBuckets ( r * http . Request , args * WebGenericArgs , reply * ListBucketsRep ) error {
2016-07-31 23:11:14 +02:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( errServerNotInitialized )
2016-11-02 22:45:11 +01:00
}
2017-01-11 22:26:42 +01:00
authErr := webReqestAuthenticate ( r )
if authErr != nil {
return toJSONError ( authErr )
2016-07-31 23:11:14 +02:00
}
buckets , err := objectAPI . ListBuckets ( )
2016-04-29 23:24:10 +02:00
if err != nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( err )
2016-01-24 04:44:32 +01:00
}
2016-01-25 07:26:53 +01:00
for _ , bucket := range buckets {
2017-01-11 22:26:42 +01:00
if bucket . Name == path . Base ( reservedBucket ) {
continue
2016-02-20 06:45:37 +01:00
}
2017-01-11 22:26:42 +01:00
reply . Buckets = append ( reply . Buckets , WebBucketInfo {
Name : bucket . Name ,
CreationDate : bucket . Created ,
} )
2016-01-24 04:44:32 +01:00
}
2016-02-23 22:05:47 +01:00
reply . UIVersion = miniobrowser . UIVersion
2016-01-24 04:44:32 +01:00
return nil
}
2016-02-19 09:00:32 +01:00
// ListObjectsArgs - list object args.
type ListObjectsArgs struct {
BucketName string ` json:"bucketName" `
Prefix string ` json:"prefix" `
}
// ListObjectsRep - list objects response.
type ListObjectsRep struct {
objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 10:34:20 +02:00
Objects [ ] WebObjectInfo ` json:"objects" `
2017-01-11 22:26:42 +01:00
Writable bool ` json:"writable" ` // Used by client to show "upload file" button.
objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 10:34:20 +02:00
UIVersion string ` json:"uiVersion" `
2016-02-19 09:00:32 +01:00
}
objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 10:34:20 +02:00
// WebObjectInfo container for list objects metadata.
type WebObjectInfo struct {
2016-02-19 09:00:32 +01:00
// 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" `
}
2016-01-24 04:44:32 +01:00
// ListObjects - list objects api.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) ListObjects ( r * http . Request , args * ListObjectsArgs , reply * ListObjectsRep ) error {
2017-01-11 22:26:42 +01:00
reply . UIVersion = miniobrowser . UIVersion
2016-11-02 22:45:11 +01:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( errServerNotInitialized )
2016-11-02 22:45:11 +01:00
}
2017-01-11 22:26:42 +01:00
prefix := args . Prefix + "test" // To test if GetObject/PutObject with the specified prefix is allowed.
readable := isBucketActionAllowed ( "s3:GetObject" , args . BucketName , prefix )
writable := isBucketActionAllowed ( "s3:PutObject" , args . BucketName , prefix )
authErr := webReqestAuthenticate ( r )
switch {
case authErr == errAuthentication :
return toJSONError ( authErr )
case authErr == nil :
break
case readable && writable :
reply . Writable = true
break
case readable :
break
case writable :
reply . Writable = true
return nil
default :
return errAuthentication
2016-01-24 04:44:32 +01:00
}
2016-11-22 20:12:38 +01:00
marker := ""
2016-03-31 15:57:29 +02:00
for {
2016-07-31 23:11:14 +02:00
lo , err := objectAPI . ListObjects ( args . BucketName , args . Prefix , marker , "/" , 1000 )
2016-03-31 15:57:29 +02:00
if err != nil {
2016-04-29 23:24:10 +02:00
return & json2 . Error { Message : err . Error ( ) }
2016-01-24 04:44:32 +01:00
}
2016-03-31 15:57:29 +02:00
marker = lo . NextMarker
for _ , obj := range lo . Objects {
objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 10:34:20 +02:00
reply . Objects = append ( reply . Objects , WebObjectInfo {
2016-03-31 15:57:29 +02:00
Key : obj . Name ,
2016-04-08 19:37:38 +02:00
LastModified : obj . ModTime ,
2016-03-31 15:57:29 +02:00
Size : obj . Size ,
2016-11-13 21:26:40 +01:00
ContentType : obj . ContentType ,
2016-03-31 15:57:29 +02:00
} )
2016-02-01 21:47:46 +01:00
}
2016-03-31 15:57:29 +02:00
for _ , prefix := range lo . Prefixes {
objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 10:34:20 +02:00
reply . Objects = append ( reply . Objects , WebObjectInfo {
2016-03-31 15:57:29 +02:00
Key : prefix ,
} )
}
if ! lo . IsTruncated {
break
2016-02-01 21:19:54 +01:00
}
2016-01-24 04:44:32 +01:00
}
return nil
}
2016-02-19 09:00:32 +01:00
// RemoveObjectArgs - args to remove an object
type RemoveObjectArgs struct {
TargetHost string ` json:"targetHost" `
BucketName string ` json:"bucketName" `
ObjectName string ` json:"objectName" `
}
2016-02-05 15:16:36 +01:00
// RemoveObject - removes an object.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) RemoveObject ( r * http . Request , args * RemoveObjectArgs , reply * WebGenericRep ) error {
2016-07-31 23:11:14 +02:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( errServerNotInitialized )
2016-11-02 22:45:11 +01:00
}
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-07-31 23:11:14 +02:00
}
2016-12-11 01:15:12 +01:00
objectLock := globalNSMutex . NewNSLock ( args . BucketName , args . ObjectName )
objectLock . Lock ( )
defer objectLock . Unlock ( )
2016-07-31 23:11:14 +02:00
if err := objectAPI . DeleteObject ( args . BucketName , args . ObjectName ) ; err != nil {
2016-11-17 01:42:23 +01:00
if isErrObjectNotFound ( err ) {
2016-11-11 00:02:03 +01:00
// Ignore object not found error.
reply . UIVersion = miniobrowser . UIVersion
return nil
}
2016-11-22 20:12:38 +01:00
return toJSONError ( err , args . BucketName , args . ObjectName )
2016-02-13 03:55:17 +01:00
}
2016-11-10 16:42:55 +01:00
// Notify object deleted event.
eventNotify ( eventData {
Type : ObjectRemovedDelete ,
Bucket : args . BucketName ,
ObjInfo : ObjectInfo {
Name : args . ObjectName ,
} ,
ReqParams : map [ string ] string {
"sourceIPAddress" : r . RemoteAddr ,
} ,
} )
2016-10-23 21:32:35 +02:00
reply . UIVersion = miniobrowser . UIVersion
2016-02-13 03:55:17 +01:00
return nil
2016-02-05 15:16:36 +01:00
}
2016-02-19 09:00:32 +01:00
// 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" `
}
2016-01-24 04:44:32 +01:00
// Login - user login handler.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) Login ( r * http . Request , args * LoginArgs , reply * LoginRep ) error {
2016-12-27 17:28:10 +01:00
token , err := authenticateWeb ( args . Username , args . Password )
2016-07-12 06:57:40 +02:00
if err != nil {
2017-01-03 10:33:00 +01:00
// Make sure to log errors related to browser login,
// for security and auditing reasons.
errorIf ( err , "Unable to login request from %s" , r . RemoteAddr )
2016-11-22 20:12:38 +01:00
return toJSONError ( err )
2016-07-12 06:57:40 +02:00
}
reply . Token = token
reply . UIVersion = miniobrowser . UIVersion
return nil
2016-01-24 04:44:32 +01:00
}
2016-03-21 19:15:08 +01:00
// GenerateAuthReply - reply for GenerateAuth
type GenerateAuthReply struct {
AccessKey string ` json:"accessKey" `
SecretKey string ` json:"secretKey" `
UIVersion string ` json:"uiVersion" `
}
2016-04-12 21:45:15 +02:00
func ( web webAPIHandlers ) GenerateAuth ( r * http . Request , args * WebGenericArgs , reply * GenerateAuthReply ) error {
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-03-21 19:15:08 +01:00
}
2016-12-26 19:21:23 +01:00
cred := newCredential ( )
reply . AccessKey = cred . AccessKey
reply . SecretKey = cred . SecretKey
2016-03-21 19:15:08 +01:00
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 {
2016-10-18 05:18:08 +02:00
Token string ` json:"token" `
UIVersion string ` json:"uiVersion" `
PeerErrMsgs map [ string ] string ` json:"peerErrMsgs" `
2016-03-21 19:15:08 +01:00
}
// SetAuth - Set accessKey and secretKey credentials.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) SetAuth ( r * http . Request , args * SetAuthArgs , reply * SetAuthReply ) error {
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-03-21 19:15:08 +01:00
}
2016-11-22 20:12:38 +01:00
2016-12-27 17:28:10 +01:00
// As we already validated the authentication, we save given access/secret keys.
2017-01-27 01:51:51 +01:00
creds , err := getCredential ( args . AccessKey , args . SecretKey )
2016-11-22 20:12:38 +01:00
if err != nil {
return toJSONError ( err )
2016-03-21 19:15:08 +01:00
}
2016-11-22 20:12:38 +01:00
2016-10-18 05:18:08 +02:00
// Notify all other Minio peers to update credentials
2017-01-27 01:51:51 +01:00
errsMap := updateCredsOnPeers ( creds )
2016-10-18 05:18:08 +02:00
// Update local credentials
2017-01-27 01:51:51 +01:00
serverConfig . SetCredential ( creds )
// Persist updated credentials.
2016-11-22 20:12:38 +01:00
if err = serverConfig . Save ( ) ; err != nil {
2016-10-18 05:18:08 +02:00
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 ( )
}
2016-11-07 20:43:35 +01:00
// If we were unable to update locally, we return an error to the user/browser.
2016-10-18 05:18:08 +02:00
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.
2016-12-27 17:28:10 +01:00
return toJSONError ( errors . New ( "unexpected error(s) occurred - please check minio server logs" ) )
2016-10-18 05:18:08 +02:00
}
2016-12-27 17:28:10 +01:00
// As we have updated access/secret key, generate new auth token.
token , err := authenticateWeb ( args . AccessKey , args . SecretKey )
2016-03-21 19:15:08 +01:00
if err != nil {
2016-12-27 17:28:10 +01:00
// Did we have peer errors?
if len ( errsMap ) > 0 {
err = fmt . Errorf (
"we gave up due to: '%s', but there were more errors. Please check minio server logs" ,
err . Error ( ) ,
)
}
return toJSONError ( err )
2016-03-21 19:15:08 +01:00
}
2016-10-18 05:18:08 +02:00
2016-03-21 19:15:08 +01:00
reply . Token = token
reply . UIVersion = miniobrowser . UIVersion
return nil
}
2016-03-31 15:57:29 +02:00
// 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.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) GetAuth ( r * http . Request , args * WebGenericArgs , reply * GetAuthReply ) error {
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-03-31 15:57:29 +02:00
}
creds := serverConfig . GetCredential ( )
2016-12-26 19:21:23 +01:00
reply . AccessKey = creds . AccessKey
reply . SecretKey = creds . SecretKey
2016-03-31 15:57:29 +02:00
reply . UIVersion = miniobrowser . UIVersion
return nil
}
// Upload - file upload handler.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) Upload ( w http . ResponseWriter , r * http . Request ) {
2016-11-02 22:45:11 +01:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
writeWebErrorResponse ( w , errServerNotInitialized )
return
}
2016-03-31 15:57:29 +02:00
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
object := vars [ "object" ]
2016-07-28 06:11:15 +02:00
2017-01-11 22:26:42 +01:00
authErr := webReqestAuthenticate ( r )
if authErr == errAuthentication {
writeWebErrorResponse ( w , errAuthentication )
return
}
if authErr != nil && ! isBucketActionAllowed ( "s3:PutObject" , bucket , object ) {
writeWebErrorResponse ( w , errAuthentication )
return
}
2016-07-28 06:11:15 +02:00
// Extract incoming metadata if any.
metadata := extractMetadataFromHeader ( r . Header )
2016-12-11 01:15:12 +01:00
// Lock the object.
objectLock := globalNSMutex . NewNSLock ( bucket , object )
objectLock . Lock ( )
defer objectLock . Unlock ( )
2016-07-28 06:11:15 +02:00
2016-12-11 01:15:12 +01:00
sha256sum := ""
objInfo , err := objectAPI . PutObject ( bucket , object , - 1 , r . Body , metadata , sha256sum )
2016-07-28 06:11:15 +02:00
if err != nil {
2016-12-11 01:15:12 +01:00
writeWebErrorResponse ( w , err )
2016-07-28 06:11:15 +02:00
return
}
2016-09-29 07:46:19 +02:00
// Notify object created event.
eventNotify ( eventData {
Type : ObjectCreatedPut ,
Bucket : bucket ,
ObjInfo : objInfo ,
ReqParams : map [ string ] string {
"sourceIPAddress" : r . RemoteAddr ,
} ,
} )
2016-03-31 15:57:29 +02:00
}
// Download - file download handler.
2016-04-12 21:45:15 +02:00
func ( web * webAPIHandlers ) Download ( w http . ResponseWriter , r * http . Request ) {
2016-11-02 22:45:11 +01:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
writeWebErrorResponse ( w , errServerNotInitialized )
return
}
2016-03-31 15:57:29 +02:00
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
object := vars [ "object" ]
2016-12-27 17:28:10 +01:00
token := r . URL . Query ( ) . Get ( "token" )
2016-07-12 06:57:40 +02:00
2017-01-11 22:26:42 +01:00
if ! isAuthTokenValid ( token ) && ! isBucketActionAllowed ( "s3:GetObject" , bucket , object ) {
2016-11-02 22:45:11 +01:00
writeWebErrorResponse ( w , errAuthentication )
2016-03-31 15:57:29 +02:00
return
}
2016-12-27 17:28:10 +01:00
2016-04-08 08:44:08 +02:00
// Add content disposition.
2016-08-17 22:26:08 +02:00
w . Header ( ) . Set ( "Content-Disposition" , fmt . Sprintf ( "attachment; filename=\"%s\"" , path . Base ( object ) ) )
2016-03-31 15:57:29 +02:00
2016-12-11 01:15:12 +01:00
// Lock the object before reading.
objectLock := globalNSMutex . NewNSLock ( bucket , object )
objectLock . RLock ( )
defer objectLock . RUnlock ( )
2017-01-27 01:51:51 +01:00
if err := objectAPI . GetObject ( bucket , object , 0 , - 1 , w ) ; 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-31 01:15:28 +02:00
/// No need to print error, response writer already written to.
return
2016-03-31 15:57:29 +02:00
}
}
2016-08-30 19:04:50 +02:00
// 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 {
2016-09-23 07:27:21 +02:00
return policy . BucketAccessPolicy { Version : "2012-10-17" } , nil
2016-08-30 19:04:50 +02:00
}
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 {
2016-08-31 04:22:27 +02:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( errServerNotInitialized )
2016-08-31 04:22:27 +02:00
}
2016-11-02 22:45:11 +01:00
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-11-02 22:45:11 +01:00
}
2016-08-31 04:22:27 +02:00
policyInfo , err := readBucketAccessPolicy ( objectAPI , args . BucketName )
2016-08-30 19:04:50 +02:00
if err != nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( err , args . BucketName )
2016-08-30 19:04:50 +02:00
}
reply . UIVersion = miniobrowser . UIVersion
2016-09-26 06:53:19 +02:00
reply . Policy = policy . GetPolicy ( policyInfo . Statements , args . BucketName , args . Prefix )
2016-08-30 19:04:50 +02:00
return nil
}
2016-09-26 06:53:19 +02:00
// ListAllBucketPoliciesArgs - get all bucket policies.
type ListAllBucketPoliciesArgs struct {
2016-09-23 08:06:45 +02:00
BucketName string ` json:"bucketName" `
}
2016-09-26 06:53:19 +02:00
// 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" `
2016-09-23 08:06:45 +02:00
}
2016-09-26 06:53:19 +02:00
// GetllBucketPolicy - get all bucket policy.
func ( web * webAPIHandlers ) ListAllBucketPolicies ( r * http . Request , args * ListAllBucketPoliciesArgs , reply * ListAllBucketPoliciesRep ) error {
2016-09-23 08:06:45 +02:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( errServerNotInitialized )
2016-11-02 22:45:11 +01:00
}
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-09-23 08:06:45 +02:00
}
2016-11-02 22:45:11 +01:00
2016-09-23 08:06:45 +02:00
policyInfo , err := readBucketAccessPolicy ( objectAPI , args . BucketName )
if err != nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( err , args . BucketName )
2016-09-23 08:06:45 +02:00
}
reply . UIVersion = miniobrowser . UIVersion
2016-09-26 06:53:19 +02:00
for prefix , policy := range policy . GetPolicies ( policyInfo . Statements , args . BucketName ) {
reply . Policies = append ( reply . Policies , bucketAccessPolicy {
Prefix : prefix ,
Policy : policy ,
} )
}
2016-09-23 08:06:45 +02:00
return nil
}
2016-08-30 19:04:50 +02:00
// 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 {
2016-09-23 01:35:12 +02:00
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( errServerNotInitialized )
2016-11-02 22:45:11 +01:00
}
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-09-23 01:35:12 +02:00
}
2016-08-30 19:04:50 +02:00
2016-09-23 07:27:21 +02:00
bucketP := policy . BucketPolicy ( args . Policy )
if ! bucketP . IsValidBucketPolicy ( ) {
2016-11-22 20:12:38 +01:00
return & json2 . Error {
Message : "Invalid policy type " + args . Policy ,
}
2016-08-30 19:04:50 +02:00
}
2016-08-31 04:22:27 +02:00
policyInfo , err := readBucketAccessPolicy ( objectAPI , args . BucketName )
2016-08-30 19:04:50 +02:00
if err != nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( err , args . BucketName )
2016-08-30 19:04:50 +02:00
}
2016-09-23 07:27:21 +02:00
policyInfo . Statements = policy . SetPolicy ( policyInfo . Statements , bucketP , args . BucketName , args . Prefix )
2016-09-26 12:11:22 +02:00
if len ( policyInfo . Statements ) == 0 {
2016-11-22 20:12:38 +01:00
err = persistAndNotifyBucketPolicyChange ( args . BucketName , policyChange { true , nil } , objectAPI )
if err != nil {
return toJSONError ( err , args . BucketName )
2016-09-26 12:11:22 +02:00
}
2016-10-23 21:32:35 +02:00
reply . UIVersion = miniobrowser . UIVersion
2016-09-26 12:11:22 +02:00
return nil
}
2016-08-30 19:04:50 +02:00
data , err := json . Marshal ( policyInfo )
if err != nil {
2016-11-22 20:12:38 +01:00
return toJSONError ( err )
2016-08-30 19:04:50 +02:00
}
2016-12-11 01:15:12 +01:00
// Parse validate and save bucket policy.
if s3Error := parseAndPersistBucketPolicy ( args . BucketName , data , objectAPI ) ; s3Error != ErrNone {
2016-11-22 20:12:38 +01:00
apiErr := getAPIError ( s3Error )
2016-11-24 02:31:11 +01:00
var err error
if apiErr . Code == "XMinioPolicyNesting" {
err = PolicyNesting { }
} else {
err = errors . New ( apiErr . Description )
}
return toJSONError ( err , args . BucketName )
2016-09-23 07:27:21 +02:00
}
2016-08-30 19:04:50 +02:00
reply . UIVersion = miniobrowser . UIVersion
return nil
}
2016-09-23 10:24:49 +02:00
// 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" `
2016-11-22 20:12:38 +01:00
// Expiry in seconds.
Expiry int64 ` json:"expiry" `
2016-09-23 10:24:49 +02:00
}
// PresignedGetRep - presigned-get URL reply.
type PresignedGetRep struct {
2016-10-23 21:32:35 +02:00
UIVersion string ` json:"uiVersion" `
2016-09-23 10:24:49 +02:00
// 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 {
2016-12-27 17:28:10 +01:00
if ! isHTTPRequestValid ( r ) {
2016-11-22 20:12:38 +01:00
return toJSONError ( errAuthentication )
2016-09-23 10:24:49 +02:00
}
2016-11-22 20:12:38 +01:00
2016-09-23 10:24:49 +02:00
if args . BucketName == "" || args . ObjectName == "" {
2016-11-22 20:12:38 +01:00
return & json2 . Error {
Message : "Bucket and Object are mandatory arguments." ,
}
2016-09-23 10:24:49 +02:00
}
2016-10-23 21:32:35 +02:00
reply . UIVersion = miniobrowser . UIVersion
2016-11-22 20:12:38 +01:00
reply . URL = presignedGet ( args . HostName , args . BucketName , args . ObjectName , args . Expiry )
2016-09-23 10:24:49 +02:00
return nil
}
// Returns presigned url for GET method.
2016-11-22 20:12:38 +01:00
func presignedGet ( host , bucket , object string , expiry int64 ) string {
2016-09-23 10:24:49 +02:00
cred := serverConfig . GetCredential ( )
region := serverConfig . GetRegion ( )
2016-12-26 19:21:23 +01:00
accessKey := cred . AccessKey
secretKey := cred . SecretKey
2016-09-23 10:24:49 +02:00
date := time . Now ( ) . UTC ( )
2016-11-11 06:57:15 +01:00
dateStr := date . Format ( iso8601Format )
2016-09-23 10:24:49 +02:00
credential := fmt . Sprintf ( "%s/%s" , accessKey , getScope ( date , region ) )
2016-11-22 20:12:38 +01:00
var expiryStr = "604800" // Default set to be expire in 7days.
if expiry < 604800 && expiry > 0 {
expiryStr = strconv . FormatInt ( expiry , 10 )
}
2016-09-23 10:24:49 +02:00
query := strings . Join ( [ ] string {
"X-Amz-Algorithm=" + signV4Algorithm ,
"X-Amz-Credential=" + strings . Replace ( credential , "/" , "%2F" , - 1 ) ,
"X-Amz-Date=" + dateStr ,
2016-11-22 20:12:38 +01:00
"X-Amz-Expires=" + expiryStr ,
2016-09-23 10:24:49 +02:00
"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
}
2016-11-22 20:12:38 +01:00
// toJSONError converts regular errors into more user friendly
// and consumable error message for the browser UI.
func toJSONError ( err error , params ... string ) ( jerr * json2 . Error ) {
apiErr := toWebAPIError ( err )
jerr = & json2 . Error {
Message : apiErr . Description ,
}
switch apiErr . Code {
// Bucket name invalid with custom error message.
case "InvalidBucketName" :
if len ( params ) > 0 {
jerr = & json2 . Error {
2016-12-16 18:59:37 +01:00
Message : fmt . Sprintf ( "Bucket Name %s is invalid. Lowercase letters, period, numerals are the only allowed characters and should be minimum 3 characters in length." , params [ 0 ] ) ,
2016-11-22 20:12:38 +01:00
}
}
// Bucket not found custom error message.
case "NoSuchBucket" :
if len ( params ) > 0 {
jerr = & json2 . Error {
Message : fmt . Sprintf ( "The specified bucket %s does not exist." , params [ 0 ] ) ,
}
}
// Object not found custom error message.
case "NoSuchKey" :
if len ( params ) > 1 {
jerr = & json2 . Error {
Message : fmt . Sprintf ( "The specified key %s does not exist" , params [ 1 ] ) ,
}
}
// Add more custom error messages here with more context.
}
return jerr
}
// toWebAPIError - convert into error into APIError.
func toWebAPIError ( err error ) APIError {
err = errorCause ( err )
if err == errAuthentication {
return APIError {
Code : "AccessDenied" ,
HTTPStatusCode : http . StatusForbidden ,
Description : err . Error ( ) ,
}
2017-01-03 10:33:00 +01:00
} else if err == errServerNotInitialized {
2016-11-22 20:12:38 +01:00
return APIError {
Code : "XMinioServerNotInitialized" ,
HTTPStatusCode : http . StatusServiceUnavailable ,
Description : err . Error ( ) ,
}
2017-01-03 10:33:00 +01:00
} else if err == errInvalidAccessKeyLength {
return APIError {
Code : "AccessDenied" ,
HTTPStatusCode : http . StatusForbidden ,
Description : err . Error ( ) ,
}
} else if err == errInvalidSecretKeyLength {
return APIError {
Code : "AccessDenied" ,
HTTPStatusCode : http . StatusForbidden ,
Description : err . Error ( ) ,
}
} else if err == errInvalidAccessKeyID {
return APIError {
Code : "AccessDenied" ,
HTTPStatusCode : http . StatusForbidden ,
Description : err . Error ( ) ,
}
2016-11-22 20:12:38 +01:00
}
// Convert error type to api error code.
var apiErrCode APIErrorCode
switch err . ( type ) {
case StorageFull :
apiErrCode = ErrStorageFull
case BucketNotFound :
apiErrCode = ErrNoSuchBucket
2016-11-24 02:31:11 +01:00
case BucketExists :
apiErrCode = ErrBucketAlreadyOwnedByYou
2016-11-22 20:12:38 +01:00
case BucketNameInvalid :
apiErrCode = ErrInvalidBucketName
case BadDigest :
apiErrCode = ErrBadDigest
case IncompleteBody :
apiErrCode = ErrIncompleteBody
case ObjectExistsAsDirectory :
apiErrCode = ErrObjectExistsAsDirectory
case ObjectNotFound :
apiErrCode = ErrNoSuchKey
case ObjectNameInvalid :
apiErrCode = ErrNoSuchKey
case InsufficientWriteQuorum :
apiErrCode = ErrWriteQuorum
case InsufficientReadQuorum :
apiErrCode = ErrReadQuorum
2016-11-24 02:31:11 +01:00
case PolicyNesting :
apiErrCode = ErrPolicyNesting
2016-11-22 20:12:38 +01:00
default :
2016-11-24 02:31:11 +01:00
// Log unexpected and unhandled errors.
errorIf ( err , errUnexpected . Error ( ) )
2016-11-22 20:12:38 +01:00
apiErrCode = ErrInternalError
}
apiErr := getAPIError ( apiErrCode )
return apiErr
}
// writeWebErrorResponse - set HTTP status code and write error description to the body.
func writeWebErrorResponse ( w http . ResponseWriter , err error ) {
apiErr := toWebAPIError ( err )
w . WriteHeader ( apiErr . HTTPStatusCode )
w . Write ( [ ] byte ( apiErr . Description ) )
}