From d9674f752445acbeb0e053f471aa2c191a84c321 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 2 Nov 2016 14:45:11 -0700 Subject: [PATCH] Improve coverage of web-handlers.go (#3157) This patch additionally relaxes the requirement for accesskeys to be in a regexy set of values. Fixes #3063 --- cmd/access-key.go | 13 +-- cmd/main.go | 4 +- cmd/signature-jwt.go | 27 ++++--- cmd/signature-jwt_test.go | 34 ++++---- cmd/signature-v4-parser.go | 2 +- cmd/signature-v4-parser_test.go | 4 +- cmd/web-handlers.go | 135 +++++++++++++++++--------------- cmd/web-handlers_test.go | 120 ++++++++++++++++++++++------ 8 files changed, 211 insertions(+), 128 deletions(-) diff --git a/cmd/access-key.go b/cmd/access-key.go index 1d23aec0e..8bef177fd 100644 --- a/cmd/access-key.go +++ b/cmd/access-key.go @@ -19,7 +19,6 @@ package cmd import ( "crypto/rand" "encoding/base64" - "regexp" ) // credential container for access and secret keys. @@ -33,11 +32,15 @@ const ( minioSecretID = 40 ) -// isValidSecretKey - validate secret key. -var isValidSecretKey = regexp.MustCompile(`^.{8,40}$`) +// isValidAccessKey - validate access key for right length. +func isValidAccessKey(accessKey string) bool { + return len(accessKey) >= 5 && len(accessKey) <= 20 +} -// isValidAccessKey - validate access key. -var isValidAccessKey = regexp.MustCompile(`^[a-zA-Z0-9\\-\\.\\_\\~]{5,20}$`) +// isValidSecretKey - validate secret key for right length. +func isValidSecretKey(secretKey string) bool { + return len(secretKey) >= 8 && len(secretKey) <= 40 +} // mustGenAccessKeys - must generate access credentials. func mustGenAccessKeys() (creds credential) { diff --git a/cmd/main.go b/cmd/main.go index 7d5769b3f..3e5c50ac0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -184,10 +184,10 @@ func Main() { SecretAccessKey: secretKey, }) } - if !isValidAccessKey.MatchString(serverConfig.GetCredential().AccessKeyID) { + if !isValidAccessKey(serverConfig.GetCredential().AccessKeyID) { fatalIf(errInvalidArgument, "Invalid access key. Accept only a string starting with a alphabetic and containing from 5 to 20 characters.") } - if !isValidSecretKey.MatchString(serverConfig.GetCredential().SecretAccessKey) { + if !isValidSecretKey(serverConfig.GetCredential().SecretAccessKey) { fatalIf(errInvalidArgument, "Invalid secret key. Accept only a string containing from 8 to 40 characters.") } diff --git a/cmd/signature-jwt.go b/cmd/signature-jwt.go index 9ec5153b9..a61f07280 100644 --- a/cmd/signature-jwt.go +++ b/cmd/signature-jwt.go @@ -44,28 +44,30 @@ const ( // newJWT - returns new JWT object. func newJWT(expiry time.Duration) (*JWT, error) { if serverConfig == nil { - return nil, errors.New("Server not initialized") + return nil, errServerNotInitialized } // Save access, secret keys. cred := serverConfig.GetCredential() - if !isValidAccessKey.MatchString(cred.AccessKeyID) { - return nil, errors.New("Invalid access key") + if !isValidAccessKey(cred.AccessKeyID) { + return nil, errInvalidAccessKeyLength } - if !isValidSecretKey.MatchString(cred.SecretAccessKey) { - return nil, errors.New("Invalid secret key") + if !isValidSecretKey(cred.SecretAccessKey) { + return nil, errInvalidSecretKeyLength } - return &JWT{cred, expiry}, nil } +var errInvalidAccessKeyLength = errors.New("Invalid access key, access key should be 5 to 20 characters in length.") +var errInvalidSecretKeyLength = errors.New("Invalid secret key, secret key should be 8 to 40 characters in length.") + // GenerateToken - generates a new Json Web Token based on the incoming access key. func (jwt *JWT) GenerateToken(accessKey string) (string, error) { // Trim spaces. accessKey = strings.TrimSpace(accessKey) - if !isValidAccessKey.MatchString(accessKey) { - return "", errors.New("Invalid access key") + if !isValidAccessKey(accessKey) { + return "", errInvalidAccessKeyLength } tUTCNow := time.Now().UTC() @@ -79,7 +81,6 @@ func (jwt *JWT) GenerateToken(accessKey string) (string, error) { } var errInvalidAccessKeyID = errors.New("The access key ID you provided does not exist in our records.") - var errAuthentication = errors.New("Authentication failed, check your access credentials.") // Authenticate - authenticates incoming access key and secret key. @@ -87,11 +88,11 @@ func (jwt *JWT) Authenticate(accessKey, secretKey string) error { // Trim spaces. accessKey = strings.TrimSpace(accessKey) - if !isValidAccessKey.MatchString(accessKey) { - return errors.New("Invalid access key") + if !isValidAccessKey(accessKey) { + return errInvalidAccessKeyLength } - if !isValidSecretKey.MatchString(secretKey) { - return errors.New("Invalid secret key") + if !isValidSecretKey(secretKey) { + return errInvalidSecretKeyLength } if accessKey != jwt.AccessKeyID { diff --git a/cmd/signature-jwt_test.go b/cmd/signature-jwt_test.go index 3e72d5c52..bf1df8fb1 100644 --- a/cmd/signature-jwt_test.go +++ b/cmd/signature-jwt_test.go @@ -17,7 +17,6 @@ package cmd import ( - "fmt" "io/ioutil" "os" "path" @@ -72,25 +71,23 @@ func TestNewJWT(t *testing.T) { expectedErr error }{ // Test non-existent config directory. - {path.Join(path1, "non-existent-dir"), false, nil, fmt.Errorf("Server not initialized")}, + {path.Join(path1, "non-existent-dir"), false, nil, errServerNotInitialized}, // Test empty config directory. - {path2, false, nil, fmt.Errorf("Server not initialized")}, + {path2, false, nil, errServerNotInitialized}, // Test empty config file. - {path3, false, nil, fmt.Errorf("Server not initialized")}, + {path3, false, nil, errServerNotInitialized}, // Test initialized config file. {path4, true, nil, nil}, // Test to read already created config file. {path4, false, nil, nil}, // Access key is too small. - {path4, false, &credential{"user", "pass"}, fmt.Errorf("Invalid access key")}, + {path4, false, &credential{"user", "pass"}, errInvalidAccessKeyLength}, // Access key is too long. - {path4, false, &credential{"user12345678901234567", "pass"}, fmt.Errorf("Invalid access key")}, - // Access key contains unsupported characters. - {path4, false, &credential{"!@#$%^&*()", "pass"}, fmt.Errorf("Invalid access key")}, + {path4, false, &credential{"user12345678901234567", "pass"}, errInvalidAccessKeyLength}, // Secret key is too small. - {path4, false, &credential{"myuser", "pass"}, fmt.Errorf("Invalid secret key")}, + {path4, false, &credential{"myuser", "pass"}, errInvalidSecretKeyLength}, // Secret key is too long. - {path4, false, &credential{"myuser", "pass1234567890123456789012345678901234567"}, fmt.Errorf("Invalid secret key")}, + {path4, false, &credential{"myuser", "pass1234567890123456789012345678901234567"}, errInvalidSecretKeyLength}, // Valid access/secret keys. {path4, false, &credential{"myuser", "mypassword"}, nil}, } @@ -114,7 +111,6 @@ func TestNewJWT(t *testing.T) { if err == nil { t.Fatalf("%+v: expected: %s, got: ", testCase, testCase.expectedErr) } - if testCase.expectedErr.Error() != err.Error() { t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err) } @@ -143,11 +139,11 @@ func TestGenerateToken(t *testing.T) { expectedErr error }{ // Access key is too small. - {"user", fmt.Errorf("Invalid access key")}, + {"user", errInvalidAccessKeyLength}, // Access key is too long. - {"user12345678901234567", fmt.Errorf("Invalid access key")}, + {"user12345678901234567", errInvalidAccessKeyLength}, // Access key contains unsupported characters. - {"!@#$%^&*()", fmt.Errorf("Invalid access key")}, + {"!@#$", errInvalidAccessKeyLength}, // Valid access key. {"myuser", nil}, // Valid access key with leading/trailing spaces. @@ -191,15 +187,15 @@ func TestAuthenticate(t *testing.T) { expectedErr error }{ // Access key too small. - {"user", "pass", fmt.Errorf("Invalid access key")}, + {"user", "pass", errInvalidAccessKeyLength}, // Access key too long. - {"user12345678901234567", "pass", fmt.Errorf("Invalid access key")}, + {"user12345678901234567", "pass", errInvalidAccessKeyLength}, // Access key contains unsuppported characters. - {"!@#$%^&*()", "pass", fmt.Errorf("Invalid access key")}, + {"!@#$", "pass", errInvalidAccessKeyLength}, // Secret key too small. - {"myuser", "pass", fmt.Errorf("Invalid secret key")}, + {"myuser", "pass", errInvalidSecretKeyLength}, // Secret key too long. - {"myuser", "pass1234567890123456789012345678901234567", fmt.Errorf("Invalid secret key")}, + {"myuser", "pass1234567890123456789012345678901234567", errInvalidSecretKeyLength}, // Authentication error. {"myuser", "mypassword", errInvalidAccessKeyID}, // Authentication error. diff --git a/cmd/signature-v4-parser.go b/cmd/signature-v4-parser.go index a90945bc9..8f984b900 100644 --- a/cmd/signature-v4-parser.go +++ b/cmd/signature-v4-parser.go @@ -47,7 +47,7 @@ func parseCredentialHeader(credElement string) (credentialHeader, APIErrorCode) if len(credElements) != 5 { return credentialHeader{}, ErrCredMalformed } - if !isValidAccessKey.MatchString(credElements[0]) { + if !isValidAccessKey(credElements[0]) { return credentialHeader{}, ErrInvalidAccessKeyID } // Save access key id. diff --git a/cmd/signature-v4-parser_test.go b/cmd/signature-v4-parser_test.go index 94db8fa20..975a5b6cf 100644 --- a/cmd/signature-v4-parser_test.go +++ b/cmd/signature-v4-parser_test.go @@ -116,10 +116,10 @@ func TestParseCredentialHeader(t *testing.T) { expectedErrCode: ErrCredMalformed, }, // Test Case - 4. - // Test case with malformed AccessKey. + // Test case with AccessKey of length 4. { inputCredentialStr: generateCredentialStr( - "^#@..!23", + "^#@.", time.Now().UTC().Format(yyyymmdd), "ABCD", "ABCD", diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index fb2e45681..7e6eaded0 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -19,7 +19,6 @@ package cmd import ( "bytes" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" @@ -86,7 +85,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: "Unauthorized request"} + return &json2.Error{Message: errAuthentication.Error()} } host, err := os.Hostname() if err != nil { @@ -122,12 +121,12 @@ type StorageInfoRep struct { // 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"} + return &json2.Error{Message: errServerNotInitialized.Error()} + } + if !isJWTReqAuthenticated(r) { + return &json2.Error{Message: errAuthentication.Error()} } reply.StorageInfo = objectAPI.StorageInfo() reply.UIVersion = miniobrowser.UIVersion @@ -141,12 +140,12 @@ type MakeBucketArgs struct { // 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"} - } objectAPI := web.ObjectAPI() if objectAPI == nil { - return &json2.Error{Message: "Server not initialized"} + return &json2.Error{Message: errServerNotInitialized.Error()} + } + if !isJWTReqAuthenticated(r) { + return &json2.Error{Message: errAuthentication.Error()} } if err := objectAPI.MakeBucket(args.BucketName); err != nil { return &json2.Error{Message: err.Error()} @@ -171,12 +170,12 @@ type WebBucketInfo struct { // 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"} + return &json2.Error{Message: errServerNotInitialized.Error()} + } + if !isJWTReqAuthenticated(r) { + return &json2.Error{Message: errAuthentication.Error()} } buckets, err := objectAPI.ListBuckets() if err != nil { @@ -221,15 +220,15 @@ type WebObjectInfo struct { // ListObjects - list objects api. func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error { + objectAPI := web.ObjectAPI() + if objectAPI == nil { + return &json2.Error{Message: errServerNotInitialized.Error()} + } marker := "" if !isJWTReqAuthenticated(r) { - return &json2.Error{Message: "Unauthorized request"} + return &json2.Error{Message: errAuthentication.Error()} } 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()} @@ -264,13 +263,12 @@ type RemoveObjectArgs struct { // 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"} + return &json2.Error{Message: errServerNotInitialized.Error()} + } + if !isJWTReqAuthenticated(r) { + return &json2.Error{Message: errAuthentication.Error()} } if err := objectAPI.DeleteObject(args.BucketName, args.ObjectName); err != nil { return &json2.Error{Message: err.Error()} @@ -320,7 +318,7 @@ type GenerateAuthReply struct { func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, reply *GenerateAuthReply) error { if !isJWTReqAuthenticated(r) { - return &json2.Error{Message: "Unauthorized request"} + return &json2.Error{Message: errAuthentication.Error()} } cred := mustGenAccessKeys() reply.AccessKey = cred.AccessKeyID @@ -345,13 +343,13 @@ 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: "Unauthorized request"} + return &json2.Error{Message: errAuthentication.Error()} } - if !isValidAccessKey.MatchString(args.AccessKey) { - return &json2.Error{Message: "Invalid Access Key"} + if !isValidAccessKey(args.AccessKey) { + return &json2.Error{Message: errInvalidAccessKeyLength.Error()} } - if !isValidSecretKey.MatchString(args.SecretKey) { - return &json2.Error{Message: "Invalid Secret Key"} + if !isValidSecretKey(args.SecretKey) { + return &json2.Error{Message: errInvalidSecretKeyLength.Error()} } cred := credential{args.AccessKey, args.SecretKey} @@ -430,7 +428,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: "Unauthorized request"} + return &json2.Error{Message: errAuthentication.Error()} } creds := serverConfig.GetCredential() reply.AccessKey = creds.AccessKeyID @@ -441,8 +439,14 @@ func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply // Upload - file upload handler. func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { + objectAPI := web.ObjectAPI() + if objectAPI == nil { + writeWebErrorResponse(w, errServerNotInitialized) + return + } + if !isJWTReqAuthenticated(r) { - writeWebErrorResponse(w, errInvalidToken) + writeWebErrorResponse(w, errAuthentication) return } vars := mux.Vars(r) @@ -452,11 +456,6 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { // 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) @@ -483,6 +482,12 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { // Download - file download handler. func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { + objectAPI := web.ObjectAPI() + if objectAPI == nil { + writeWebErrorResponse(w, errServerNotInitialized) + return + } + vars := mux.Vars(r) bucket := vars["bucket"] object := vars["object"] @@ -501,17 +506,12 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { return []byte(jwt.SecretAccessKey), nil }) if e != nil || !token.Valid { - writeWebErrorResponse(w, errInvalidToken) + writeWebErrorResponse(w, errAuthentication) return } // 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) if err != nil { writeWebErrorResponse(w, err) @@ -527,12 +527,17 @@ 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) { - // Handle invalid token as a special case. - if err == errInvalidToken { + 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) { @@ -602,14 +607,15 @@ func readBucketAccessPolicy(objAPI ObjectLayer, bucketName string) (policy.Bucke // 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"} + return &json2.Error{Message: errServerNotInitialized.Error()} } + + if !isJWTReqAuthenticated(r) { + return &json2.Error{Message: errAuthentication.Error()} + } + policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName) if err != nil { return &json2.Error{Message: err.Error()} @@ -640,14 +646,15 @@ type ListAllBucketPoliciesRep struct { // 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"} + return &json2.Error{Message: errServerNotInitialized.Error()} + } + + if !isJWTReqAuthenticated(r) { + return &json2.Error{Message: errAuthentication.Error()} } + policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName) if err != nil { return &json2.Error{Message: err.Error()} @@ -672,17 +679,18 @@ type SetBucketPolicyArgs struct { // 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"} + return &json2.Error{Message: errServerNotInitialized.Error()} + } + + if !isJWTReqAuthenticated(r) { + return &json2.Error{Message: errAuthentication.Error()} } bucketP := policy.BucketPolicy(args.Policy) if !bucketP.IsValidBucketPolicy() { - return &json2.Error{Message: "Invalid policy " + args.Policy} + return &json2.Error{Message: "Invalid policy type " + args.Policy} } policyInfo, err := readBucketAccessPolicy(objectAPI, args.BucketName) @@ -745,11 +753,14 @@ 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: "Unauthorized request"} + return &json2.Error{Message: errAuthentication.Error()} } if args.BucketName == "" || args.ObjectName == "" { - return &json2.Error{Message: "Required arguments: Host, Bucket, Object"} + return &json2.Error{Message: "Bucket, Object are mandatory arguments."} } reply.UIVersion = miniobrowser.UIVersion reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName) diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 8a85cc88b..bce8eb36a 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -31,6 +31,69 @@ import ( "github.com/minio/minio-go/pkg/set" ) +// Tests private function writeWebErrorResponse. +func TestWriteWebErrorResponse(t *testing.T) { + var buffer bytes.Buffer + testCases := []struct { + webErr error + apiErrCode APIErrorCode + }{ + // List of various errors and their corresponding API errors. + { + webErr: StorageFull{}, + apiErrCode: ErrStorageFull, + }, + { + webErr: BucketNotFound{}, + apiErrCode: ErrNoSuchBucket, + }, + { + webErr: BucketNameInvalid{}, + apiErrCode: ErrInvalidBucketName, + }, + { + webErr: BadDigest{}, + apiErrCode: ErrBadDigest, + }, + { + webErr: IncompleteBody{}, + apiErrCode: ErrIncompleteBody, + }, + { + webErr: ObjectExistsAsDirectory{}, + apiErrCode: ErrObjectExistsAsDirectory, + }, + { + webErr: ObjectNotFound{}, + apiErrCode: ErrNoSuchKey, + }, + { + webErr: ObjectNameInvalid{}, + apiErrCode: ErrNoSuchKey, + }, + { + webErr: InsufficientWriteQuorum{}, + apiErrCode: ErrWriteQuorum, + }, + { + webErr: InsufficientReadQuorum{}, + apiErrCode: ErrReadQuorum, + }, + } + + // Validate all the test cases. + for i, testCase := range testCases { + writeWebErrorResponse(newFlushWriter(&buffer), testCase.webErr) + desc := getAPIError(testCase.apiErrCode).Description + recvDesc := buffer.Bytes() + // Check if the written desc is same as the one expected. + if !bytes.Equal(recvDesc, []byte(desc)) { + t.Errorf("Test %d: Unexpected response, expecting %s, got %s", i+1, desc, string(buffer.Bytes())) + } + buffer.Reset() + } +} + // Authenticate and get JWT token - will be called before every webrpc handler invocation func getWebRPCToken(apiRouter http.Handler, accessKey, secretKey string) (token string, err error) { rec := httptest.NewRecorder() @@ -823,8 +886,8 @@ func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrH if err == nil { t.Fatalf("Failed, %v", err) } - if err.Error() != "Required arguments: Host, Bucket, Object" { - t.Fatalf("Unexpected, expected `Required arguments: Host, Bucket, Object`, got %s", err) + if err.Error() != "Bucket, Object are mandatory arguments." { + t.Fatalf("Unexpected, expected `Bucket, Object are mandatory arguments`, got %s", err) } } @@ -1118,8 +1181,13 @@ func TestWebCheckAuthorization(t *testing.T) { rec := httptest.NewRecorder() // Check if web rpc calls return unauthorized request with an incorrect token - webRPCs := []string{"ServerInfo", "StorageInfo", "MakeBucket", "ListBuckets", "ListObjects", "RemoveObject", "GenerateAuth", - "SetAuth", "GetAuth", "GetBucketPolicy", "SetBucketPolicy"} + webRPCs := []string{ + "ServerInfo", "StorageInfo", "MakeBucket", + "ListBuckets", "ListObjects", "RemoveObject", + "GenerateAuth", "SetAuth", "GetAuth", + "GetBucketPolicy", "SetBucketPolicy", "ListAllBucketPolicies", + "PresignedGet", + } for _, rpcCall := range webRPCs { args := &GenericArgs{} reply := &WebGenericRep{} @@ -1135,26 +1203,28 @@ func TestWebCheckAuthorization(t *testing.T) { if err == nil { t.Fatalf("Test %s: Should fail", rpcCall) } else { - if !strings.Contains(err.Error(), "Unauthorized request") { + if !strings.Contains(err.Error(), errAuthentication.Error()) { t.Fatalf("Test %s: should fail with Unauthorized request. Found error: %v", rpcCall, err) } } } + rec = httptest.NewRecorder() // Test authorization of Web.Download req, err := http.NewRequest("GET", "/minio/download/bucket/object?token=wrongauth", nil) if err != nil { t.Fatalf("Cannot create upload 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) + if rec.Code != http.StatusForbidden { + t.Fatalf("Expected the response status to be 403, but instead found `%d`", rec.Code) } resp := string(rec.Body.Bytes()) - if !strings.Contains(resp, "Invalid token") { - t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + if !strings.EqualFold(resp, errAuthentication.Error()) { + t.Fatalf("Unexpected error message, expected: %s, found: `%s`", errAuthentication, resp) } + rec = httptest.NewRecorder() // Test authorization of Web.Upload content := []byte("temporary file's content") req, err = http.NewRequest("PUT", "/minio/upload/bucket/object", nil) @@ -1167,12 +1237,12 @@ func TestWebCheckAuthorization(t *testing.T) { t.Fatalf("Cannot create upload 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) + if rec.Code != http.StatusForbidden { + t.Fatalf("Expected the response status to be 403, but instead found `%d`", rec.Code) } resp = string(rec.Body.Bytes()) - if !strings.Contains(resp, "Invalid token") { - t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + if !strings.EqualFold(resp, errAuthentication.Error()) { + t.Fatalf("Unexpected error message, expected: `%s`, found: `%s`", errAuthentication, resp) } } @@ -1201,7 +1271,7 @@ func TestWebObjectLayerNotReady(t *testing.T) { // Check if web rpc calls return Server not initialized. ServerInfo, GenerateAuth, // SetAuth and GetAuth are not concerned webRPCs := []string{"StorageInfo", "MakeBucket", "ListBuckets", "ListObjects", "RemoveObject", - "GetBucketPolicy", "SetBucketPolicy"} + "GetBucketPolicy", "SetBucketPolicy", "ListAllBucketPolicies"} for _, rpcCall := range webRPCs { args := &GenericArgs{} reply := &WebGenericRep{} @@ -1217,26 +1287,28 @@ func TestWebObjectLayerNotReady(t *testing.T) { if err == nil { t.Fatalf("Test %s: Should fail", rpcCall) } else { - if !strings.Contains(err.Error(), "Server not initialized") { - t.Fatalf("Test %s: should fail with Unauthorized request. Found error: %v", rpcCall, err) + if !strings.EqualFold(err.Error(), errServerNotInitialized.Error()) { + t.Fatalf("Test %s: should fail with %s Found error: %v", rpcCall, errServerNotInitialized, err) } } } + rec = httptest.NewRecorder() // Test authorization of Web.Download req, err := http.NewRequest("GET", "/minio/download/bucket/object?token="+authorization, nil) if err != nil { t.Fatalf("Cannot create upload 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) + if rec.Code != http.StatusServiceUnavailable { + t.Fatalf("Expected the response status to be 503, but instead found `%d`", rec.Code) } resp := string(rec.Body.Bytes()) - if !strings.Contains(resp, "We encountered an internal error, please try again.") { - t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + if !strings.EqualFold(resp, errServerNotInitialized.Error()) { + t.Fatalf("Unexpected error message, expected: `%s`, found: `%s`", errServerNotInitialized, resp) } + rec = httptest.NewRecorder() // Test authorization of Web.Upload content := []byte("temporary file's content") req, err = http.NewRequest("PUT", "/minio/upload/bucket/object", nil) @@ -1249,12 +1321,12 @@ func TestWebObjectLayerNotReady(t *testing.T) { t.Fatalf("Cannot create upload 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) + if rec.Code != http.StatusServiceUnavailable { + t.Fatalf("Expected the response status to be 503, but instead found `%d`", rec.Code) } resp = string(rec.Body.Bytes()) - if !strings.Contains(resp, "We encountered an internal error, please try again.") { - t.Fatalf("Unexpected error message, expected: `Invalid token`, found: `%s`", resp) + if !strings.EqualFold(resp, errServerNotInitialized.Error()) { + t.Fatalf("Unexpected error message, expected: `%s`, found: `%s`", errServerNotInitialized, resp) } }