mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
signature: Add legacy signature v2 support transparently. (#2811)
Add new tests as well.
This commit is contained in:
parent
9fb1c89f81
commit
5885ffc8ae
@ -42,12 +42,24 @@ func isRequestSignatureV4(r *http.Request) bool {
|
||||
return strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm)
|
||||
}
|
||||
|
||||
// Verify if request has AWS Signature Version '2'.
|
||||
func isRequestSignatureV2(r *http.Request) bool {
|
||||
return (!strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm) &&
|
||||
strings.HasPrefix(r.Header.Get("Authorization"), signV2Algorithm))
|
||||
}
|
||||
|
||||
// Verify if request has AWS PreSign Version '4'.
|
||||
func isRequestPresignedSignatureV4(r *http.Request) bool {
|
||||
_, ok := r.URL.Query()["X-Amz-Credential"]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Verify request has AWS PreSign Version '2'.
|
||||
func isRequestPresignedSignatureV2(r *http.Request) bool {
|
||||
_, ok := r.URL.Query()["AWSAccessKeyId"]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Verify if request has AWS Post policy Signature Version '4'.
|
||||
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
||||
return strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") && r.Method == "POST"
|
||||
@ -66,15 +78,21 @@ const (
|
||||
authTypeUnknown authType = iota
|
||||
authTypeAnonymous
|
||||
authTypePresigned
|
||||
authTypePresignedV2
|
||||
authTypePostPolicy
|
||||
authTypeStreamingSigned
|
||||
authTypeSigned
|
||||
authTypeSignedV2
|
||||
authTypeJWT
|
||||
)
|
||||
|
||||
// Get request authentication type.
|
||||
func getRequestAuthType(r *http.Request) authType {
|
||||
if isRequestSignStreamingV4(r) {
|
||||
if isRequestSignatureV2(r) {
|
||||
return authTypeSignedV2
|
||||
} else if isRequestPresignedSignatureV2(r) {
|
||||
return authTypePresignedV2
|
||||
} else if isRequestSignStreamingV4(r) {
|
||||
return authTypeStreamingSigned
|
||||
} else if isRequestSignatureV4(r) {
|
||||
return authTypeSigned
|
||||
@ -104,6 +122,14 @@ func sumMD5(data []byte) []byte {
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
// Verify if request has valid AWS Signature Version '2'.
|
||||
func isReqAuthenticatedV2(r *http.Request) (s3Error APIErrorCode) {
|
||||
if isRequestSignatureV2(r) {
|
||||
return doesSignV2Match(r)
|
||||
}
|
||||
return doesPresignV2SignatureMatch(r)
|
||||
}
|
||||
|
||||
// Verify if request has valid AWS Signature Version '4'.
|
||||
func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) {
|
||||
if r == nil {
|
||||
@ -111,6 +137,7 @@ func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) {
|
||||
}
|
||||
payload, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to read request body for signature verification")
|
||||
return ErrInternalError
|
||||
}
|
||||
// Verify Content-Md5, if payload is set.
|
||||
@ -144,7 +171,6 @@ func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) {
|
||||
// request headers and body are used to calculate the signature validating
|
||||
// the client signature present in request.
|
||||
func checkAuth(r *http.Request) APIErrorCode {
|
||||
// Validates the request for both Presigned and Signed
|
||||
return checkAuthWithRegion(r, serverConfig.GetRegion())
|
||||
}
|
||||
|
||||
@ -152,11 +178,14 @@ func checkAuth(r *http.Request) APIErrorCode {
|
||||
func checkAuthWithRegion(r *http.Request, region string) APIErrorCode {
|
||||
// Validates the request for both Presigned and Signed.
|
||||
aType := getRequestAuthType(r)
|
||||
if aType != authTypePresigned && aType != authTypeSigned {
|
||||
switch aType {
|
||||
case authTypeSignedV2, authTypePresignedV2: // Signature V2.
|
||||
return isReqAuthenticatedV2(r)
|
||||
case authTypeSigned, authTypePresigned: // Signature V4.
|
||||
return isReqAuthenticated(r, region)
|
||||
}
|
||||
// For all unhandled auth types return error AccessDenied.
|
||||
return ErrAccessDenied
|
||||
}
|
||||
return isReqAuthenticated(r, region)
|
||||
}
|
||||
|
||||
// authHandler - handles all the incoming authorization headers and validates them if possible.
|
||||
@ -173,7 +202,9 @@ func setAuthHandler(h http.Handler) http.Handler {
|
||||
var supportedS3AuthTypes = map[authType]struct{}{
|
||||
authTypeAnonymous: {},
|
||||
authTypePresigned: {},
|
||||
authTypePresignedV2: {},
|
||||
authTypeSigned: {},
|
||||
authTypeSignedV2: {},
|
||||
authTypePostPolicy: {},
|
||||
authTypeStreamingSigned: {},
|
||||
}
|
||||
|
@ -151,19 +151,29 @@ func TestS3SupportedAuthType(t *testing.T) {
|
||||
authT: authTypeStreamingSigned,
|
||||
pass: true,
|
||||
},
|
||||
// Test 6 - JWT is not supported s3 type.
|
||||
// Test 6 - supported s3 type with signature v2.
|
||||
{
|
||||
authT: authTypeSignedV2,
|
||||
pass: true,
|
||||
},
|
||||
// Test 7 - supported s3 type with presign v2.
|
||||
{
|
||||
authT: authTypePresignedV2,
|
||||
pass: true,
|
||||
},
|
||||
// Test 8 - JWT is not supported s3 type.
|
||||
{
|
||||
authT: authTypeJWT,
|
||||
pass: false,
|
||||
},
|
||||
// Test 7 - unknown auth header is not supported s3 type.
|
||||
// Test 9 - unknown auth header is not supported s3 type.
|
||||
{
|
||||
authT: authTypeUnknown,
|
||||
pass: false,
|
||||
},
|
||||
// Test 8 - some new auth type is not supported s3 type.
|
||||
// Test 10 - some new auth type is not supported s3 type.
|
||||
{
|
||||
authT: authType(7),
|
||||
authT: authType(9),
|
||||
pass: false,
|
||||
},
|
||||
}
|
||||
@ -210,6 +220,39 @@ func TestIsRequestUnsignedPayload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequestPresignedSignatureV2(t *testing.T) {
|
||||
testCases := []struct {
|
||||
inputQueryKey string
|
||||
inputQueryValue string
|
||||
expectedResult bool
|
||||
}{
|
||||
// Test case - 1.
|
||||
// Test case with query key "AWSAccessKeyId" set.
|
||||
{"", "", false},
|
||||
// Test case - 2.
|
||||
{"AWSAccessKeyId", "", true},
|
||||
// Test case - 3.
|
||||
{"X-Amz-Content-Sha256", "", false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
// creating an input HTTP request.
|
||||
// Only the query parameters are relevant for this particular test.
|
||||
inputReq, err := http.NewRequest("GET", "http://example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error initializing input HTTP request: %v", err)
|
||||
}
|
||||
q := inputReq.URL.Query()
|
||||
q.Add(testCase.inputQueryKey, testCase.inputQueryValue)
|
||||
inputReq.URL.RawQuery = q.Encode()
|
||||
|
||||
actualResult := isRequestPresignedSignatureV2(inputReq)
|
||||
if testCase.expectedResult != actualResult {
|
||||
t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection.
|
||||
func TestIsRequestPresignedSignatureV4(t *testing.T) {
|
||||
testCases := []struct {
|
||||
@ -258,7 +301,7 @@ func mustNewRequest(method string, urlStr string, contentLength int64, body io.R
|
||||
func mustNewSignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
|
||||
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
||||
cred := serverConfig.GetCredential()
|
||||
if err := signRequest(req, cred.AccessKeyID, cred.SecretAccessKey); err != nil {
|
||||
if err := signRequestV4(req, cred.AccessKeyID, cred.SecretAccessKey); err != nil {
|
||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||
}
|
||||
return req
|
||||
|
@ -81,6 +81,13 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -148,6 +155,13 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
|
@ -93,6 +93,13 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
if s3Error := isReqAuthenticated(r, "us-east-1"); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -149,6 +156,13 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -198,6 +212,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
||||
|
||||
// List buckets does not support bucket policies, no need to enforce it.
|
||||
// Proceed to validate signature.
|
||||
// Validates the request for both Presigned and Signed.
|
||||
if s3Error := checkAuthWithRegion(r, ""); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
@ -243,6 +258,13 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -490,6 +512,13 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
|
@ -94,7 +94,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType string, t TestEr
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Get bucket location.
|
||||
req, err := newTestSignedRequest("GET", getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey)
|
||||
req, err := newTestSignedRequestV4("GET", getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
@ -187,7 +187,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType string, t TestErrHandle
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for HEAD bucket.
|
||||
req, err := newTestSignedRequest("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey)
|
||||
req, err := newTestSignedRequestV4("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for HeadBucketHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
@ -272,7 +272,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType string, t Tes
|
||||
|
||||
// construct HTTP request for List multipart uploads endpoint.
|
||||
u := getListMultipartUploadsURLWithParams("", testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads)
|
||||
req, gerr := newTestSignedRequest("GET", u, 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
req, gerr := newTestSignedRequestV4("GET", u, 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if gerr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", i+1, instanceType, gerr)
|
||||
}
|
||||
@ -289,7 +289,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType string, t Tes
|
||||
|
||||
// construct HTTP request for List multipart uploads endpoint.
|
||||
u := getListMultipartUploadsURLWithParams("", bucketName, "", "", "", "", "")
|
||||
req, err := newTestSignedRequest("GET", u, 0, nil, "", "") // Generate an anonymous request.
|
||||
req, err := newTestSignedRequestV4("GET", u, 0, nil, "", "") // Generate an anonymous request.
|
||||
if err != nil {
|
||||
t.Fatalf("Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", instanceType, err)
|
||||
}
|
||||
@ -359,7 +359,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType string, t TestErrHandl
|
||||
for i, testCase := range testCases {
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
req, lerr := newTestSignedRequest("GET", getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey)
|
||||
req, lerr := newTestSignedRequestV4("GET", getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey)
|
||||
if lerr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListBucketsHandler: <ERROR> %v", i+1, instanceType, lerr)
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
// Prepare notification config for one of the test cases.
|
||||
req, err := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
req, err := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes),
|
||||
credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if err != nil {
|
||||
@ -251,7 +251,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
signatureMismatchCode := getAPIError(ErrContentSHA256Mismatch).Code
|
||||
for i, test := range testCases {
|
||||
testRec := httptest.NewRecorder()
|
||||
testReq, tErr := newTestSignedRequest("GET", getGetBucketNotificationURL("", test.bucketName),
|
||||
testReq, tErr := newTestSignedRequestV4("GET", getGetBucketNotificationURL("", test.bucketName),
|
||||
int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: <ERROR> %v",
|
||||
@ -308,7 +308,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
"PutBucketNotificationHandler",
|
||||
})
|
||||
testRec := httptest.NewRecorder()
|
||||
testReq, tErr := newTestSignedRequest("GET", getGetBucketNotificationURL("", randBucket),
|
||||
testReq, tErr := newTestSignedRequestV4("GET", getGetBucketNotificationURL("", randBucket),
|
||||
int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: <ERROR> %v",
|
||||
@ -391,7 +391,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
}
|
||||
for i, test := range testCases {
|
||||
testRec := httptest.NewRecorder()
|
||||
testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", test.bucketName),
|
||||
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", test.bucketName),
|
||||
int64(len(test.expectedNotificationBytes)), bytes.NewReader(test.expectedNotificationBytes),
|
||||
credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
@ -416,7 +416,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
switch test.kind {
|
||||
case CompareBytes:
|
||||
|
||||
testReq, tErr = newTestSignedRequest("GET", getGetBucketNotificationURL("", test.bucketName),
|
||||
testReq, tErr = newTestSignedRequestV4("GET", getGetBucketNotificationURL("", test.bucketName),
|
||||
int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: <ERROR> %v",
|
||||
@ -464,7 +464,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
|
||||
"PutBucketNotificationHandler",
|
||||
})
|
||||
testRec := httptest.NewRecorder()
|
||||
testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes),
|
||||
credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
@ -526,7 +526,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
|
||||
instanceType, err)
|
||||
}
|
||||
testRec := httptest.NewRecorder()
|
||||
testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes),
|
||||
credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
@ -562,7 +562,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
|
||||
|
||||
for i, test := range testCases {
|
||||
testRec = httptest.NewRecorder()
|
||||
testReq, tErr = newTestSignedRequest("GET",
|
||||
testReq, tErr = newTestSignedRequestV4("GET",
|
||||
getListenBucketNotificationURL("", test.bucketName, test.prefix, test.suffix, test.events),
|
||||
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
@ -611,7 +611,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
|
||||
"ListenBucketNotificationHandler",
|
||||
})
|
||||
testRec = httptest.NewRecorder()
|
||||
testReq, tErr = newTestSignedRequest("GET",
|
||||
testReq, tErr = newTestSignedRequestV4("GET",
|
||||
getListenBucketNotificationURL("", randBucket, "", "*.jpg", []string{"s3:ObjectCreated:*", "s3:ObjectRemoved:*"}),
|
||||
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
@ -670,7 +670,7 @@ func testRemoveNotificationConfig(obj ObjectLayer, instanceType string, t TestEr
|
||||
}
|
||||
// Set sample bucket notification on randBucket.
|
||||
testRec := httptest.NewRecorder()
|
||||
testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
|
||||
int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes),
|
||||
credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if tErr != nil {
|
||||
|
@ -132,19 +132,15 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
// PutBucketPolicy does not support bucket policies, use checkAuth to validate signature.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
// If Content-Length is unknown or zero, deny the
|
||||
// request. PutBucketPolicy always needs a Content-Length if
|
||||
@ -214,20 +210,15 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
// DeleteBucketPolicy does not support bucket policies, use checkAuth to validate signature.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
// Delete bucket access policy.
|
||||
if err := removeBucketPolicy(bucket, objAPI); err != nil {
|
||||
@ -260,20 +251,15 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
// For all unknown auth types return error.
|
||||
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
|
||||
return
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
// GetBucketPolicy does not support bucket policies, use checkAuth to validate signature.
|
||||
if s3Error := checkAuth(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
// Read bucket access policy.
|
||||
policy, err := readBucketPolicy(bucket, objAPI)
|
||||
|
@ -289,18 +289,32 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
|
||||
// obtain the put bucket policy request body.
|
||||
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testCase.bucketName, testCase.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testCase.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testCase.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testCase.accessKey, testCase.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
|
||||
apiRouter.ServeHTTP(recV4, reqV4)
|
||||
if recV4.Code != testCase.expectedRespStatus {
|
||||
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV4.Code)
|
||||
}
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testCase.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testCase.accessKey, testCase.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(recV2, reqV2)
|
||||
if recV2.Code != testCase.expectedRespStatus {
|
||||
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -357,18 +371,32 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
|
||||
// obtain the put bucket policy request body.
|
||||
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testPolicy.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code)
|
||||
apiRouter.ServeHTTP(recV4, reqV4)
|
||||
if recV4.Code != testPolicy.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV4.Code)
|
||||
}
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(recV2, reqV2)
|
||||
if recV2.Code != testPolicy.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV2.Code)
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,9 +416,9 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
|
||||
// expected bucket policy json string.
|
||||
expectedBucketPolicyStr := fmt.Sprintf(testCase.expectedBucketPolicy, testCase.bucketName, testCase.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("GET", getGetPolicyURL("", testCase.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4("GET", getGetPolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey)
|
||||
|
||||
if err != nil {
|
||||
@ -398,13 +426,37 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler, GetBucketPolicyHandler handles the request.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
apiRouter.ServeHTTP(recV4, reqV4)
|
||||
// Assert the response code with the expected status.
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
|
||||
if recV4.Code != testCase.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code)
|
||||
}
|
||||
// read the response body.
|
||||
bucketPolicyReadBuf, err := ioutil.ReadAll(rec.Body)
|
||||
bucketPolicyReadBuf, err := ioutil.ReadAll(recV4.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
// Verify whether the bucket policy fetched is same as the one inserted.
|
||||
if expectedBucketPolicyStr != string(bucketPolicyReadBuf) {
|
||||
t.Errorf("Test %d: %s: Bucket policy differs from expected value.", i+1, instanceType)
|
||||
}
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("GET", getGetPolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler, GetBucketPolicyHandler handles the request.
|
||||
apiRouter.ServeHTTP(recV2, reqV2)
|
||||
// Assert the response code with the expected status.
|
||||
if recV2.Code != testCase.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code)
|
||||
}
|
||||
// read the response body.
|
||||
bucketPolicyReadBuf, err = ioutil.ReadAll(recV2.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
@ -502,20 +554,21 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE
|
||||
// obtain the put bucket policy request body.
|
||||
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testPolicy.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code)
|
||||
apiRouter.ServeHTTP(recV4, reqV4)
|
||||
if recV4.Code != testPolicy.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV4.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// testcases with input and expected output for DeleteBucketPolicyHandler.
|
||||
testCases := []struct {
|
||||
bucketName string
|
||||
@ -529,21 +582,58 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE
|
||||
// Iterating over the cases and deleting the bucket policy and then asserting response.
|
||||
for i, testCase := range testCases {
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
recV4 := httptest.NewRecorder()
|
||||
// construct HTTP request for Delete bucket policy endpoint.
|
||||
req, err := newTestSignedRequest("DELETE", getDeletePolicyURL("", testCase.bucketName),
|
||||
reqV4, err := newTestSignedRequestV4("DELETE", getDeletePolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler, DeleteBucketPolicyHandler handles the request.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
apiRouter.ServeHTTP(recV4, reqV4)
|
||||
// Assert the response code with the expected status.
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
|
||||
if recV4.Code != testCase.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// Iterating over the cases and writing the bucket policy.
|
||||
// its required to write the policies first before running tests on GetBucketPolicy.
|
||||
for i, testPolicy := range putTestPolicies {
|
||||
// obtain the put bucket policy request body.
|
||||
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName)
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for PUT bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testPolicy.bucketName),
|
||||
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(recV2, reqV2)
|
||||
if recV2.Code != testPolicy.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV2.Code)
|
||||
}
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
recV2 := httptest.NewRecorder()
|
||||
// construct HTTP request for Delete bucket policy endpoint.
|
||||
reqV2, err := newTestSignedRequestV2("DELETE", getDeletePolicyURL("", testCase.bucketName),
|
||||
0, nil, testCase.accessKey, testCase.secretKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler, DeleteBucketPolicyHandler handles the request.
|
||||
apiRouter.ServeHTTP(recV2, reqV2)
|
||||
// Assert the response code with the expected status.
|
||||
if recV2.Code != testCase.expectedRespStatus {
|
||||
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ func generateSystemLockResponse() (SystemLockState, error) {
|
||||
defer nsMutex.lockMapMutex.Unlock()
|
||||
|
||||
if nsMutex.debugLockMap == nil {
|
||||
return SystemLockState{}, LockInfoNil{}
|
||||
return SystemLockState{}, errLockNotInitialized
|
||||
}
|
||||
|
||||
lockState := SystemLockState{}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
@ -58,14 +59,6 @@ func newDebugLockInfoPerVolumePath() *debugLockInfoPerVolumePath {
|
||||
}
|
||||
}
|
||||
|
||||
// LockInfoNil - Returned if the lock info map is not initialized.
|
||||
type LockInfoNil struct {
|
||||
}
|
||||
|
||||
func (l LockInfoNil) Error() string {
|
||||
return fmt.Sprintf("Debug Lock Map not initialized:\n1. Enable Lock Debugging using right ENV settings \n2. Make sure initNSLock() is called.")
|
||||
}
|
||||
|
||||
// LockInfoOriginNotFound - While changing the state of the lock info its important that the entry for
|
||||
// lock at a given origin exists, if not `LockInfoOriginNotFound` is returned.
|
||||
type LockInfoOriginNotFound struct {
|
||||
@ -114,13 +107,15 @@ func (l LockInfoStateNotBlocked) Error() string {
|
||||
return fmt.Sprintf("Lock state should be \"Blocked\" for <volume> %s, <path> %s, <operationID> %s.", l.volume, l.path, l.operationID)
|
||||
}
|
||||
|
||||
var errLockNotInitialized = errors.New("Debug Lock Map not initialized:\n1. Enable Lock Debugging using right ENV settings \n2. Make sure initNSLock() is called.")
|
||||
|
||||
// change the state of the lock from Blocked to Running.
|
||||
func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationID string, readLock bool) error {
|
||||
// This operation is not executed under the scope nsLockMap.mutex.Lock(), lock has to be explicitly held here.
|
||||
n.lockMapMutex.Lock()
|
||||
defer n.lockMapMutex.Unlock()
|
||||
if n.debugLockMap == nil {
|
||||
return LockInfoNil{}
|
||||
return errLockNotInitialized
|
||||
}
|
||||
// new state info to be set for the lock.
|
||||
newLockInfo := debugLockInfo{
|
||||
@ -140,7 +135,7 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationI
|
||||
if debugLockMap, ok := n.debugLockMap[param]; ok {
|
||||
// ``*debugLockInfoPerVolumePath` entry containing lock info for `param <volume, path>` is `nil`.
|
||||
if debugLockMap == nil {
|
||||
return LockInfoNil{}
|
||||
return errLockNotInitialized
|
||||
}
|
||||
} else {
|
||||
// The lock state info foe given <volume, path> pair should already exist.
|
||||
@ -186,7 +181,7 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationI
|
||||
// change the state of the lock from Ready to Blocked.
|
||||
func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID string, readLock bool) error {
|
||||
if n.debugLockMap == nil {
|
||||
return LockInfoNil{}
|
||||
return errLockNotInitialized
|
||||
}
|
||||
|
||||
newLockInfo := debugLockInfo{
|
||||
@ -230,7 +225,7 @@ func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID s
|
||||
// deleteLockInfoEntry - Deletes the lock state information for given <volume, path> pair. Called when nsLk.ref count is 0.
|
||||
func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error {
|
||||
if n.debugLockMap == nil {
|
||||
return LockInfoNil{}
|
||||
return errLockNotInitialized
|
||||
}
|
||||
// delete the lock info for the given operation.
|
||||
if _, found := n.debugLockMap[param]; found {
|
||||
@ -246,7 +241,7 @@ func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error {
|
||||
// called when the nsLk ref count for the given <volume, path> pair is not 0.
|
||||
func (n *nsLockMap) deleteLockInfoEntryForOps(param nsParam, operationID string) error {
|
||||
if n.debugLockMap == nil {
|
||||
return LockInfoNil{}
|
||||
return errLockNotInitialized
|
||||
}
|
||||
// delete the lock info for the given operation.
|
||||
if infoMap, found := n.debugLockMap[param]; found {
|
||||
|
@ -327,7 +327,7 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
actualErr := nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedNilErr := LockInfoNil{}
|
||||
expectedNilErr := errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
}
|
||||
@ -338,12 +338,12 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
|
||||
lockMap: make(map[nsParam]*nsLock),
|
||||
}
|
||||
// Entry for <volume, path> pair is set to nil.
|
||||
// Should fail with `LockInfoNil{}`.
|
||||
// Should fail with `errLockNotInitialized`.
|
||||
nsMutex.debugLockMap[param] = nil
|
||||
actualErr = nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedNilErr = LockInfoNil{}
|
||||
expectedNilErr = errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
}
|
||||
@ -524,7 +524,7 @@ func TestNsLockMapStatusNoneToBlocked(t *testing.T) {
|
||||
actualErr := nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
|
||||
testCases[0].opsID, testCases[0].readLock)
|
||||
|
||||
expectedNilErr := LockInfoNil{}
|
||||
expectedNilErr := errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
}
|
||||
@ -569,7 +569,7 @@ func TestNsLockMapDeleteLockInfoEntryForOps(t *testing.T) {
|
||||
|
||||
actualErr := nsMutex.deleteLockInfoEntryForOps(param, testCases[0].opsID)
|
||||
|
||||
expectedNilErr := LockInfoNil{}
|
||||
expectedNilErr := errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
}
|
||||
@ -667,7 +667,7 @@ func TestNsLockMapDeleteLockInfoEntryForVolumePath(t *testing.T) {
|
||||
|
||||
actualErr := nsMutex.deleteLockInfoEntryForVolumePath(param)
|
||||
|
||||
expectedNilErr := LockInfoNil{}
|
||||
expectedNilErr := errLockNotInitialized
|
||||
if actualErr != expectedNilErr {
|
||||
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
|
||||
}
|
||||
|
@ -110,6 +110,13 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -222,6 +229,13 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -279,6 +293,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -399,12 +420,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
// If the matching failed, it means that the X-Amz-Copy-Source was
|
||||
// wrong, fail right here.
|
||||
// X-Amz-Copy-Source shouldn't be set for this call.
|
||||
if _, ok := r.Header["X-Amz-Copy-Source"]; ok {
|
||||
writeErrorResponse(w, r, ErrInvalidCopySource, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
object := vars["object"]
|
||||
@ -416,6 +437,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
writeErrorResponse(w, r, ErrInvalidDigest, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
/// if Content-Length is unknown/missing, deny the request
|
||||
size := r.ContentLength
|
||||
rAuthType := getRequestAuthType(r)
|
||||
@ -432,6 +454,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
writeErrorResponse(w, r, ErrMissingContentLength, r.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
/// maximum Upload size for objects in a single operation
|
||||
if isMaxObjectSize(size) {
|
||||
writeErrorResponse(w, r, ErrEntityTooLarge, r.URL.Path)
|
||||
@ -466,6 +489,14 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
objInfo, err = objectAPI.PutObject(bucket, object, size, reader, metadata)
|
||||
case authTypeSignedV2, authTypePresignedV2:
|
||||
s3Error := isReqAuthenticatedV2(r)
|
||||
if s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata)
|
||||
case authTypePresigned, authTypeSigned:
|
||||
// Initialize signature verifier.
|
||||
reader := newSignVerify(r)
|
||||
@ -517,6 +548,13 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -626,6 +664,14 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
partMD5, err = objectAPI.PutObjectPart(bucket, object, uploadID, partID, size, reader, incomingMD5)
|
||||
case authTypeSignedV2, authTypePresignedV2:
|
||||
s3Error := isReqAuthenticatedV2(r)
|
||||
if s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
partMD5, err = objectAPI.PutObjectPart(bucket, object, uploadID, partID, size, r.Body, incomingMD5)
|
||||
case authTypePresigned, authTypeSigned:
|
||||
// Initialize signature verifier.
|
||||
reader := newSignVerify(r)
|
||||
@ -666,6 +712,13 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter,
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -706,6 +759,13 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -764,6 +824,13 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresigned, authTypeSigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
@ -873,6 +940,13 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypePresignedV2, authTypeSignedV2:
|
||||
// Signature V2 validation.
|
||||
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
writeErrorResponse(w, r, s3Error, r.URL.Path)
|
||||
return
|
||||
}
|
||||
case authTypeSigned, authTypePresigned:
|
||||
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
|
||||
errorIf(errSignatureMismatch, dumpRequest(r))
|
||||
|
@ -128,7 +128,7 @@ func testAPIGetOjectHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Get Object end point.
|
||||
req, err := newTestSignedRequest("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
req, err := newTestSignedRequestV4("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
|
||||
if err != nil {
|
||||
@ -342,7 +342,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Get Object end point.
|
||||
req, err := newTestSignedRequest("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
req, err := newTestSignedRequestV4("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
int64(testCase.dataLen), bytes.NewReader(testCase.data), credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for Put Object: <ERROR> %v", i+1, err)
|
||||
@ -484,7 +484,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for copy object.
|
||||
req, err := newTestSignedRequest("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName),
|
||||
req, err := newTestSignedRequestV4("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName),
|
||||
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
|
||||
if err != nil {
|
||||
@ -529,7 +529,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
objectName := "test-object-new-multipart"
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for copy object.
|
||||
req, err := newTestSignedRequest("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create HTTP request for copy Object: <ERROR> %v", err)
|
||||
@ -581,7 +581,7 @@ func testAPINewMultipartHandlerParallel(obj ObjectLayer, instanceType, bucketNam
|
||||
defer wg.Done()
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for copy object.
|
||||
req, err := newTestSignedRequest("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create HTTP request for copy Object: <ERROR> %v", err)
|
||||
@ -828,7 +828,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
t.Fatalf("Error XML encoding of parts: <ERROR> %s.", err)
|
||||
}
|
||||
// Indicating that all parts are uploaded and initiating completeMultipartUpload.
|
||||
req, err = newTestSignedRequest("POST", getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID),
|
||||
req, err = newTestSignedRequestV4("POST", getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID),
|
||||
int64(len(completeBytes)), bytes.NewReader(completeBytes), credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create HTTP request for copy Object: <ERROR> %v", err)
|
||||
@ -930,7 +930,7 @@ func testAPIDeleteOjectHandler(obj ObjectLayer, instanceType, bucketName string,
|
||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||
rec := httptest.NewRecorder()
|
||||
// construct HTTP request for Get Object end point.
|
||||
req, err := newTestSignedRequest("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
req, err := newTestSignedRequestV4("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
|
||||
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
||||
|
||||
if err != nil {
|
||||
|
File diff suppressed because it is too large
Load Diff
2453
cmd/server_v2_test.go
Normal file
2453
cmd/server_v2_test.go
Normal file
File diff suppressed because it is too large
Load Diff
56
cmd/signature-v2-utils.go
Normal file
56
cmd/signature-v2-utils.go
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Replaces any occurring '/' in string, into its encoded representation.
|
||||
func percentEncodeSlash(s string) string {
|
||||
return strings.Replace(s, "/", "%2F", -1)
|
||||
}
|
||||
|
||||
// queryEncode - encodes query values in their URL encoded form. In
|
||||
// addition to the percent encoding performed by getURLEncodedName() used
|
||||
// here, it also percent encodes '/' (forward slash)
|
||||
func queryEncode(v url.Values) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
keys := make([]string, 0, len(v))
|
||||
for k := range v {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
vs := v[k]
|
||||
prefix := percentEncodeSlash(getURLEncodedName(k)) + "="
|
||||
for _, v := range vs {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
buf.WriteString(prefix)
|
||||
buf.WriteString(percentEncodeSlash(getURLEncodedName(v)))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
303
cmd/signature-v2.go
Normal file
303
cmd/signature-v2.go
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Signature and API related constants.
|
||||
const (
|
||||
signV2Algorithm = "AWS"
|
||||
)
|
||||
|
||||
// TODO add post policy signature.
|
||||
|
||||
// doesPresignV2SignatureMatch - Verify query headers with presigned signature
|
||||
// - http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth
|
||||
// returns ErrNone if matches. S3 errors otherwise.
|
||||
func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
|
||||
// Access credentials.
|
||||
cred := serverConfig.GetCredential()
|
||||
|
||||
// Copy request
|
||||
req := *r
|
||||
|
||||
// Validate if we do have query params.
|
||||
if req.URL.Query().Encode() == "" {
|
||||
return ErrInvalidQueryParams
|
||||
}
|
||||
|
||||
// Validate if access key id same.
|
||||
if req.URL.Query().Get("AWSAccessKeyId") != cred.AccessKeyID {
|
||||
return ErrInvalidAccessKeyID
|
||||
}
|
||||
|
||||
// Parse expires param into its native form.
|
||||
expired, err := strconv.ParseInt(req.URL.Query().Get("Expires"), 10, 64)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to parse expires query param")
|
||||
return ErrMalformedExpires
|
||||
}
|
||||
|
||||
// Validate if the request has already expired.
|
||||
if expired < time.Now().UTC().Unix() {
|
||||
return ErrExpiredPresignRequest
|
||||
}
|
||||
|
||||
// Get presigned string to sign.
|
||||
stringToSign := preStringifyHTTPReq(req)
|
||||
hm := hmac.New(sha1.New, []byte(cred.SecretAccessKey))
|
||||
hm.Write([]byte(stringToSign))
|
||||
|
||||
// Calculate signature and validate.
|
||||
signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
|
||||
if req.URL.Query().Get("Signature") != signature {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
|
||||
// Success.
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
|
||||
// Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
|
||||
//
|
||||
// StringToSign = HTTP-Verb + "\n" +
|
||||
// Content-Md5 + "\n" +
|
||||
// Content-Type + "\n" +
|
||||
// Date + "\n" +
|
||||
// CanonicalizedProtocolHeaders +
|
||||
// CanonicalizedResource;
|
||||
//
|
||||
// CanonicalizedResource = [ "/" + Bucket ] +
|
||||
// <HTTP-Request-URI, from the protocol name up to the query string> +
|
||||
// [ subresource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
|
||||
//
|
||||
// CanonicalizedProtocolHeaders = <described below>
|
||||
|
||||
// doesSignV2Match - Verify authorization header with calculated header in accordance with
|
||||
// - http://docs.aws.amazon.com/AmazonS3/latest/dev/auth-request-sig-v2.html
|
||||
// returns true if matches, false otherwise. if error is not nil then it is always false
|
||||
func doesSignV2Match(r *http.Request) APIErrorCode {
|
||||
// Access credentials.
|
||||
cred := serverConfig.GetCredential()
|
||||
|
||||
// Copy request.
|
||||
req := *r
|
||||
|
||||
// Save authorization header.
|
||||
v2Auth := req.Header.Get("Authorization")
|
||||
if v2Auth == "" {
|
||||
return ErrAuthHeaderEmpty
|
||||
}
|
||||
|
||||
// Add date if not present.
|
||||
if date := req.Header.Get("Date"); date == "" {
|
||||
if date = req.Header.Get("X-Amz-Date"); date == "" {
|
||||
return ErrMissingDateHeader
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate HMAC for secretAccessKey.
|
||||
stringToSign := stringifyHTTPReq(req)
|
||||
hm := hmac.New(sha1.New, []byte(cred.SecretAccessKey))
|
||||
hm.Write([]byte(stringToSign))
|
||||
|
||||
// Prepare auth header.
|
||||
authHeader := new(bytes.Buffer)
|
||||
authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, cred.AccessKeyID))
|
||||
encoder := base64.NewEncoder(base64.StdEncoding, authHeader)
|
||||
encoder.Write(hm.Sum(nil))
|
||||
encoder.Close()
|
||||
|
||||
// Verify if signature match.
|
||||
if authHeader.String() != v2Auth {
|
||||
return ErrSignatureDoesNotMatch
|
||||
}
|
||||
|
||||
return ErrNone
|
||||
}
|
||||
|
||||
// From the Amazon docs:
|
||||
//
|
||||
// StringToSign = HTTP-Verb + "\n" +
|
||||
// Content-Md5 + "\n" +
|
||||
// Content-Type + "\n" +
|
||||
// Expires + "\n" +
|
||||
// CanonicalizedProtocolHeaders +
|
||||
// CanonicalizedResource;
|
||||
func preStringifyHTTPReq(req http.Request) string {
|
||||
buf := new(bytes.Buffer)
|
||||
// Write standard headers.
|
||||
writePreSignV2Headers(buf, req)
|
||||
// Write canonicalized protocol headers if any.
|
||||
writeCanonicalizedHeaders(buf, req)
|
||||
// Write canonicalized Query resources if any.
|
||||
isPreSign := true
|
||||
writeCanonicalizedResource(buf, req, isPreSign)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// writePreSignV2Headers - write preSign v2 required headers.
|
||||
func writePreSignV2Headers(buf *bytes.Buffer, req http.Request) {
|
||||
buf.WriteString(req.Method + "\n")
|
||||
buf.WriteString(req.Header.Get("Content-Md5") + "\n")
|
||||
buf.WriteString(req.Header.Get("Content-Type") + "\n")
|
||||
buf.WriteString(req.Header.Get("Expires") + "\n")
|
||||
}
|
||||
|
||||
// From the Amazon docs:
|
||||
//
|
||||
// StringToSign = HTTP-Verb + "\n" +
|
||||
// Content-Md5 + "\n" +
|
||||
// Content-Type + "\n" +
|
||||
// Date + "\n" +
|
||||
// CanonicalizedProtocolHeaders +
|
||||
// CanonicalizedResource;
|
||||
func stringifyHTTPReq(req http.Request) string {
|
||||
buf := new(bytes.Buffer)
|
||||
// Write standard headers.
|
||||
writeSignV2Headers(buf, req)
|
||||
// Write canonicalized protocol headers if any.
|
||||
writeCanonicalizedHeaders(buf, req)
|
||||
// Write canonicalized Query resources if any.
|
||||
isPreSign := false
|
||||
writeCanonicalizedResource(buf, req, isPreSign)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// writeSignV2Headers - write signV2 required headers.
|
||||
func writeSignV2Headers(buf *bytes.Buffer, req http.Request) {
|
||||
buf.WriteString(req.Method + "\n")
|
||||
buf.WriteString(req.Header.Get("Content-Md5") + "\n")
|
||||
buf.WriteString(req.Header.Get("Content-Type") + "\n")
|
||||
buf.WriteString(req.Header.Get("Date") + "\n")
|
||||
}
|
||||
|
||||
// writeCanonicalizedHeaders - write canonicalized headers.
|
||||
func writeCanonicalizedHeaders(buf *bytes.Buffer, req http.Request) {
|
||||
var protoHeaders []string
|
||||
vals := make(map[string][]string)
|
||||
for k, vv := range req.Header {
|
||||
// All the AMZ headers should be lowercase
|
||||
lk := strings.ToLower(k)
|
||||
if strings.HasPrefix(lk, "x-amz") {
|
||||
protoHeaders = append(protoHeaders, lk)
|
||||
vals[lk] = vv
|
||||
}
|
||||
}
|
||||
sort.Strings(protoHeaders)
|
||||
for _, k := range protoHeaders {
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte(':')
|
||||
for idx, v := range vals[k] {
|
||||
if idx > 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
if strings.Contains(v, "\n") {
|
||||
// TODO: "Unfold" long headers that
|
||||
// span multiple lines (as allowed by
|
||||
// RFC 2616, section 4.2) by replacing
|
||||
// the folding white-space (including
|
||||
// new-line) by a single space.
|
||||
buf.WriteString(v)
|
||||
} else {
|
||||
buf.WriteString(v)
|
||||
}
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
// The following list is already sorted and should always be, otherwise we could
|
||||
// have signature-related issues
|
||||
var resourceList = []string{
|
||||
"acl",
|
||||
"delete",
|
||||
"location",
|
||||
"logging",
|
||||
"notification",
|
||||
"partNumber",
|
||||
"policy",
|
||||
"requestPayment",
|
||||
"torrent",
|
||||
"uploadId",
|
||||
"uploads",
|
||||
"versionId",
|
||||
"versioning",
|
||||
"versions",
|
||||
"website",
|
||||
}
|
||||
|
||||
// From the Amazon docs:
|
||||
//
|
||||
// CanonicalizedResource = [ "/" + Bucket ] +
|
||||
// <HTTP-Request-URI, from the protocol name up to the query string> +
|
||||
// [ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
|
||||
func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request, isPreSign bool) {
|
||||
// Save request URL.
|
||||
requestURL := req.URL
|
||||
// Get encoded URL path.
|
||||
path := getURLEncodedName(requestURL.Path)
|
||||
if isPreSign {
|
||||
// Get encoded URL path.
|
||||
if len(requestURL.Query()) > 0 {
|
||||
// Keep the usual queries unescaped for string to sign.
|
||||
query, _ := url.QueryUnescape(queryEncode(requestURL.Query()))
|
||||
path = path + "?" + query
|
||||
}
|
||||
buf.WriteString(path)
|
||||
return
|
||||
}
|
||||
buf.WriteString(path)
|
||||
if requestURL.RawQuery != "" {
|
||||
var n int
|
||||
vals, _ := url.ParseQuery(requestURL.RawQuery)
|
||||
// Verify if any sub resource queries are present, if yes
|
||||
// canonicallize them.
|
||||
for _, resource := range resourceList {
|
||||
if vv, ok := vals[resource]; ok && len(vv) > 0 {
|
||||
n++
|
||||
// First element
|
||||
switch n {
|
||||
case 1:
|
||||
buf.WriteByte('?')
|
||||
// The rest
|
||||
default:
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
buf.WriteString(resource)
|
||||
// Request parameters
|
||||
if len(vv[0]) > 0 {
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(strings.Replace(url.QueryEscape(vv[0]), "+", "%20", -1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
159
cmd/signature-v2_test.go
Normal file
159
cmd/signature-v2_test.go
Normal file
@ -0,0 +1,159 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Tests for 'func TestResourceListSorting(t *testing.T)'.
|
||||
func TestResourceListSorting(t *testing.T) {
|
||||
sortedResourceList := make([]string, len(resourceList))
|
||||
copy(sortedResourceList, resourceList)
|
||||
sort.Strings(sortedResourceList)
|
||||
for i := 0; i < len(resourceList); i++ {
|
||||
if resourceList[i] != sortedResourceList[i] {
|
||||
t.Errorf("Expected resourceList[%d] = \"%s\", resourceList is not correctly sorted.", i, sortedResourceList[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests validate the query encoding.
|
||||
func TestQueryEncode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
// Input.
|
||||
input url.Values
|
||||
// Expected result.
|
||||
result string
|
||||
}{
|
||||
// % should be encoded as %25
|
||||
{url.Values{
|
||||
"key": []string{"thisisthe%url"},
|
||||
}, "key=thisisthe%25url"},
|
||||
// UTF-8 encoding.
|
||||
{url.Values{
|
||||
"key": []string{"本語"},
|
||||
}, "key=%E6%9C%AC%E8%AA%9E"},
|
||||
// UTF-8 encoding with ASCII.
|
||||
{url.Values{
|
||||
"key": []string{"本語.1"},
|
||||
}, "key=%E6%9C%AC%E8%AA%9E.1"},
|
||||
// Unusual ASCII characters.
|
||||
{url.Values{
|
||||
"key": []string{">123"},
|
||||
}, "key=%3E123"},
|
||||
// Fragment path characters.
|
||||
{url.Values{
|
||||
"key": []string{"myurl#link"},
|
||||
}, "key=myurl%23link"},
|
||||
// Space should be set to %20 not '+'.
|
||||
{url.Values{
|
||||
"key": []string{"space in url"},
|
||||
}, "key=space%20in%20url"},
|
||||
// '+' shouldn't be treated as space.
|
||||
{url.Values{
|
||||
"key": []string{"url+path"},
|
||||
}, "key=url%2Bpath"},
|
||||
// '/' shouldn't be treated as '/' should be percent coded.
|
||||
{url.Values{
|
||||
"key": []string{"url/+path"},
|
||||
}, "key=url%2F%2Bpath"},
|
||||
// Values is empty and empty string.
|
||||
{nil, ""},
|
||||
}
|
||||
|
||||
// Tests generated values from url encoded name.
|
||||
for i, testCase := range testCases {
|
||||
result := queryEncode(testCase.input)
|
||||
if testCase.result != result {
|
||||
t.Errorf("Test %d: Expected queryEncoded result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoesPresignedV2SignatureMatch(t *testing.T) {
|
||||
root, err := newTestConfig("us-east-1")
|
||||
if err != nil {
|
||||
t.Fatal("Unable to initialize test config.")
|
||||
}
|
||||
defer removeAll(root)
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
testCases := []struct {
|
||||
queryParams map[string]string
|
||||
headers map[string]string
|
||||
expected APIErrorCode
|
||||
}{
|
||||
// (0) Should error without a set URL query.
|
||||
{
|
||||
expected: ErrInvalidQueryParams,
|
||||
},
|
||||
// (1) Should error on an invalid access key.
|
||||
{
|
||||
queryParams: map[string]string{
|
||||
"Expires": "60",
|
||||
"Signature": "badsignature",
|
||||
"AWSAccessKeyId": "Z7IXGOO6BZ0REAN1Q26I",
|
||||
},
|
||||
expected: ErrInvalidAccessKeyID,
|
||||
},
|
||||
// (2) Should error with malformed expires.
|
||||
{
|
||||
queryParams: map[string]string{
|
||||
"Expires": "60s",
|
||||
"Signature": "badsignature",
|
||||
"AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID,
|
||||
},
|
||||
expected: ErrMalformedExpires,
|
||||
},
|
||||
// (3) Should give an expired request if it has expired.
|
||||
{
|
||||
queryParams: map[string]string{
|
||||
"Expires": "60",
|
||||
"Signature": "badsignature",
|
||||
"AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID,
|
||||
},
|
||||
expected: ErrExpiredPresignRequest,
|
||||
},
|
||||
// (4) Should error when the signature does not match.
|
||||
{
|
||||
queryParams: map[string]string{
|
||||
"Expires": fmt.Sprintf("%d", now.Unix()+60),
|
||||
"Signature": "badsignature",
|
||||
"AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID,
|
||||
},
|
||||
expected: ErrSignatureDoesNotMatch,
|
||||
},
|
||||
}
|
||||
|
||||
// Run each test case individually.
|
||||
for i, testCase := range testCases {
|
||||
// Turn the map[string]string into map[string][]string, because Go.
|
||||
query := url.Values{}
|
||||
for key, value := range testCase.queryParams {
|
||||
query.Set(key, value)
|
||||
}
|
||||
|
||||
// Create a request to use.
|
||||
req, e := http.NewRequest(http.MethodGet, "http://host/a/b?"+query.Encode(), nil)
|
||||
if e != nil {
|
||||
t.Errorf("(%d) failed to create http.Request, got %v", i, e)
|
||||
}
|
||||
|
||||
// Do the same for the headers.
|
||||
for key, value := range testCase.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
// Check if it matches!
|
||||
err := doesPresignV2SignatureMatch(req)
|
||||
if err != testCase.expected {
|
||||
t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(err))
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestSkipContentSha256Cksum - Test validate the logic which decides whether to skip checksum validation based on the request header.
|
||||
// TestSkipContentSha256Cksum - Test validate the logic which decides whether
|
||||
// to skip checksum validation based on the request header.
|
||||
func TestSkipContentSha256Cksum(t *testing.T) {
|
||||
testCases := []struct {
|
||||
inputHeaderKey string
|
||||
@ -124,7 +125,7 @@ func TestGetURLEncodedName(t *testing.T) {
|
||||
for i, testCase := range testCases {
|
||||
result := getURLEncodedName(testCase.inputStr)
|
||||
if testCase.result != result {
|
||||
t.Errorf("Test %d: Expected queryEncode result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
|
||||
t.Errorf("Test %d: Expected URLEncoded result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@ -31,14 +33,12 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
router "github.com/gorilla/mux"
|
||||
)
|
||||
@ -476,8 +476,76 @@ func newTestStreamingSignedRequest(method, urlStr string, contentLength, chunkSi
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// preSignV2 - presign the request in following style.
|
||||
// https://${S3_BUCKET}.s3.amazonaws.com/${S3_OBJECT}?AWSAccessKeyId=${S3_ACCESS_KEY}&Expires=${TIMESTAMP}&Signature=${SIGNATURE}.
|
||||
func preSignV2(req *http.Request, accessKeyID, secretAccessKey string, expires int64) error {
|
||||
// Presign is not needed for anonymous credentials.
|
||||
if accessKeyID == "" || secretAccessKey == "" {
|
||||
return errors.New("Presign cannot be generated without access and secret keys")
|
||||
}
|
||||
|
||||
d := time.Now().UTC()
|
||||
// Find epoch expires when the request will expire.
|
||||
epochExpires := d.Unix() + expires
|
||||
|
||||
// Add expires header if not present.
|
||||
if expiresStr := req.Header.Get("Expires"); expiresStr == "" {
|
||||
req.Header.Set("Expires", strconv.FormatInt(epochExpires, 10))
|
||||
}
|
||||
|
||||
// Get presigned string to sign.
|
||||
stringToSign := preStringifyHTTPReq(*req)
|
||||
hm := hmac.New(sha1.New, []byte(secretAccessKey))
|
||||
hm.Write([]byte(stringToSign))
|
||||
|
||||
// Calculate signature.
|
||||
signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
|
||||
|
||||
query := req.URL.Query()
|
||||
// Handle specially for Google Cloud Storage.
|
||||
query.Set("AWSAccessKeyId", accessKeyID)
|
||||
// Fill in Expires for presigned query.
|
||||
query.Set("Expires", strconv.FormatInt(epochExpires, 10))
|
||||
|
||||
// Encode query and save.
|
||||
req.URL.RawQuery = queryEncode(query)
|
||||
|
||||
// Save signature finally.
|
||||
req.URL.RawQuery += "&Signature=" + getURLEncodedName(signature)
|
||||
|
||||
// Success.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign given request using Signature V2.
|
||||
func signRequestV2(req *http.Request, accessKey, secretKey string) error {
|
||||
// Initial time.
|
||||
d := time.Now().UTC()
|
||||
|
||||
// Add date if not present.
|
||||
if date := req.Header.Get("Date"); date == "" {
|
||||
req.Header.Set("Date", d.Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
// Calculate HMAC for secretAccessKey.
|
||||
stringToSign := stringifyHTTPReq(*req)
|
||||
hm := hmac.New(sha1.New, []byte(secretKey))
|
||||
hm.Write([]byte(stringToSign))
|
||||
|
||||
// Prepare auth header.
|
||||
authHeader := new(bytes.Buffer)
|
||||
authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, accessKey))
|
||||
encoder := base64.NewEncoder(base64.StdEncoding, authHeader)
|
||||
encoder.Write(hm.Sum(nil))
|
||||
encoder.Close()
|
||||
|
||||
// Set Authorization header.
|
||||
req.Header.Set("Authorization", authHeader.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign given request using Signature V4.
|
||||
func signRequest(req *http.Request, accessKey, secretKey string) error {
|
||||
func signRequestV4(req *http.Request, accessKey, secretKey string) error {
|
||||
// Get hashed payload.
|
||||
hashedPayload := req.Header.Get("x-amz-content-sha256")
|
||||
if hashedPayload == "" {
|
||||
@ -611,9 +679,9 @@ func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeek
|
||||
case body == nil:
|
||||
hashedPayload = hex.EncodeToString(sum256([]byte{}))
|
||||
default:
|
||||
payloadBytes, e := ioutil.ReadAll(body)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
payloadBytes, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedPayload = hex.EncodeToString(sum256(payloadBytes))
|
||||
md5Base64 := base64.StdEncoding.EncodeToString(sumMD5(payloadBytes))
|
||||
@ -635,8 +703,50 @@ func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeek
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func newTestSignedRequestV2ContentType(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey, contentType string) (*http.Request, error) {
|
||||
req, err := newTestRequest(method, urlStr, contentLength, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Del("x-amz-content-sha256")
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
// Anonymous request return quickly.
|
||||
if accessKey == "" || secretKey == "" {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
err = signRequestV2(req, accessKey, secretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Returns new HTTP request object signed with signature v2.
|
||||
func newTestSignedRequestV2(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) {
|
||||
req, err := newTestRequest(method, urlStr, contentLength, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Del("x-amz-content-sha256")
|
||||
|
||||
// Anonymous request return quickly.
|
||||
if accessKey == "" || secretKey == "" {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
err = signRequestV2(req, accessKey, secretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Returns new HTTP request object signed with signature v4.
|
||||
func newTestSignedRequest(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) {
|
||||
func newTestSignedRequestV4(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) {
|
||||
req, err := newTestRequest(method, urlStr, contentLength, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -647,7 +757,7 @@ func newTestSignedRequest(method, urlStr string, contentLength int64, body io.Re
|
||||
return req, nil
|
||||
}
|
||||
|
||||
err = signRequest(req, accessKey, secretKey)
|
||||
err = signRequestV4(req, accessKey, secretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -842,71 +952,6 @@ func (t *EOFWriter) Write(p []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// queryEncode - encodes query values in their URL encoded form.
|
||||
func queryEncode(v url.Values) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
keys := make([]string, 0, len(v))
|
||||
for k := range v {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
vs := v[k]
|
||||
prefix := urlEncodePath(k) + "="
|
||||
for _, v := range vs {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
buf.WriteString(prefix)
|
||||
buf.WriteString(urlEncodePath(v))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// urlEncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
|
||||
//
|
||||
// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
|
||||
// non english characters cannot be parsed due to the nature in which url.Encode() is written
|
||||
//
|
||||
// This function on the other hand is a direct replacement for url.Encode() technique to support
|
||||
// pretty much every UTF-8 character.
|
||||
func urlEncodePath(pathName string) string {
|
||||
// if object matches reserved string, no need to encode them
|
||||
reservedNames := regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
|
||||
if reservedNames.MatchString(pathName) {
|
||||
return pathName
|
||||
}
|
||||
var encodedPathname string
|
||||
for _, s := range pathName {
|
||||
if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
|
||||
encodedPathname = encodedPathname + string(s)
|
||||
continue
|
||||
}
|
||||
switch s {
|
||||
case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
|
||||
encodedPathname = encodedPathname + string(s)
|
||||
continue
|
||||
default:
|
||||
len := utf8.RuneLen(s)
|
||||
if len < 0 {
|
||||
// if utf8 cannot convert return the same string as is
|
||||
return pathName
|
||||
}
|
||||
u := make([]byte, len)
|
||||
utf8.EncodeRune(u, s)
|
||||
for _, r := range u {
|
||||
hex := hex.EncodeToString([]byte{r})
|
||||
encodedPathname = encodedPathname + "%" + strings.ToUpper(hex)
|
||||
}
|
||||
}
|
||||
}
|
||||
return encodedPathname
|
||||
}
|
||||
|
||||
// construct URL for http requests for bucket operations.
|
||||
func makeTestTargetURL(endPoint, bucketName, objectName string, queryValues url.Values) string {
|
||||
urlStr := endPoint + "/"
|
||||
@ -914,7 +959,7 @@ func makeTestTargetURL(endPoint, bucketName, objectName string, queryValues url.
|
||||
urlStr = urlStr + bucketName + "/"
|
||||
}
|
||||
if objectName != "" {
|
||||
urlStr = urlStr + urlEncodePath(objectName)
|
||||
urlStr = urlStr + getURLEncodedName(objectName)
|
||||
}
|
||||
if len(queryValues) > 0 {
|
||||
urlStr = urlStr + "?" + queryEncode(queryValues)
|
||||
|
Loading…
Reference in New Issue
Block a user