diff --git a/assets b/assets index 51c246e..9dd43c0 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 51c246ef02e248259ddcf4c2d90e0c5a1af11a48 +Subproject commit 9dd43c0b54fe9c300d992c83c8687d8606b19601 diff --git a/middleware/captcha.go b/middleware/captcha.go index f22c60c..cb1f845 100644 --- a/middleware/captcha.go +++ b/middleware/captcha.go @@ -45,13 +45,17 @@ func CaptchaRequired(configName string) gin.HandlerFunc { if err != nil { c.JSON(200, serializer.ParamErr("验证码错误", err)) c.Abort() + return } + bodyData := bodyCopy.Bytes() err = json.Unmarshal(bodyData, &service) if err != nil { c.JSON(200, serializer.ParamErr("验证码错误", err)) c.Abort() + return } + c.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyData)) switch options["captcha_type"] { case "normal": @@ -60,19 +64,26 @@ func CaptchaRequired(configName string) gin.HandlerFunc { if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) { c.JSON(200, serializer.ParamErr("验证码错误", nil)) c.Abort() + return } + break case "recaptcha": reCAPTCHA, err := recaptcha.NewReCAPTCHA(options["captcha_ReCaptchaSecret"], recaptcha.V2, 10*time.Second) if err != nil { util.Log().Warning("reCAPTCHA 验证错误, %s", err) + c.Abort() + break } + err = reCAPTCHA.Verify(service.CaptchaCode) if err != nil { util.Log().Warning("reCAPTCHA 验证错误, %s", err) c.JSON(200, serializer.ParamErr("验证失败,请刷新网页后再次验证", nil)) c.Abort() + return } + break case "tcaptcha": credential := common.NewCredential( @@ -82,9 +93,7 @@ func CaptchaRequired(configName string) gin.HandlerFunc { cpf := profile.NewClientProfile() cpf.HttpProfile.Endpoint = "captcha.tencentcloudapi.com" client, _ := captcha.NewClient(credential, "", cpf) - request := captcha.NewDescribeCaptchaResultRequest() - request.CaptchaType = common.Uint64Ptr(9) appid, _ := strconv.Atoi(options["captcha_TCaptcha_CaptchaAppId"]) request.CaptchaAppId = common.Uint64Ptr(uint64(appid)) @@ -92,15 +101,19 @@ func CaptchaRequired(configName string) gin.HandlerFunc { request.Ticket = common.StringPtr(service.Ticket) request.Randstr = common.StringPtr(service.Randstr) request.UserIp = common.StringPtr(c.ClientIP()) - response, err := client.DescribeCaptchaResult(request) if err != nil { util.Log().Warning("TCaptcha 验证错误, %s", err) + c.Abort() + break } + if *response.Response.CaptchaCode != int64(1) { c.JSON(200, serializer.ParamErr("验证失败,请刷新网页后再次验证", nil)) c.Abort() + return } + break } } diff --git a/middleware/captcha_test.go b/middleware/captcha_test.go new file mode 100644 index 0000000..1846d31 --- /dev/null +++ b/middleware/captcha_test.go @@ -0,0 +1,177 @@ +package middleware + +import ( + "bytes" + "errors" + "github.com/cloudreve/Cloudreve/v3/pkg/cache" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +type errReader int + +func (errReader) Read(p []byte) (n int, err error) { + return 0, errors.New("test error") +} + +func TestCaptchaRequired_General(t *testing.T) { + asserts := assert.New(t) + rec := httptest.NewRecorder() + + // 未启用验证码 + { + cache.SetSettings(map[string]string{ + "login_captcha": "0", + "captcha_type": "1", + "captcha_ReCaptchaSecret": "1", + "captcha_TCaptcha_SecretId": "1", + "captcha_TCaptcha_SecretKey": "1", + "captcha_TCaptcha_CaptchaAppId": "1", + "captcha_TCaptcha_AppSecretKey": "1", + }, "setting_") + TestFunc := CaptchaRequired("login_captcha") + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + c.Request, _ = http.NewRequest("GET", "/", nil) + TestFunc(c) + asserts.False(c.IsAborted()) + } + + // body 无法读取 + { + cache.SetSettings(map[string]string{ + "login_captcha": "1", + "captcha_type": "1", + "captcha_ReCaptchaSecret": "1", + "captcha_TCaptcha_SecretId": "1", + "captcha_TCaptcha_SecretKey": "1", + "captcha_TCaptcha_CaptchaAppId": "1", + "captcha_TCaptcha_AppSecretKey": "1", + }, "setting_") + TestFunc := CaptchaRequired("login_captcha") + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + c.Request, _ = http.NewRequest("GET", "/", errReader(1)) + TestFunc(c) + asserts.True(c.IsAborted()) + } + + // body JSON 解析失败 + { + cache.SetSettings(map[string]string{ + "login_captcha": "1", + "captcha_type": "1", + "captcha_ReCaptchaSecret": "1", + "captcha_TCaptcha_SecretId": "1", + "captcha_TCaptcha_SecretKey": "1", + "captcha_TCaptcha_CaptchaAppId": "1", + "captcha_TCaptcha_AppSecretKey": "1", + }, "setting_") + TestFunc := CaptchaRequired("login_captcha") + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + r := bytes.NewReader([]byte("123")) + c.Request, _ = http.NewRequest("GET", "/", r) + TestFunc(c) + asserts.True(c.IsAborted()) + } +} + +func TestCaptchaRequired_Normal(t *testing.T) { + asserts := assert.New(t) + rec := httptest.NewRecorder() + + // 验证码错误 + { + cache.SetSettings(map[string]string{ + "login_captcha": "1", + "captcha_type": "normal", + "captcha_ReCaptchaSecret": "1", + "captcha_TCaptcha_SecretId": "1", + "captcha_TCaptcha_SecretKey": "1", + "captcha_TCaptcha_CaptchaAppId": "1", + "captcha_TCaptcha_AppSecretKey": "1", + }, "setting_") + TestFunc := CaptchaRequired("login_captcha") + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + r := bytes.NewReader([]byte("{}")) + c.Request, _ = http.NewRequest("GET", "/", r) + Session("233")(c) + TestFunc(c) + asserts.True(c.IsAborted()) + } +} + +func TestCaptchaRequired_Recaptcha(t *testing.T) { + asserts := assert.New(t) + rec := httptest.NewRecorder() + + // 无法初始化reCaptcha实例 + { + cache.SetSettings(map[string]string{ + "login_captcha": "1", + "captcha_type": "recaptcha", + "captcha_ReCaptchaSecret": "", + "captcha_TCaptcha_SecretId": "1", + "captcha_TCaptcha_SecretKey": "1", + "captcha_TCaptcha_CaptchaAppId": "1", + "captcha_TCaptcha_AppSecretKey": "1", + }, "setting_") + TestFunc := CaptchaRequired("login_captcha") + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + r := bytes.NewReader([]byte("{}")) + c.Request, _ = http.NewRequest("GET", "/", r) + TestFunc(c) + asserts.True(c.IsAborted()) + } + + // 验证码错误 + { + cache.SetSettings(map[string]string{ + "login_captcha": "1", + "captcha_type": "recaptcha", + "captcha_ReCaptchaSecret": "233", + "captcha_TCaptcha_SecretId": "1", + "captcha_TCaptcha_SecretKey": "1", + "captcha_TCaptcha_CaptchaAppId": "1", + "captcha_TCaptcha_AppSecretKey": "1", + }, "setting_") + TestFunc := CaptchaRequired("login_captcha") + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + r := bytes.NewReader([]byte("{}")) + c.Request, _ = http.NewRequest("GET", "/", r) + TestFunc(c) + asserts.True(c.IsAborted()) + } +} + +func TestCaptchaRequired_Tcaptcha(t *testing.T) { + asserts := assert.New(t) + rec := httptest.NewRecorder() + + // 验证出错 + { + cache.SetSettings(map[string]string{ + "login_captcha": "1", + "captcha_type": "tcaptcha", + "captcha_ReCaptchaSecret": "", + "captcha_TCaptcha_SecretId": "1", + "captcha_TCaptcha_SecretKey": "1", + "captcha_TCaptcha_CaptchaAppId": "1", + "captcha_TCaptcha_AppSecretKey": "1", + }, "setting_") + TestFunc := CaptchaRequired("login_captcha") + c, _ := gin.CreateTestContext(rec) + c.Params = []gin.Param{} + r := bytes.NewReader([]byte("{}")) + c.Request, _ = http.NewRequest("GET", "/", r) + TestFunc(c) + asserts.True(c.IsAborted()) + } +}