fix: registration fills only an explicit allow-list and never takes the IP columns from the body
User.RegisterFillable lists what a registration body may set: name, surname, email, password, preferred_locale and marketing_consent. RegisterUser fills from it instead of Fillable, so organisation_id, organisation_role, is_onboarded and must_change_password can no longer be mass-assigned through POST /register, and created_ip_address and last_ip_address come only from RegisterOptions.IP. Fillable is unchanged for the admin form and trusted application code.
This commit is contained in:
@@ -248,6 +248,81 @@ func TestRegisterEventPayload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterIgnoresProtectedFields: a registration body sets only the
|
||||
// RegisterFillable columns. Organisation membership, the IP columns and the
|
||||
// account flags in the body are ignored, over HTTP and through RegisterUser,
|
||||
// and the IP columns come only from the server-side address.
|
||||
func TestRegisterIgnoresProtectedFields(t *testing.T) {
|
||||
app, gdb := sessionApp(t, "0s")
|
||||
disableRegisterThrottle(t, app)
|
||||
org := &models.Organisation{Name: "Mass Assign Org", Slug: "mass-assign-org"}
|
||||
if err := gdb.Create(org).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
protected := func(email string) map[string]any {
|
||||
return map[string]any{
|
||||
"email": email, "password": "correct-horse", "password_confirmation": "correct-horse", "name": "Mal", "surname": "Lory",
|
||||
"preferred_locale": "pl", "marketing_consent": true,
|
||||
"organisation_id": org.ID, "organisation_role": "owner",
|
||||
"created_ip_address": "203.0.113.9", "last_ip_address": "203.0.113.9",
|
||||
"is_onboarded": true, "must_change_password": true,
|
||||
}
|
||||
}
|
||||
check := func(t *testing.T, email string, wantIP *string) {
|
||||
t.Helper()
|
||||
var stored models.User
|
||||
if err := gdb.Where("email = ?", email).Take(&stored).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stored.Name == nil || *stored.Name != "Mal" || stored.Surname == nil || *stored.Surname != "Lory" ||
|
||||
stored.PreferredLocale == nil || *stored.PreferredLocale != "pl" || !stored.MarketingConsent {
|
||||
t.Fatalf("allowed columns not stored: %+v", stored)
|
||||
}
|
||||
if stored.OrganisationID != nil || stored.OrganisationRole != nil {
|
||||
t.Fatalf("organisation from the body: id %v role %v", stored.OrganisationID, stored.OrganisationRole)
|
||||
}
|
||||
if stored.IsOnboarded || stored.MustChangePassword {
|
||||
t.Fatalf("account flags from the body: onboarded %v must_change %v", stored.IsOnboarded, stored.MustChangePassword)
|
||||
}
|
||||
for name, got := range map[string]*string{"created_ip_address": stored.CreatedIPAddress, "last_ip_address": stored.LastIPAddress} {
|
||||
switch {
|
||||
case wantIP == nil && got != nil:
|
||||
t.Fatalf("%s = %q, want NULL", name, *got)
|
||||
case wantIP != nil && (got == nil || *got != *wantIP):
|
||||
t.Fatalf("%s = %v, want %q", name, got, *wantIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("http", func(t *testing.T) {
|
||||
body, err := json.Marshal(protected("mass-http@example.test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := postRegister(t, app, "192.0.2.44", string(body))
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("register %d %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
ip := "192.0.2.44"
|
||||
check(t, "mass-http@example.test", &ip)
|
||||
})
|
||||
t.Run("RegisterUser with IP", func(t *testing.T) {
|
||||
_, errs, err := controllers.RegisterUser(t.Context(), app, gdb, protected("mass-ip@example.test"), controllers.RegisterOptions{IP: "198.51.100.7"})
|
||||
if err != nil || len(errs) != 0 {
|
||||
t.Fatalf("RegisterUser = %v, %v", errs, err)
|
||||
}
|
||||
ip := "198.51.100.7"
|
||||
check(t, "mass-ip@example.test", &ip)
|
||||
})
|
||||
t.Run("RegisterUser without IP", func(t *testing.T) {
|
||||
_, errs, err := controllers.RegisterUser(t.Context(), app, gdb, protected("mass-noip@example.test"), controllers.RegisterOptions{})
|
||||
if err != nil || len(errs) != 0 {
|
||||
t.Fatalf("RegisterUser = %v, %v", errs, err)
|
||||
}
|
||||
check(t, "mass-noip@example.test", nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRegisterImportDirection(t *testing.T) {
|
||||
cmd := exec.Command("go", "list", "-deps", "./...")
|
||||
cmd.Dir = moduleRoot(t)
|
||||
|
||||
Reference in New Issue
Block a user