mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-22 13:33:09 -06:00
Add auth fallback tests
This commit is contained in:
parent
6535be51ff
commit
f9a08db24d
|
|
@ -15,6 +15,7 @@
|
||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
|
@ -102,6 +103,22 @@ func AuthFallback(
|
||||||
w http.ResponseWriter, req *http.Request, authType string,
|
w http.ResponseWriter, req *http.Request, authType string,
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI,
|
||||||
) *util.JSONResponse {
|
) *util.JSONResponse {
|
||||||
|
// We currently only support reCaptcha, so fail early if that's not requested
|
||||||
|
if authType == authtypes.LoginTypeRecaptcha {
|
||||||
|
if !cfg.RecaptchaEnabled {
|
||||||
|
return writeHTTPMessage(w, req,
|
||||||
|
"Recaptcha login is disabled on this Homeserver",
|
||||||
|
http.StatusBadRequest,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = writeHTTPMessage(w, req, fmt.Sprintf("Unknown authtype %q", authType), http.StatusNotImplemented)
|
||||||
|
return &util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("Unknown auth stage type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sessionID := req.URL.Query().Get("session")
|
sessionID := req.URL.Query().Get("session")
|
||||||
|
|
||||||
if sessionID == "" {
|
if sessionID == "" {
|
||||||
|
|
@ -130,72 +147,37 @@ func AuthFallback(
|
||||||
|
|
||||||
if req.Method == http.MethodGet {
|
if req.Method == http.MethodGet {
|
||||||
// Handle Recaptcha
|
// Handle Recaptcha
|
||||||
if authType == authtypes.LoginTypeRecaptcha {
|
serveRecaptcha()
|
||||||
if err := checkRecaptchaEnabled(cfg, w, req); err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
serveRecaptcha()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: jsonerror.NotFound("Unknown auth stage type"),
|
|
||||||
}
|
|
||||||
} else if req.Method == http.MethodPost {
|
} else if req.Method == http.MethodPost {
|
||||||
// Handle Recaptcha
|
// Handle Recaptcha
|
||||||
if authType == authtypes.LoginTypeRecaptcha {
|
clientIP := req.RemoteAddr
|
||||||
if err := checkRecaptchaEnabled(cfg, w, req); err != nil {
|
err := req.ParseForm()
|
||||||
return err
|
if err != nil {
|
||||||
}
|
util.GetLogger(req.Context()).WithError(err).Error("req.ParseForm failed")
|
||||||
|
res := jsonerror.InternalServerError()
|
||||||
clientIP := req.RemoteAddr
|
return &res
|
||||||
err := req.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("req.ParseForm failed")
|
|
||||||
res := jsonerror.InternalServerError()
|
|
||||||
return &res
|
|
||||||
}
|
|
||||||
|
|
||||||
response := req.Form.Get(cfg.RecaptchaFormField)
|
|
||||||
if err := validateRecaptcha(cfg, response, clientIP); err != nil {
|
|
||||||
util.GetLogger(req.Context()).Error(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success. Add recaptcha as a completed login flow
|
|
||||||
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeRecaptcha)
|
|
||||||
|
|
||||||
serveSuccess()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &util.JSONResponse{
|
response := req.Form.Get(cfg.RecaptchaFormField)
|
||||||
Code: http.StatusNotFound,
|
if err := validateRecaptcha(cfg, response, clientIP); err != nil {
|
||||||
JSON: jsonerror.NotFound("Unknown auth stage type"),
|
util.GetLogger(req.Context()).Error(err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Success. Add recaptcha as a completed login flow
|
||||||
|
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeRecaptcha)
|
||||||
|
|
||||||
|
serveSuccess()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
_ = writeHTTPMessage(w, req, "Bad method", http.StatusMethodNotAllowed)
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusMethodNotAllowed,
|
Code: http.StatusMethodNotAllowed,
|
||||||
JSON: jsonerror.NotFound("Bad method"),
|
JSON: jsonerror.NotFound("Bad method"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRecaptchaEnabled creates an error response if recaptcha is not usable on homeserver.
|
|
||||||
func checkRecaptchaEnabled(
|
|
||||||
cfg *config.ClientAPI,
|
|
||||||
w http.ResponseWriter,
|
|
||||||
req *http.Request,
|
|
||||||
) *util.JSONResponse {
|
|
||||||
if !cfg.RecaptchaEnabled {
|
|
||||||
return writeHTTPMessage(w, req,
|
|
||||||
"Recaptcha login is disabled on this Homeserver",
|
|
||||||
http.StatusBadRequest,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeHTTPMessage writes the given header and message to the HTTP response writer.
|
// writeHTTPMessage writes the given header and message to the HTTP response writer.
|
||||||
// Returns an error JSONResponse obtained through httputil.LogThenError if the writing failed, otherwise nil.
|
// Returns an error JSONResponse obtained through httputil.LogThenError if the writing failed, otherwise nil.
|
||||||
func writeHTTPMessage(
|
func writeHTTPMessage(
|
||||||
|
|
|
||||||
105
clientapi/routing/auth_fallback_test.go
Normal file
105
clientapi/routing/auth_fallback_test.go
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/test/testrig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AuthFallback(t *testing.T) {
|
||||||
|
base, _, _ := testrig.Base(nil)
|
||||||
|
defer base.Close()
|
||||||
|
|
||||||
|
for _, useHCaptcha := range []bool{false, true} {
|
||||||
|
for _, recaptchaEnabled := range []bool{false, true} {
|
||||||
|
for _, wantErr := range []bool{false, true} {
|
||||||
|
t.Run(fmt.Sprintf("useHCaptcha(%v) - recaptchaEnabled(%v) - wantErr(%v)", useHCaptcha, recaptchaEnabled, wantErr), func(t *testing.T) {
|
||||||
|
// Set the defaults for each test
|
||||||
|
base.Cfg.ClientAPI.Defaults(config.DefaultOpts{Generate: true, Monolithic: true})
|
||||||
|
base.Cfg.ClientAPI.RecaptchaEnabled = recaptchaEnabled
|
||||||
|
base.Cfg.ClientAPI.RecaptchaPublicKey = "pub"
|
||||||
|
base.Cfg.ClientAPI.RecaptchaPrivateKey = "priv"
|
||||||
|
if useHCaptcha {
|
||||||
|
base.Cfg.ClientAPI.RecaptchaSiteVerifyAPI = "https://hcaptcha.com/siteverify"
|
||||||
|
base.Cfg.ClientAPI.RecaptchaApiJsUrl = "https://js.hcaptcha.com/1/api.js"
|
||||||
|
base.Cfg.ClientAPI.RecaptchaFormField = "h-captcha-response"
|
||||||
|
base.Cfg.ClientAPI.RecaptchaSitekeyClass = "h-captcha"
|
||||||
|
}
|
||||||
|
cfgErrs := &config.ConfigErrors{}
|
||||||
|
base.Cfg.ClientAPI.Verify(cfgErrs, true)
|
||||||
|
if len(*cfgErrs) > 0 {
|
||||||
|
t.Fatalf("(hCaptcha=%v) unexpected config errors: %s", useHCaptcha, cfgErrs.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/?session=1337", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
AuthFallback(rec, req, authtypes.LoginTypeRecaptcha, &base.Cfg.ClientAPI)
|
||||||
|
if !recaptchaEnabled {
|
||||||
|
if rec.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("unexpected response code: %d, want %d", rec.Code, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
if rec.Body.String() != "Recaptcha login is disabled on this Homeserver" {
|
||||||
|
t.Fatalf("unexpected response body: %s", rec.Body.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !strings.Contains(rec.Body.String(), base.Cfg.ClientAPI.RecaptchaSitekeyClass) {
|
||||||
|
t.Fatalf("body does not contain %s: %s", base.Cfg.ClientAPI.RecaptchaSitekeyClass, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if wantErr {
|
||||||
|
_, _ = w.Write([]byte(`{"success":false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"success":true}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close() // nolint: errcheck
|
||||||
|
|
||||||
|
base.Cfg.ClientAPI.RecaptchaSiteVerifyAPI = srv.URL
|
||||||
|
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/?session=1337", nil)
|
||||||
|
req.Form = url.Values{}
|
||||||
|
req.Form.Add(base.Cfg.ClientAPI.RecaptchaFormField, "someRandomValue")
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
AuthFallback(rec, req, authtypes.LoginTypeRecaptcha, &base.Cfg.ClientAPI)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("unknown fallbacks are handled correctly", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/?session=1337", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
AuthFallback(rec, req, "DoesNotExist", &base.Cfg.ClientAPI)
|
||||||
|
if rec.Code != http.StatusNotImplemented {
|
||||||
|
t.Fatalf("unexpected http status: %d, want %d", rec.Code, http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown methods are handled correctly", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/?session=1337", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
AuthFallback(rec, req, authtypes.LoginTypeRecaptcha, &base.Cfg.ClientAPI)
|
||||||
|
if rec.Code != http.StatusMethodNotAllowed {
|
||||||
|
t.Fatalf("unexpected http status: %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing session parameter is handled correctly", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
AuthFallback(rec, req, authtypes.LoginTypeRecaptcha, &base.Cfg.ClientAPI)
|
||||||
|
if rec.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("unexpected http status: %d, want %d", rec.Code, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -78,9 +78,6 @@ func (c *ClientAPI) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||||
c.TURN.Verify(configErrs)
|
c.TURN.Verify(configErrs)
|
||||||
c.RateLimiting.Verify(configErrs)
|
c.RateLimiting.Verify(configErrs)
|
||||||
if c.RecaptchaEnabled {
|
if c.RecaptchaEnabled {
|
||||||
checkNotEmpty(configErrs, "client_api.recaptcha_public_key", c.RecaptchaPublicKey)
|
|
||||||
checkNotEmpty(configErrs, "client_api.recaptcha_private_key", c.RecaptchaPrivateKey)
|
|
||||||
checkNotEmpty(configErrs, "client_api.recaptcha_siteverify_api", c.RecaptchaSiteVerifyAPI)
|
|
||||||
if c.RecaptchaSiteVerifyAPI == "" {
|
if c.RecaptchaSiteVerifyAPI == "" {
|
||||||
c.RecaptchaSiteVerifyAPI = "https://www.google.com/recaptcha/api/siteverify"
|
c.RecaptchaSiteVerifyAPI = "https://www.google.com/recaptcha/api/siteverify"
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +90,10 @@ func (c *ClientAPI) Verify(configErrs *ConfigErrors, isMonolith bool) {
|
||||||
if c.RecaptchaSitekeyClass == "" {
|
if c.RecaptchaSitekeyClass == "" {
|
||||||
c.RecaptchaSitekeyClass = "g-recaptcha-response"
|
c.RecaptchaSitekeyClass = "g-recaptcha-response"
|
||||||
}
|
}
|
||||||
|
checkNotEmpty(configErrs, "client_api.recaptcha_public_key", c.RecaptchaPublicKey)
|
||||||
|
checkNotEmpty(configErrs, "client_api.recaptcha_private_key", c.RecaptchaPrivateKey)
|
||||||
|
checkNotEmpty(configErrs, "client_api.recaptcha_siteverify_api", c.RecaptchaSiteVerifyAPI)
|
||||||
|
checkNotEmpty(configErrs, "client_api.recaptcha_sitekey_class", c.RecaptchaSitekeyClass)
|
||||||
}
|
}
|
||||||
// Ensure there is any spam counter measure when enabling registration
|
// Ensure there is any spam counter measure when enabling registration
|
||||||
if !c.RegistrationDisabled && !c.OpenRegistrationWithoutVerificationEnabled {
|
if !c.RegistrationDisabled && !c.OpenRegistrationWithoutVerificationEnabled {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue