diff --git a/cmd/auth-rpc-client.go b/cmd/auth-rpc-client.go index 5a79c9a3a..078d940b5 100644 --- a/cmd/auth-rpc-client.go +++ b/cmd/auth-rpc-client.go @@ -65,7 +65,7 @@ type RPCLoginReply struct { // Validates if incoming token is valid. func isRPCTokenValid(tokenStr string) bool { - jwt, err := newJWT(defaultInterNodeJWTExpiry) + jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential()) if err != nil { errorIf(err, "Unable to initialize JWT") return false diff --git a/cmd/browser-peer-rpc.go b/cmd/browser-peer-rpc.go index b83200ef6..f62ff80fe 100644 --- a/cmd/browser-peer-rpc.go +++ b/cmd/browser-peer-rpc.go @@ -25,7 +25,7 @@ import ( // Login handler implements JWT login token generator, which upon login request // along with username and password is generated. func (br *browserPeerAPIHandlers) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error { - jwt, err := newJWT(defaultInterNodeJWTExpiry) + jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential()) if err != nil { return err } diff --git a/cmd/lock-rpc-server.go b/cmd/lock-rpc-server.go index d3a8f370b..2b4a9e83c 100644 --- a/cmd/lock-rpc-server.go +++ b/cmd/lock-rpc-server.go @@ -127,7 +127,7 @@ func registerStorageLockers(mux *router.Router, lockServers []*lockServer) error // LoginHandler - handles LoginHandler RPC call. func (l *lockServer) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error { - jwt, err := newJWT(defaultInterNodeJWTExpiry) + jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential()) if err != nil { return err } diff --git a/cmd/lock-rpc-server_test.go b/cmd/lock-rpc-server_test.go index 5cc55c840..050192fc0 100644 --- a/cmd/lock-rpc-server_test.go +++ b/cmd/lock-rpc-server_test.go @@ -48,7 +48,7 @@ func createLockTestServer(t *testing.T) (string, *lockServer, string) { t.Fatalf("unable initialize config file, %s", err) } - jwt, err := newJWT(defaultJWTExpiry) + jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential()) if err != nil { t.Fatalf("unable to get new JWT, %s", err) } diff --git a/cmd/s3-peer-rpc-handlers.go b/cmd/s3-peer-rpc-handlers.go index c97e3c8a7..f4f992ebb 100644 --- a/cmd/s3-peer-rpc-handlers.go +++ b/cmd/s3-peer-rpc-handlers.go @@ -19,7 +19,7 @@ package cmd import "time" func (s3 *s3PeerAPIHandlers) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error { - jwt, err := newJWT(defaultInterNodeJWTExpiry) + jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential()) if err != nil { return err } diff --git a/cmd/signature-jwt.go b/cmd/signature-jwt.go index a4e0278f7..7866eef4e 100644 --- a/cmd/signature-jwt.go +++ b/cmd/signature-jwt.go @@ -42,13 +42,7 @@ const ( ) // newJWT - returns new JWT object. -func newJWT(expiry time.Duration) (*JWT, error) { - if serverConfig == nil { - return nil, errServerNotInitialized - } - - // Save access, secret keys. - cred := serverConfig.GetCredential() +func newJWT(expiry time.Duration, cred credential) (*JWT, error) { if !isValidAccessKey(cred.AccessKeyID) { return nil, errInvalidAccessKeyLength } diff --git a/cmd/signature-jwt_test.go b/cmd/signature-jwt_test.go index bf1df8fb1..5b0588e27 100644 --- a/cmd/signature-jwt_test.go +++ b/cmd/signature-jwt_test.go @@ -70,16 +70,10 @@ func TestNewJWT(t *testing.T) { cred *credential expectedErr error }{ - // Test non-existent config directory. - {path.Join(path1, "non-existent-dir"), false, nil, errServerNotInitialized}, - // Test empty config directory. - {path2, false, nil, errServerNotInitialized}, - // Test empty config file. - {path3, false, nil, errServerNotInitialized}, // Test initialized config file. {path4, true, nil, nil}, // Test to read already created config file. - {path4, false, nil, nil}, + {path4, true, nil, nil}, // Access key is too small. {path4, false, &credential{"user", "pass"}, errInvalidAccessKeyLength}, // Access key is too long. @@ -100,13 +94,10 @@ func TestNewJWT(t *testing.T) { t.Fatalf("unable initialize config file, %s", err) } } - if testCase.cred != nil { serverConfig.SetCredential(*testCase.cred) } - - _, err := newJWT(defaultJWTExpiry) - + _, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential()) if testCase.expectedErr != nil { if err == nil { t.Fatalf("%+v: expected: %s, got: ", testCase, testCase.expectedErr) @@ -128,7 +119,7 @@ func TestGenerateToken(t *testing.T) { } defer removeAll(testPath) - jwt, err := newJWT(defaultJWTExpiry) + jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential()) if err != nil { t.Fatalf("unable get new JWT, %s", err) } @@ -175,7 +166,7 @@ func TestAuthenticate(t *testing.T) { } defer removeAll(testPath) - jwt, err := newJWT(defaultJWTExpiry) + jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential()) if err != nil { t.Fatalf("unable get new JWT, %s", err) } diff --git a/cmd/storage-rpc-server.go b/cmd/storage-rpc-server.go index 7de7f67a3..ce20ed5f5 100644 --- a/cmd/storage-rpc-server.go +++ b/cmd/storage-rpc-server.go @@ -39,7 +39,7 @@ type storageServer struct { // Login - login handler. func (s *storageServer) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error { - jwt, err := newJWT(defaultInterNodeJWTExpiry) + jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential()) if err != nil { return err } diff --git a/cmd/storage-rpc-server_test.go b/cmd/storage-rpc-server_test.go index 13959427b..4dc47475c 100644 --- a/cmd/storage-rpc-server_test.go +++ b/cmd/storage-rpc-server_test.go @@ -40,7 +40,7 @@ func createTestStorageServer(t *testing.T) *testStorageRPCServer { t.Fatalf("unable initialize config file, %s", err) } - jwt, err := newJWT(defaultInterNodeJWTExpiry) + jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential()) if err != nil { t.Fatalf("unable to get new JWT, %s", err) } diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 467b63afd..5a4209a10 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -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) + } + if !isJWTReqAuthenticated(r) { + return toJSONError(errAuthentication) } marker := "" - if !isJWTReqAuthenticated(r) { - return &json2.Error{Message: errAuthentication.Error()} - } 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) + + } + + unexpErrsMsg := "Unexpected error(s) occurred - please check minio server logs." + gaveUpMsg := func(errMsg error, moreErrors bool) *json2.Error { + msg := fmt.Sprintf( + "We gave up due to: '%s', but there were more errors. Please check minio server logs.", + errMsg.Error(), + ) + var err *json2.Error + if moreErrors { + err = toJSONError(errors.New(msg)) + } else { + err = toJSONError(errMsg) + } + return err } 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 { + 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)) +} diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 83d6722a2..b421f3765 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -485,8 +485,8 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH data := bytes.Repeat([]byte("a"), objectSize) - _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}, "") - + _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), + map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}, "") if err != nil { t.Fatalf("Was not able to upload an object, %v", err) } @@ -505,6 +505,21 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH if err != nil { t.Fatalf("Failed, %v", err) } + + removeObjectRequest = RemoveObjectArgs{BucketName: bucketName, ObjectName: objectName} + removeObjectReply = &WebGenericRep{} + req, err = newTestWebRPCRequest("Web.RemoveObject", authorization, removeObjectRequest) + if err != nil { + t.Fatalf("Failed to create HTTP request: %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + err = getTestWebRPCResponse(rec, &removeObjectReply) + if err != nil { + t.Fatalf("Failed, %v", err) + } } // Wrapper for calling Generate Auth Handler @@ -585,6 +600,7 @@ func testSetAuthWebHandler(obj ObjectLayer, instanceType string, t TestErrHandle success bool }{ {"", "", false}, + {"1", "1", false}, {"azerty", "foooooooooooooo", true}, } @@ -826,6 +842,7 @@ func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrH HostName: "", BucketName: bucketName, ObjectName: objectName, + Expiry: 1000, } presignGetRep := &PresignedGetRep{} req, err := newTestWebRPCRequest("Web.PresignedGet", authorization, presignGetReq) @@ -885,8 +902,8 @@ func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrH if err == nil { t.Fatalf("Failed, %v", err) } - if err.Error() != "Bucket, Object are mandatory arguments." { - t.Fatalf("Unexpected, expected `Bucket, Object are mandatory arguments`, got %s", err) + if err.Error() != "Bucket and Object are mandatory arguments." { + t.Fatalf("Unexpected, expected `Bucket and Object are mandatory arguments`, got %s", err) } } @@ -1329,6 +1346,12 @@ func TestWebObjectLayerNotReady(t *testing.T) { // TestWebObjectLayerFaultyDisks - Test Web RPC responses with faulty disks func TestWebObjectLayerFaultyDisks(t *testing.T) { + root, err := newTestConfig("us-east-1") + if err != nil { + t.Fatal(err) + } + defer removeAll(root) + // Prepare XL backend obj, fsDirs, err := prepareXL() if err != nil {