// Copyright 2022 The Casdoor Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package captcha import ( "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "sort" "strconv" "strings" "time" "github.com/casdoor/casdoor/util" ) const AliyunCaptchaVerifyUrl = "http://afs.aliyuncs.com" type captchaSuccessResponse struct { Code int `json:"Code"` Msg string `json:"Msg"` } type captchaFailResponse struct { Code string `json:"Code"` Message string `json:"Message"` } type AliyunCaptchaProvider struct{} func NewAliyunCaptchaProvider() *AliyunCaptchaProvider { captcha := &AliyunCaptchaProvider{} return captcha } func contentEscape(str string) string { str = strings.Replace(str, " ", "%20", -1) str = url.QueryEscape(str) return str } func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) { pathData, err := url.ParseQuery(token) if err != nil { return false, err } pathData["Action"] = []string{"AuthenticateSig"} pathData["Format"] = []string{"json"} pathData["SignatureMethod"] = []string{"HMAC-SHA1"} pathData["SignatureNonce"] = []string{strconv.FormatInt(time.Now().UnixNano(), 10)} pathData["SignatureVersion"] = []string{"1.0"} pathData["Timestamp"] = []string{time.Now().UTC().Format("2006-01-02T15:04:05Z")} pathData["Version"] = []string{"2018-01-12"} var keys []string for k := range pathData { keys = append(keys, k) } sort.Strings(keys) sortQuery := "" for _, k := range keys { sortQuery += k + "=" + contentEscape(pathData[k][0]) + "&" } sortQuery = strings.TrimSuffix(sortQuery, "&") stringToSign := fmt.Sprintf("GET&%s&%s", url.QueryEscape("/"), url.QueryEscape(sortQuery)) signature := util.GetHmacSha1(clientSecret+"&", stringToSign) resp, err := http.Get(fmt.Sprintf("%s?%s&Signature=%s", AliyunCaptchaVerifyUrl, sortQuery, url.QueryEscape(signature))) if err != nil { return false, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return false, err } return handleCaptchaResponse(body) } func handleCaptchaResponse(body []byte) (bool, error) { captchaResp := &captchaSuccessResponse{} err := json.Unmarshal(body, captchaResp) if err != nil { captchaFailResp := &captchaFailResponse{} err = json.Unmarshal(body, captchaFailResp) if err != nil { return false, err } return false, errors.New(captchaFailResp.Message) } return true, nil }