Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions internal/controller/api/node/user/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

package user

import (
sha512crypt "github.com/GehirnInc/crypt/sha512_crypt"
)

// HashPasswordForTest exposes defaultHashPassword to external tests.
func HashPasswordForTest(
password string,
Expand All @@ -38,3 +42,15 @@ func SetHashPasswordFn(
func ResetHashPasswordFn() {
hashPassword = defaultHashPassword
}

// SetGenerateHashFn overrides the underlying crypt library call for testing.
func SetGenerateHashFn(
fn func([]byte, []byte) (string, error),
) {
generateHash = fn
}

// ResetGenerateHashFn restores the default crypt library call.
func ResetGenerateHashFn() {
generateHash = sha512crypt.New().Generate
}
12 changes: 12 additions & 0 deletions internal/controller/api/node/user/group_delete_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ func (s *GroupDeletePublicTestSuite) TestDeleteNodeGroup() {
s.True(ok)
},
},
{
name: "validation error invalid name",
request: gen.DeleteNodeGroupRequestObject{
Hostname: "server1",
Name: "Invalid",
},
setupMock: func() {},
validateFunc: func(resp gen.DeleteNodeGroupResponseObject) {
_, ok := resp.(gen.DeleteNodeGroup400JSONResponse)
s.True(ok)
},
},
{
name: "not found",
request: gen.DeleteNodeGroupRequestObject{
Expand Down
12 changes: 12 additions & 0 deletions internal/controller/api/node/user/group_get_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ func (s *GroupGetPublicTestSuite) TestGetNodeGroupByName() {
s.True(ok)
},
},
{
name: "validation error invalid name",
request: gen.GetNodeGroupByNameRequestObject{
Hostname: "server1",
Name: "Invalid",
},
setupMock: func() {},
validateFunc: func(resp gen.GetNodeGroupByNameResponseObject) {
_, ok := resp.(gen.GetNodeGroupByName400JSONResponse)
s.True(ok)
},
},
{
name: "not found",
request: gen.GetNodeGroupByNameRequestObject{
Expand Down
26 changes: 26 additions & 0 deletions internal/controller/api/node/user/group_update_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,32 @@ func (s *GroupUpdatePublicTestSuite) TestPutNodeGroup() {
s.True(ok)
},
},
{
name: "validation error invalid name",
request: gen.PutNodeGroupRequestObject{
Hostname: "server1",
Name: "Invalid",
Body: &gen.GroupUpdateRequest{Members: &members},
},
setupMock: func() {},
validateFunc: func(resp gen.PutNodeGroupResponseObject) {
_, ok := resp.(gen.PutNodeGroup400JSONResponse)
s.True(ok)
},
},
{
name: "validation error invalid member in body",
request: gen.PutNodeGroupRequestObject{
Hostname: "server1",
Name: "devops",
Body: &gen.GroupUpdateRequest{Members: &[]string{"Invalid"}},
},
setupMock: func() {},
validateFunc: func(resp gen.PutNodeGroupResponseObject) {
_, ok := resp.(gen.PutNodeGroup400JSONResponse)
s.True(ok)
},
},
{
name: "not found",
request: gen.PutNodeGroupRequestObject{
Expand Down
17 changes: 11 additions & 6 deletions internal/controller/api/node/user/password_hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import (
// failure. See export_test.go for the setter/reset pair.
var hashPassword = defaultHashPassword

// generateHash is the underlying crypt library call behind
// defaultHashPassword, held in a package-level variable so tests can inject
// a failure from it directly. See export_test.go for the setter/reset pair.
var generateHash = sha512crypt.New().Generate

// defaultHashPassword hashes a plaintext password into a SHA-512 crypt hash
// ("$6$<salt>$<hash>"), the format /etc/shadow and "chpasswd -e" accept. The
// salt is generated by the underlying library from crypto/rand.
Expand All @@ -39,12 +44,12 @@ var hashPassword = defaultHashPassword
func defaultHashPassword(
password string,
) (string, error) {
// A nil salt tells Generate to create its own, valid by construction, so
// this error is not reachable through any password value. The check is
// kept because Generate's signature can fail, and callers that inject a
// different hasher via SetHashPasswordFn (see export_test.go) rely on
// this path being wired up.
hash, err := sha512crypt.New().Generate([]byte(password), nil)
// A nil salt tells generateHash to create its own, valid by construction,
// so this error is not reachable through any password value in
// production. The check is kept because generateHash's signature can
// fail, and SetGenerateHashFn (see export_test.go) injects a failure to
// cover it.
hash, err := generateHash([]byte(password), nil)
if err != nil {
return "", fmt.Errorf("hash password: %w", err)
}
Expand Down
25 changes: 25 additions & 0 deletions internal/controller/api/node/user/password_hash_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package user_test

import (
"errors"
"strings"
"testing"

Expand All @@ -33,15 +34,21 @@ type PasswordHashPublicTestSuite struct {
suite.Suite
}

func (s *PasswordHashPublicTestSuite) TearDownSubTest() {
apiuser.ResetGenerateHashFn()
}

func (s *PasswordHashPublicTestSuite) TestHashPassword() {
tests := []struct {
name string
password string
setup func()
validateFunc func(hash string, err error)
}{
{
name: "produces a sha-512 crypt hash",
password: "correct horse battery staple",
setup: func() {},
validateFunc: func(hash string, err error) {
s.NoError(err)
s.True(strings.HasPrefix(hash, "$6$"))
Expand All @@ -51,6 +58,7 @@ func (s *PasswordHashPublicTestSuite) TestHashPassword() {
{
name: "empty password still hashes",
password: "",
setup: func() {},
validateFunc: func(hash string, err error) {
s.NoError(err)
s.True(strings.HasPrefix(hash, "$6$"))
Expand All @@ -59,17 +67,34 @@ func (s *PasswordHashPublicTestSuite) TestHashPassword() {
{
name: "two calls produce different hashes",
password: "same-password",
setup: func() {},
validateFunc: func(hash string, err error) {
s.NoError(err)
other, err := apiuser.HashPasswordForTest("same-password")
s.NoError(err)
s.NotEqual(hash, other, "salts must be random per call")
},
},
{
name: "when the underlying generator fails",
password: "correct horse battery staple",
setup: func() {
apiuser.SetGenerateHashFn(func(_ []byte, _ []byte) (string, error) {
return "", errors.New("salt decode failed")
})
},
validateFunc: func(hash string, err error) {
s.Error(err)
s.Empty(hash)
s.Contains(err.Error(), "hash password: salt decode failed")
},
},
}

for _, tc := range tests {
s.Run(tc.name, func() {
tc.setup()

hash, err := apiuser.HashPasswordForTest(tc.password)
tc.validateFunc(hash, err)
})
Expand Down
15 changes: 15 additions & 0 deletions internal/controller/api/node/user/ssh_key_create_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,21 @@ func (s *SSHKeyCreatePublicTestSuite) TestPostNodeUserSSHKey() {
s.True(ok)
},
},
{
name: "validation error invalid name",
request: gen.PostNodeUserSSHKeyRequestObject{
Hostname: "server1",
Name: "Invalid",
Body: &gen.SSHKeyAddRequest{
Key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITest user@host",
},
},
setupMock: func() {},
validateFunc: func(resp gen.PostNodeUserSSHKeyResponseObject) {
_, ok := resp.(gen.PostNodeUserSSHKey400JSONResponse)
s.True(ok)
},
},
{
name: "when job skipped",
request: gen.PostNodeUserSSHKeyRequestObject{
Expand Down
13 changes: 13 additions & 0 deletions internal/controller/api/node/user/ssh_key_delete_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,19 @@ func (s *SSHKeyDeletePublicTestSuite) TestDeleteNodeUserSSHKey() {
s.Contains(*r.Error, "required")
},
},
{
name: "validation error invalid name",
request: gen.DeleteNodeUserSSHKeyRequestObject{
Hostname: "server1",
Name: "Invalid",
Fingerprint: "SHA256:abc123",
},
setupMock: func() {},
validateFunc: func(resp gen.DeleteNodeUserSSHKeyResponseObject) {
_, ok := resp.(gen.DeleteNodeUserSSHKey400JSONResponse)
s.True(ok)
},
},
{
name: "broadcast target _all",
request: gen.DeleteNodeUserSSHKeyRequestObject{
Expand Down
12 changes: 12 additions & 0 deletions internal/controller/api/node/user/ssh_key_list_get_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,18 @@ func (s *SSHKeyListGetPublicTestSuite) TestGetNodeUserSSHKey() {
s.Contains(*r.Error, "required")
},
},
{
name: "validation error invalid name",
request: gen.GetNodeUserSSHKeyRequestObject{
Hostname: "server1",
Name: "Invalid",
},
setupMock: func() {},
validateFunc: func(resp gen.GetNodeUserSSHKeyResponseObject) {
_, ok := resp.(gen.GetNodeUserSSHKey400JSONResponse)
s.True(ok)
},
},
{
name: "broadcast target _all",
request: gen.GetNodeUserSSHKeyRequestObject{
Expand Down
12 changes: 12 additions & 0 deletions internal/controller/api/node/user/user_delete_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ func (s *UserDeletePublicTestSuite) TestDeleteNodeUser() {
s.True(ok)
},
},
{
name: "validation error invalid name",
request: gen.DeleteNodeUserRequestObject{
Hostname: "server1",
Name: "Invalid",
},
setupMock: func() {},
validateFunc: func(resp gen.DeleteNodeUserResponseObject) {
_, ok := resp.(gen.DeleteNodeUser400JSONResponse)
s.True(ok)
},
},
{
name: "not found",
request: gen.DeleteNodeUserRequestObject{
Expand Down
12 changes: 12 additions & 0 deletions internal/controller/api/node/user/user_get_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ func (s *UserGetPublicTestSuite) TestGetNodeUserByName() {
s.True(ok)
},
},
{
name: "validation error invalid name",
request: gen.GetNodeUserByNameRequestObject{
Hostname: "server1",
Name: "Invalid",
},
setupMock: func() {},
validateFunc: func(resp gen.GetNodeUserByNameResponseObject) {
_, ok := resp.(gen.GetNodeUserByName400JSONResponse)
s.True(ok)
},
},
{
name: "not found",
request: gen.GetNodeUserByNameRequestObject{
Expand Down
13 changes: 13 additions & 0 deletions internal/controller/api/node/user/user_password_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,19 @@ func (s *UserPasswordPublicTestSuite) TestPostNodeUserPassword() {
s.True(ok)
},
},
{
name: "validation error invalid name",
request: gen.PostNodeUserPasswordRequestObject{
Hostname: "server1",
Name: "Invalid",
Body: &gen.UserPasswordRequest{Password: "newpass123"},
},
setupMock: func() {},
validateFunc: func(resp gen.PostNodeUserPasswordResponseObject) {
_, ok := resp.(gen.PostNodeUserPassword400JSONResponse)
s.True(ok)
},
},
{
name: "not found",
request: gen.PostNodeUserPasswordRequestObject{
Expand Down
26 changes: 26 additions & 0 deletions internal/controller/api/node/user/user_update_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,32 @@ func (s *UserUpdatePublicTestSuite) TestPutNodeUser() {
s.True(ok)
},
},
{
name: "validation error invalid name",
request: gen.PutNodeUserRequestObject{
Hostname: "server1",
Name: "Invalid",
Body: &gen.UserUpdateRequest{Shell: &shell},
},
setupMock: func() {},
validateFunc: func(resp gen.PutNodeUserResponseObject) {
_, ok := resp.(gen.PutNodeUser400JSONResponse)
s.True(ok)
},
},
{
name: "validation error invalid group in body",
request: gen.PutNodeUserRequestObject{
Hostname: "server1",
Name: "testuser",
Body: &gen.UserUpdateRequest{Groups: &[]string{"Invalid"}},
},
setupMock: func() {},
validateFunc: func(resp gen.PutNodeUserResponseObject) {
_, ok := resp.(gen.PutNodeUser400JSONResponse)
s.True(ok)
},
},
{
name: "not found",
request: gen.PutNodeUserRequestObject{
Expand Down
Loading
Loading