@ -19,6 +19,7 @@ package cmd
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@ -41,7 +42,7 @@ import (
// isJWTReqAuthenticated validates if any incoming request to be a
// valid JWT authenticated request.
func isJWTReqAuthenticated ( req * http . Request ) bool {
jwt , err := newJWT ( defaultJWTExpiry )
jwt , err := newJWT ( defaultJWTExpiry , serverConfig . GetCredential ( ) )
if err != nil {
errorIf ( err , "unable to initialize a new JWT" )
return false
@ -85,7 +86,7 @@ type ServerInfoRep struct {
// ServerInfo - get server info.
func ( web * webAPIHandlers ) ServerInfo ( r * http . Request , args * WebGenericArgs , reply * ServerInfoRep ) error {
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
host , err := os . Hostname ( )
if err != nil {
@ -123,10 +124,10 @@ type StorageInfoRep struct {
func ( web * webAPIHandlers ) StorageInfo ( r * http . Request , args * GenericArgs , reply * StorageInfoRep ) error {
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
return toJSONError ( errServerNotInitialized )
}
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
reply . StorageInfo = objectAPI . StorageInfo ( )
reply . UIVersion = miniobrowser . UIVersion
@ -142,13 +143,13 @@ type MakeBucketArgs struct {
func ( web * webAPIHandlers ) MakeBucket ( r * http . Request , args * MakeBucketArgs , reply * WebGenericRep ) error {
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
return toJSONError ( errServerNotInitialized )
}
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
if err := objectAPI . MakeBucket ( args . BucketName ) ; err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err , args . BucketName )
}
reply . UIVersion = miniobrowser . UIVersion
return nil
@ -172,14 +173,14 @@ type WebBucketInfo struct {
func ( web * webAPIHandlers ) ListBuckets ( r * http . Request , args * WebGenericArgs , reply * ListBucketsRep ) error {
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
return toJSONError ( errServerNotInitialized )
}
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
buckets , err := objectAPI . ListBuckets ( )
if err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err )
}
for _ , bucket := range buckets {
// List all buckets which are not private.
@ -222,12 +223,12 @@ type WebObjectInfo struct {
func ( web * webAPIHandlers ) ListObjects ( r * http . Request , args * ListObjectsArgs , reply * ListObjectsRep ) error {
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
return toJSONError ( errServerNotInitialized )
}
marker := ""
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
marker := ""
for {
lo , err := objectAPI . ListObjects ( args . BucketName , args . Prefix , marker , "/" , 1000 )
if err != nil {
@ -266,10 +267,10 @@ type RemoveObjectArgs struct {
func ( web * webAPIHandlers ) RemoveObject ( r * http . Request , args * RemoveObjectArgs , reply * WebGenericRep ) error {
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
return toJSONError ( errServerNotInitialized )
}
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
if err := objectAPI . DeleteObject ( args . BucketName , args . ObjectName ) ; err != nil {
if isErrObjectNotFound ( err ) {
@ -277,7 +278,7 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs,
reply . UIVersion = miniobrowser . UIVersion
return nil
}
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err , args . BucketName , args . ObjectName )
}
// Notify object deleted event.
@ -310,18 +311,18 @@ type LoginRep struct {
// Login - user login handler.
func ( web * webAPIHandlers ) Login ( r * http . Request , args * LoginArgs , reply * LoginRep ) error {
jwt , err := newJWT ( defaultJWTExpiry )
jwt , err := newJWT ( defaultJWTExpiry , serverConfig . GetCredential ( ) )
if err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err )
}
if err = jwt . Authenticate ( args . Username , args . Password ) ; err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err )
}
token , err := jwt . GenerateToken ( args . Username )
if err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err )
}
reply . Token = token
reply . UIVersion = miniobrowser . UIVersion
@ -337,7 +338,7 @@ type GenerateAuthReply struct {
func ( web webAPIHandlers ) GenerateAuth ( r * http . Request , args * WebGenericArgs , reply * GenerateAuthReply ) error {
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
cred := mustGenAccessKeys ( )
reply . AccessKey = cred . AccessKeyID
@ -362,34 +363,46 @@ type SetAuthReply struct {
// 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 : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
if ! isValidAccessKey ( args . AccessKey ) {
return & json2 . Error { Message : errInvalidAccessKeyLength . Error ( ) }
// Initialize jwt with the new access keys, fail if not possible.
jwt , err := newJWT ( defaultJWTExpiry , credential {
AccessKeyID : args . AccessKey ,
SecretAccessKey : args . SecretKey ,
} ) // JWT Expiry set to 24Hrs.
if err != nil {
return toJSONError ( err )
}
if ! isValidSecretKey ( args . SecretKey ) {
return & json2 . Error { Message : errInvalidSecretKeyLength . Error ( ) }
// Authenticate the secret key properly.
if err = jwt . Authenticate ( args . AccessKey , args . SecretKey ) ; err != nil {
return toJSONError ( err )
}
cred := credential { args . AccessKey , args . SecretKey }
unexpErrsMsg := "ALERT: Unexpected error(s) happened - please check the server logs."
unexpErrsMsg := "Unexpected error(s) occurred - please check minio 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." ,
"We gave up due to: '%s', but there were more errors. Please check minio server logs." ,
errMsg . Error ( ) ,
)
var err * json2 . Error
if moreErrors {
return & json2 . Error { Message : msg }
err = toJSONError ( errors . New ( msg ) )
} else {
err = toJSONError ( errMsg )
}
return & json2 . Error { Message : errMsg . Error ( ) }
return err
}
cred := credential { args . AccessKey , args . SecretKey }
// Notify all other Minio peers to update credentials
errsMap := updateCredsOnPeers ( cred )
// Update local credentials
serverConfig . SetCredential ( cred )
if err : = serverConfig . Save ( ) ; err != nil {
if err = serverConfig . Save ( ) ; err != nil {
errsMap [ globalMinioAddr ] = err
}
@ -407,7 +420,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
// 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 }
return toJSONError ( errors . New ( unexpErrsMsg ) )
}
// Did we have peer errors?
@ -416,16 +429,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
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 )
}
// Generate a JWT token.
token , err := jwt . GenerateToken ( args . AccessKey )
if err != nil {
return gaveUpMsg ( err , moreErrors )
@ -446,7 +450,7 @@ type GetAuthReply struct {
// 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 : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
creds := serverConfig . GetCredential ( )
reply . AccessKey = creds . AccessKeyID
@ -511,7 +515,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
object := vars [ "object" ]
tokenStr := r . URL . Query ( ) . Get ( "token" )
jwt , err := newJWT ( defaultJWTExpiry ) // Expiry set to 24Hrs.
jwt , err := newJWT ( defaultJWTExpiry , serverConfig . GetCredential ( ) ) // Expiry set to 24Hrs.
if err != nil {
errorIf ( err , "error in getting new JWT" )
return
@ -543,50 +547,6 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
}
}
// writeWebErrorResponse - set HTTP status code and write error description to the body.
func writeWebErrorResponse ( w http . ResponseWriter , err error ) {
if err == errAuthentication {
w . WriteHeader ( http . StatusForbidden )
w . Write ( [ ] byte ( err . Error ( ) ) )
return
}
if err == errServerNotInitialized {
w . WriteHeader ( http . StatusServiceUnavailable )
w . Write ( [ ] byte ( err . Error ( ) ) )
return
}
// Convert error type to api error code.
var apiErrCode APIErrorCode
switch err . ( type ) {
case StorageFull :
apiErrCode = ErrStorageFull
case BucketNotFound :
apiErrCode = ErrNoSuchBucket
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
default :
apiErrCode = ErrInternalError
}
apiErr := getAPIError ( apiErrCode )
w . WriteHeader ( apiErr . HTTPStatusCode )
w . Write ( [ ] byte ( apiErr . Description ) )
}
// GetBucketPolicyArgs - get bucket policy args.
type GetBucketPolicyArgs struct {
BucketName string ` json:"bucketName" `
@ -627,16 +587,16 @@ func readBucketAccessPolicy(objAPI ObjectLayer, bucketName string) (policy.Bucke
func ( web * webAPIHandlers ) GetBucketPolicy ( r * http . Request , args * GetBucketPolicyArgs , reply * GetBucketPolicyRep ) error {
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
return toJSONError ( errServerNotInitialized )
}
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
policyInfo , err := readBucketAccessPolicy ( objectAPI , args . BucketName )
if err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err , args . BucketName )
}
reply . UIVersion = miniobrowser . UIVersion
@ -666,16 +626,16 @@ type ListAllBucketPoliciesRep struct {
func ( web * webAPIHandlers ) ListAllBucketPolicies ( r * http . Request , args * ListAllBucketPoliciesArgs , reply * ListAllBucketPoliciesRep ) error {
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
return toJSONError ( errServerNotInitialized )
}
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
policyInfo , err := readBucketAccessPolicy ( objectAPI , args . BucketName )
if err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err , args . BucketName )
}
reply . UIVersion = miniobrowser . UIVersion
@ -699,33 +659,36 @@ type SetBucketPolicyArgs struct {
func ( web * webAPIHandlers ) SetBucketPolicy ( r * http . Request , args * SetBucketPolicyArgs , reply * WebGenericRep ) error {
objectAPI := web . ObjectAPI ( )
if objectAPI == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
return toJSONError ( errServerNotInitialized )
}
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
bucketP := policy . BucketPolicy ( args . Policy )
if ! bucketP . IsValidBucketPolicy ( ) {
return & json2 . Error { Message : "Invalid policy type " + args . Policy }
return & json2 . Error {
Message : "Invalid policy type " + args . Policy ,
}
}
policyInfo , err := readBucketAccessPolicy ( objectAPI , args . BucketName )
if err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err , args . BucketName )
}
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 ( ) }
err = persistAndNotifyBucketPolicyChange ( args . BucketName , policyChange { true , nil } , objectAPI )
if err != nil {
return toJSONError ( err , args . BucketName )
}
reply . UIVersion = miniobrowser . UIVersion
return nil
}
data , err := json . Marshal ( policyInfo )
if err != nil {
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err )
}
// Parse bucket policy.
@ -733,18 +696,19 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
err = parseBucketPolicy ( bytes . NewReader ( data ) , policy )
if err != nil {
errorIf ( err , "Unable to parse bucket policy." )
return & json2 . Error { Message : err . Error ( ) }
return toJSONError ( err )
}
// Parse check bucket policy.
if s3Error := checkBucketPolicyResources ( args . BucketName , policy ) ; s3Error != ErrNone {
return & json2 . Error { Message : getAPIError ( s3Error ) . Description }
apiErr := getAPIError ( s3Error )
return toJSONError ( errors . New ( apiErr . Description ) , args . BucketName )
}
// 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 ( ) }
return toJSONError ( err , args . BucketName )
}
reply . UIVersion = miniobrowser . UIVersion
return nil
@ -760,6 +724,9 @@ type PresignedGetArgs struct {
// Object name to be presigned.
ObjectName string ` json:"object" `
// Expiry in seconds.
Expiry int64 ` json:"expiry" `
}
// PresignedGetRep - presigned-get URL reply.
@ -771,22 +738,22 @@ type PresignedGetRep struct {
// PresignedGET - returns presigned-Get url.
func ( web * webAPIHandlers ) PresignedGet ( r * http . Request , args * PresignedGetArgs , reply * PresignedGetRep ) error {
if web . ObjectAPI ( ) == nil {
return & json2 . Error { Message : errServerNotInitialized . Error ( ) }
}
if ! isJWTReqAuthenticated ( r ) {
return & json2 . Error { Message : errAuthentication . Error ( ) }
return toJSONError ( errAuthentication )
}
if args . BucketName == "" || args . ObjectName == "" {
return & json2 . Error { Message : "Bucket, Object are mandatory arguments." }
return & json2 . Error {
Message : "Bucket and Object are mandatory arguments." ,
}
}
reply . UIVersion = miniobrowser . UIVersion
reply . URL = presignedGet ( args . HostName , args . BucketName , args . ObjectName )
reply . URL = presignedGet ( args . HostName , args . BucketName , args . ObjectName , args . Expiry )
return nil
}
// Returns presigned url for GET method.
func presignedGet ( host , bucket , object string ) string {
func presignedGet ( host , bucket , object string , expiry int64 ) string {
cred := serverConfig . GetCredential ( )
region := serverConfig . GetRegion ( )
@ -797,11 +764,15 @@ func presignedGet(host, bucket, object string) string {
dateStr := date . Format ( iso8601Format )
credential := fmt . Sprintf ( "%s/%s" , accessKey , getScope ( date , region ) )
var expiryStr = "604800" // Default set to be expire in 7days.
if expiry < 604800 && expiry > 0 {
expiryStr = strconv . FormatInt ( expiry , 10 )
}
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-Expires=" + expiryStr ,
"X-Amz-SignedHeaders=host" ,
} , "&" )
@ -818,3 +789,93 @@ func presignedGet(host, bucket, object string) string {
// Construct the final presigned URL.
return host + path + "?" + query + "&" + "X-Amz-Signature=" + signature
}
// 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 {
Message : fmt . Sprintf ( "Bucket Name %s is invalid. Lowercase letters, period and numerals are the only allowed characters." ,
params [ 0 ] ) ,
}
}
// 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 ( ) ,
}
}
if err == errServerNotInitialized {
return APIError {
Code : "XMinioServerNotInitialized" ,
HTTPStatusCode : http . StatusServiceUnavailable ,
Description : err . Error ( ) ,
}
}
// Convert error type to api error code.
var apiErrCode APIErrorCode
switch err . ( type ) {
case StorageFull :
apiErrCode = ErrStorageFull
case BucketNotFound :
apiErrCode = ErrNoSuchBucket
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
default :
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 ) )
}