2016-09-26 18:48:55 +00:00
|
|
|
package service
|
2016-08-28 03:59:17 +00:00
|
|
|
|
|
|
|
import (
|
2016-09-15 14:52:17 +00:00
|
|
|
"errors"
|
2016-08-28 03:59:17 +00:00
|
|
|
"testing"
|
2016-09-15 14:52:17 +00:00
|
|
|
"time"
|
2016-08-28 03:59:17 +00:00
|
|
|
|
2016-09-26 18:48:55 +00:00
|
|
|
"github.com/kolide/kolide-ose/server/config"
|
2016-09-29 02:44:05 +00:00
|
|
|
"github.com/kolide/kolide-ose/server/contexts/viewer"
|
2016-11-16 13:47:49 +00:00
|
|
|
"github.com/kolide/kolide-ose/server/datastore/inmem"
|
|
|
|
kolide_errors "github.com/kolide/kolide-ose/server/errors"
|
2016-09-26 18:48:55 +00:00
|
|
|
"github.com/kolide/kolide-ose/server/kolide"
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2016-12-14 18:11:43 +00:00
|
|
|
"github.com/WatchBeam/clock"
|
|
|
|
pkg_errors "github.com/pkg/errors"
|
2016-09-14 18:40:51 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2016-09-15 19:27:55 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2016-09-03 17:25:16 +00:00
|
|
|
"golang.org/x/net/context"
|
2016-08-28 03:59:17 +00:00
|
|
|
)
|
|
|
|
|
2016-09-16 04:35:52 +00:00
|
|
|
func TestAuthenticatedUser(t *testing.T) {
|
2016-11-25 18:08:22 +00:00
|
|
|
ds, err := inmem.New(config.TestConfig())
|
2016-09-16 04:35:52 +00:00
|
|
|
assert.Nil(t, err)
|
|
|
|
createTestUsers(t, ds)
|
2016-11-14 18:22:54 +00:00
|
|
|
svc, err := newTestService(ds, nil)
|
2016-09-16 04:35:52 +00:00
|
|
|
assert.Nil(t, err)
|
|
|
|
admin1, err := ds.User("admin1")
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
ctx := context.Background()
|
2016-09-26 17:14:39 +00:00
|
|
|
ctx = viewer.NewContext(ctx, viewer.Viewer{User: admin1})
|
2016-09-16 04:35:52 +00:00
|
|
|
user, err := svc.AuthenticatedUser(ctx)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, user, admin1)
|
|
|
|
}
|
|
|
|
|
2016-09-15 14:52:17 +00:00
|
|
|
func TestRequestPasswordReset(t *testing.T) {
|
2016-11-25 18:08:22 +00:00
|
|
|
ds, err := inmem.New(config.TestConfig())
|
2016-09-15 14:52:17 +00:00
|
|
|
assert.Nil(t, err)
|
|
|
|
createTestUsers(t, ds)
|
|
|
|
admin1, err := ds.User("admin1")
|
|
|
|
assert.Nil(t, err)
|
|
|
|
user1, err := ds.User("user1")
|
|
|
|
assert.Nil(t, err)
|
|
|
|
var defaultEmailFn = func(e kolide.Email) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var errEmailFn = func(e kolide.Email) error {
|
|
|
|
return errors.New("test err")
|
|
|
|
}
|
|
|
|
svc := service{
|
|
|
|
ds: ds,
|
|
|
|
config: config.TestConfig(),
|
|
|
|
}
|
|
|
|
|
|
|
|
var requestPasswordResetTests = []struct {
|
|
|
|
email string
|
|
|
|
emailFn func(e kolide.Email) error
|
2016-09-15 19:27:55 +00:00
|
|
|
wantErr error
|
2016-09-15 14:52:17 +00:00
|
|
|
user *kolide.User
|
2016-09-26 17:14:39 +00:00
|
|
|
vc *viewer.Viewer
|
2016-09-15 14:52:17 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
email: admin1.Email,
|
|
|
|
emailFn: defaultEmailFn,
|
|
|
|
user: admin1,
|
2016-09-26 17:14:39 +00:00
|
|
|
vc: &viewer.Viewer{User: admin1},
|
2016-09-15 14:52:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
email: admin1.Email,
|
|
|
|
emailFn: defaultEmailFn,
|
|
|
|
user: admin1,
|
2016-09-26 17:14:39 +00:00
|
|
|
vc: nil,
|
2016-09-15 14:52:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
email: user1.Email,
|
|
|
|
emailFn: defaultEmailFn,
|
|
|
|
user: user1,
|
2016-09-26 17:14:39 +00:00
|
|
|
vc: &viewer.Viewer{User: admin1},
|
2016-09-15 14:52:17 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
email: admin1.Email,
|
|
|
|
emailFn: errEmailFn,
|
|
|
|
user: user1,
|
2016-09-26 17:14:39 +00:00
|
|
|
vc: nil,
|
2016-09-15 14:52:17 +00:00
|
|
|
wantErr: errors.New("test err"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range requestPasswordResetTests {
|
2016-11-09 16:52:25 +00:00
|
|
|
t.Run("", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
tt := tt
|
2016-09-15 14:52:17 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
if tt.vc != nil {
|
2016-09-26 17:14:39 +00:00
|
|
|
ctx = viewer.NewContext(ctx, *tt.vc)
|
2016-09-15 14:52:17 +00:00
|
|
|
}
|
|
|
|
mailer := &mockMailService{SendEmailFn: tt.emailFn}
|
|
|
|
svc.mailService = mailer
|
|
|
|
serviceErr := svc.RequestPasswordReset(ctx, tt.email)
|
|
|
|
assert.Equal(t, tt.wantErr, serviceErr)
|
2016-09-26 17:14:39 +00:00
|
|
|
if tt.vc != nil && tt.vc.IsAdmin() {
|
2016-11-09 16:52:25 +00:00
|
|
|
assert.False(t, mailer.Invoked, "email should not be sent if reset requested by admin")
|
|
|
|
assert.True(t, tt.user.AdminForcedPasswordReset, "AdminForcedPasswordReset should be true if reset requested by admin")
|
2016-09-15 14:52:17 +00:00
|
|
|
} else {
|
2016-11-09 16:52:25 +00:00
|
|
|
assert.True(t, mailer.Invoked, "email should be sent if vc is not admin")
|
2016-09-15 14:52:17 +00:00
|
|
|
if serviceErr == nil {
|
|
|
|
req, err := ds.FindPassswordResetsByUserID(tt.user.ID)
|
2016-11-09 16:52:25 +00:00
|
|
|
assert.Nil(t, err)
|
|
|
|
assert.NotEmpty(t, req, "user should have at least one password reset request")
|
2016-09-15 14:52:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-28 03:59:17 +00:00
|
|
|
func TestCreateUser(t *testing.T) {
|
2016-11-25 18:08:22 +00:00
|
|
|
ds, _ := inmem.New(config.TestConfig())
|
2016-11-14 18:22:54 +00:00
|
|
|
svc, _ := newTestService(ds, nil)
|
2016-09-29 02:44:05 +00:00
|
|
|
invites := setupInvites(t, ds, []string{"admin2@example.com"})
|
2016-09-01 04:51:38 +00:00
|
|
|
ctx := context.Background()
|
2016-09-04 05:13:42 +00:00
|
|
|
|
2016-08-28 03:59:17 +00:00
|
|
|
var createUserTests = []struct {
|
|
|
|
Username *string
|
|
|
|
Password *string
|
|
|
|
Email *string
|
|
|
|
NeedsPasswordReset *bool
|
|
|
|
Admin *bool
|
2016-09-29 02:44:05 +00:00
|
|
|
InviteToken *string
|
2016-09-15 19:27:55 +00:00
|
|
|
wantErr error
|
2016-08-28 03:59:17 +00:00
|
|
|
}{
|
|
|
|
{
|
2016-09-29 02:44:05 +00:00
|
|
|
Username: stringPtr("admin2"),
|
|
|
|
Password: stringPtr("foobar"),
|
|
|
|
InviteToken: &invites["admin2@example.com"].Token,
|
|
|
|
wantErr: &invalidArgumentError{invalidArgument{name: "email", reason: "missing required argument"}},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Username: stringPtr("admin2"),
|
2016-08-28 03:59:17 +00:00
|
|
|
Password: stringPtr("foobar"),
|
2016-09-29 02:44:05 +00:00
|
|
|
Email: stringPtr("admin2@example.com"),
|
|
|
|
wantErr: &invalidArgumentError{invalidArgument{name: "invite_token", reason: "missing required argument"}},
|
2016-08-28 03:59:17 +00:00
|
|
|
},
|
|
|
|
{
|
2016-09-29 02:44:05 +00:00
|
|
|
Username: stringPtr("admin2"),
|
2016-08-28 03:59:17 +00:00
|
|
|
Password: stringPtr("foobar"),
|
2016-09-29 02:44:05 +00:00
|
|
|
Email: stringPtr("admin2@example.com"),
|
2016-08-28 03:59:17 +00:00
|
|
|
NeedsPasswordReset: boolPtr(true),
|
|
|
|
Admin: boolPtr(false),
|
2016-09-29 02:44:05 +00:00
|
|
|
InviteToken: &invites["admin2@example.com"].Token,
|
|
|
|
},
|
|
|
|
{ // should return ErrNotFound because the invite is deleted
|
|
|
|
// after a user signs up
|
|
|
|
Username: stringPtr("admin2"),
|
|
|
|
Password: stringPtr("foobar"),
|
|
|
|
Email: stringPtr("admin2@example.com"),
|
|
|
|
NeedsPasswordReset: boolPtr(true),
|
|
|
|
Admin: boolPtr(false),
|
|
|
|
InviteToken: &invites["admin2@example.com"].Token,
|
2016-11-16 13:47:49 +00:00
|
|
|
wantErr: kolide_errors.ErrNotFound,
|
2016-08-28 03:59:17 +00:00
|
|
|
},
|
2016-09-15 19:27:55 +00:00
|
|
|
{
|
2016-09-29 02:44:05 +00:00
|
|
|
Username: stringPtr("admin3"),
|
2016-09-15 19:27:55 +00:00
|
|
|
Password: stringPtr("foobar"),
|
2016-09-29 02:44:05 +00:00
|
|
|
Email: &invites["expired"].Email,
|
2016-09-15 19:27:55 +00:00
|
|
|
NeedsPasswordReset: boolPtr(true),
|
|
|
|
Admin: boolPtr(false),
|
2016-09-29 02:44:05 +00:00
|
|
|
InviteToken: &invites["expired"].Token,
|
2016-12-07 16:25:48 +00:00
|
|
|
wantErr: &invalidArgumentError{{name: "invite_token", reason: "Invite token has expired."}},
|
2016-09-15 19:27:55 +00:00
|
|
|
},
|
|
|
|
{
|
2016-09-29 02:44:05 +00:00
|
|
|
Username: stringPtr("@admin2"),
|
2016-09-15 19:27:55 +00:00
|
|
|
Password: stringPtr("foobar"),
|
2016-09-29 02:44:05 +00:00
|
|
|
Email: stringPtr("admin2@example.com"),
|
2016-09-15 19:27:55 +00:00
|
|
|
NeedsPasswordReset: boolPtr(true),
|
|
|
|
Admin: boolPtr(false),
|
2016-09-29 02:44:05 +00:00
|
|
|
InviteToken: &invites["admin2@example.com"].Token,
|
|
|
|
wantErr: &invalidArgumentError{invalidArgument{name: "username", reason: "'@' character not allowed in usernames"}},
|
2016-09-15 19:27:55 +00:00
|
|
|
},
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
|
|
|
|
2016-11-09 16:52:25 +00:00
|
|
|
for _, tt := range createUserTests {
|
|
|
|
t.Run("", func(t *testing.T) {
|
|
|
|
payload := kolide.UserPayload{
|
|
|
|
Username: tt.Username,
|
|
|
|
Password: tt.Password,
|
|
|
|
Email: tt.Email,
|
|
|
|
Admin: tt.Admin,
|
|
|
|
InviteToken: tt.InviteToken,
|
|
|
|
AdminForcedPasswordReset: tt.NeedsPasswordReset,
|
|
|
|
}
|
|
|
|
user, err := svc.NewUser(ctx, payload)
|
|
|
|
require.Equal(t, tt.wantErr, err)
|
|
|
|
if err != nil {
|
|
|
|
// skip rest of the test if error is not nil
|
|
|
|
return
|
|
|
|
}
|
2016-08-28 03:59:17 +00:00
|
|
|
|
2016-11-09 16:52:25 +00:00
|
|
|
assert.NotZero(t, user.ID)
|
2016-08-28 03:59:17 +00:00
|
|
|
|
2016-11-09 16:52:25 +00:00
|
|
|
err = user.ValidatePassword(*tt.Password)
|
|
|
|
assert.Nil(t, err)
|
2016-08-28 03:59:17 +00:00
|
|
|
|
2016-11-09 16:52:25 +00:00
|
|
|
err = user.ValidatePassword("different_password")
|
|
|
|
assert.NotNil(t, err)
|
2016-08-28 03:59:17 +00:00
|
|
|
|
2016-11-09 16:52:25 +00:00
|
|
|
assert.Equal(t, user.AdminForcedPasswordReset, *tt.NeedsPasswordReset)
|
|
|
|
assert.Equal(t, user.Admin, *tt.Admin)
|
|
|
|
})
|
2016-08-28 03:59:17 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-29 02:44:05 +00:00
|
|
|
func setupInvites(t *testing.T, ds kolide.Datastore, emails []string) map[string]*kolide.Invite {
|
|
|
|
invites := make(map[string]*kolide.Invite)
|
|
|
|
users := createTestUsers(t, ds)
|
|
|
|
mockClock := clock.NewMockClock()
|
|
|
|
for _, e := range emails {
|
|
|
|
invite, err := ds.NewInvite(&kolide.Invite{
|
|
|
|
InvitedBy: users["admin1"].ID,
|
|
|
|
Token: e,
|
|
|
|
Email: e,
|
2016-11-16 13:47:49 +00:00
|
|
|
UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{
|
|
|
|
CreateTimestamp: kolide.CreateTimestamp{
|
|
|
|
CreatedAt: mockClock.Now(),
|
|
|
|
},
|
|
|
|
},
|
2016-09-29 02:44:05 +00:00
|
|
|
})
|
|
|
|
require.Nil(t, err)
|
|
|
|
invites[e] = invite
|
|
|
|
}
|
|
|
|
// add an expired invitation
|
|
|
|
invite, err := ds.NewInvite(&kolide.Invite{
|
|
|
|
InvitedBy: users["admin1"].ID,
|
|
|
|
Token: "expired",
|
|
|
|
Email: "expiredinvite@gmail.com",
|
2016-11-16 13:47:49 +00:00
|
|
|
UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{
|
|
|
|
CreateTimestamp: kolide.CreateTimestamp{
|
|
|
|
CreatedAt: mockClock.Now().AddDate(-1, 0, 0),
|
|
|
|
},
|
|
|
|
},
|
2016-09-29 02:44:05 +00:00
|
|
|
})
|
|
|
|
require.Nil(t, err)
|
|
|
|
invites["expired"] = invite
|
|
|
|
return invites
|
|
|
|
}
|
|
|
|
|
2016-12-14 18:11:43 +00:00
|
|
|
func TestChangePassword(t *testing.T) {
|
2016-11-25 18:08:22 +00:00
|
|
|
ds, _ := inmem.New(config.TestConfig())
|
2016-11-14 18:22:54 +00:00
|
|
|
svc, _ := newTestService(ds, nil)
|
2016-12-14 18:11:43 +00:00
|
|
|
users := createTestUsers(t, ds)
|
2016-08-29 00:29:56 +00:00
|
|
|
var passwordChangeTests = []struct {
|
2016-12-14 18:11:43 +00:00
|
|
|
user kolide.User
|
|
|
|
oldPassword string
|
|
|
|
newPassword string
|
|
|
|
anyErr bool
|
|
|
|
wantErr error
|
|
|
|
}{
|
|
|
|
{ // all good
|
|
|
|
user: users["admin1"],
|
|
|
|
oldPassword: "foobar",
|
|
|
|
newPassword: "123cat!",
|
|
|
|
},
|
|
|
|
{ // all good
|
|
|
|
user: users["user1"],
|
|
|
|
oldPassword: "foobar",
|
|
|
|
newPassword: "newpass",
|
|
|
|
},
|
|
|
|
{ // bad old password
|
|
|
|
user: users["user1"],
|
|
|
|
oldPassword: "wrong_password",
|
|
|
|
newPassword: "123cat!",
|
|
|
|
anyErr: true,
|
|
|
|
},
|
|
|
|
{ // missing old password
|
|
|
|
newPassword: "123cat!",
|
|
|
|
wantErr: &invalidArgumentError{invalidArgument{name: "old_password", reason: "cannot be empty field"}},
|
|
|
|
},
|
|
|
|
{ // missing new password
|
|
|
|
oldPassword: "abcd",
|
|
|
|
wantErr: &invalidArgumentError{invalidArgument{name: "new_password", reason: "cannot be empty field"}},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range passwordChangeTests {
|
|
|
|
t.Run("", func(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &tt.user})
|
|
|
|
|
|
|
|
err := svc.ChangePassword(ctx, tt.oldPassword, tt.newPassword)
|
|
|
|
if tt.anyErr {
|
|
|
|
require.NotNil(t, err)
|
|
|
|
} else {
|
|
|
|
require.Equal(t, tt.wantErr, pkg_errors.Cause(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt login after successful change
|
|
|
|
_, _, err = svc.Login(context.Background(), tt.user.Username, tt.newPassword)
|
|
|
|
require.Nil(t, err, "should be able to login with new password")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestResetPassword(t *testing.T) {
|
|
|
|
ds, _ := inmem.New(config.TestConfig())
|
|
|
|
svc, _ := newTestService(ds, nil)
|
|
|
|
createTestUsers(t, ds)
|
|
|
|
var passwordResetTests = []struct {
|
2016-09-15 14:52:17 +00:00
|
|
|
token string
|
|
|
|
newPassword string
|
2016-09-15 19:27:55 +00:00
|
|
|
wantErr error
|
2016-08-29 00:29:56 +00:00
|
|
|
}{
|
2016-09-15 14:52:17 +00:00
|
|
|
{ // all good
|
|
|
|
token: "abcd",
|
|
|
|
newPassword: "123cat!",
|
|
|
|
},
|
|
|
|
{ // bad token
|
|
|
|
token: "dcbaz",
|
|
|
|
newPassword: "123cat!",
|
2016-11-16 13:47:49 +00:00
|
|
|
wantErr: kolide_errors.ErrNotFound,
|
2016-09-15 14:52:17 +00:00
|
|
|
},
|
|
|
|
{ // missing token
|
|
|
|
newPassword: "123cat!",
|
2016-09-29 02:44:05 +00:00
|
|
|
wantErr: &invalidArgumentError{invalidArgument{name: "token", reason: "cannot be empty field"}},
|
2016-09-15 14:52:17 +00:00
|
|
|
},
|
|
|
|
{ // missing password
|
|
|
|
token: "abcd",
|
2016-09-29 02:44:05 +00:00
|
|
|
wantErr: &invalidArgumentError{invalidArgument{name: "new_password", reason: "cannot be empty field"}},
|
2016-08-29 00:29:56 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-12-14 18:11:43 +00:00
|
|
|
for _, tt := range passwordResetTests {
|
2016-11-09 16:52:25 +00:00
|
|
|
t.Run("", func(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
request := &kolide.PasswordResetRequest{
|
2016-11-16 13:47:49 +00:00
|
|
|
UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{
|
|
|
|
CreateTimestamp: kolide.CreateTimestamp{
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
},
|
|
|
|
UpdateTimestamp: kolide.UpdateTimestamp{
|
|
|
|
UpdatedAt: time.Now(),
|
|
|
|
},
|
|
|
|
},
|
2016-11-09 16:52:25 +00:00
|
|
|
ExpiresAt: time.Now().Add(time.Hour * 24),
|
|
|
|
UserID: 1,
|
|
|
|
Token: "abcd",
|
|
|
|
}
|
|
|
|
_, err := ds.NewPasswordResetRequest(request)
|
|
|
|
assert.Nil(t, err)
|
2016-08-29 00:29:56 +00:00
|
|
|
|
2016-11-09 16:52:25 +00:00
|
|
|
serr := svc.ResetPassword(ctx, tt.token, tt.newPassword)
|
2016-12-14 18:11:43 +00:00
|
|
|
assert.Equal(t, tt.wantErr, pkg_errors.Cause(serr))
|
2016-11-09 16:52:25 +00:00
|
|
|
})
|
2016-08-29 00:29:56 +00:00
|
|
|
}
|
|
|
|
}
|