From 72d858539ff134b61344559eecdeb64c3cce6847 Mon Sep 17 00:00:00 2001 From: lwl0608 Date: Mon, 27 May 2024 16:55:55 +0800 Subject: [PATCH] vendor --- .../yop-platform/yop-go-sdk/LICENSE | 201 ++++++++++++++++++ .../yop-go-sdk/yop/auth/yop_signer.go | 124 +++++++++++ .../yop-go-sdk/yop/client/yop_client.go | 183 ++++++++++++++++ .../yop-go-sdk/yop/constants/constants.go | 37 ++++ .../yop-go-sdk/yop/request/yop_request.go | 175 +++++++++++++++ .../yop/response/http_response_analyzer.go | 85 ++++++++ .../yop-go-sdk/yop/response/yop_response.go | 37 ++++ .../yop/response/yop_service_error.go | 19 ++ .../yop/utils/callback_decrypt_utils.go | 77 +++++++ .../yop-go-sdk/yop/utils/http_utils.go | 73 +++++++ .../yop-go-sdk/yop/utils/json_utils.go | 13 ++ .../yop-go-sdk/yop/utils/rsa_utils.go | 122 +++++++++++ 12 files changed, 1146 insertions(+) create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/LICENSE create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/auth/yop_signer.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/client/yop_client.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/constants/constants.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/request/yop_request.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/response/http_response_analyzer.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/response/yop_response.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/response/yop_service_error.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/utils/callback_decrypt_utils.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/utils/http_utils.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/utils/json_utils.go create mode 100644 vendor/github.com/yop-platform/yop-go-sdk/yop/utils/rsa_utils.go diff --git a/vendor/github.com/yop-platform/yop-go-sdk/LICENSE b/vendor/github.com/yop-platform/yop-go-sdk/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/auth/yop_signer.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/auth/yop_signer.go new file mode 100644 index 00000000..e891c8f6 --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/auth/yop_signer.go @@ -0,0 +1,124 @@ +// Package auth +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/20 2:43 PM +package auth + +import ( + "crypto" + "crypto/sha256" + "fmt" + "github.com/yop-platform/yop-go-sdk/yop/constants" + "github.com/yop-platform/yop-go-sdk/yop/request" + "github.com/yop-platform/yop-go-sdk/yop/utils" + "log" + "regexp" + "sort" + "strconv" + "strings" + "time" +) + +var FormatISOTime = "2006-01-02T15:04:05Z" +var DEFAULT_HEADERS_TO_SIGN []string = []string{constants.YOP_APPKEY_HEADER_KEY, constants.YOP_REQUEST_ID, constants.YOP_CONTENT_SHA256} + +type YopSigner interface { + // SignRequest 请求报文签名 + SignRequest(yopRequest request.YopRequest) + + // VerifyResponse 响应报文验签 + VerifyResponse(content string, signature string, pubKey request.PlatformPubKey) bool +} + +type RsaSigner struct { +} + +func (signer *RsaSigner) SignRequest(yopRequest request.YopRequest) { + var authString = buildAuthString(yopRequest.AppId) + log.Println("authString:" + authString) + + var contentHash = calculateContentHash(yopRequest) + log.Println("contentHash:" + contentHash) + yopRequest.Headers[constants.YOP_CONTENT_SHA256] = contentHash + + var headerToSign = getHeaderToSign(yopRequest) + var canonicalRequest = buildCanonicalRequest(yopRequest, authString, headerToSign) + log.Println("canonicalRequest:" + canonicalRequest) + + signature, _ := utils.RsaSignBase64(canonicalRequest, yopRequest.IsvPriKey.Value, crypto.SHA256) + signature += "$" + "SHA256" + log.Println("signature:" + signature) + var authorizationHeader = buildAuthzHeader(authString, signature, headerToSign) + log.Println("Authorization:" + authorizationHeader) + yopRequest.Headers[constants.AUTHORIZATION] = authorizationHeader +} + +func (signer *RsaSigner) VerifyResponse(content string, signature string, pubKey request.PlatformPubKey) bool { + re := regexp.MustCompile("[ \t\n]") + content = re.ReplaceAllString(content, "") + return utils.VerifySign(content, signature, pubKey.Value, crypto.SHA256) +} + +func calculateContentHash(yopRequest request.YopRequest) string { + var encodedParameters = "" + if utils.UsePayloadForQueryParameters(yopRequest) { + encodedParameters = utils.GetCanonicalQueryString(yopRequest.Params) + } else { + encodedParameters = yopRequest.Content + } + log.Println("encodedParameters:" + encodedParameters) + return fmt.Sprintf("%x", sha256.Sum256([]byte(encodedParameters))) +} + +func buildCanonicalRequest(yopRequest request.YopRequest, authString string, headerToSign []string) string { + var canonicalQueryString = getCanonicalQueryString(yopRequest) + var canonicalURI = getCanonicalURIPath(yopRequest.ApiUri) + return authString + "\n" + yopRequest.HttpMethod + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + getCanonicalHeaders(yopRequest, headerToSign) +} +func buildAuthString(appId string) string { + var t = time.Now() + return constants.DEFAULT_YOP_PROTOCOL_VERSION + "/" + appId + "/" + t.Format(FormatISOTime) + "/" + strconv.Itoa(constants.DEFAULT_EXPIRATION_IN_SECONDS) +} + +func getCanonicalQueryString(yopRequest request.YopRequest) string { + if utils.UsePayloadForQueryParameters(yopRequest) { + return "" + } + return utils.GetCanonicalQueryString(yopRequest.Params) +} + +func getCanonicalURIPath(path string) string { + if 0 == len(path) { + return "/" + } else if strings.HasPrefix(path, "/") { + return utils.NormalizePath(path) + } else { + return "/" + utils.NormalizePath(path) + } +} + +func getHeaderToSign(yopRequest request.YopRequest) []string { + var result []string + for header := range DEFAULT_HEADERS_TO_SIGN { + var value = yopRequest.Headers[DEFAULT_HEADERS_TO_SIGN[header]] + if 0 != len(value) { + result = append(result, DEFAULT_HEADERS_TO_SIGN[header]) + } + } + sort.Strings(result) + return result +} + +func getCanonicalHeaders(yopRequest request.YopRequest, headerToSign []string) string { + var headerStrings []string + for header := range headerToSign { + headerStrings = append(headerStrings, utils.Normalize(headerToSign[header])+":"+utils.Normalize(yopRequest.Headers[headerToSign[header]])) + } + sort.Strings(headerStrings) + return strings.Join(headerStrings, "\n") +} + +func buildAuthzHeader(authString string, signature string, headerToSign []string) string { + return "YOP-RSA2048-SHA256" + " " + authString + "/" + strings.Join(headerToSign, ";") + "/" + signature +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/client/yop_client.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/client/yop_client.go new file mode 100644 index 00000000..4283a248 --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/client/yop_client.go @@ -0,0 +1,183 @@ +// Package client +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/16 3:22 PM +package client + +import ( + "bytes" + "context" + "errors" + uuid "github.com/satori/go.uuid" + "github.com/yop-platform/yop-go-sdk/yop/auth" + "github.com/yop-platform/yop-go-sdk/yop/constants" + "github.com/yop-platform/yop-go-sdk/yop/request" + "github.com/yop-platform/yop-go-sdk/yop/response" + "github.com/yop-platform/yop-go-sdk/yop/utils" + "io" + "io/ioutil" + "log" + "mime/multipart" + "net/http" + "net/url" + "runtime" + "strings" + "time" +) + +var DefaultClient = YopClient{&http.Client{Transport: http.DefaultTransport}} + +type YopClient struct { + *http.Client +} + +// Request 普通请求 +func (yopClient *YopClient) Request(request *request.YopRequest) (*response.YopResponse, error) { + initRequest(request) + var signer = auth.RsaSigner{} + signer.SignRequest(*request) + + httpRequest, err := buildHttpRequest(*request) + if nil != err { + return nil, err + } + httpResp, err := yopClient.Client.Do(&httpRequest) + if nil != err { + return nil, err + } + defer httpResp.Body.Close() + body, err := ioutil.ReadAll(httpResp.Body) + if nil != err { + return nil, err + } + var yopResponse = response.YopResponse{Content: body} + metaData := response.YopResponseMetadata{} + metaData.YopSign = httpResp.Header.Get("X-Yop-Sign") + metaData.YopRequestId = httpResp.Header.Get("X-Yop-Request-Id") + yopResponse.Metadata = &metaData + context := response.RespHandleContext{YopSigner: &signer, YopResponse: &yopResponse, YopRequest: *request} + for i := range response.ANALYZER_CHAIN { + err = response.ANALYZER_CHAIN[i].Analyze(context, httpResp) + if nil != err { + return nil, err + } + } + return &yopResponse, nil +} +func initRequest(yopRequest *request.YopRequest) { + yopRequest.RequestId = uuid.NewV4().String() + log.Println("requestId:" + yopRequest.RequestId) + if 0 == len(yopRequest.ServerRoot) { + yopRequest.HandleServerRoot() + } + if 0 == len(yopRequest.PlatformPubKey.Value) { + yopRequest.PlatformPubKey.Value = request.YOP_PLATFORM_PUBLIC_KEY + yopRequest.PlatformPubKey.CertType = request.RSA2048 + } + addStandardHeaders(yopRequest) +} +func addStandardHeaders(yopRequest *request.YopRequest) { + yopRequest.Headers = map[string]string{} + yopRequest.Headers[constants.YOP_REQUEST_ID] = yopRequest.RequestId + yopRequest.Headers[constants.YOP_APPKEY_HEADER_KEY] = yopRequest.AppId + yopRequest.Headers[constants.USER_AGENT_HEADER_KEY] = buildUserAgent() +} + +func buildUserAgent() string { + return "go" + "/" + constants.SDK_VERSION + "/" + runtime.GOOS + "/" + runtime.Version() +} + +func buildHttpRequest(yopRequest request.YopRequest) (http.Request, error) { + if yopRequest.Timeout == 0 { + yopRequest.Timeout = 10 * time.Second + } + ctx, _ := context.WithTimeout(context.Background(), yopRequest.Timeout) + //defer cancel() + + var uri = yopRequest.ServerRoot + yopRequest.ApiUri + isMultiPart, err := checkForMultiPart(yopRequest) + if nil != err { + return http.Request{}, err + } + var result http.Request + if isMultiPart { + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + + for k, v := range yopRequest.Params { + for i := range v { + bodyWriter.WriteField(k, url.QueryEscape(v[i])) + } + } + + for k, v := range yopRequest.Files { + fileWriter, _ := bodyWriter.CreateFormFile(k, v.Name()) + io.Copy(fileWriter, v) + } + bodyWriter.Close() + + if err != nil { + return http.Request{}, err + } + req, err := http.NewRequestWithContext(ctx, "POST", uri, bodyBuf) + if nil != err { + return http.Request{}, err + } + req.Header.Set("Content-Type", bodyWriter.FormDataContentType()) + result = *req + } else { + var encodedParam = utils.EncodeParameters(yopRequest.Params) + var requestHasPayload = 0 < len(yopRequest.Content) + var requestIsPost = 0 == strings.Compare(constants.POST_HTTP_METHOD, yopRequest.HttpMethod) + var putParamsInUri = !requestIsPost || requestHasPayload + if 0 < len(encodedParam) && putParamsInUri { + uri += "?" + encodedParam + } + var body io.Reader = nil + if 0 == strings.Compare(constants.POST_HTTP_METHOD, yopRequest.HttpMethod) { + if 0 < len(yopRequest.Content) { + body = bytes.NewBuffer([]byte(yopRequest.Content)) + } else { + formValues := url.Values{} + for k, v := range yopRequest.Params { + for i := range v { + formValues.Set(k, url.QueryEscape(v[i])) + } + } + formDataStr := formValues.Encode() + body = bytes.NewBuffer([]byte(formDataStr)) + } + } + httpRequest, err := http.NewRequestWithContext(ctx, yopRequest.HttpMethod, uri, body) + if err != nil { + return http.Request{}, err + } + result = *httpRequest + result.Header.Set(constants.CONTENT_TYPE, getContentType(yopRequest)) + } + for k, v := range yopRequest.Headers { + result.Header.Set(k, v) + } + return result, err +} + +func checkForMultiPart(yopRequest request.YopRequest) (bool, error) { + var result = nil != yopRequest.Files && 0 < len(yopRequest.Files) + if result && 0 != strings.Compare(constants.POST_HTTP_METHOD, yopRequest.HttpMethod) { + var errorMsg = "ContentType:multipart/form-data only support Post Request" + log.Fatal(errorMsg) + return false, errors.New(errorMsg) + } + return result, nil +} + +func getContentType(yopRequest request.YopRequest) string { + if 0 == strings.Compare("POST", yopRequest.HttpMethod) && 0 < len(yopRequest.Content) { + return constants.YOP_HTTP_CONTENT_TYPE_JSON + } + if 0 < len(yopRequest.Params) { + return constants.YOP_HTTP_CONTENT_TYPE_FORM + } + return constants.YOP_HTTP_CONTENT_TYPE_FORM +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/constants/constants.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/constants/constants.go new file mode 100644 index 00000000..eb4929fd --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/constants/constants.go @@ -0,0 +1,37 @@ +// Package constants +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/21 3:24 PM +package constants + +const ( + DEFAULT_YOP_PROTOCOL_VERSION = "yop-auth-v3" + DEFAULT_EXPIRATION_IN_SECONDS = 1800 + YOP_CONTENT_SHA256 = "x-yop-content-sha256" + AUTHORIZATION = "Authorization" + YOP_REQUEST_ID = "x-yop-request-id" + YOP_APPKEY_HEADER_KEY = "x-yop-appkey" + YOP_SIGN_HEADER_KEY = "x-yop-sign" + USER_AGENT_HEADER_KEY = "User-Agent" + CONTENT_TYPE = "Content-Type" + DEFAULT_USER_AGENT = "" + YOP_SIGN = "x-yop-sign" + YOP_SIGN_CERT_SERIAL_NO = "x-yop-sign-serial-no" + DATE = "Date" + YOP_HASH_CRC64ECMA = "x-yop-hash-crc64ecma" + + YOP_HTTP_CONTENT_TYPE_JSON = "application/json" + YOP_HTTP_CONTENT_TYPE_FORM = "application/x-www-form-urlencoded;charset=utf-8" + YOP_HTTP_CONTENT_TYPE_MULTIPART_FORM = "multipart/form-data" + YOP_HTTP_CONTENT_TYPE_STREAM = "application/octet-stream" + YOP_HTTP_CONTENT_TYPE_TEXT = "text/plain;charset=UTF-8" + + POST_HTTP_METHOD = "POST" + GET_HTTP_METHOD = "GET" + + SC_OK = 200 + SC_NO_CONTENT = 204 + + SDK_VERSION = "4.3.6" +) diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/request/yop_request.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/request/yop_request.go new file mode 100644 index 00000000..e2c4c515 --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/request/yop_request.go @@ -0,0 +1,175 @@ +// Package request +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/16 3:22 PM +package request + +import ( + "encoding/json" + "fmt" + uuid "github.com/satori/go.uuid" + "html/template" + "log" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +const ( + SERVER_ROOT = "https://openapi.yeepay.com/yop-center" + YOS_SERVER_ROOT = "https://yos.yeepay.com/yop-center" + RSA2048 = "RSA2048" + YOP_PLATFORM_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6p0XWjscY+gsyqKRhw9MeLsEmhFdBRhT2emOck/F1Omw38ZWhJxh9kDfs5HzFJMrVozgU+SJFDONxs8UB0wMILKRmqfLcfClG9MyCNuJkkfm0HFQv1hRGdOvZPXj3Bckuwa7FrEXBRYUhK7vJ40afumspthmse6bs6mZxNn/mALZ2X07uznOrrc2rk41Y2HftduxZw6T4EmtWuN2x4CZ8gwSyPAW5ZzZJLQ6tZDojBK4GZTAGhnn3bg5bBsBlw2+FLkCQBuDsJVsFPiGh/b6K/+zGTvWyUcu+LUj2MejYQELDO3i2vQXVDk7lVi2/TcUYefvIcssnzsfCfjaorxsuwIDAQAB" +) + +type YopRequest struct { + // 服务地址,一般情况无需指定 + ServerRoot string + RequestId string + ApiUri string + HttpMethod string + AppId string + IsvPriKey IsvPriKey + // 平台公钥,一般情况无需指定 + PlatformPubKey PlatformPubKey + // form请求的参数 + Params map[string][]string + // json请求参数 + Content string + // 请求头 + Headers map[string]string + // 文件 + Files map[string]*os.File + + // 超时时间 + Timeout time.Duration +} + +// NewYopRequest 创建请求 +func NewYopRequest(httpMethod string, apiUri string) *YopRequest { + return &YopRequest{HttpMethod: httpMethod, ApiUri: apiUri, Timeout: 10 * time.Second} +} + +func (request *YopRequest) AddParam(name string, value any) { + if nil == request.Params { + request.Params = map[string][]string{} + } + var strValue = ToStringE(value) + var paramArray = []string{strValue} + request.Params[name] = paramArray +} + +func (request *YopRequest) AddFile(name string, f *os.File) { + if nil == request.Files { + request.Files = map[string]*os.File{} + } + request.Files[name] = f +} + +type IsvPriKey struct { + // 密钥类型:RSA2048 + CertType string + // 私钥值 + Value string +} + +type PlatformPubKey struct { + // 密钥类型:RSA2048 + CertType string + // 公钥值 + Value string +} + +func BuildYopRequest() *YopRequest { + var isvPriKey = IsvPriKey{CertType: RSA2048} + var platformCert = PlatformPubKey{Value: YOP_PLATFORM_PUBLIC_KEY, CertType: RSA2048} + return &YopRequest{RequestId: uuid.NewV4().String(), IsvPriKey: isvPriKey, PlatformPubKey: platformCert, Params: map[string][]string{}, Headers: map[string]string{}, Files: map[string]*os.File{}} +} + +func (request *YopRequest) HandleServerRoot() { + if 0 != len(request.ServerRoot) { + return + } + + if 0 != len(request.ApiUri) || strings.HasPrefix(request.ApiUri, "/yos") { + request.ServerRoot = YOS_SERVER_ROOT + } + request.ServerRoot = SERVER_ROOT + +} + +var ( + errorType = reflect.TypeOf((*error)(nil)).Elem() + fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() +) + +func ToStringE(i any) string { + i = indirectToStringerOrError(i) + switch s := i.(type) { + case string: + return s + case bool: + return strconv.FormatBool(s) + case float64: + return strconv.FormatFloat(s, 'f', -1, 64) + case float32: + return strconv.FormatFloat(float64(s), 'f', -1, 32) + case int: + return strconv.Itoa(s) + case int64: + return strconv.FormatInt(s, 10) + case int32: + return strconv.Itoa(int(s)) + case int16: + return strconv.FormatInt(int64(s), 10) + case int8: + return strconv.FormatInt(int64(s), 10) + case uint: + return strconv.FormatUint(uint64(s), 10) + case uint64: + return strconv.FormatUint(uint64(s), 10) + case uint32: + return strconv.FormatUint(uint64(s), 10) + case uint16: + return strconv.FormatUint(uint64(s), 10) + case uint8: + return strconv.FormatUint(uint64(s), 10) + case json.Number: + return s.String() + case []byte: + return string(s) + case template.HTML: + return string(s) + case template.URL: + return string(s) + case template.JS: + return string(s) + case template.CSS: + return string(s) + case template.HTMLAttr: + return string(s) + case nil: + return "" + case fmt.Stringer: + return s.String() + case error: + return s.Error() + default: + log.Fatal(fmt.Sprintf("unable to cast %#v of type %T to string", i, i)) + return "" + } +} + +func indirectToStringerOrError(a any) any { + if a == nil { + return nil + } + v := reflect.ValueOf(a) + for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Pointer && !v.IsNil() { + v = v.Elem() + } + return v.Interface() +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/response/http_response_analyzer.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/response/http_response_analyzer.go new file mode 100644 index 00000000..b7136684 --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/response/http_response_analyzer.go @@ -0,0 +1,85 @@ +// Package response +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/22 10:54 PM +package response + +import ( + "encoding/json" + "errors" + "github.com/yop-platform/yop-go-sdk/yop/constants" + "log" + "net/http" + "strconv" + "strings" + "time" +) + +var ANALYZER_CHAIN = []HttpResponseAnalyzer{ + &YopMetadataResponseAnalyzer{}, + &YopSignatureCheckAnalyzer{}, + &YopErrorResponseAnalyzer{}, + &YopJsonResponseAnalyzer{}, +} + +type HttpResponseAnalyzer interface { + Analyze(context RespHandleContext, httpResponse *http.Response) error +} + +type YopMetadataResponseAnalyzer struct { +} + +func (yopMetadataResponseAnalyzer *YopMetadataResponseAnalyzer) Analyze(context RespHandleContext, httpResponse *http.Response) error { + var metadata = YopResponseMetadata{} + metadata.YopRequestId = httpResponse.Header.Get(constants.YOP_REQUEST_ID) + metadata.YopContentSha256 = httpResponse.Header.Get(constants.YOP_CONTENT_SHA256) + metadata.YopSign = httpResponse.Header.Get(constants.YOP_SIGN) + metadata.ContentType = httpResponse.Header.Get(constants.CONTENT_TYPE) + d, _ := time.Parse(time.RFC1123, httpResponse.Header.Get(constants.DATE)) + metadata.Date = d + metadata.YopCertSerialNo = httpResponse.Header.Get(constants.YOP_SIGN_CERT_SERIAL_NO) + metadata.Crc64ECMA = httpResponse.Header.Get(constants.YOP_HASH_CRC64ECMA) + context.YopResponse.Metadata = &metadata + return nil +} + +type YopSignatureCheckAnalyzer struct { +} + +func (yopSignatureCheckAnalyzer *YopSignatureCheckAnalyzer) Analyze(context RespHandleContext, httpResponse *http.Response) error { + var signature = context.YopResponse.Metadata.YopSign + if 0 < len(signature) { + if !context.YopSigner.VerifyResponse(string(context.YopResponse.Content), signature, context.YopRequest.PlatformPubKey) { + return errors.New("response sign verify failure") + } + } + return nil +} + +type YopErrorResponseAnalyzer struct { +} + +func (yopErrorResponseAnalyzer *YopErrorResponseAnalyzer) Analyze(context RespHandleContext, httpResponse *http.Response) error { + var statusCode = httpResponse.StatusCode + log.Println("statusCode:" + strconv.Itoa(statusCode)) + if statusCode/100 == constants.SC_OK && statusCode != constants.SC_NO_CONTENT { + return nil + } + var yopServiceError = YopServiceError{} + json.Unmarshal(context.YopResponse.Content, &yopServiceError) + if 0 < len(yopServiceError.Message) { + return &yopServiceError + } + return nil +} + +type YopJsonResponseAnalyzer struct { +} + +func (yopJsonResponseAnalyzer *YopJsonResponseAnalyzer) Analyze(context RespHandleContext, httpResponse *http.Response) error { + if 0 < len(context.YopResponse.Content) && strings.HasPrefix(context.YopResponse.Metadata.ContentType, constants.YOP_HTTP_CONTENT_TYPE_JSON) { + json.Unmarshal(context.YopResponse.Content, &context.YopResponse) + } + return nil +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/response/yop_response.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/response/yop_response.go new file mode 100644 index 00000000..c463990e --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/response/yop_response.go @@ -0,0 +1,37 @@ +// Package response +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/16 3:22 PM +package response + +import ( + "github.com/yop-platform/yop-go-sdk/yop/auth" + "github.com/yop-platform/yop-go-sdk/yop/request" + "time" +) + +type YopResponse struct { + Metadata *YopResponseMetadata + Result any + // http请求收到的原始响应体 + // 若接口类型为文件下载,则该值为文件内容 + Content []byte +} + +type YopResponseMetadata struct { + YopRequestId string + YopContentSha256 string + YopSign string + ContentType string + Date time.Time + Server string + YopCertSerialNo string + Crc64ECMA string +} + +type RespHandleContext struct { + auth.YopSigner + *YopResponse + request.YopRequest +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/response/yop_service_error.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/response/yop_service_error.go new file mode 100644 index 00000000..89ecac4d --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/response/yop_service_error.go @@ -0,0 +1,19 @@ +// Package response +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/22 11:09 PM +package response + +type YopServiceError struct { + RequestId string + Code string + Message string + SubCode string + SubMessage string + DocUrl string +} + +func (err *YopServiceError) Error() string { + return err.Message + "(Error Code: " + err.Code + "; Sub Code: " + err.SubCode + "; Sub Message: " + err.SubMessage + "; Request ID: " + err.RequestId + "; docUrl: " + err.DocUrl + ")" +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/callback_decrypt_utils.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/callback_decrypt_utils.go new file mode 100644 index 00000000..9b9f2ad4 --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/callback_decrypt_utils.go @@ -0,0 +1,77 @@ +// Package utils +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/15 2:22 PM +package utils + +import ( + "crypto" + "crypto/aes" + "encoding/base64" + "errors" + "log" + "strings" +) + +// DecryptCallback 解密回调通知内容 +func DecryptCallback(platformPubKey string, isvPriKey string, callBack string) (string, error) { + cipherText := strings.Split(callBack, "$") + if len(cipherText) != 4 { + return "", errors.New("response invalid") + } + randomKey, err := RsaDecrypt(isvPriKey, cipherText[0]) + if err != nil { + log.Println("random key rsa error ", err) + return "", err + } + cipherBytes := base64Decode(cipherText[1]) + body := string(AesDecryptECB(cipherBytes, randomKey)) + dollarPosition := strings.LastIndex(body, "$") + signature := strings.TrimSpace(body[dollarPosition+1:]) + body = body[:dollarPosition] + + if !VerifySign(body, signature, platformPubKey, crypto.SHA256) { + return "", errors.New("rsa sign verify fail") + } + return body, nil +} + +// 解析非标 base64_encode +func base64Decode(b string) []byte { + b = strings.Replace(b, "-", "+", -1) + b = strings.Replace(b, "_", "/", -1) + r, err := base64.RawStdEncoding.DecodeString(b) + if err != nil { + log.Println("base64 decode error ", err) + return nil + } + + return r +} +func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) { + cipher, _ := aes.NewCipher(generateKey(key)) + decrypted = make([]byte, len(encrypted)) + // + for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { + cipher.Decrypt(decrypted[bs:be], encrypted[bs:be]) + } + + trim := 0 + if len(decrypted) > 0 { + trim = len(decrypted) - int(decrypted[len(decrypted)-1]) + } + + return decrypted[:trim] +} + +func generateKey(key []byte) (genKey []byte) { + genKey = make([]byte, 16) + copy(genKey, key) + for i := 16; i < len(key); { + for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 { + genKey[j] ^= key[i] + } + } + return genKey +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/http_utils.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/http_utils.go new file mode 100644 index 00000000..52ec2fa4 --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/http_utils.go @@ -0,0 +1,73 @@ +// Package utils +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/20 4:15 PM +package utils + +import ( + "github.com/yop-platform/yop-go-sdk/yop/request" + "net/url" + "sort" + "strings" +) + +func NormalizePath(path string) string { + + return strings.ReplaceAll(Normalize(path), "%2F", "/") +} + +func Normalize(value string) string { + var firstEncodeStr = url.QueryEscape(value) + return encodeSpecialChar(firstEncodeStr) +} + +func encodeSpecialChar(str string) string { + // 空格 + str = strings.ReplaceAll(str, "+", "%20") + return str +} + +func EncodeParameters(params map[string][]string) string { + if 0 == len(params) { + return "" + } + var encodedNameValuePair []string + for k, v := range params { + for i := range v { + encodedNameValuePair = append(encodedNameValuePair, toNameValuePair(k, v[i])) + } + } + return strings.Join(encodedNameValuePair, "&") +} + +func toNameValuePair(paramName string, paramValue string) string { + return Normalize(paramName) + "=" + Normalize(paramValue) +} + +func GetCanonicalQueryString(params map[string][]string) string { + if 0 == len(params) { + return "" + } + + var parameterStrings []string + + for k, v := range params { + if nil == v || 0 == len(v) { + parameterStrings = append(parameterStrings, Normalize(k)+"=") + } else { + for i := range v { + parameterStrings = append(parameterStrings, Normalize(k)+"="+Normalize(v[i])) + } + } + + } + sort.Strings(parameterStrings) + return strings.Join(parameterStrings, "&") +} + +func UsePayloadForQueryParameters(yopRequest request.YopRequest) bool { + var requestIsPOST = 0 == strings.Compare("POST", yopRequest.HttpMethod) + var requestHasNoPayload = 0 == len(yopRequest.Content) + return requestIsPOST && requestHasNoPayload +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/json_utils.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/json_utils.go new file mode 100644 index 00000000..0ec06638 --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/json_utils.go @@ -0,0 +1,13 @@ +// Package utils +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/29 9:56 AM +package utils + +import "encoding/json" + +func ParseToJsonStr(params map[string]any) string { + marshal, _ := json.Marshal(params) + return string(marshal) +} diff --git a/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/rsa_utils.go b/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/rsa_utils.go new file mode 100644 index 00000000..4cae7120 --- /dev/null +++ b/vendor/github.com/yop-platform/yop-go-sdk/yop/utils/rsa_utils.go @@ -0,0 +1,122 @@ +// Package utils +// Copyright: Copyright (c) 2020
+// Company: 易宝支付(YeePay)
+// @author : yunmei.wu +// @time : 2023/3/14 10:23 AM +package utils + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "strings" +) + +// RsaSignBase64 base64UrlEncode签名 +func RsaSignBase64(content string, privateKey string, hash crypto.Hash) (string, error) { + signature, err := Sign(content, privateKey, hash) + if err != nil { + return "", err + } + + return base64.RawURLEncoding.EncodeToString(signature), nil +} + +// VerifySign 验签 +func VerifySign(content string, signature string, pubKey string, hash crypto.Hash) bool { + pubKey = FormatPemKey(pubKey, "PUBLIC KEY") + publicKey, err := ParsePublicKey(pubKey) + if err != nil { + return false + } + var sig []byte + if strings.Contains(signature, "*") || strings.Contains(signature, "+") || strings.Contains(signature, "=") { + sig, _ = base64.StdEncoding.DecodeString(signature) + } else { + sig, _ = base64.RawURLEncoding.DecodeString(signature) + } + return Verify([]byte(content), sig, publicKey, crypto.SHA256) +} + +func Verify(content []byte, signature []byte, pub *rsa.PublicKey, hash crypto.Hash) bool { + hashed := sha256.Sum256(content) + err := rsa.VerifyPKCS1v15(pub, hash, hashed[:], signature) + if err != nil { + return false + } + return true +} + +// Sign rsa签名 +func Sign(content string, privateKey string, hash crypto.Hash) ([]byte, error) { + shaNew := sha256.New() + shaNew.Write([]byte(content)) + hashed := shaNew.Sum(nil) + + priKey, err := ParsePrivateKey(privateKey) + if err != nil { + return nil, err + } + + signature, err := rsa.SignPKCS1v15(rand.Reader, priKey.(*rsa.PrivateKey), hash, hashed) + + return signature, err +} + +func ParsePrivateKey(privateKey string) (any, error) { + privateKey = FormatPemKey(privateKey, "PRIVATE KEY") + // 2、解码私钥字节,生成加密对象 + block, _ := pem.Decode([]byte(privateKey)) + if block == nil { + return nil, errors.New("私钥信息错误!") + } + + // 3、解析DER编码的私钥,生成私钥对象 + priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return priKey, nil +} + +func ParsePublicKey(publicKey string) (*rsa.PublicKey, error) { + block, _ := pem.Decode([]byte(publicKey)) + if block == nil { + return nil, errors.New("公钥信息错误!") + } + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + return pubKey.(*rsa.PublicKey), nil +} + +// FormatPemKey /** +func FormatPemKey(yopFormKey string, pemHeader string) string { + var sb = strings.Builder{} + sb.WriteString("-----BEGIN ") + sb.WriteString(pemHeader) + sb.WriteString("-----\n") + for i := 0; i < len(yopFormKey); i++ { + sb.WriteString(string([]rune(yopFormKey)[i])) + if (i+1)%64 == 0 { + sb.WriteString("\n") + } + } + sb.WriteString("\n-----END ") + sb.WriteString(pemHeader) + sb.WriteString("-----\n") + return sb.String() +} + +func RsaDecrypt(priKey string, cipher string) ([]byte, error) { + privateKey, _ := ParsePrivateKey(priKey) + cipherBytes, _ := base64.RawURLEncoding.DecodeString(cipher) + return rsa.DecryptPKCS1v15(rand.Reader, privateKey.(*rsa.PrivateKey), cipherBytes) +}