web: Add more data for jsonrpc responses. (#3296)

This change adds more richer error response
for JSON-RPC by interpreting object layer
errors to corresponding meaningful errors
for the web browser.

```go
&json2.Error{
   Message: "Bucket Name Invalid, Only lowercase letters, full stops, and numbers are allowed.",
}
```

Additionally this patch also allows PresignedGetObject()
to take expiry parameter to have variable expiry.
master
Harshavardhana 8 years ago committed by GitHub
parent 4098025c11
commit dd93f808c8
  1. 2
      cmd/auth-rpc-client.go
  2. 2
      cmd/browser-peer-rpc.go
  3. 2
      cmd/lock-rpc-server.go
  4. 2
      cmd/lock-rpc-server_test.go
  5. 2
      cmd/s3-peer-rpc-handlers.go
  6. 8
      cmd/signature-jwt.go
  7. 17
      cmd/signature-jwt_test.go
  8. 2
      cmd/storage-rpc-server.go
  9. 2
      cmd/storage-rpc-server_test.go
  10. 287
      cmd/web-handlers.go
  11. 31
      cmd/web-handlers_test.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

@ -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
}

@ -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
}

@ -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)
}

@ -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
}

@ -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
}

@ -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: <nil>", 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)
}

@ -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
}

@ -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)
}

@ -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))
}

@ -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: <ERROR> %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 {

Loading…
Cancel
Save