Session: set authID and authenticatedBy (#85806)

* Authn: Resolve authenticate by and auth id when fethcing signed in user

* Change logout client interface to only take Requester interface

* Session: Fetch external auth info when authenticating sessions

* Use authenticated by from identity

* Move call to get auth-info into session client and use GetAuthenticatedBy in various places
This commit is contained in:
Karl Persson
2024-04-11 10:25:29 +02:00
committed by GitHub
parent f375af793f
commit 895222725c
21 changed files with 230 additions and 185 deletions

View File

@@ -4,7 +4,6 @@ import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"net/http"
"strings"
@@ -20,7 +19,6 @@ import (
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
@@ -112,7 +110,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
HelpFlags1: c.HelpFlags1,
HasEditPermissionInFolders: hasEditPerm,
Analytics: hs.buildUserAnalyticsSettings(c),
AuthenticatedBy: hs.getUserAuthenticatedBy(c, userID),
AuthenticatedBy: c.GetAuthenticatedBy(),
},
Settings: settings,
ThemeType: theme.Type,
@@ -168,7 +166,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
}
func (hs *HTTPServer) buildUserAnalyticsSettings(c *contextmodel.ReqContext) dtos.AnalyticsSettings {
namespace, id := c.SignedInUser.GetNamespacedID()
namespace, _ := c.SignedInUser.GetNamespacedID()
// Anonymous users do not have an email or auth info
if namespace != identity.NamespaceUser {
@@ -179,21 +177,10 @@ func (hs *HTTPServer) buildUserAnalyticsSettings(c *contextmodel.ReqContext) dto
return dtos.AnalyticsSettings{}
}
userID, err := identity.IntIdentifier(namespace, id)
if err != nil {
hs.log.Error("Failed to parse user ID", "error", err)
return dtos.AnalyticsSettings{Identifier: "@" + hs.Cfg.AppURL}
}
identifier := c.SignedInUser.GetEmail() + "@" + hs.Cfg.AppURL
authInfo, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &login.GetAuthInfoQuery{UserId: userID})
if err != nil && !errors.Is(err, user.ErrUserNotFound) {
hs.log.Error("Failed to get auth info for analytics", "error", err)
}
if authInfo != nil && authInfo.AuthModule == login.GrafanaComAuthModule {
identifier = authInfo.AuthId
if authenticatedBy := c.SignedInUser.GetAuthenticatedBy(); authenticatedBy == login.GrafanaComAuthModule {
identifier = c.SignedInUser.GetAuthID()
}
return dtos.AnalyticsSettings{
@@ -216,32 +203,6 @@ func (hs *HTTPServer) getUserOrgCount(c *contextmodel.ReqContext, userID int64)
return len(userOrgs)
}
// getUserAuthenticatedBy returns external authentication method used for user.
// If user does not have an external authentication method an empty string is returned
func (hs *HTTPServer) getUserAuthenticatedBy(c *contextmodel.ReqContext, userID int64) string {
if userID == 0 {
return ""
}
// Special case for image renderer. Frontend relies on this information
// to render dashboards in a bit different way.
if c.IsRenderCall {
return login.RenderModule
}
info, err := hs.authInfoService.GetAuthInfo(c.Req.Context(), &login.GetAuthInfoQuery{UserId: userID})
// we ignore errors where a user does not have external user auth
if err != nil && !errors.Is(err, user.ErrUserNotFound) {
hs.log.FromContext(c.Req.Context()).Error("Failed to fetch auth info", "userId", c.SignedInUser.UserID, "error", err)
}
if err != nil {
return ""
}
return info.AuthModule
}
func hashUserIdentifier(identifier string, secret string) string {
if secret == "" {
return ""

View File

@@ -243,13 +243,7 @@ func (hs *HTTPServer) loginUserWithUser(user *user.User, c *contextmodel.ReqCont
func (hs *HTTPServer) Logout(c *contextmodel.ReqContext) {
// FIXME: restructure saml client to implement authn.LogoutClient
if hs.samlSingleLogoutEnabled() {
id, err := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
if err != nil {
hs.log.Error("failed to retrieve user ID", "error", err)
}
authInfo, _ := hs.authInfoService.GetAuthInfo(c.Req.Context(), &loginservice.GetAuthInfoQuery{UserId: id})
if authInfo != nil && authInfo.AuthModule == loginservice.SAMLAuthModule {
if c.SignedInUser.GetAuthenticatedBy() == loginservice.SAMLAuthModule {
c.Redirect(hs.Cfg.AppSubURL + "/logout/saml")
return
}

View File

@@ -670,7 +670,8 @@ func TestLogoutSaml(t *testing.T) {
assert.Equal(t, true, hs.samlSingleLogoutEnabled())
sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
c.SignedInUser = &user.SignedInUser{
UserID: 1,
UserID: 1,
AuthenticatedBy: loginservice.SAMLAuthModule,
}
hs.Logout(c)
return response.Empty(http.StatusOK)