|
|
|
|
@ -7,8 +7,12 @@ import ( |
|
|
|
|
"strings" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"go.opentelemetry.io/otel/attribute" |
|
|
|
|
"go.opentelemetry.io/otel/trace" |
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/localcache" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/tracing" |
|
|
|
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/org" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/quota" |
|
|
|
|
@ -27,6 +31,7 @@ type Service struct { |
|
|
|
|
teamService team.Service |
|
|
|
|
cacheService *localcache.CacheService |
|
|
|
|
cfg *setting.Cfg |
|
|
|
|
tracer tracing.Tracer |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func ProvideService( |
|
|
|
|
@ -34,9 +39,8 @@ func ProvideService( |
|
|
|
|
orgService org.Service, |
|
|
|
|
cfg *setting.Cfg, |
|
|
|
|
teamService team.Service, |
|
|
|
|
cacheService *localcache.CacheService, |
|
|
|
|
quotaService quota.Service, |
|
|
|
|
bundleRegistry supportbundles.Service, |
|
|
|
|
cacheService *localcache.CacheService, tracer tracing.Tracer, |
|
|
|
|
quotaService quota.Service, bundleRegistry supportbundles.Service, |
|
|
|
|
) (user.Service, error) { |
|
|
|
|
store := ProvideStore(db, cfg) |
|
|
|
|
s := &Service{ |
|
|
|
|
@ -45,6 +49,7 @@ func ProvideService( |
|
|
|
|
cfg: cfg, |
|
|
|
|
teamService: teamService, |
|
|
|
|
cacheService: cacheService, |
|
|
|
|
tracer: tracer, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
defaultLimits, err := readQuotaConfig(cfg) |
|
|
|
|
@ -55,7 +60,7 @@ func ProvideService( |
|
|
|
|
if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{ |
|
|
|
|
TargetSrv: quota.TargetSrv(user.QuotaTargetSrv), |
|
|
|
|
DefaultLimits: defaultLimits, |
|
|
|
|
Reporter: s.Usage, |
|
|
|
|
Reporter: s.usage, |
|
|
|
|
}); err != nil { |
|
|
|
|
return s, err |
|
|
|
|
} |
|
|
|
|
@ -87,21 +92,10 @@ func (s *Service) GetUsageStats(ctx context.Context) map[string]any { |
|
|
|
|
return stats |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) Usage(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) { |
|
|
|
|
u := "a.Map{} |
|
|
|
|
if used, err := s.store.Count(ctx); err != nil { |
|
|
|
|
return u, err |
|
|
|
|
} else { |
|
|
|
|
tag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope) |
|
|
|
|
if err != nil { |
|
|
|
|
return u, err |
|
|
|
|
} |
|
|
|
|
u.Set(tag, used) |
|
|
|
|
} |
|
|
|
|
return u, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.Create") |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
if len(cmd.Login) == 0 { |
|
|
|
|
cmd.Login = cmd.Email |
|
|
|
|
} |
|
|
|
|
@ -126,8 +120,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use |
|
|
|
|
cmd.Email = cmd.Login |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = s.store.LoginConflict(ctx, cmd.Login, cmd.Email) |
|
|
|
|
if err != nil { |
|
|
|
|
if err := s.store.LoginConflict(ctx, cmd.Login, cmd.Email); err != nil { |
|
|
|
|
return nil, user.ErrUserAlreadyExists |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -202,27 +195,48 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) Delete(ctx context.Context, cmd *user.DeleteUserCommand) error { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.Delete", trace.WithAttributes( |
|
|
|
|
attribute.Int64("userID", cmd.UserID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
_, err := s.store.GetByID(ctx, cmd.UserID) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
// delete from all the stores
|
|
|
|
|
|
|
|
|
|
return s.store.Delete(ctx, cmd.UserID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*user.User, error) { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.GetByID", trace.WithAttributes( |
|
|
|
|
attribute.Int64("userID", query.ID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
return s.store.GetByID(ctx, query.ID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.GetByLogin") |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
return s.store.GetByLogin(ctx, query) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) GetByEmail(ctx context.Context, query *user.GetUserByEmailQuery) (*user.User, error) { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.GetByEmail") |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
return s.store.GetByEmail(ctx, query) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) Update(ctx context.Context, cmd *user.UpdateUserCommand) error { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.Update", trace.WithAttributes( |
|
|
|
|
attribute.Int64("userID", cmd.UserID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
usr, err := s.store.GetByID(ctx, cmd.UserID) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
@ -273,6 +287,11 @@ func (s *Service) Update(ctx context.Context, cmd *user.UpdateUserCommand) error |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) UpdateLastSeenAt(ctx context.Context, cmd *user.UpdateUserLastSeenAtCommand) error { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.UpdateLastSeen", trace.WithAttributes( |
|
|
|
|
attribute.Int64("userID", cmd.UserID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
u, err := s.GetSignedInUserWithCacheCtx(ctx, &user.GetSignedInUserQuery{ |
|
|
|
|
UserID: cmd.UserID, |
|
|
|
|
OrgID: cmd.OrgID, |
|
|
|
|
@ -294,6 +313,12 @@ func shouldUpdateLastSeen(t time.Time) bool { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.GetSignedInUserWithCacheCtx", trace.WithAttributes( |
|
|
|
|
attribute.Int64("userID", query.UserID), |
|
|
|
|
attribute.Int64("orgID", query.OrgID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
var signedInUser *user.SignedInUser |
|
|
|
|
|
|
|
|
|
// only check cache if we have a user ID and an org ID in query
|
|
|
|
|
@ -321,54 +346,62 @@ func newSignedInUserCacheKey(orgID, userID int64) string { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) GetSignedInUser(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { |
|
|
|
|
signedInUser, err := s.store.GetSignedInUser(ctx, query) |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.GetSignedInUser", trace.WithAttributes( |
|
|
|
|
attribute.Int64("userID", query.UserID), |
|
|
|
|
attribute.Int64("orgID", query.OrgID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
usr, err := s.store.GetSignedInUser(ctx, query) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getTeamsByUserQuery := &team.GetTeamIDsByUserQuery{ |
|
|
|
|
OrgID: signedInUser.OrgID, |
|
|
|
|
UserID: signedInUser.UserID, |
|
|
|
|
} |
|
|
|
|
signedInUser.Teams, err = s.teamService.GetTeamIDsByUser(ctx, getTeamsByUserQuery) |
|
|
|
|
usr.Teams, err = s.teamService.GetTeamIDsByUser(ctx, &team.GetTeamIDsByUserQuery{ |
|
|
|
|
OrgID: usr.OrgID, |
|
|
|
|
UserID: usr.UserID, |
|
|
|
|
}) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return signedInUser, err |
|
|
|
|
return usr, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.Search", trace.WithAttributes( |
|
|
|
|
attribute.Int64("orgID", query.OrgID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
return s.store.Search(ctx, query) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.BatchDisableUsers", trace.WithAttributes( |
|
|
|
|
attribute.Int64Slice("userIDs", cmd.UserIDs), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
return s.store.BatchDisableUsers(ctx, cmd) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) GetProfile(ctx context.Context, query *user.GetUserProfileQuery) (*user.UserProfileDTO, error) { |
|
|
|
|
result, err := s.store.GetProfile(ctx, query) |
|
|
|
|
return result, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) { |
|
|
|
|
limits := "a.Map{} |
|
|
|
|
|
|
|
|
|
if cfg == nil { |
|
|
|
|
return limits, nil |
|
|
|
|
} |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.GetProfile", trace.WithAttributes( |
|
|
|
|
attribute.Int64("userID", query.UserID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
globalQuotaTag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope) |
|
|
|
|
if err != nil { |
|
|
|
|
return limits, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
limits.Set(globalQuotaTag, cfg.Quota.Global.User) |
|
|
|
|
return limits, nil |
|
|
|
|
return s.store.GetProfile(ctx, query) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// CreateServiceAccount creates a service account in the user table and adds service account to an organisation in the org_user table
|
|
|
|
|
func (s *Service) CreateServiceAccount(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) { |
|
|
|
|
ctx, span := s.tracer.Start(ctx, "user.CreateServiceAccount", trace.WithAttributes( |
|
|
|
|
attribute.Int64("orgID", cmd.OrgID), |
|
|
|
|
)) |
|
|
|
|
defer span.End() |
|
|
|
|
|
|
|
|
|
cmd.Email = cmd.Login |
|
|
|
|
err := s.store.LoginConflict(ctx, cmd.Login, cmd.Email) |
|
|
|
|
if err != nil { |
|
|
|
|
@ -462,6 +495,36 @@ func (s *Service) supportBundleCollector() supportbundles.Collector { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Service) usage(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) { |
|
|
|
|
u := "a.Map{} |
|
|
|
|
if used, err := s.store.Count(ctx); err != nil { |
|
|
|
|
return u, err |
|
|
|
|
} else { |
|
|
|
|
tag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope) |
|
|
|
|
if err != nil { |
|
|
|
|
return u, err |
|
|
|
|
} |
|
|
|
|
u.Set(tag, used) |
|
|
|
|
} |
|
|
|
|
return u, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) { |
|
|
|
|
limits := "a.Map{} |
|
|
|
|
|
|
|
|
|
if cfg == nil { |
|
|
|
|
return limits, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
globalQuotaTag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope) |
|
|
|
|
if err != nil { |
|
|
|
|
return limits, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
limits.Set(globalQuotaTag, cfg.Quota.Global.User) |
|
|
|
|
return limits, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// This is just to ensure that all users have a valid uid.
|
|
|
|
|
// To protect against upgrade / downgrade we need to run this for a couple of releases.
|
|
|
|
|
// FIXME: Remove this migration and make uid field required https://github.com/grafana/identity-access-team/issues/552
|
|
|
|
|
|