From 766fa4e7d552e0a172ba1fd22989c4a1ae2ad834 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Tue, 17 Jan 2023 13:50:58 +0100 Subject: [PATCH] AuthN: Add last seen sync hooks for user and api keys (#61571) * AUthN: Add last seen sync hooks for user / service account and move api key last seen to own hook * ContextHandler: only run sync for last seen if auth.Service is not enabled --- pkg/services/authn/authnimpl/service.go | 2 + .../usersync/apikey_last_seen_sync.go | 38 ++++++++++++++ .../authnimpl/usersync/user_last_seen_sync.go | 50 +++++++++++++++++++ pkg/services/authn/clients/api_key.go | 11 ---- pkg/services/contexthandler/contexthandler.go | 13 +++-- 5 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 pkg/services/authn/authnimpl/usersync/apikey_last_seen_sync.go create mode 100644 pkg/services/authn/authnimpl/usersync/user_last_seen_sync.go diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 63f76c9e80b..01c30af77d5 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -108,6 +108,8 @@ func ProvideService( orgUserSyncService := sync.ProvideOrgSync(userService, orgService, accessControlService) s.RegisterPostAuthHook(userSyncService.SyncUser) s.RegisterPostAuthHook(orgUserSyncService.SyncOrgUser) + s.RegisterPostAuthHook(sync.ProvideUserLastSeenSync(userService).SyncLastSeen) + s.RegisterPostAuthHook(sync.ProvideAPIKeyLastSeenSync(apikeyService).SyncLastSeen) return s } diff --git a/pkg/services/authn/authnimpl/usersync/apikey_last_seen_sync.go b/pkg/services/authn/authnimpl/usersync/apikey_last_seen_sync.go new file mode 100644 index 00000000000..293fc3e88bf --- /dev/null +++ b/pkg/services/authn/authnimpl/usersync/apikey_last_seen_sync.go @@ -0,0 +1,38 @@ +package usersync + +import ( + "context" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/apikey" + "github.com/grafana/grafana/pkg/services/authn" +) + +func ProvideAPIKeyLastSeenSync(service apikey.Service) *APIKeyLastSeenSync { + return &APIKeyLastSeenSync{log.New("apikeylastseen.sync"), service} +} + +type APIKeyLastSeenSync struct { + log log.Logger + service apikey.Service +} + +func (s *APIKeyLastSeenSync) SyncLastSeen(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { + namespace, id := identity.NamespacedID() + if namespace != authn.NamespaceAPIKey { + return nil + } + + go func(apikeyID int64) { + defer func() { + if err := recover(); err != nil { + s.log.Error("panic during user last seen sync", "err", err) + } + }() + if err := s.service.UpdateAPIKeyLastUsedDate(context.Background(), apikeyID); err != nil { + s.log.Warn("failed to update last use date for api key", "id", apikeyID) + } + }(id) + + return nil +} diff --git a/pkg/services/authn/authnimpl/usersync/user_last_seen_sync.go b/pkg/services/authn/authnimpl/usersync/user_last_seen_sync.go new file mode 100644 index 00000000000..881cd18e333 --- /dev/null +++ b/pkg/services/authn/authnimpl/usersync/user_last_seen_sync.go @@ -0,0 +1,50 @@ +package usersync + +import ( + "context" + "time" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/services/user" +) + +func ProvideUserLastSeenSync(service user.Service) *UserLastSeenSync { + return &UserLastSeenSync{log.New("userlastseen.sync"), service} +} + +type UserLastSeenSync struct { + log log.Logger + service user.Service +} + +func (s *UserLastSeenSync) SyncLastSeen(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { + namespace, id := identity.NamespacedID() + + if namespace != authn.NamespaceUser && namespace != authn.NamespaceServiceAccount { + // skip sync + return nil + } + + if !shouldUpdateLastSeen(identity.LastSeenAt) { + return nil + } + + go func(userID int64) { + defer func() { + if err := recover(); err != nil { + s.log.Error("panic during user last seen sync", "err", err) + } + }() + + if err := s.service.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: userID}); err != nil { + s.log.Error("failed to update last_seen_at", "err", err, "userId", userID) + } + }(id) + + return nil +} + +func shouldUpdateLastSeen(t time.Time) bool { + return time.Since(t) > time.Minute*5 +} diff --git a/pkg/services/authn/clients/api_key.go b/pkg/services/authn/clients/api_key.go index ab684a5672d..958cdf7fb63 100644 --- a/pkg/services/authn/clients/api_key.go +++ b/pkg/services/authn/clients/api_key.go @@ -56,17 +56,6 @@ func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide return nil, errAPIKeyRevoked.Errorf("Api key is revoked") } - go func(id int64) { - defer func() { - if err := recover(); err != nil { - s.log.Error("api key authentication panic", "err", err) - } - }() - if err := s.apiKeyService.UpdateAPIKeyLastUsedDate(context.Background(), id); err != nil { - s.log.Warn("failed to update last use date for api key", "id", id) - } - }(apiKey.Id) - // if the api key don't belong to a service account construct the identity and return it if apiKey.ServiceAccountId == nil || *apiKey.ServiceAccountId < 1 { return &authn.Identity{ diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index 4888c6cee86..1f84d4484f3 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -180,11 +180,14 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler { {Num: reqContext.UserID}}, ) - // update last seen every 5min - if reqContext.ShouldUpdateLastSeenAt() { - reqContext.Logger.Debug("Updating last user_seen_at", "user_id", reqContext.UserID) - if err := h.userService.UpdateLastSeenAt(mContext.Req.Context(), &user.UpdateUserLastSeenAtCommand{UserID: reqContext.UserID}); err != nil { - reqContext.Logger.Error("Failed to update last_seen_at", "error", err) + // when using authn service this is implemented as a post auth hook + if !h.features.IsEnabled(featuremgmt.FlagAuthnService) { + // update last seen every 5min + if reqContext.ShouldUpdateLastSeenAt() { + reqContext.Logger.Debug("Updating last user_seen_at", "user_id", reqContext.UserID) + if err := h.userService.UpdateLastSeenAt(mContext.Req.Context(), &user.UpdateUserLastSeenAtCommand{UserID: reqContext.UserID}); err != nil { + reqContext.Logger.Error("Failed to update last_seen_at", "error", err) + } } }