2016-08-18 21:16:44 +00:00
|
|
|
package kolide
|
|
|
|
|
|
|
|
import (
|
2016-09-29 02:44:05 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
2016-08-18 21:16:44 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
2016-09-04 05:13:42 +00:00
|
|
|
"golang.org/x/net/context"
|
2016-08-18 21:16:44 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// UserStore contains methods for managing users in a datastore
|
|
|
|
type UserStore interface {
|
|
|
|
NewUser(user *User) (*User, error)
|
|
|
|
User(username string) (*User, error)
|
2016-10-13 18:21:47 +00:00
|
|
|
Users(opt ListOptions) ([]*User, error)
|
2016-09-15 14:52:17 +00:00
|
|
|
UserByEmail(email string) (*User, error)
|
2016-08-18 21:16:44 +00:00
|
|
|
UserByID(id uint) (*User, error)
|
|
|
|
SaveUser(user *User) error
|
|
|
|
}
|
|
|
|
|
2016-09-15 14:52:17 +00:00
|
|
|
// UserService contains methods for managing a Kolide User
|
2016-09-04 05:13:42 +00:00
|
|
|
type UserService interface {
|
2016-09-15 14:52:17 +00:00
|
|
|
// NewUser creates a new User from a request Payload
|
2016-09-28 11:35:15 +00:00
|
|
|
NewUser(ctx context.Context, p UserPayload) (user *User, err error)
|
2016-09-15 14:52:17 +00:00
|
|
|
|
|
|
|
// User returns a valid User given a User ID
|
2016-09-28 11:35:15 +00:00
|
|
|
User(ctx context.Context, id uint) (user *User, err error)
|
2016-09-15 14:52:17 +00:00
|
|
|
|
2016-09-16 04:35:52 +00:00
|
|
|
// AuthenticatedUser returns the current user
|
|
|
|
// from the viewer context
|
2016-09-28 11:35:15 +00:00
|
|
|
AuthenticatedUser(ctx context.Context) (user *User, err error)
|
2016-09-16 04:35:52 +00:00
|
|
|
|
2016-09-15 14:52:17 +00:00
|
|
|
// Users returns all users
|
2016-10-13 18:21:47 +00:00
|
|
|
ListUsers(ctx context.Context, opt ListOptions) (users []*User, err error)
|
2016-09-15 14:52:17 +00:00
|
|
|
|
|
|
|
// RequestPasswordReset generates a password reset request for
|
|
|
|
// a user. The request results in a token emailed to the user.
|
|
|
|
// If the person making the request is an admin the AdminForcedPasswordReset
|
|
|
|
// parameter is enabled instead of sending an email with a password reset token
|
2016-09-28 11:35:15 +00:00
|
|
|
RequestPasswordReset(ctx context.Context, email string) (err error)
|
2016-09-15 14:52:17 +00:00
|
|
|
|
|
|
|
// ResetPassword validate a password reset token and updates
|
|
|
|
// a user's password
|
2016-09-28 11:35:15 +00:00
|
|
|
ResetPassword(ctx context.Context, token, password string) (err error)
|
2016-09-15 14:52:17 +00:00
|
|
|
|
|
|
|
// ModifyUser updates a user's parameters given a UserPayload
|
2016-09-28 11:35:15 +00:00
|
|
|
ModifyUser(ctx context.Context, userID uint, p UserPayload) (user *User, err error)
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
2016-08-18 21:16:44 +00:00
|
|
|
// User is the model struct which represents a kolide user
|
|
|
|
type User struct {
|
2016-10-11 16:22:11 +00:00
|
|
|
ID uint `json:"id" gorm:"primary_key"`
|
|
|
|
CreatedAt time.Time `json:"-"`
|
|
|
|
UpdatedAt time.Time `json:"-"`
|
|
|
|
Username string `json:"username" gorm:"not null;unique_index:idx_user_unique_username"`
|
|
|
|
Password []byte `json:"-" gorm:"not null"`
|
|
|
|
Salt string `json:"-" gorm:"not null"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Email string `json:"email" gorm:"not null;unique_index:idx_user_unique_email"`
|
|
|
|
Admin bool `json:"admin" gorm:"not null"`
|
|
|
|
Enabled bool `json:"enabled" gorm:"not null"`
|
|
|
|
AdminForcedPasswordReset bool `json:"force_password_reset"`
|
|
|
|
GravatarURL string `json:"gravatar_url"`
|
|
|
|
Position string `json:"position,omitempty"` // job role
|
2016-08-18 21:16:44 +00:00
|
|
|
}
|
|
|
|
|
2016-08-28 03:59:17 +00:00
|
|
|
// UserPayload is used to modify an existing user
|
|
|
|
type UserPayload struct {
|
2016-09-08 22:57:05 +00:00
|
|
|
Username *string `json:"username"`
|
|
|
|
Name *string `json:"name"`
|
|
|
|
Email *string `json:"email"`
|
|
|
|
Admin *bool `json:"admin"`
|
|
|
|
Enabled *bool `json:"enabled"`
|
|
|
|
AdminForcedPasswordReset *bool `json:"force_password_reset"`
|
|
|
|
Password *string `json:"password"`
|
2016-09-15 14:52:17 +00:00
|
|
|
GravatarURL *string `json:"gravatar_url"`
|
|
|
|
Position *string `json:"position"`
|
2016-09-29 02:44:05 +00:00
|
|
|
InviteToken *string `json:"invite_token"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// User creates a user from payload.
|
|
|
|
func (p UserPayload) User(keySize, cost int) (*User, error) {
|
|
|
|
|
|
|
|
user := &User{
|
|
|
|
Username: *p.Username,
|
|
|
|
Email: *p.Email,
|
|
|
|
Admin: falseIfNil(p.Admin),
|
|
|
|
AdminForcedPasswordReset: falseIfNil(p.AdminForcedPasswordReset),
|
|
|
|
Enabled: true,
|
|
|
|
}
|
|
|
|
if err := user.SetPassword(*p.Password, keySize, cost); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// add optional fields
|
|
|
|
if p.Name != nil {
|
|
|
|
user.Name = *p.Name
|
|
|
|
}
|
|
|
|
if p.Position != nil {
|
|
|
|
user.Position = *p.Position
|
|
|
|
}
|
|
|
|
|
|
|
|
return user, nil
|
2016-08-18 21:16:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ValidatePassword accepts a potential password for a given user and attempts
|
|
|
|
// to validate it against the hash stored in the database after joining the
|
|
|
|
// supplied password with the stored password salt
|
|
|
|
func (u *User) ValidatePassword(password string) error {
|
|
|
|
saltAndPass := []byte(fmt.Sprintf("%s%s", password, u.Salt))
|
|
|
|
return bcrypt.CompareHashAndPassword(u.Password, saltAndPass)
|
|
|
|
}
|
2016-09-29 02:44:05 +00:00
|
|
|
|
|
|
|
func (u *User) SetPassword(plaintext string, keySize, cost int) error {
|
|
|
|
salt, err := generateRandomText(keySize)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
withSalt := []byte(fmt.Sprintf("%s%s", plaintext, salt))
|
|
|
|
hashed, err := bcrypt.GenerateFromPassword(withSalt, cost)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
u.Salt = salt
|
|
|
|
u.Password = hashed
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// generateRandomText return a string generated by filling in keySize bytes with
|
|
|
|
// random data and then base64 encoding those bytes
|
|
|
|
func generateRandomText(keySize int) (string, error) {
|
|
|
|
key := make([]byte, keySize)
|
|
|
|
_, err := rand.Read(key)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return base64.StdEncoding.EncodeToString(key), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// helper to convert a bool pointer false
|
|
|
|
func falseIfNil(b *bool) bool {
|
|
|
|
if b == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return *b
|
|
|
|
}
|