diff --git a/oss/auth.go b/oss/auth.go index 9cb9c3c..3cd9b94 100644 --- a/oss/auth.go +++ b/oss/auth.go @@ -72,8 +72,8 @@ func (conn Conn) getAdditionalHeaderKeysV4(req *http.Request) ([]string, map[str } // signHeader signs the header and sets it as the authorization header. -func (conn Conn) signHeader(req *http.Request, canonicalizedResource string) { - akIf := conn.config.GetCredentials() +func (conn Conn) signHeader(req *http.Request, canonicalizedResource string, credentials Credentials) { + akIf := credentials authorizationStr := "" if conn.config.AuthVersion == AuthV4 { strDay := "" diff --git a/oss/bucket.go b/oss/bucket.go index 84dae99..4bd88ea 100644 --- a/oss/bucket.go +++ b/oss/bucket.go @@ -892,7 +892,7 @@ func (bucket Bucket) RestoreObjectXML(objectKey, configXML string, options ...Op // string returns the signed URL, when error is nil. // error it's nil if no error, otherwise it's an error object. func (bucket Bucket) SignURL(objectKey string, method HTTPMethod, expiredInSec int64, options ...Option) (string, error) { - err := CheckObjectName(objectKey) + err := CheckObjectNameEx(objectKey, bucket.GetConfig().VerifyObjectStrict) if err != nil { return "", err } @@ -913,7 +913,7 @@ func (bucket Bucket) SignURL(objectKey string, method HTTPMethod, expiredInSec i return "", err } - return bucket.Client.Conn.signURL(method, bucket.BucketName, objectKey, expiration, params, headers), nil + return bucket.Client.Conn.signURL(method, bucket.BucketName, objectKey, expiration, params, headers) } // PutObjectWithURL uploads an object with the URL. If the object exists, it will be overwritten. diff --git a/oss/bucket_test.go b/oss/bucket_test.go index dee5139..c9a9b91 100644 --- a/oss/bucket_test.go +++ b/oss/bucket_test.go @@ -1222,7 +1222,7 @@ func (s *OssBucketSuite) TestGetObjectToFile(c *C) { err = s.bucket.GetObjectToFile(objectName, newFile, NormalizedRange("-10")) c.Assert(err, IsNil) - eq, err = compareFileData(newFile, val[(len(val)-10):len(val)]) + eq, err = compareFileData(newFile, val[(len(val)-10):]) c.Assert(err, IsNil) c.Assert(eq, Equals, true) os.Remove(newFile) @@ -4364,8 +4364,8 @@ func (s *OssBucketSuite) TestVersioningBatchDeleteVersionObjects(c *C) { c.Assert(versionIdV1 != versionIdV2, Equals, true) //batch delete objects - versionIds := []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1}, - DeleteObject{Key: objectName2, VersionId: versionIdV2}} + versionIds := []DeleteObject{{Key: objectName1, VersionId: versionIdV1}, + {Key: objectName2, VersionId: versionIdV2}} deleteResult, err := bucket.DeleteObjectVersions(versionIds) c.Assert(err, IsNil) c.Assert(len(deleteResult.DeletedObjectsDetail), Equals, 2) @@ -4395,8 +4395,8 @@ func (s *OssBucketSuite) TestVersioningBatchDeleteVersionObjects(c *C) { c.Assert(versionIdV1 != versionIdV2, Equals, true) //batch delete objects - versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1}, - DeleteObject{Key: objectName2, VersionId: versionIdV2}} + versionIds = []DeleteObject{{Key: objectName1, VersionId: versionIdV1}, + {Key: objectName2, VersionId: versionIdV2}} deleteResult, err = bucket.DeleteObjectVersions(versionIds) c.Assert(err, IsNil) c.Assert(len(deleteResult.DeletedObjectsDetail), Equals, 2) @@ -4415,8 +4415,8 @@ func (s *OssBucketSuite) TestVersioningBatchDeleteVersionObjects(c *C) { c.Assert(versionIdV1 != versionIdV2, Equals, true) //batch delete objects - versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1}, - DeleteObject{Key: objectName2, VersionId: versionIdV2}} + versionIds = []DeleteObject{{Key: objectName1, VersionId: versionIdV1}, + {Key: objectName2, VersionId: versionIdV2}} deleteResult, err = bucket.DeleteObjectVersions(versionIds) c.Assert(err, IsNil) c.Assert(len(deleteResult.DeletedObjectsDetail), Equals, 2) @@ -4470,8 +4470,8 @@ func (s *OssBucketSuite) TestVersioningBatchDeleteDefaultVersionObjects(c *C) { c.Assert(versionIdV1 != versionIdV2, Equals, true) //batch delete objects - versionIds := []DeleteObject{DeleteObject{Key: objectName1, VersionId: ""}, - DeleteObject{Key: objectName2, VersionId: ""}} + versionIds := []DeleteObject{{Key: objectName1, VersionId: ""}, + {Key: objectName2, VersionId: ""}} deleteResult, err := bucket.DeleteObjectVersions(versionIds) c.Assert(err, IsNil) @@ -4500,14 +4500,14 @@ func (s *OssBucketSuite) TestVersioningBatchDeleteDefaultVersionObjects(c *C) { c.Assert(len(listResult.ObjectVersions), Equals, 2) // delete version object - versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1}, - DeleteObject{Key: objectName2, VersionId: versionIdV2}} + versionIds = []DeleteObject{{Key: objectName1, VersionId: versionIdV1}, + {Key: objectName2, VersionId: versionIdV2}} deleteResult, err = bucket.DeleteObjectVersions(versionIds) c.Assert(err, IsNil) // delete deleteMark object - versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: keyInfo1.DeleteMarkerVersionId}, - DeleteObject{Key: objectName2, VersionId: keyInfo2.DeleteMarkerVersionId}} + versionIds = []DeleteObject{{Key: objectName1, VersionId: keyInfo1.DeleteMarkerVersionId}, + {Key: objectName2, VersionId: keyInfo2.DeleteMarkerVersionId}} deleteResult, err = bucket.DeleteObjectVersions(versionIds) c.Assert(err, IsNil) @@ -5113,7 +5113,7 @@ func (s *OssBucketSuite) TestVersioningObjectTagging(c *C) { // ObjectTagging v1 var tagging1 Tagging - tagging1.Tags = []Tag{Tag{Key: "testkey1", Value: "testvalue1"}} + tagging1.Tags = []Tag{{Key: "testkey1", Value: "testvalue1"}} err = bucket.PutObjectTagging(objectName, tagging1, VersionId(versionIdV1)) c.Assert(err, IsNil) getResult, err := bucket.GetObjectTagging(objectName, VersionId(versionIdV1)) @@ -5123,7 +5123,7 @@ func (s *OssBucketSuite) TestVersioningObjectTagging(c *C) { // ObjectTagging v2 var tagging2 Tagging - tagging2.Tags = []Tag{Tag{Key: "testkey2", Value: "testvalue2"}} + tagging2.Tags = []Tag{{Key: "testkey2", Value: "testvalue2"}} err = bucket.PutObjectTagging(objectName, tagging2, VersionId(versionIdV2)) c.Assert(err, IsNil) getResult, err = bucket.GetObjectTagging(objectName, VersionId(versionIdV2)) @@ -6022,3 +6022,156 @@ func (s *OssBucketSuite) TestPutObjectWithCallbackResult(c *C) { c.Assert(err, IsNil) c.Assert(str, Equals, objectValue) } + +type TestCredentialsProviderError struct { + AccessKeyId string + AccessKeySecret string + SecurityToken string +} + +func (testInfBuild *TestCredentialsProviderError) GetCredentials() Credentials { + cred, _ := testInfBuild.GetCredentialsE() + return cred +} + +func (testInfBuild *TestCredentialsProviderError) GetCredentialsE() (Credentials, error) { + var provider *TestCredentials + keyId := testInfBuild.AccessKeyId + if keyId == "" { + return provider, fmt.Errorf("access key id is empty!") + } + secret := testInfBuild.AccessKeySecret + if secret == "" { + return provider, fmt.Errorf("access key secret is empty!") + } + return &envCredentials{ + AccessKeyId: keyId, + AccessKeySecret: secret, + SecurityToken: "", + }, nil +} + +func (s *OssBucketSuite) TestCredentialsProviderError(c *C) { + var bucketNameTest = bucketNamePrefix + RandLowStr(6) + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + // Create + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketNameTest) + c.Assert(err, IsNil) + + objectName := objectNamePrefix + RandStr(8) + objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。 乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。" + + "遥想公谨当年,小乔初嫁了,雄姿英发。 羽扇纶巾,谈笑间、樯橹灰飞烟灭。故国神游,多情应笑我,早生华发,人生如梦,一尊还酹江月。" + + // Put string + var respHeader http.Header + err = s.bucket.PutObject(objectName, strings.NewReader(objectValue), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + + var defaultBuild TestCredentialsProviderError + defaultBuild.AccessKeyId = "" + defaultBuild.AccessKeySecret = accessKey + client, err = New(endpoint, "", "", SetCredentialsProvider(&defaultBuild)) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "access key id is empty!") + + bucket, err = client.Bucket(bucketNameTest) + c.Assert(err, IsNil) + _, err = bucket.SignURL(objectName, http.MethodGet, 3600) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "access key id is empty!") + + channelName := "test-sign-rtmp-url" + playlistName := "playlist.m3u8" + _, err = bucket.SignRtmpURL(channelName, playlistName, 3600) + c.Assert(err, IsNil) + + defaultBuild.AccessKeyId = accessID + defaultBuild.AccessKeySecret = "" + client, err = New(endpoint, "", "", SetCredentialsProvider(&defaultBuild)) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "access key secret is empty!") + + bucket, err = client.Bucket(bucketNameTest) + c.Assert(err, IsNil) + _, err = bucket.SignURL(objectName, http.MethodGet, 3600) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "access key secret is empty!") + + _, err = bucket.SignRtmpURL(channelName, playlistName, 3600) + c.Assert(err, IsNil) + + defaultBuild.AccessKeyId = accessID + defaultBuild.AccessKeySecret = accessKey + client, err = New(endpoint, "", "", SetCredentialsProvider(&defaultBuild)) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + + _, err = bucket.SignURL(objectName, http.MethodGet, 3600) + c.Assert(err, IsNil) + + _, err = bucket.SignRtmpURL(channelName, playlistName, 3600) + c.Assert(err, IsNil) +} + +func (s *OssBucketSuite) TestVerifyObjectStrict(c *C) { + var bucketNameTest = bucketNamePrefix + RandLowStr(6) + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + // Create + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketNameTest) + c.Assert(err, IsNil) + + objectName := "?" + objectNamePrefix + RandStr(8) + objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。 乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。" + + "遥想公谨当年,小乔初嫁了,雄姿英发。 羽扇纶巾,谈笑间、樯橹灰飞烟灭。故国神游,多情应笑我,早生华发,人生如梦,一尊还酹江月。" + + // Put string + var respHeader http.Header + err = s.bucket.PutObject(objectName, strings.NewReader(objectValue), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + + // Sign + _, err = bucket.SignURL(objectName, http.MethodGet, 3600) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "object name is invalid, can't start with '?'") + + // + objectName = "" + _, err = bucket.SignURL(objectName, http.MethodGet, 3600) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "object name is empty") + + //Disable VerifyObjectStrictFlag + client, err = New(endpoint, accessID, accessKey, VerifyObjectStrict(false)) + c.Assert(err, IsNil) + + bucket, err = client.Bucket(bucketNameTest) + c.Assert(err, IsNil) + + objectName = "?" + url, err := bucket.SignURL(objectName, http.MethodGet, 3600) + c.Assert(err, IsNil) + c.Assert(strings.Contains(url, "/%3F?Expires="), Equals, true) + + objectName = "?123" + url, err = bucket.SignURL(objectName, http.MethodGet, 3600) + c.Assert(err, IsNil) + c.Assert(strings.Contains(url, "/%3F123?Expires="), Equals, true) + + objectName = "123?" + url, err = bucket.SignURL(objectName, http.MethodGet, 3600) + c.Assert(err, IsNil) + c.Assert(strings.Contains(url, "/123%3F?Expires="), Equals, true) +} diff --git a/oss/client.go b/oss/client.go index c30d795..5910bb3 100644 --- a/oss/client.go +++ b/oss/client.go @@ -2908,6 +2908,13 @@ func Product(product string) ClientOption { } } +// VerifyObjectStrict sets the flag of verifying object name strictly. +func VerifyObjectStrict(enable bool) ClientOption { + return func(client *Client) { + client.Config.VerifyObjectStrict = enable + } +} + // Private func (client Client) do(method, bucketName string, params map[string]interface{}, headers map[string]string, data io.Reader, options ...Option) (*Response, error) { diff --git a/oss/conf.go b/oss/conf.go index e569541..305fbcf 100644 --- a/oss/conf.go +++ b/oss/conf.go @@ -37,18 +37,23 @@ type HTTPMaxConns struct { MaxConnsPerHost int } -// CredentialInf is interface for get AccessKeyID,AccessKeySecret,SecurityToken +// Credentials is interface for get AccessKeyID,AccessKeySecret,SecurityToken type Credentials interface { GetAccessKeyID() string GetAccessKeySecret() string GetSecurityToken() string } -// CredentialInfBuild is interface for get CredentialInf +// CredentialsProvider is interface for get Credential Info type CredentialsProvider interface { GetCredentials() Credentials } +type CredentialsProviderE interface { + CredentialsProvider + GetCredentialsE() (Credentials, error) +} + type defaultCredentials struct { config *Config } @@ -173,6 +178,7 @@ type Config struct { Region string // such as cn-hangzhou CloudBoxId string // Product string // oss or oss-cloudbox, default is oss + VerifyObjectStrict bool // a flag of verifying object name strictly. Default is enable. } // LimitUploadSpeed uploadSpeed:KB/s, 0 is unlimited,default is 0 @@ -289,5 +295,7 @@ func getDefaultOssConfig() *Config { config.Product = "oss" + config.VerifyObjectStrict = true + return &config } diff --git a/oss/conn.go b/oss/conn.go index 7d40475..b24ec35 100644 --- a/oss/conn.go +++ b/oss/conn.go @@ -338,7 +338,15 @@ func (conn Conn) doRequest(ctx context.Context, method string, uri *url.URL, can req.Header.Set(HttpHeaderOssContentSha256, DefaultContentSha256) } - akIf := conn.config.GetCredentials() + var akIf Credentials + if providerE, ok := conn.config.CredentialsProvider.(CredentialsProviderE); ok { + if akIf, err = providerE.GetCredentialsE(); err != nil { + return nil, err + } + } else { + akIf = conn.config.GetCredentials() + } + if akIf.GetSecurityToken() != "" { req.Header.Set(HTTPHeaderOssSecurityToken, akIf.GetSecurityToken()) } @@ -349,7 +357,7 @@ func (conn Conn) doRequest(ctx context.Context, method string, uri *url.URL, can } } - conn.signHeader(req, canonicalizedResource) + conn.signHeader(req, canonicalizedResource, akIf) // Transfer started event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength, 0) @@ -381,8 +389,17 @@ func (conn Conn) doRequest(ctx context.Context, method string, uri *url.URL, can return conn.handleResponse(resp, crc) } -func (conn Conn) signURL(method HTTPMethod, bucketName, objectName string, expiration int64, params map[string]interface{}, headers map[string]string) string { - akIf := conn.config.GetCredentials() +func (conn Conn) signURL(method HTTPMethod, bucketName, objectName string, expiration int64, params map[string]interface{}, headers map[string]string) (string, error) { + var akIf Credentials + var err error + if providerE, ok := conn.config.CredentialsProvider.(CredentialsProviderE); ok { + if akIf, err = providerE.GetCredentialsE(); err != nil { + return "", err + } + } else { + akIf = conn.config.GetCredentials() + } + if akIf.GetSecurityToken() != "" { params[HTTPParamSecurityToken] = akIf.GetSecurityToken() } @@ -430,7 +447,7 @@ func (conn Conn) signURL(method HTTPMethod, bucketName, objectName string, expir params[HTTPParamSignatureV2] = signedStr } urlParams := conn.getURLParams(params) - return conn.url.getSignURL(bucketName, objectName, urlParams) + return conn.url.getSignURL(bucketName, objectName, urlParams), nil } func (conn Conn) signRtmpURL(bucketName, channelName, playlistName string, expiration int64) string { diff --git a/oss/conn_test.go b/oss/conn_test.go index b4ceedd..9ed7eb1 100644 --- a/oss/conn_test.go +++ b/oss/conn_test.go @@ -128,7 +128,10 @@ func (s *OssConnSuite) TestAuth(c *C) { req.Header.Set("X-OSS-Magic", "abracadabra") req.Header.Set("Content-Md5", "ODBGOERFMDMzQTczRUY3NUE3NzA5QzdFNUYzMDQxNEM=") - conn.signHeader(req, conn.getResource("bucket", "object", "")) + var akIf Credentials + credProvider := conn.config.CredentialsProvider + akIf = credProvider.(CredentialsProvider).GetCredentials() + conn.signHeader(req, conn.getResource("bucket", "object", ""), akIf) testLogger.Println("AUTHORIZATION:", req.Header.Get(HTTPHeaderAuthorization)) } diff --git a/oss/crc_test.go b/oss/crc_test.go index aff8322..e2011c7 100644 --- a/oss/crc_test.go +++ b/oss/crc_test.go @@ -23,6 +23,7 @@ var _ = Suite(&OssCrcSuite{}) // SetUpSuite runs once when the suite starts running func (s *OssCrcSuite) SetUpSuite(c *C) { + bucketName = bucketNamePrefix + RandLowStr(6) if cloudboxControlEndpoint == "" { client, err := New(endpoint, accessID, accessKey) c.Assert(err, IsNil) diff --git a/oss/progress_test.go b/oss/progress_test.go index 939fe74..6a836fb 100644 --- a/oss/progress_test.go +++ b/oss/progress_test.go @@ -23,6 +23,7 @@ var _ = Suite(&OssProgressSuite{}) // SetUpSuite runs once when the suite starts running func (s *OssProgressSuite) SetUpSuite(c *C) { + bucketName = bucketNamePrefix + RandLowStr(6) if cloudboxControlEndpoint == "" { client, err := New(endpoint, accessID, accessKey) c.Assert(err, IsNil) diff --git a/oss/utils.go b/oss/utils.go index 0d92a53..b9e7ac4 100644 --- a/oss/utils.go +++ b/oss/utils.go @@ -449,6 +449,18 @@ func CheckObjectName(objectName string) error { return nil } +func CheckObjectNameEx(objectName string, strict bool) error { + if err := CheckObjectName(objectName); err != nil { + return err + } + + if strict && strings.HasPrefix(objectName, "?") { + return fmt.Errorf("object name is invalid, can't start with '?'") + } + + return nil +} + func GetReaderLen(reader io.Reader) (int64, error) { var contentLength int64 var err error