Add test for captcha registration

This commit is contained in:
Till Faelligen 2022-12-23 10:19:03 +01:00
parent adedb38f8c
commit 508e4c69eb
No known key found for this signature in database
GPG key ID: ACCDC9606D472758
4 changed files with 91 additions and 21 deletions

View file

@ -292,7 +292,7 @@ func validateRecaptcha(
return ErrMissingResponse return ErrMissingResponse
} }
// Make a POST request to Google's API to check the captcha response // Make a POST request to the captcha provider API to check the captcha response
resp, err := http.PostForm(cfg.RecaptchaSiteVerifyAPI, resp, err := http.PostForm(cfg.RecaptchaSiteVerifyAPI,
url.Values{ url.Values{
"secret": {cfg.RecaptchaPrivateKey}, "secret": {cfg.RecaptchaPrivateKey},
@ -509,11 +509,6 @@ func Register(
if resErr := httputil.UnmarshalJSON(reqBody, &r); resErr != nil { if resErr := httputil.UnmarshalJSON(reqBody, &r); resErr != nil {
return *resErr return *resErr
} }
var l string
var d gomatrixserverlib.ServerName
if l, d, err = cfg.Matrix.SplitLocalID('@', r.Username); err == nil {
r.Username, r.ServerName = l, d
}
if req.URL.Query().Get("kind") == "guest" { if req.URL.Query().Get("kind") == "guest" {
return handleGuestRegistration(req, r, cfg, userAPI) return handleGuestRegistration(req, r, cfg, userAPI)
} }

View file

@ -290,6 +290,8 @@ func Test_register(t *testing.T) {
forceEmpty bool forceEmpty bool
registrationDisabled bool registrationDisabled bool
guestsDisabled bool guestsDisabled bool
enableRecaptcha bool
captchaBody string
wantResponse util.JSONResponse wantResponse util.JSONResponse
}{ }{
{ {
@ -340,7 +342,7 @@ func Test_register(t *testing.T) {
}, },
}, },
{ {
name: "successful registration", name: "successful registration uppercase username",
username: "LOWERCASED", // this is going to be lower-cased username: "LOWERCASED", // this is going to be lower-cased
}, },
{ {
@ -356,6 +358,46 @@ func Test_register(t *testing.T) {
JSON: jsonerror.InvalidUsername("Numeric user IDs are reserved"), JSON: jsonerror.InvalidUsername("Numeric user IDs are reserved"),
}, },
}, },
{
name: "disabled recaptcha login",
loginType: authtypes.LoginTypeRecaptcha,
wantResponse: util.JSONResponse{
Code: http.StatusForbidden,
JSON: jsonerror.Unknown(ErrCaptchaDisabled.Error()),
},
},
{
name: "enabled recaptcha, no response defined",
enableRecaptcha: true,
loginType: authtypes.LoginTypeRecaptcha,
wantResponse: util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON(ErrMissingResponse.Error()),
},
},
{
name: "invalid captcha response",
enableRecaptcha: true,
loginType: authtypes.LoginTypeRecaptcha,
captchaBody: `notvalid`,
wantResponse: util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: jsonerror.BadJSON(ErrInvalidCaptcha.Error()),
},
},
{
name: "valid captcha response",
enableRecaptcha: true,
loginType: authtypes.LoginTypeRecaptcha,
captchaBody: `success`,
},
{
name: "captcha invalid from remote",
enableRecaptcha: true,
loginType: authtypes.LoginTypeRecaptcha,
captchaBody: `i should fail for other reasons`,
wantResponse: util.JSONResponse{Code: http.StatusInternalServerError, JSON: jsonerror.InternalServerError()},
},
} }
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) { test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
@ -367,12 +409,37 @@ func Test_register(t *testing.T) {
userAPI := userapi.NewInternalAPI(base, &base.Cfg.UserAPI, nil, keyAPI, rsAPI, nil) userAPI := userapi.NewInternalAPI(base, &base.Cfg.UserAPI, nil, keyAPI, rsAPI, nil)
keyAPI.SetUserAPI(userAPI) keyAPI.SetUserAPI(userAPI)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.enableRecaptcha {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
t.Fatal(err)
}
response := r.Form.Get("response")
// Respond with valid JSON or no JSON at all to test happy/error cases
switch response {
case "success":
json.NewEncoder(w).Encode(recaptchaResponse{Success: true})
case "notvalid":
json.NewEncoder(w).Encode(recaptchaResponse{Success: false})
default:
}
}))
defer srv.Close()
base.Cfg.ClientAPI.RecaptchaSiteVerifyAPI = srv.URL
}
if err := base.Cfg.Derive(); err != nil { if err := base.Cfg.Derive(); err != nil {
t.Fatalf("failed to derive config: %s", err) t.Fatalf("failed to derive config: %s", err)
} }
for _, tc := range testCases { base.Cfg.ClientAPI.RecaptchaEnabled = tc.enableRecaptcha
t.Run(tc.name, func(t *testing.T) { base.Cfg.ClientAPI.RegistrationDisabled = tc.registrationDisabled
base.Cfg.ClientAPI.GuestsDisabled = tc.guestsDisabled
if tc.kind == "" { if tc.kind == "" {
tc.kind = "user" tc.kind = "user"
} }
@ -392,10 +459,6 @@ func Test_register(t *testing.T) {
} }
body := &bytes.Buffer{} body := &bytes.Buffer{}
base.Cfg.ClientAPI.RegistrationDisabled = tc.registrationDisabled
base.Cfg.ClientAPI.GuestsDisabled = tc.guestsDisabled
err := json.NewEncoder(body).Encode(reg) err := json.NewEncoder(body).Encode(reg)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -450,6 +513,11 @@ func Test_register(t *testing.T) {
Type: authtypes.LoginType(tc.loginType), Type: authtypes.LoginType(tc.loginType),
Session: uia.Session, Session: uia.Session,
} }
if tc.captchaBody != "" {
reg.Auth.Response = tc.captchaBody
}
dummy := "dummy" dummy := "dummy"
reg.DeviceID = &dummy reg.DeviceID = &dummy
reg.InitialDisplayName = &dummy reg.InitialDisplayName = &dummy
@ -470,6 +538,11 @@ func Test_register(t *testing.T) {
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse) t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
} }
return return
case util.JSONResponse:
if !reflect.DeepEqual(tc.wantResponse, resp) {
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
}
return
} }
rr, ok := resp.JSON.(registerResponse) rr, ok := resp.JSON.(registerResponse)

View file

@ -41,7 +41,7 @@ var (
validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-=./]+$`) validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-=./]+$`)
) )
// ValidatePassword returns an error response if the password is invalid // ValidatePassword returns an error if the password is invalid
func ValidatePassword(password string) error { func ValidatePassword(password string) error {
// https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/rest/client/v2_alpha/register.py#L161 // https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/rest/client/v2_alpha/register.py#L161
if len(password) > maxPasswordLength { if len(password) > maxPasswordLength {

View file

@ -29,7 +29,7 @@ import (
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
yaml "gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
jaegerconfig "github.com/uber/jaeger-client-go/config" jaegerconfig "github.com/uber/jaeger-client-go/config"
jaegermetrics "github.com/uber/jaeger-lib/metrics" jaegermetrics "github.com/uber/jaeger-lib/metrics"
@ -314,11 +314,13 @@ func (config *Dendrite) Derive() error {
if config.ClientAPI.RecaptchaEnabled { if config.ClientAPI.RecaptchaEnabled {
config.Derived.Registration.Params[authtypes.LoginTypeRecaptcha] = map[string]string{"public_key": config.ClientAPI.RecaptchaPublicKey} config.Derived.Registration.Params[authtypes.LoginTypeRecaptcha] = map[string]string{"public_key": config.ClientAPI.RecaptchaPublicKey}
config.Derived.Registration.Flows = append(config.Derived.Registration.Flows, config.Derived.Registration.Flows = []authtypes.Flow{
authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeRecaptcha}}) {Stages: []authtypes.LoginType{authtypes.LoginTypeRecaptcha}},
}
} else { } else {
config.Derived.Registration.Flows = append(config.Derived.Registration.Flows, config.Derived.Registration.Flows = []authtypes.Flow{
authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeDummy}}) {Stages: []authtypes.LoginType{authtypes.LoginTypeDummy}},
}
} }
// Load application service configuration files // Load application service configuration files