285 lines
8.4 KiB
Go
285 lines
8.4 KiB
Go
package organizations
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/influxdata/chronograf"
|
|
)
|
|
|
|
// Ensure UsersStore implements chronograf.UsersStore.
|
|
var _ chronograf.UsersStore = &UsersStore{}
|
|
|
|
// UsersStore facade on a UserStore that filters a users roles
|
|
// by organization.
|
|
//
|
|
// The high level idea here is to use the same underlying store for all users.
|
|
// In particular, this is done by having all the users Roles field be a set of
|
|
// all of the users roles in all organizations. Each CRUD method here takes care
|
|
// to ensure that the only roles that are modified are the roles for the organization
|
|
// that was provided on the UsersStore.
|
|
type UsersStore struct {
|
|
organization string
|
|
store chronograf.UsersStore
|
|
}
|
|
|
|
// NewUsersStore creates a new UsersStore from an existing
|
|
// chronograf.UserStore and an organization string
|
|
func NewUsersStore(s chronograf.UsersStore, org string) *UsersStore {
|
|
return &UsersStore{
|
|
store: s,
|
|
organization: org,
|
|
}
|
|
}
|
|
|
|
// validOrganizationRoles ensures that each User Role has both an associated Organization and a Name
|
|
func validOrganizationRoles(orgID string, u *chronograf.User) error {
|
|
if u == nil || u.Roles == nil {
|
|
return nil
|
|
}
|
|
for _, r := range u.Roles {
|
|
if r.Organization == "" {
|
|
return fmt.Errorf("user role must have an Organization")
|
|
}
|
|
if r.Organization != orgID {
|
|
return fmt.Errorf("organizationID %s does not match %s", r.Organization, orgID)
|
|
}
|
|
if r.Name == "" {
|
|
return fmt.Errorf("user role must have a Name")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Get searches the UsersStore for using the query.
|
|
// The roles returned on the user are filtered to only contain roles that are for the organization
|
|
// specified on the organization store.
|
|
func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
|
|
err := validOrganization(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
usr, err := s.store.Get(ctx, q)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// This filters a users roles so that the resulting struct only contains roles
|
|
// from the organization on the UsersStore.
|
|
roles := usr.Roles[:0]
|
|
for _, r := range usr.Roles {
|
|
if r.Organization == s.organization {
|
|
roles = append(roles, r)
|
|
}
|
|
}
|
|
|
|
if len(roles) == 0 {
|
|
// This means that the user does not belong to the organization
|
|
// and therefore, is not found.
|
|
return nil, chronograf.ErrUserNotFound
|
|
}
|
|
|
|
usr.Roles = roles
|
|
return usr, nil
|
|
}
|
|
|
|
// Add creates a new User in the UsersStore. It validates that the user provided only
|
|
// has roles for the organization set on the UsersStore.
|
|
// If a user is not found in the underlying, it calls the underlying UsersStore Add method.
|
|
// If a user is found, it removes any existing roles a user has for an organization and appends
|
|
// the roles specified on the provided user and calls the uderlying UsersStore Update method.
|
|
func (s *UsersStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
|
|
err := validOrganization(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validates that the users roles are only for the current organization.
|
|
if err := validOrganizationRoles(s.organization, u); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// retrieve the user from the underlying store
|
|
usr, err := s.store.Get(ctx, chronograf.UserQuery{
|
|
Name: &u.Name,
|
|
Provider: &u.Provider,
|
|
Scheme: &u.Scheme,
|
|
})
|
|
|
|
switch err {
|
|
case nil:
|
|
// If there is no error continue to the rest of the code
|
|
break
|
|
case chronograf.ErrUserNotFound:
|
|
// If user is not found in the backed store, attempt to add the user
|
|
return s.store.Add(ctx, u)
|
|
default:
|
|
// return the error
|
|
return nil, err
|
|
}
|
|
|
|
// Filter the retrieved users roles so that the resulting struct only contains roles
|
|
// that are not from the organization on the UsersStore.
|
|
roles := usr.Roles[:0]
|
|
for _, r := range usr.Roles {
|
|
if r.Organization != s.organization {
|
|
roles = append(roles, r)
|
|
}
|
|
}
|
|
|
|
// If the user already has a role in the organization then the user
|
|
// cannot be "created".
|
|
// This can be thought of as:
|
|
// (total # of roles a user has) - (# of roles not in the organization) = (# of roles in organization)
|
|
// if this value is greater than 1 the user cannot be "added".
|
|
numRolesInOrganization := len(usr.Roles) - len(roles)
|
|
if numRolesInOrganization > 0 {
|
|
return nil, chronograf.ErrUserAlreadyExists
|
|
}
|
|
|
|
// Set the users roles to be the union of the roles set on the provided user
|
|
// and the user that was found in the underlying store
|
|
usr.Roles = append(roles, u.Roles...)
|
|
|
|
// u.SuperAdmin == true is logically equivalent to u.SuperAdmin, however
|
|
// it is more clear on a conceptual level to check equality
|
|
//
|
|
// TODO(desa): this should go away with https://github.com/influxdata/chronograf/issues/2207
|
|
// I do not like checking super admin here. The organization users store should only be
|
|
// concerned about organizations.
|
|
//
|
|
// If the user being added already existed in a previous organization, and was already a SuperAdmin,
|
|
// then this ensures that they retain their SuperAdmin status. And if they weren't a SuperAdmin, and
|
|
// the user being added has been granted SuperAdmin status, they will be promoted
|
|
if u.SuperAdmin == true {
|
|
usr.SuperAdmin = true
|
|
}
|
|
|
|
// Update the user in the underlying store
|
|
if err := s.store.Update(ctx, usr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Return the provided user with ID set
|
|
u.ID = usr.ID
|
|
return u, nil
|
|
}
|
|
|
|
// Delete a user from the UsersStore. This is done by stripping a user of
|
|
// any roles it has in the organization speicified on the UsersStore.
|
|
func (s *UsersStore) Delete(ctx context.Context, usr *chronograf.User) error {
|
|
err := validOrganization(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// retrieve the user from the underlying store
|
|
u, err := s.store.Get(ctx, chronograf.UserQuery{ID: &usr.ID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Filter the retrieved users roles so that the resulting slice contains
|
|
// roles that are not scoped to the organization provided
|
|
roles := u.Roles[:0]
|
|
for _, r := range u.Roles {
|
|
if r.Organization != s.organization {
|
|
roles = append(roles, r)
|
|
}
|
|
}
|
|
u.Roles = roles
|
|
return s.store.Update(ctx, u)
|
|
}
|
|
|
|
// Update a user in the UsersStore.
|
|
func (s *UsersStore) Update(ctx context.Context, usr *chronograf.User) error {
|
|
err := validOrganization(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validates that the users roles are only for the current organization.
|
|
if err := validOrganizationRoles(s.organization, usr); err != nil {
|
|
return err
|
|
}
|
|
|
|
// retrieve the user from the underlying store
|
|
u, err := s.store.Get(ctx, chronograf.UserQuery{ID: &usr.ID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Filter the retrieved users roles so that the resulting slice contains
|
|
// roles that are not scoped to the organization provided
|
|
roles := u.Roles[:0]
|
|
for _, r := range u.Roles {
|
|
if r.Organization != s.organization {
|
|
roles = append(roles, r)
|
|
}
|
|
}
|
|
|
|
// Make a copy of the usr so that we dont modify the underlying add roles on to
|
|
// the user that was passed in
|
|
user := *usr
|
|
|
|
// Set the users roles to be the union of the roles set on the provided user
|
|
// and the user that was found in the underlying store
|
|
user.Roles = append(roles, usr.Roles...)
|
|
|
|
return s.store.Update(ctx, &user)
|
|
}
|
|
|
|
// All returns all users where roles have been filters to be exclusively for
|
|
// the organization provided on the UsersStore.
|
|
func (s *UsersStore) All(ctx context.Context) ([]chronograf.User, error) {
|
|
err := validOrganization(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// retrieve all users from the underlying UsersStore
|
|
usrs, err := s.store.All(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Filter users to only contain users that have at least one role
|
|
// in the provided organization.
|
|
us := usrs[:0]
|
|
for _, usr := range usrs {
|
|
roles := usr.Roles[:0]
|
|
// This filters a users roles so that the resulting struct only contains roles
|
|
// from the organization on the UsersStore.
|
|
for _, r := range usr.Roles {
|
|
if r.Organization == s.organization {
|
|
roles = append(roles, r)
|
|
}
|
|
}
|
|
if len(roles) != 0 {
|
|
// Only add users if they have a role in the associated organization
|
|
usr.Roles = roles
|
|
us = append(us, usr)
|
|
}
|
|
}
|
|
|
|
return us, nil
|
|
}
|
|
|
|
// Num returns the number of users in the UsersStore
|
|
// This is unperformant, but should rarely be used.
|
|
func (s *UsersStore) Num(ctx context.Context) (int, error) {
|
|
err := validOrganization(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// retrieve all users from the underlying UsersStore
|
|
usrs, err := s.All(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return len(usrs), nil
|
|
}
|