diff --git a/sdk/auth/credentials/ali_appcode_credential.go b/sdk/auth/credentials/ali_appcode_credential.go new file mode 100644 index 0000000..a6a227a --- /dev/null +++ b/sdk/auth/credentials/ali_appcode_credential.go @@ -0,0 +1,13 @@ +package credentials + +type AliAppcodeCredential struct { + AccessKeyId string `json:"access_key_id" yaml:"access_key_id"` + AccessKeySecret string `json:"access_key_secret" yaml:"access_key_secret"` +} + +func NewAliAppcodeCredential(accessKeyId, accessKeySecret string) *AliAppcodeCredential { + return &AliAppcodeCredential{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + } +} diff --git a/sdk/auth/rali_signature_composer.go b/sdk/auth/rali_signature_composer.go new file mode 100644 index 0000000..e4fbd2c --- /dev/null +++ b/sdk/auth/rali_signature_composer.go @@ -0,0 +1,156 @@ +package auth + +import ( + "bytes" + "errors" + "fmt" + "golib.gaore.com/GaoreGo/gaore-common-sdk-go/sdk/requests" + "golib.gaore.com/GaoreGo/gaore-common-sdk-go/sdk/utils" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" +) + +func signRaliRequest(request requests.AcsRequest, signer Signer) (err error) { + err = completeRaliSignParams(request, signer) + if err != nil { + return + } + + stringToSign := buildRaliStringToSign(request) + request.SetStringToSign(stringToSign) + signature := signer.Sign(stringToSign, "") + request.GetHeaders()["X-Ca-Signature"] = signature + + debug("GrSdk sign: %s", signature) + debug("GrSdk sign string: %s", strings.ReplaceAll(stringToSign, "\n", "#")) + debug("GrSdk sign: \r\n") + return +} + +func completeRaliSignParams(request requests.AcsRequest, signer Signer) (err error) { + + request.GetHeaders()["X-Ca-Timestamp"] = fmt.Sprintf("%d", time.Now().Unix()*1000) + request.GetHeaders()["X-Ca-Signature-Method"] = signer.GetName() + request.GetHeaders()["X-Ca-Nonce"] = utils.GetUUID() + request.GetHeaders()["X-Ca-Key"], err = signer.GetAccessKeyId() + + if request.GetMethod() == requests.POST { + request.GetHeaders()["Content-type"] = requests.Form + } + + formString := utils.GetUrlFormedMap(request.GetFormParams()) + request.SetContent(bytes.NewBufferString(formString).Bytes()) + return +} + +func buildRaliSignHeader(request requests.AcsRequest) (str1, str2 string) { + + headParams := make(map[string]string) + signatureHeaders := make([]string, 0) + + for key, val := range request.GetHeaders() { + headParams[key] = val + } + + delete(headParams, "X-Ca-Signature") + delete(headParams, "X-Ca-Signature-Headers") + delete(headParams, "Accept") + delete(headParams, "Content-MD5") + delete(headParams, "Content-Type") + delete(headParams, "Date") + + for key, _ := range headParams { + signatureHeaders = append(signatureHeaders, key) + } + + sort.Strings(signatureHeaders) + + for _, key := range signatureHeaders { + str1 += fmt.Sprintf("%s:%s\n", key, headParams[key]) + } + + return strings.TrimRight(str1, "\n"), strings.Join(signatureHeaders, ",") +} + +func buildRaliStringToSign(request requests.AcsRequest) (stringToSign string) { + signParams := make(map[string]string) + for key, value := range request.GetQueryParams() { + signParams[key] = value + } + + if strings.ToUpper(request.GetMethod()) == requests.POST { + for key, value := range request.GetFormParams() { + signParams[key] = value + } + } + + str1, str2 := buildRaliSignHeader(request) + + stringToSign = request.GetMethod() + "\n" + stringToSign += request.GetHeaders()["Accept"] + "\n\n" + stringToSign += request.GetHeaders()["Content-type"] + "\n\n" + stringToSign += str1 + "\n" + stringToSign += request.GetActionName() + "?" + utils.GetUrlByKeySort(signParams) + request.GetHeaders()["X-Ca-Signature-Headers"] = str2 + + //stringToSign = utils.GetUrlFormedMap(signParams) + //stringToSign = strings.Replace(stringToSign, "+", "%20", -1) + //stringToSign = strings.Replace(stringToSign, "*", "%2A", -1) + //stringToSign = strings.Replace(stringToSign, "%7E", "~", -1) + //stringToSign = url.QueryEscape(stringToSign) + //stringToSign = request.GetMethod() + "&%2F&" + stringToSign + return +} + +func unsignRaliRequest(request *http.Request, signer Signer) (err error) { + signParams := make(map[string]string) + for key, value := range request.URL.Query() { + signParams[key] = value[0] + } + + if strings.ToUpper(request.Method) == requests.POST { + for key, value := range request.Form { + signParams[key] = value[0] + } + } + + if accessKey, err := signer.GetAccessKeyId(); err != nil { + return err + } else if accessKey == "" { + return errors.New("access key is not allow empty") + } else if accessKey != signParams["access_key"] { + return errors.New("illegal access key") + } + + signValue, ok := signParams["sign"] + if !ok { + return errors.New("sign value is not exists") + } else { + delete(signParams, "sign") + } + + stringToSign := utils.GetUrlFormedMap(signParams) + stringToSign = strings.Replace(stringToSign, "+", "%20", -1) + stringToSign = strings.Replace(stringToSign, "*", "%2A", -1) + stringToSign = strings.Replace(stringToSign, "%7E", "~", -1) + stringToSign = url.QueryEscape(stringToSign) + stringToSign = request.Method + "&%2F&" + stringToSign + debug("GrSdk sign: %s", stringToSign) + + if timestamp, err := strconv.ParseInt(signParams["access_time"], 10, 64); err != nil { + return err + } else { + if time.Unix(timestamp, 0).Before(time.Now().Add(-5 * time.Minute)) { + err = errors.New("sign timeout 5 minute") + } + } + + if signer.Sign(stringToSign, "&") != signValue { + return errors.New("sign string is not correct") + } + return +} diff --git a/sdk/auth/roa_signature_composer.go b/sdk/auth/roa_signature_composer.go new file mode 100644 index 0000000..342e99d --- /dev/null +++ b/sdk/auth/roa_signature_composer.go @@ -0,0 +1,128 @@ +package auth + +import ( + "bytes" + "errors" + "fmt" + "golib.gaore.com/GaoreGo/gaore-common-sdk-go/sdk/requests" + "golib.gaore.com/GaoreGo/gaore-common-sdk-go/sdk/utils" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +func signRoaRequest(request requests.AcsRequest, signer Signer) (err error) { + err = completeRoaSignParams(request, signer) + if err != nil { + return + } + if _, isContainsSign := request.GetQueryParams()["sign"]; isContainsSign { + delete(request.GetQueryParams(), "sign") + } + + stringToSign := buildRoaStringToSign(request) + request.SetStringToSign(stringToSign) + signature := signer.Sign(stringToSign, "&") + request.GetQueryParams()["sign"] = signature + debug("GrSdk sign: %s", signature) + debug("GrSdk sign string: %s", stringToSign) + debug("GrSdk sign: \r\n") + return +} + +func completeRoaSignParams(request requests.AcsRequest, signer Signer) (err error) { + + var accessKeyFrom string + if accessKeyFrom, err = signer.GetAccessKeyFrom(); err != nil { + return + } + + queryParams := request.GetQueryParams() + queryParams["access_time"] = fmt.Sprintf("%d", time.Now().Unix()) + queryParams["access_key"], err = signer.GetAccessKeyId() + queryParams["access_from"] = accessKeyFrom + + if err != nil { + return + } + + if request.GetMethod() == requests.POST { + request.GetHeaders()["Content-type"] = requests.Form + } + request.GetHeaders()["Gr-Sdk-From"] = accessKeyFrom + formString := utils.GetUrlFormedMap(request.GetFormParams()) + request.SetContent(bytes.NewBufferString(formString).Bytes()) + return +} + +func buildRoaStringToSign(request requests.AcsRequest) (stringToSign string) { + signParams := make(map[string]string) + for key, value := range request.GetQueryParams() { + signParams[key] = value + } + + if strings.ToUpper(request.GetMethod()) == requests.POST { + for key, value := range request.GetFormParams() { + signParams[key] = value + } + } + + stringToSign = utils.GetUrlFormedMap(signParams) + stringToSign = strings.Replace(stringToSign, "+", "%20", -1) + stringToSign = strings.Replace(stringToSign, "*", "%2A", -1) + stringToSign = strings.Replace(stringToSign, "%7E", "~", -1) + stringToSign = url.QueryEscape(stringToSign) + stringToSign = request.GetMethod() + "&%2F&" + stringToSign + return +} + +func unsignRoaRequest(request *http.Request, signer Signer) (err error) { + signParams := make(map[string]string) + for key, value := range request.URL.Query() { + signParams[key] = value[0] + } + + if strings.ToUpper(request.Method) == requests.POST { + for key, value := range request.Form { + signParams[key] = value[0] + } + } + + if accessKey, err := signer.GetAccessKeyId(); err != nil { + return err + } else if accessKey == "" { + return errors.New("access key is not allow empty") + } else if accessKey != signParams["access_key"] { + return errors.New("illegal access key") + } + + signValue, ok := signParams["sign"] + if !ok { + return errors.New("sign value is not exists") + } else { + delete(signParams, "sign") + } + + stringToSign := utils.GetUrlFormedMap(signParams) + stringToSign = strings.Replace(stringToSign, "+", "%20", -1) + stringToSign = strings.Replace(stringToSign, "*", "%2A", -1) + stringToSign = strings.Replace(stringToSign, "%7E", "~", -1) + stringToSign = url.QueryEscape(stringToSign) + stringToSign = request.Method + "&%2F&" + stringToSign + debug("GrSdk sign: %s", stringToSign) + + if timestamp, err := strconv.ParseInt(signParams["access_time"], 10, 64); err != nil { + return err + } else { + if time.Unix(timestamp, 0).Before(time.Now().Add(-5 * time.Minute)) { + err = errors.New("sign timeout 5 minute") + } + } + + if signer.Sign(stringToSign, "&") != signValue { + return errors.New("sign string is not correct") + } + return +} diff --git a/sdk/auth/signer.go b/sdk/auth/signer.go index 2b29ec5..71f262b 100644 --- a/sdk/auth/signer.go +++ b/sdk/auth/signer.go @@ -24,6 +24,8 @@ func NewSignerWithCredential(credential Credential, commonApi func(request *requ signer = signers.NewAccessKeySigner(instance.ToAccessKeyCredential()) case *credentials.StdTokenCredential: signer = signers.NewStsTokenSigner(instance) + case *credentials.AliAppcodeCredential: + signer = signers.NewAliAppcodeSigner(instance) default: err = errors.New("UnsupportedCredentialErrorCode = SDK.UnsupportedCredential") } @@ -31,8 +33,19 @@ func NewSignerWithCredential(credential Credential, commonApi func(request *requ } func Sign(request requests.AcsRequest, signer Signer) (err error) { + switch signer.(type) { + case *signers.AliAppcodeSigner: + err = signRaliRequest(request, signer) + return + } + //TODO 根据rpc和roa两种风格签名,自行选择 - err = signRpcRequest(request, signer) + switch request.GetStyle() { + case requests.RPC: + err = signRpcRequest(request, signer) + case requests.ROA: + err = signRoaRequest(request, signer) + } return } diff --git a/sdk/auth/signers/ali_appcode_signer.go b/sdk/auth/signers/ali_appcode_signer.go new file mode 100644 index 0000000..2ec4c50 --- /dev/null +++ b/sdk/auth/signers/ali_appcode_signer.go @@ -0,0 +1,43 @@ +package signers + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "golib.gaore.com/GaoreGo/gaore-common-sdk-go/sdk/auth/credentials" +) + +type AliAppcodeSigner struct { + credential *credentials.AliAppcodeCredential +} + +func (signer *AliAppcodeSigner) GetName() string { + return "HmacSHA256" +} + +func (signer *AliAppcodeSigner) GetAccessKeyId() (string, error) { + return signer.credential.AccessKeyId, nil +} + +func (signer *AliAppcodeSigner) GetAccessKeyFrom() (string, error) { + return "", nil +} + +func (signer *AliAppcodeSigner) Sign(stringToSign, secretSuffix string) string { + secret := signer.credential.AccessKeySecret + secretSuffix + return ShaHmac256(stringToSign, secret) +} + +func NewAliAppcodeSigner(credential *credentials.AliAppcodeCredential) *AliAppcodeSigner { + return &AliAppcodeSigner{ + credential: credential, + } +} + +func ShaHmac256(source, secret string) string { + key := []byte(secret) + hmac1 := hmac.New(sha256.New, key) + hmac1.Write([]byte(source)) + signedString := base64.StdEncoding.EncodeToString(hmac1.Sum(nil)) + return signedString +} diff --git a/sdk/client.go b/sdk/client.go index e638a59..de6413d 100644 --- a/sdk/client.go +++ b/sdk/client.go @@ -93,6 +93,12 @@ func (client *Client) InitWithAccessKey(accessKeyId, accessKeySecret, accessKeyF return client.InitWithOptions(config, credential) } +func (client *Client) InitWithAliAppcode(accessKeyId, accessKeySecret string) (err error) { + config := client.InitWithConfig() + credential := credentials.NewAliAppcodeCredential(accessKeyId, accessKeySecret) + return client.InitWithOptions(config, credential) +} + func (client *Client) InitWithStsToken(accessKeyId, accessKeySecret, accessKeyFrom string) (err error) { config := client.InitWithConfig() credential := &credentials.StdTokenCredential{ diff --git a/sdk/requests/common_request.go b/sdk/requests/common_request.go index cbca595..7d5f653 100644 --- a/sdk/requests/common_request.go +++ b/sdk/requests/common_request.go @@ -26,3 +26,11 @@ func (request *CommonRequest) BuildQueries() string { func (request *CommonRequest) GetBodyReader() io.Reader { return request.Ontology.GetBodyReader() } + +func (request *CommonRequest) GetStyle() string { + return request.Ontology.GetStyle() +} + +func (request *CommonRequest) InitWithApiInfo(domain, version, urlPath string) { + request.Ontology.InitWithApiInfo(domain, version, urlPath) +} diff --git a/sdk/requests/request.go b/sdk/requests/request.go index 67ee426..8ae7217 100644 --- a/sdk/requests/request.go +++ b/sdk/requests/request.go @@ -64,7 +64,10 @@ type AcsRequest interface { GetDomain() string GetActionName() string GetAcceptFormat() string + GetAccept() string GetHeaders() map[string]string + GetStyle() string + InitWithApiInfo(domain, version, urlPath string) BuildUrl() string BuildQueries() string @@ -119,6 +122,16 @@ func (request *baseRequest) GetAcceptFormat() string { return request.AcceptFormat } +func (request *baseRequest) GetAccept() string { + switch request.GetAcceptFormat() { + case JSON: + return Json + case XML: + return Xml + } + return "" +} + func (request *baseRequest) GetHeaders() map[string]string { return request.Headers } diff --git a/sdk/requests/rpc_request.go b/sdk/requests/rpc_request.go index 3c31614..712b948 100644 --- a/sdk/requests/rpc_request.go +++ b/sdk/requests/rpc_request.go @@ -24,6 +24,10 @@ func (request *RpcRequest) BuildUrl() string { return url + request.BuildQueries() } +func (request *RpcRequest) GetStyle() string { + return RPC +} + func (request *RpcRequest) BuildQueries() string { path := strings.TrimLeft(strings.TrimSpace(request.GetActionName()), "/") request.queries = "/" + path + "?" + utils.GetUrlFormedMap(request.QueryParams) @@ -34,12 +38,11 @@ func (request *RpcRequest) GetActionName() string { return request.actionName } -func (request *RpcRequest) InitWithApiInfo(domain, version, urlPath string) *RpcRequest { +func (request *RpcRequest) InitWithApiInfo(domain, version, urlPath string) { request.init() request.SetDomain(domain) request.version = version request.actionName = urlPath - return request } func (request *RpcRequest) GetBodyReader() io.Reader { diff --git a/sdk/utils/utils.go b/sdk/utils/utils.go index b8b6125..c655962 100644 --- a/sdk/utils/utils.go +++ b/sdk/utils/utils.go @@ -1,14 +1,63 @@ package utils import ( + "crypto/md5" + "crypto/rand" + "encoding/hex" "fmt" + "hash" + rand2 "math/rand" "net/url" "reflect" + "sort" "strconv" "strings" "time" ) +type UUID [16]byte + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +func GetUUID() (uuidHex string) { + uuid := NewUUID() + uuidHex = hex.EncodeToString(uuid[:]) + return +} + +func NewUUID() UUID { + ns := UUID{} + safeRandom(ns[:]) + u := newFromHash(md5.New(), ns, RandStringBytes(16)) + u[6] = (u[6] & 0x0f) | (byte(2) << 4) + u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) + + return u +} + +func RandStringBytes(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand2.Intn(len(letterBytes))] + } + return string(b) +} + +func newFromHash(h hash.Hash, ns UUID, name string) UUID { + u := UUID{} + h.Write(ns[:]) + h.Write([]byte(name)) + copy(u[:], h.Sum(nil)) + + return u +} + +func safeRandom(dest []byte) { + if _, err := rand.Read(dest); err != nil { + panic(err) + } +} + func GetUrlFormedMap(source map[string]string) (urlEncoded string) { urlEncoder := url.Values{} for key, value := range source { @@ -18,6 +67,18 @@ func GetUrlFormedMap(source map[string]string) (urlEncoded string) { return } +func GetUrlByKeySort(source map[string]string) (url string) { + keys := make([]string, 0, len(source)) + for k := range source { + keys = append(keys, k) + } + sort.Strings(keys) + for _, key := range keys { + url += fmt.Sprintf("%s=%s&", key, source[key]) + } + return strings.TrimRight(url, "&") +} + func InitStructWithDefaultTag(bean interface{}) { beantype := reflect.TypeOf(bean) for i := 0; i < beantype.Elem().NumField(); i++ { diff --git a/services/jedi/client.go b/services/jedi/client.go index 4c6ddd1..84a1a0e 100644 --- a/services/jedi/client.go +++ b/services/jedi/client.go @@ -6,7 +6,7 @@ import ( ) const ( - HOST = "jedi" + HOST = "jedi.oapi.gaore.com" VERSION = "2020-08-04" ) @@ -34,3 +34,9 @@ func NewClientWithAccessKey(accesskey, secrect, source string) (client *Client, err = client.InitWithAccessKey(accesskey, secrect, source) return } + +func NewClientWithAliAppcode(accesskey, secrect string) (client *Client, err error) { + client = &Client{} + err = client.InitWithAliAppcode(accesskey, secrect) + return +} diff --git a/services/jedi/send_sms.go b/services/jedi/send_sms.go index 54fe727..d9c1c87 100644 --- a/services/jedi/send_sms.go +++ b/services/jedi/send_sms.go @@ -7,9 +7,9 @@ import ( type SendSmsRequest struct { *requests.RpcRequest - User string `position:"Query" field:"user" default:"" ` - Code string `position:"Query" field:"code" default:"" ` - Params string `position:"Query" field:"params" default:"" ` + User string `position:"Body" field:"user" default:"" ` + Code string `position:"Body" field:"code" default:"" ` + Params string `position:"Body" field:"params" default:"" ` ParamsArray []string } @@ -28,7 +28,7 @@ func CreateSendSmsRequest() (req *SendSmsRequest) { RpcRequest: &requests.RpcRequest{}, } req.InitWithApiInfo(HOST, VERSION, "/api/sms/send") - req.Method = requests.GET + req.Method = requests.POST return }