influxdb/tenant/service_user.go

265 lines
6.5 KiB
Go

package tenant
import (
"context"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/influxdata/influxdb/v2/kv"
"golang.org/x/crypto/bcrypt"
)
type UserSvc struct {
store *Store
svc *Service
}
func NewUserSvc(st *Store, svc *Service) *UserSvc {
return &UserSvc{
store: st,
svc: svc,
}
}
// Returns a single user by ID.
func (s *UserSvc) FindUserByID(ctx context.Context, id platform.ID) (*influxdb.User, error) {
var user *influxdb.User
err := s.store.View(ctx, func(tx kv.Tx) error {
u, err := s.store.GetUser(ctx, tx, id)
if err != nil {
return err
}
user = u
return nil
})
if err != nil {
return nil, err
}
return user, nil
}
// Returns the first user that matches filter.
func (s *UserSvc) FindUser(ctx context.Context, filter influxdb.UserFilter) (*influxdb.User, error) {
// if im given no filters its not a valid find user request. (leaving it unchecked seems dangerous)
if filter.ID == nil && filter.Name == nil {
return nil, ErrUserNotFound
}
if filter.ID != nil {
return s.FindUserByID(ctx, *filter.ID)
}
var user *influxdb.User
err := s.store.View(ctx, func(tx kv.Tx) error {
u, err := s.store.GetUserByName(ctx, tx, *filter.Name)
if err != nil {
return err
}
user = u
return nil
})
if err != nil {
return nil, err
}
return user, nil
}
// Returns a list of users that match filter and the total count of matching users.
// Additional options provide pagination & sorting. {
func (s *UserSvc) FindUsers(ctx context.Context, filter influxdb.UserFilter, opt ...influxdb.FindOptions) ([]*influxdb.User, int, error) {
// if a id is provided we will reroute to findUserByID
if filter.ID != nil {
user, err := s.FindUserByID(ctx, *filter.ID)
if err != nil {
return nil, 0, err
}
return []*influxdb.User{user}, 1, nil
}
// if a name is provided we will reroute to findUser with a name filter
if filter.Name != nil {
user, err := s.FindUser(ctx, filter)
if err != nil {
return nil, 0, err
}
return []*influxdb.User{user}, 1, nil
}
var users []*influxdb.User
err := s.store.View(ctx, func(tx kv.Tx) error {
us, err := s.store.ListUsers(ctx, tx, opt...)
if err != nil {
return err
}
users = us
return nil
})
if err != nil {
return nil, 0, err
}
return users, len(users), nil
}
// Creates a new user and sets u.ID with the new identifier.
func (s *UserSvc) CreateUser(ctx context.Context, u *influxdb.User) error {
err := s.store.Update(ctx, func(tx kv.Tx) error {
return s.store.CreateUser(ctx, tx, u)
})
return err
}
// Updates a single user with changeset.
// Returns the new user state after update. {
func (s *UserSvc) UpdateUser(ctx context.Context, id platform.ID, upd influxdb.UserUpdate) (*influxdb.User, error) {
var user *influxdb.User
err := s.store.Update(ctx, func(tx kv.Tx) error {
u, err := s.store.UpdateUser(ctx, tx, id, upd)
if err != nil {
return err
}
user = u
return nil
})
if err != nil {
return nil, err
}
return user, nil
}
// Removes a user by ID.
func (s *UserSvc) DeleteUser(ctx context.Context, id platform.ID) error {
err := s.store.Update(ctx, func(tx kv.Tx) error {
err := s.store.DeletePassword(ctx, tx, id)
if err != nil {
return err
}
return s.store.DeleteUser(ctx, tx, id)
})
return err
}
// FindPermissionForUser gets the full set of permission for a specified user id
func (s *UserSvc) FindPermissionForUser(ctx context.Context, uid platform.ID) (influxdb.PermissionSet, error) {
mappings, _, err := s.svc.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{UserID: uid}, influxdb.FindOptions{Limit: 100})
if err != nil {
return nil, err
}
permissions, err := permissionFromMapping(mappings)
if err != nil {
return nil, err
}
if len(mappings) >= 100 {
// if we got 100 mappings we probably need to pull more pages
// account for paginated results
for i := len(mappings); len(mappings) > 0; i += len(mappings) {
mappings, _, err = s.svc.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{UserID: uid}, influxdb.FindOptions{Offset: i, Limit: 100})
if err != nil {
return nil, err
}
pms, err := permissionFromMapping(mappings)
if err != nil {
return nil, err
}
permissions = append(permissions, pms...)
}
}
permissions = append(permissions, influxdb.MePermissions(uid)...)
return permissions, nil
}
// SetPassword overrides the password of a known user.
func (s *UserSvc) SetPassword(ctx context.Context, userID platform.ID, password string) error {
if len(password) < MinPasswordLen {
return EShortPassword
}
passHash, err := encryptPassword(password)
if err != nil {
return err
}
// set password
return s.store.Update(ctx, func(tx kv.Tx) error {
_, err := s.store.GetUser(ctx, tx, userID)
if err != nil {
return EIncorrectUser
}
return s.store.SetPassword(ctx, tx, userID, passHash)
})
}
// ComparePassword checks if the password matches the password recorded.
// Passwords that do not match return errors.
func (s *UserSvc) ComparePassword(ctx context.Context, userID platform.ID, password string) error {
// get password
var hash []byte
err := s.store.View(ctx, func(tx kv.Tx) error {
_, err := s.store.GetUser(ctx, tx, userID)
if err != nil {
return EIncorrectUser
}
h, err := s.store.GetPassword(ctx, tx, userID)
if err != nil {
if err == kv.ErrKeyNotFound {
return EIncorrectPassword
}
return err
}
hash = []byte(h)
return nil
})
if err != nil {
return err
}
// compare password
if err := bcrypt.CompareHashAndPassword(hash, []byte(password)); err != nil {
return EIncorrectPassword
}
return nil
}
// CompareAndSetPassword checks the password and if they match
// updates to the new password.
func (s *UserSvc) CompareAndSetPassword(ctx context.Context, userID platform.ID, old, new string) error {
err := s.ComparePassword(ctx, userID, old)
if err != nil {
return err
}
return s.SetPassword(ctx, userID, new)
}
func encryptPassword(password string) (string, error) {
passHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(passHash), nil
}
func permissionFromMapping(mappings []*influxdb.UserResourceMapping) ([]influxdb.Permission, error) {
ps := make([]influxdb.Permission, 0, len(mappings))
for _, m := range mappings {
p, err := m.ToPermissions()
if err != nil {
return nil, &errors.Error{
Err: err,
}
}
ps = append(ps, p...)
}
return ps, nil
}