mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Apply password requirements to admin-created users (#6667)
This was requested by a customer.
This commit is contained in:
parent
80070cd273
commit
710c304d94
3 changed files with 127 additions and 32 deletions
1
changes/admin-user
Normal file
1
changes/admin-user
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Enforce the same password requirements when admins create users directly as are already applied for users created through invites.
|
||||
|
|
@ -144,28 +144,7 @@ type UserPayload struct {
|
|||
|
||||
func (p *UserPayload) VerifyInviteCreate() error {
|
||||
invalid := &InvalidArgumentError{}
|
||||
if p.Name == nil {
|
||||
invalid.Append("name", "Full name missing required argument")
|
||||
} else if *p.Name == "" {
|
||||
invalid.Append("name", "Full name cannot be empty")
|
||||
}
|
||||
|
||||
// we don't need a password for single sign on
|
||||
if p.SSOInvite == nil || !*p.SSOInvite {
|
||||
if p.Password == nil {
|
||||
invalid.Append("password", "Password missing required argument")
|
||||
} else if *p.Password == "" {
|
||||
invalid.Append("password", "Password cannot be empty")
|
||||
} else if err := ValidatePasswordRequirements(*p.Password); err != nil {
|
||||
invalid.Append("password", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if p.Email == nil {
|
||||
invalid.Append("email", "Email missing required argument")
|
||||
} else if *p.Email == "" {
|
||||
invalid.Append("email", "Email cannot be empty")
|
||||
}
|
||||
p.verifyCreateShared(invalid)
|
||||
|
||||
if p.InviteToken == nil {
|
||||
invalid.Append("invite_token", "Invite token missing required argument")
|
||||
|
|
@ -181,6 +160,19 @@ func (p *UserPayload) VerifyInviteCreate() error {
|
|||
|
||||
func (p *UserPayload) VerifyAdminCreate() error {
|
||||
invalid := &InvalidArgumentError{}
|
||||
p.verifyCreateShared(invalid)
|
||||
|
||||
if p.InviteToken != nil {
|
||||
invalid.Append("invite_token", "Invite token should not be specified with admin user creation")
|
||||
}
|
||||
|
||||
if invalid.HasErrors() {
|
||||
return invalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *UserPayload) verifyCreateShared(invalid *InvalidArgumentError) {
|
||||
if p.Name == nil {
|
||||
invalid.Append("name", "Full name missing required argument")
|
||||
} else if *p.Name == "" {
|
||||
|
|
@ -193,8 +185,9 @@ func (p *UserPayload) VerifyAdminCreate() error {
|
|||
invalid.Append("password", "Password missing required argument")
|
||||
} else if *p.Password == "" {
|
||||
invalid.Append("password", "Password cannot be empty")
|
||||
} else if err := ValidatePasswordRequirements(*p.Password); err != nil {
|
||||
invalid.Append("password", err.Error())
|
||||
}
|
||||
// Skip password validation in the case of admin created users
|
||||
}
|
||||
|
||||
if p.SSOEnabled != nil && *p.SSOEnabled && p.Password != nil && len(*p.Password) > 0 {
|
||||
|
|
@ -206,15 +199,6 @@ func (p *UserPayload) VerifyAdminCreate() error {
|
|||
} else if *p.Email == "" {
|
||||
invalid.Append("email", "Email cannot be empty")
|
||||
}
|
||||
|
||||
if p.InviteToken != nil {
|
||||
invalid.Append("invite_token", "Invite token should not be specified with admin user creation")
|
||||
}
|
||||
|
||||
if invalid.HasErrors() {
|
||||
return invalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *UserPayload) VerifyModify(ownUser bool) error {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
|
@ -98,3 +99,112 @@ func TestSaltAndHashPassword(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminCreateValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
payload UserPayload
|
||||
// if errContains is empty then no error is expected
|
||||
errContains []string
|
||||
}{
|
||||
{
|
||||
payload: UserPayload{},
|
||||
errContains: []string{"name", "email", "password"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String(""), Email: ptr.String(""), Password: ptr.String("")},
|
||||
errContains: []string{"name", "email", "password"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String(""), Password: ptr.String("")},
|
||||
errContains: []string{"email", "password"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String("foo@example.com"), Password: ptr.String("")},
|
||||
errContains: []string{"password"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String("foo@example.com"), Password: ptr.String("foo")},
|
||||
errContains: []string{"password"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String("foo@example.com"), Password: ptr.String("Foofoofoo1337#"), InviteToken: ptr.String("foo")},
|
||||
errContains: []string{"invite_token"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String("foo@example.com"), Password: ptr.String("Foofoofoo1337#")},
|
||||
errContains: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
err := tc.payload.VerifyAdminCreate()
|
||||
if len(tc.errContains) == 0 {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
ierr := err.(*InvalidArgumentError)
|
||||
require.Equal(t, len(tc.errContains), len(*ierr))
|
||||
for _, expected := range tc.errContains {
|
||||
assertContainsErrorName(t, *ierr, expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInviteCreateValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
payload UserPayload
|
||||
// if errContains is empty then no error is expected
|
||||
errContains []string
|
||||
}{
|
||||
{
|
||||
payload: UserPayload{},
|
||||
errContains: []string{"name", "email", "password", "invite_token"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String(""), Email: ptr.String(""), Password: ptr.String("")},
|
||||
errContains: []string{"name", "email", "password", "invite_token"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String(""), Password: ptr.String("")},
|
||||
errContains: []string{"email", "password", "invite_token"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String("foo@example.com"), Password: ptr.String("")},
|
||||
errContains: []string{"password", "invite_token"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String("foo@example.com"), Password: ptr.String("foo")},
|
||||
errContains: []string{"password", "invite_token"},
|
||||
},
|
||||
{
|
||||
payload: UserPayload{Name: ptr.String("Foo"), Email: ptr.String("foo@example.com"), Password: ptr.String("Foofoofoo1337#"), InviteToken: ptr.String("foo")},
|
||||
errContains: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
err := tc.payload.VerifyInviteCreate()
|
||||
if len(tc.errContains) == 0 {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
ierr := err.(*InvalidArgumentError)
|
||||
for _, expected := range tc.errContains {
|
||||
require.Equal(t, len(tc.errContains), len(*ierr))
|
||||
assertContainsErrorName(t, *ierr, expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertContainsErrorName(t *testing.T, invalid InvalidArgumentError, name string) {
|
||||
for _, argErr := range invalid {
|
||||
if argErr.name == name {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("%v does not contain error %s", invalid, name)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue