184 lines
4.7 KiB
Go
184 lines
4.7 KiB
Go
package tenant
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/influxdata/influxdb/v2/kit/platform"
|
|
"github.com/influxdata/influxdb/v2/kit/platform/errors"
|
|
|
|
"github.com/influxdata/influxdb/v2"
|
|
icontext "github.com/influxdata/influxdb/v2/context"
|
|
"github.com/influxdata/influxdb/v2/kv"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type OnboardService struct {
|
|
service *Service
|
|
authSvc influxdb.AuthorizationService
|
|
alwaysAllow bool
|
|
log *zap.Logger
|
|
}
|
|
|
|
type OnboardServiceOptionFn func(*OnboardService)
|
|
|
|
// WithAlwaysAllowInitialUser configures the OnboardService to
|
|
// always return true for IsOnboarding to allow multiple
|
|
// initial onboard requests.
|
|
func WithAlwaysAllowInitialUser() OnboardServiceOptionFn {
|
|
return func(s *OnboardService) {
|
|
s.alwaysAllow = true
|
|
}
|
|
}
|
|
|
|
func WithOnboardingLogger(logger *zap.Logger) OnboardServiceOptionFn {
|
|
return func(s *OnboardService) {
|
|
s.log = logger
|
|
}
|
|
}
|
|
|
|
func NewOnboardService(svc *Service, as influxdb.AuthorizationService, opts ...OnboardServiceOptionFn) influxdb.OnboardingService {
|
|
s := &OnboardService{
|
|
service: svc,
|
|
authSvc: as,
|
|
log: zap.NewNop(),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(s)
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// IsOnboarding determine if onboarding request is allowed.
|
|
func (s *OnboardService) IsOnboarding(ctx context.Context) (bool, error) {
|
|
if s.alwaysAllow {
|
|
return true, nil
|
|
}
|
|
|
|
allowed := false
|
|
err := s.service.store.View(ctx, func(tx kv.Tx) error {
|
|
// we are allowed to onboard a user if we have no users or orgs
|
|
users, _ := s.service.store.ListUsers(ctx, tx, influxdb.FindOptions{Limit: 1})
|
|
orgs, _ := s.service.store.ListOrgs(ctx, tx, influxdb.FindOptions{Limit: 1})
|
|
if len(users) == 0 && len(orgs) == 0 {
|
|
allowed = true
|
|
}
|
|
return nil
|
|
})
|
|
return allowed, err
|
|
}
|
|
|
|
// OnboardInitialUser allows us to onboard a new user if is onboarding is allowed
|
|
func (s *OnboardService) OnboardInitialUser(ctx context.Context, req *influxdb.OnboardingRequest) (*influxdb.OnboardingResults, error) {
|
|
allowed, err := s.IsOnboarding(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !allowed {
|
|
return nil, ErrOnboardingNotAllowed
|
|
}
|
|
|
|
return s.onboardUser(ctx, req, func(platform.ID, platform.ID) []influxdb.Permission { return influxdb.OperPermissions() })
|
|
}
|
|
|
|
// onboardUser allows us to onboard new users.
|
|
func (s *OnboardService) onboardUser(ctx context.Context, req *influxdb.OnboardingRequest, permFn func(orgID, userID platform.ID) []influxdb.Permission) (*influxdb.OnboardingResults, error) {
|
|
if req == nil {
|
|
return nil, &errors.Error{
|
|
Code: errors.EEmptyValue,
|
|
Msg: "onboarding failed: no request body provided",
|
|
}
|
|
}
|
|
|
|
var missingFields []string
|
|
if req.User == "" {
|
|
missingFields = append(missingFields, "username")
|
|
}
|
|
if req.Org == "" {
|
|
missingFields = append(missingFields, "org")
|
|
}
|
|
if req.Bucket == "" {
|
|
missingFields = append(missingFields, "bucket")
|
|
}
|
|
if len(missingFields) > 0 {
|
|
return nil, &errors.Error{
|
|
Code: errors.EUnprocessableEntity,
|
|
Msg: fmt.Sprintf("onboarding failed: missing required fields [%s]", strings.Join(missingFields, ",")),
|
|
}
|
|
}
|
|
|
|
result := &influxdb.OnboardingResults{}
|
|
|
|
// create a user
|
|
user := &influxdb.User{
|
|
Name: req.User,
|
|
Status: influxdb.Active,
|
|
}
|
|
|
|
if err := s.service.CreateUser(ctx, user); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// create users password
|
|
if req.Password != "" {
|
|
if err := s.service.SetPassword(ctx, user.ID, req.Password); err != nil {
|
|
// Try to clean up.
|
|
if cleanupErr := s.service.DeleteUser(ctx, user.ID); cleanupErr != nil {
|
|
s.log.Error(
|
|
"couldn't clean up user after failing to set password",
|
|
zap.String("user", user.Name),
|
|
zap.String("user_id", user.ID.String()),
|
|
zap.Error(cleanupErr),
|
|
)
|
|
}
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// set the new user in the context
|
|
ctx = icontext.SetAuthorizer(ctx, &influxdb.Authorization{
|
|
UserID: user.ID,
|
|
})
|
|
|
|
// create users org
|
|
org := &influxdb.Organization{
|
|
Name: req.Org,
|
|
}
|
|
|
|
if err := s.service.CreateOrganization(ctx, org); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// create orgs buckets
|
|
ub := &influxdb.Bucket{
|
|
OrgID: org.ID,
|
|
Name: req.Bucket,
|
|
Type: influxdb.BucketTypeUser,
|
|
RetentionPeriod: req.RetentionPeriod(),
|
|
}
|
|
|
|
if err := s.service.CreateBucket(ctx, ub); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result.User = user
|
|
result.Org = org
|
|
result.Bucket = ub
|
|
|
|
// bolt doesn't lock per collection or record so we have to close our transaction
|
|
// before we can reach out to the auth service.
|
|
result.Auth = &influxdb.Authorization{
|
|
Description: fmt.Sprintf("%s's Token", req.User),
|
|
Permissions: permFn(result.Org.ID, result.User.ID),
|
|
Token: req.Token,
|
|
UserID: result.User.ID,
|
|
OrgID: result.Org.ID,
|
|
}
|
|
|
|
return result, s.authSvc.CreateAuthorization(ctx, result.Auth)
|
|
}
|