github.com/chnsz/golangsdk@v0.0.0-20240506093406-85a3fbfa605b/openstack/obs/auth.go (about)

     1  // Copyright 2019 Huawei Technologies Co.,Ltd.
     2  // Licensed under the Apache License, Version 2.0 (the "License"); you may not use
     3  // this file except in compliance with the License.  You may obtain a copy of the
     4  // License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software distributed
     9  // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    10  // CONDITIONS OF ANY KIND, either express or implied.  See the License for the
    11  // specific language governing permissions and limitations under the License.
    12  
    13  package obs
    14  
    15  import (
    16  	"fmt"
    17  	"net/url"
    18  	"sort"
    19  	"strings"
    20  	"time"
    21  )
    22  
    23  func setURLWithPolicy(bucketName, canonicalizedUrl string) string {
    24  	if strings.HasPrefix(canonicalizedUrl, "/"+bucketName+"/") {
    25  		canonicalizedUrl = canonicalizedUrl[len("/"+bucketName+"/"):]
    26  	} else if strings.HasPrefix(canonicalizedUrl, "/"+bucketName) {
    27  		canonicalizedUrl = canonicalizedUrl[len("/"+bucketName):]
    28  	}
    29  	return canonicalizedUrl
    30  }
    31  
    32  func (obsClient ObsClient) doAuthTemporary(method, bucketName, objectKey string, policy string, params map[string]string,
    33  	headers map[string][]string, expires int64) (requestURL string, err error) {
    34  	sh := obsClient.getSecurity()
    35  	isAkSkEmpty := sh.ak == "" || sh.sk == ""
    36  	if isAkSkEmpty == false && sh.securityToken != "" {
    37  		if obsClient.conf.signature == SignatureObs {
    38  			params[HEADER_STS_TOKEN_OBS] = sh.securityToken
    39  		} else {
    40  			params[HEADER_STS_TOKEN_AMZ] = sh.securityToken
    41  		}
    42  	}
    43  
    44  	if policy != "" {
    45  		objectKey = ""
    46  	}
    47  
    48  	requestURL, canonicalizedURL := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
    49  	parsedRequestURL, err := url.Parse(requestURL)
    50  	if err != nil {
    51  		return "", err
    52  	}
    53  	encodeHeaders(headers)
    54  	hostName := parsedRequestURL.Host
    55  
    56  	isV4 := obsClient.conf.signature == SignatureV4
    57  	prepareHostAndDate(headers, hostName, isV4)
    58  
    59  	if isAkSkEmpty {
    60  		doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
    61  	} else {
    62  		if isV4 {
    63  			date, parseDateErr := time.Parse(RFC1123_FORMAT, headers[HEADER_DATE_CAMEL][0])
    64  			if parseDateErr != nil {
    65  				doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr)
    66  				return "", parseDateErr
    67  			}
    68  			delete(headers, HEADER_DATE_CAMEL)
    69  			shortDate := date.Format(SHORT_DATE_FORMAT)
    70  			longDate := date.Format(LONG_DATE_FORMAT)
    71  			if len(headers[HEADER_HOST_CAMEL]) != 0 {
    72  				index := strings.LastIndex(headers[HEADER_HOST_CAMEL][0], ":")
    73  				if index != -1 {
    74  					port := headers[HEADER_HOST_CAMEL][0][index+1:]
    75  					if port == "80" || port == "443" {
    76  						headers[HEADER_HOST_CAMEL] = []string{headers[HEADER_HOST_CAMEL][0][:index]}
    77  					}
    78  				}
    79  
    80  			}
    81  
    82  			signedHeaders, _headers := getSignedHeaders(headers)
    83  
    84  			credential, scope := getCredential(sh.ak, obsClient.conf.region, shortDate)
    85  			params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX
    86  			params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
    87  			params[PARAM_DATE_AMZ_CAMEL] = longDate
    88  			params[PARAM_EXPIRES_AMZ_CAMEL] = Int64ToString(expires)
    89  			params[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = strings.Join(signedHeaders, ";")
    90  
    91  			requestURL, canonicalizedURL = obsClient.conf.formatUrls(bucketName, objectKey, params, true)
    92  			parsedRequestURL, _err := url.Parse(requestURL)
    93  			if _err != nil {
    94  				return "", _err
    95  			}
    96  
    97  			stringToSign := getV4StringToSign(method, canonicalizedURL, parsedRequestURL.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, signedHeaders, _headers)
    98  			signature := getSignature(stringToSign, sh.sk, obsClient.conf.region, shortDate)
    99  
   100  			requestURL += fmt.Sprintf("&%s=%s", PARAM_SIGNATURE_AMZ_CAMEL, UrlEncode(signature, false))
   101  
   102  		} else {
   103  			originDate := headers[HEADER_DATE_CAMEL][0]
   104  			date, parseDateErr := time.Parse(RFC1123_FORMAT, originDate)
   105  			if parseDateErr != nil {
   106  				doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr)
   107  				return "", parseDateErr
   108  			}
   109  			expires += date.Unix()
   110  			if policy == "" {
   111  				headers[HEADER_DATE_CAMEL] = []string{Int64ToString(expires)}
   112  			} else {
   113  				policy = Base64Encode([]byte(policy))
   114  				headers[HEADER_DATE_CAMEL] = []string{policy}
   115  				canonicalizedURL = setURLWithPolicy(bucketName, canonicalizedURL)
   116  			}
   117  
   118  			stringToSign := getV2StringToSign(method, canonicalizedURL, headers, obsClient.conf.signature == SignatureObs)
   119  			signature := UrlEncode(Base64Encode(HmacSha1([]byte(sh.sk), []byte(stringToSign))), false)
   120  			if strings.Index(requestURL, "?") < 0 {
   121  				requestURL += "?"
   122  			} else {
   123  				requestURL += "&"
   124  			}
   125  			delete(headers, HEADER_DATE_CAMEL)
   126  
   127  			if obsClient.conf.signature != SignatureObs {
   128  				requestURL += "AWS"
   129  			}
   130  			if policy == "" {
   131  				requestURL += fmt.Sprintf("AccessKeyId=%s&Expires=%d&Signature=%s", UrlEncode(sh.ak, false),
   132  					expires, signature)
   133  				return
   134  
   135  			}
   136  			requestURL += fmt.Sprintf("AccessKeyId=%s&Policy=%s&Signature=%s", UrlEncode(sh.ak, false),
   137  				UrlEncode(policy, false), signature)
   138  		}
   139  	}
   140  
   141  	return
   142  }
   143  
   144  func (obsClient ObsClient) doAuth(method, bucketName, objectKey string, params map[string]string,
   145  	headers map[string][]string, hostName string) (requestURL string, err error) {
   146  	sh := obsClient.getSecurity()
   147  	isAkSkEmpty := sh.ak == "" || sh.sk == ""
   148  	if isAkSkEmpty == false && sh.securityToken != "" {
   149  		if obsClient.conf.signature == SignatureObs {
   150  			headers[HEADER_STS_TOKEN_OBS] = []string{sh.securityToken}
   151  		} else {
   152  			headers[HEADER_STS_TOKEN_AMZ] = []string{sh.securityToken}
   153  		}
   154  	}
   155  	isObs := obsClient.conf.signature == SignatureObs
   156  	requestURL, canonicalizedURL := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
   157  	parsedRequestURL, err := url.Parse(requestURL)
   158  	if err != nil {
   159  		return "", err
   160  	}
   161  	encodeHeaders(headers)
   162  
   163  	if hostName == "" {
   164  		hostName = parsedRequestURL.Host
   165  	}
   166  
   167  	isV4 := obsClient.conf.signature == SignatureV4
   168  	prepareHostAndDate(headers, hostName, isV4)
   169  
   170  	if isAkSkEmpty {
   171  		doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
   172  	} else {
   173  		ak := sh.ak
   174  		sk := sh.sk
   175  		var authorization string
   176  		if isV4 {
   177  			headers[HEADER_CONTENT_SHA256_AMZ] = []string{UNSIGNED_PAYLOAD}
   178  			ret := v4Auth(ak, sk, obsClient.conf.region, method, canonicalizedURL, parsedRequestURL.RawQuery, headers)
   179  			authorization = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
   180  		} else {
   181  			ret := v2Auth(ak, sk, method, canonicalizedURL, headers, isObs)
   182  			hashPrefix := V2_HASH_PREFIX
   183  			if isObs {
   184  				hashPrefix = OBS_HASH_PREFIX
   185  			}
   186  			authorization = fmt.Sprintf("%s %s:%s", hashPrefix, ak, ret["Signature"])
   187  		}
   188  		headers[HEADER_AUTH_CAMEL] = []string{authorization}
   189  	}
   190  	return
   191  }
   192  
   193  func prepareHostAndDate(headers map[string][]string, hostName string, isV4 bool) {
   194  	headers[HEADER_HOST_CAMEL] = []string{hostName}
   195  	if date, ok := headers[HEADER_DATE_AMZ]; ok {
   196  		flag := false
   197  		if len(date) == 1 {
   198  			if isV4 {
   199  				if t, err := time.Parse(LONG_DATE_FORMAT, date[0]); err == nil {
   200  					headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(t)}
   201  					flag = true
   202  				}
   203  			} else {
   204  				if strings.HasSuffix(date[0], "GMT") {
   205  					headers[HEADER_DATE_CAMEL] = []string{date[0]}
   206  					flag = true
   207  				}
   208  			}
   209  		}
   210  		if !flag {
   211  			delete(headers, HEADER_DATE_AMZ)
   212  		}
   213  	}
   214  	if _, ok := headers[HEADER_DATE_CAMEL]; !ok {
   215  		headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(time.Now().UTC())}
   216  	}
   217  
   218  }
   219  
   220  func encodeHeaders(headers map[string][]string) {
   221  	for key, values := range headers {
   222  		for index, value := range values {
   223  			values[index] = UrlEncode(value, true)
   224  		}
   225  		headers[key] = values
   226  	}
   227  }
   228  
   229  func prepareDateHeader(dataHeader, dateCamelHeader string, headers, _headers map[string][]string) {
   230  	if _, ok := _headers[HEADER_DATE_CAMEL]; ok {
   231  		if _, ok := _headers[dataHeader]; ok {
   232  			_headers[HEADER_DATE_CAMEL] = []string{""}
   233  		} else if _, ok := headers[dateCamelHeader]; ok {
   234  			_headers[HEADER_DATE_CAMEL] = []string{""}
   235  		}
   236  	} else if _, ok := _headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
   237  		if _, ok := _headers[dataHeader]; ok {
   238  			_headers[HEADER_DATE_CAMEL] = []string{""}
   239  		} else if _, ok := headers[dateCamelHeader]; ok {
   240  			_headers[HEADER_DATE_CAMEL] = []string{""}
   241  		}
   242  	}
   243  }
   244  
   245  func getStringToSign(keys []string, isObs bool, _headers map[string][]string) []string {
   246  	stringToSign := make([]string, 0, len(keys))
   247  	for _, key := range keys {
   248  		var value string
   249  		prefixHeader := HEADER_PREFIX
   250  		prefixMetaHeader := HEADER_PREFIX_META
   251  		if isObs {
   252  			prefixHeader = HEADER_PREFIX_OBS
   253  			prefixMetaHeader = HEADER_PREFIX_META_OBS
   254  		}
   255  		if strings.HasPrefix(key, prefixHeader) {
   256  			if strings.HasPrefix(key, prefixMetaHeader) {
   257  				for index, v := range _headers[key] {
   258  					value += strings.TrimSpace(v)
   259  					if index != len(_headers[key])-1 {
   260  						value += ","
   261  					}
   262  				}
   263  			} else {
   264  				value = strings.Join(_headers[key], ",")
   265  			}
   266  			value = fmt.Sprintf("%s:%s", key, value)
   267  		} else {
   268  			value = strings.Join(_headers[key], ",")
   269  		}
   270  		stringToSign = append(stringToSign, value)
   271  	}
   272  	return stringToSign
   273  }
   274  
   275  func attachHeaders(headers map[string][]string, isObs bool) string {
   276  	length := len(headers)
   277  	_headers := make(map[string][]string, length)
   278  	keys := make([]string, 0, length)
   279  
   280  	for key, value := range headers {
   281  		_key := strings.ToLower(strings.TrimSpace(key))
   282  		if _key != "" {
   283  			prefixheader := HEADER_PREFIX
   284  			if isObs {
   285  				prefixheader = HEADER_PREFIX_OBS
   286  			}
   287  			if _key == "content-md5" || _key == "content-type" || _key == "date" || strings.HasPrefix(_key, prefixheader) {
   288  				keys = append(keys, _key)
   289  				_headers[_key] = value
   290  			}
   291  		} else {
   292  			delete(headers, key)
   293  		}
   294  	}
   295  
   296  	for _, interestedHeader := range interestedHeaders {
   297  		if _, ok := _headers[interestedHeader]; !ok {
   298  			_headers[interestedHeader] = []string{""}
   299  			keys = append(keys, interestedHeader)
   300  		}
   301  	}
   302  	dateCamelHeader := PARAM_DATE_AMZ_CAMEL
   303  	dataHeader := HEADER_DATE_AMZ
   304  	if isObs {
   305  		dateCamelHeader = PARAM_DATE_OBS_CAMEL
   306  		dataHeader = HEADER_DATE_OBS
   307  	}
   308  	prepareDateHeader(dataHeader, dateCamelHeader, headers, _headers)
   309  
   310  	sort.Strings(keys)
   311  	stringToSign := getStringToSign(keys, isObs, _headers)
   312  	return strings.Join(stringToSign, "\n")
   313  }
   314  
   315  func getScope(region, shortDate string) string {
   316  	return fmt.Sprintf("%s/%s/%s/%s", shortDate, region, V4_SERVICE_NAME, V4_SERVICE_SUFFIX)
   317  }
   318  
   319  func getCredential(ak, region, shortDate string) (string, string) {
   320  	scope := getScope(region, shortDate)
   321  	return fmt.Sprintf("%s/%s", ak, scope), scope
   322  }
   323  
   324  func getSignedHeaders(headers map[string][]string) ([]string, map[string][]string) {
   325  	length := len(headers)
   326  	_headers := make(map[string][]string, length)
   327  	signedHeaders := make([]string, 0, length)
   328  	for key, value := range headers {
   329  		_key := strings.ToLower(strings.TrimSpace(key))
   330  		if _key != "" {
   331  			signedHeaders = append(signedHeaders, _key)
   332  			_headers[_key] = value
   333  		} else {
   334  			delete(headers, key)
   335  		}
   336  	}
   337  	sort.Strings(signedHeaders)
   338  	return signedHeaders, _headers
   339  }
   340  
   341  func getSignature(stringToSign, sk, region, shortDate string) string {
   342  	key := HmacSha256([]byte(V4_HASH_PRE+sk), []byte(shortDate))
   343  	key = HmacSha256(key, []byte(region))
   344  	key = HmacSha256(key, []byte(V4_SERVICE_NAME))
   345  	key = HmacSha256(key, []byte(V4_SERVICE_SUFFIX))
   346  	return Hex(HmacSha256(key, []byte(stringToSign)))
   347  }