Identity: Rename "namespace" to "type" in the requester interface (#90567)

pull/90934/head
Ryan McKinley 11 months ago committed by GitHub
parent 8cdf5ee824
commit 9db3bc926e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      pkg/api/admin_users.go
  2. 3
      pkg/api/common_test.go
  3. 18
      pkg/api/dashboard.go
  4. 2
      pkg/api/datasources.go
  5. 4
      pkg/api/folder.go
  6. 6
      pkg/api/index.go
  7. 4
      pkg/api/login.go
  8. 3
      pkg/api/login_test.go
  9. 4
      pkg/api/org.go
  10. 4
      pkg/api/org_invite.go
  11. 8
      pkg/api/org_test.go
  12. 3
      pkg/api/pluginproxy/ds_proxy_test.go
  13. 4
      pkg/api/pluginproxy/pluginproxy_test.go
  14. 8
      pkg/api/preferences.go
  15. 2
      pkg/api/render.go
  16. 2
      pkg/api/signup.go
  17. 16
      pkg/api/user.go
  18. 8
      pkg/api/user_token.go
  19. 2
      pkg/apimachinery/identity/error.go
  20. 123
      pkg/apimachinery/identity/namespace.go
  21. 31
      pkg/apimachinery/identity/requester.go
  22. 16
      pkg/apimachinery/identity/static.go
  23. 4
      pkg/apiserver/endpoints/filters/requester.go
  24. 13
      pkg/middleware/auth_test.go
  25. 3
      pkg/middleware/quota_test.go
  26. 4
      pkg/registry/apis/dashboard/legacy/sql_dashboards.go
  27. 15
      pkg/services/accesscontrol/accesscontrol.go
  28. 2
      pkg/services/accesscontrol/acimpl/accesscontrol.go
  29. 11
      pkg/services/accesscontrol/acimpl/service.go
  30. 3
      pkg/services/accesscontrol/acimpl/service_bench_test.go
  31. 17
      pkg/services/accesscontrol/acimpl/service_test.go
  32. 12
      pkg/services/accesscontrol/api/api.go
  33. 3
      pkg/services/accesscontrol/authorize_in_org_test.go
  34. 10
      pkg/services/accesscontrol/cacheutils_test.go
  35. 12
      pkg/services/accesscontrol/database/database.go
  36. 2
      pkg/services/accesscontrol/database/database_test.go
  37. 2
      pkg/services/accesscontrol/middleware.go
  38. 10
      pkg/services/anonymous/anonimpl/client.go
  39. 21
      pkg/services/anonymous/anonimpl/client_test.go
  40. 2
      pkg/services/apiserver/storage/entity/test/watch_test.go
  41. 6
      pkg/services/auth/idimpl/service.go
  42. 7
      pkg/services/auth/idimpl/service_test.go
  43. 8
      pkg/services/authn/authn.go
  44. 23
      pkg/services/authn/authnimpl/service.go
  45. 48
      pkg/services/authn/authnimpl/service_test.go
  46. 27
      pkg/services/authn/authnimpl/sync/oauth_token_sync.go
  47. 15
      pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go
  48. 13
      pkg/services/authn/authnimpl/sync/org_sync.go
  49. 20
      pkg/services/authn/authnimpl/sync/org_sync_test.go
  50. 3
      pkg/services/authn/authnimpl/sync/rbac_sync.go
  51. 30
      pkg/services/authn/authnimpl/sync/rbac_sync_test.go
  52. 77
      pkg/services/authn/authnimpl/sync/user_sync.go
  53. 33
      pkg/services/authn/authnimpl/sync/user_sync_test.go
  54. 2
      pkg/services/authn/authntest/fake.go
  55. 16
      pkg/services/authn/authntest/mock.go
  56. 27
      pkg/services/authn/clients/api_key.go
  57. 33
      pkg/services/authn/clients/api_key_test.go
  58. 5
      pkg/services/authn/clients/basic_test.go
  59. 13
      pkg/services/authn/clients/ext_jwt.go
  60. 15
      pkg/services/authn/clients/ext_jwt_test.go
  61. 3
      pkg/services/authn/clients/grafana.go
  62. 3
      pkg/services/authn/clients/grafana_test.go
  63. 6
      pkg/services/authn/clients/oauth.go
  64. 9
      pkg/services/authn/clients/password_test.go
  65. 23
      pkg/services/authn/clients/proxy.go
  66. 3
      pkg/services/authn/clients/proxy_test.go
  67. 5
      pkg/services/authn/clients/render.go
  68. 5
      pkg/services/authn/clients/render_test.go
  69. 3
      pkg/services/authn/clients/session.go
  70. 9
      pkg/services/authn/clients/session_test.go
  71. 30
      pkg/services/authn/identity.go
  72. 27
      pkg/services/authn/namespace.go
  73. 12
      pkg/services/authz/server.go
  74. 13
      pkg/services/contexthandler/contexthandler.go
  75. 6
      pkg/services/contexthandler/contexthandler_test.go
  76. 4
      pkg/services/dashboardimport/service/service.go
  77. 4
      pkg/services/dashboardimport/service/service_test.go
  78. 2
      pkg/services/dashboards/database/database.go
  79. 12
      pkg/services/dashboards/service/dashboard_service.go
  80. 20
      pkg/services/dashboards/service/dashboard_service_integration_test.go
  81. 6
      pkg/services/dashboardsnapshots/database/database.go
  82. 2
      pkg/services/dashboardsnapshots/service.go
  83. 14
      pkg/services/folder/folderimpl/folder.go
  84. 4
      pkg/services/folder/folderimpl/sqlstore.go
  85. 4
      pkg/services/guardian/accesscontrol_guardian.go
  86. 12
      pkg/services/libraryelements/database.go
  87. 2
      pkg/services/live/features/dashboard.go
  88. 4
      pkg/services/live/live.go
  89. 8
      pkg/services/live/runstream/manager_test.go
  90. 2
      pkg/services/navtree/navtreeimpl/navtree.go
  91. 8
      pkg/services/ngalert/api/api_ruler.go
  92. 2
      pkg/services/ngalert/provisioning/alert_rules.go
  93. 8
      pkg/services/oauthtoken/oauth_token.go
  94. 24
      pkg/services/oauthtoken/oauth_token_test.go
  95. 4
      pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go
  96. 5
      pkg/services/serviceaccounts/api/api.go
  97. 4
      pkg/services/sqlstore/permissions/dashboard.go
  98. 4
      pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go
  99. 20
      pkg/services/star/api/api.go
  100. 4
      pkg/services/team/teamapi/team.go
  101. Some files were not shown because too many files have changed in this diff Show More

@ -10,10 +10,10 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -366,7 +366,7 @@ func (hs *HTTPServer) AdminLogoutUser(c *contextmodel.ReqContext) response.Respo
return response.Error(http.StatusBadRequest, "id is invalid", err) return response.Error(http.StatusBadRequest, "id is invalid", err)
} }
if c.SignedInUser.GetID() == authn.NewNamespaceID(authn.NamespaceUser, userID) { if c.SignedInUser.GetID() == identity.NewTypedID(identity.TypeUser, userID) {
return response.Error(http.StatusBadRequest, "You cannot logout yourself", nil) return response.Error(http.StatusBadRequest, "You cannot logout yourself", nil)
} }

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/infra/fs"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
@ -189,7 +190,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa
return contexthandler.ProvideService( return contexthandler.ProvideService(
cfg, cfg,
tracing.InitializeTracerForTest(), tracing.InitializeTracerForTest(),
&authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: authn.AnonymousNamespaceID, SessionToken: &usertoken.UserToken{}}}, &authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: identity.AnonymousTypedID, SessionToken: &usertoken.UserToken{}}},
) )
} }

@ -43,9 +43,9 @@ func (hs *HTTPServer) isDashboardStarredByUser(c *contextmodel.ReqContext, dashI
return false, nil return false, nil
} }
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID() namespaceID, userIDstr := c.SignedInUser.GetTypedID()
if namespaceID != identity.NamespaceUser { if namespaceID != identity.TypeUser {
return false, nil return false, nil
} }
@ -436,7 +436,7 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo
return response.Error(http.StatusBadRequest, "Use folders endpoint for deleting folders.", nil) return response.Error(http.StatusBadRequest, "Use folders endpoint for deleting folders.", nil)
} }
namespaceID, userIDStr := c.SignedInUser.GetNamespacedID() namespaceID, userIDStr := c.SignedInUser.GetTypedID()
// disconnect all library elements for this dashboard // disconnect all library elements for this dashboard
err = hs.LibraryElementService.DisconnectElementsFromDashboard(c.Req.Context(), dash.ID) err = hs.LibraryElementService.DisconnectElementsFromDashboard(c.Req.Context(), dash.ID)
@ -513,8 +513,8 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
var err error var err error
userID := int64(0) userID := int64(0)
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID() namespaceID, userIDstr := c.SignedInUser.GetTypedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
hs.log.Debug("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", userIDstr) hs.log.Debug("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", userIDstr)
} else { } else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr) userID, err = identity.IntIdentifier(namespaceID, userIDstr)
@ -631,8 +631,8 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) GetHomeDashboard(c *contextmodel.ReqContext) response.Response {
userID := int64(0) userID := int64(0)
var err error var err error
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID() namespaceID, userIDstr := c.SignedInUser.GetTypedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
hs.log.Debug("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", userIDstr) hs.log.Debug("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", userIDstr)
} else { } else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr) userID, err = identity.IntIdentifier(namespaceID, userIDstr)
@ -1083,8 +1083,8 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
} }
userID := int64(0) userID := int64(0)
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID() namespaceID, userIDstr := c.SignedInUser.GetTypedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
hs.log.Warn("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", userIDstr) hs.log.Warn("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", userIDstr)
} else { } else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr) userID, err = identity.IntIdentifier(namespaceID, userIDstr)

@ -442,7 +442,7 @@ func (hs *HTTPServer) AddDataSource(c *contextmodel.ReqContext) response.Respons
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
userID, err := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, err := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, return response.Error(http.StatusInternalServerError,
"Failed to add datasource", err) "Failed to add datasource", err)

@ -194,8 +194,8 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int
var permissions []accesscontrol.SetResourcePermissionCommand var permissions []accesscontrol.SetResourcePermissionCommand
var userID int64 var userID int64
namespace, id := user.GetNamespacedID() namespace, id := user.GetTypedID()
if namespace == identity.NamespaceUser { if namespace == identity.TypeUser {
var errID error var errID error
userID, errID = identity.IntIdentifier(namespace, id) userID, errID = identity.IntIdentifier(namespace, id)
if errID != nil { if errID != nil {

@ -28,7 +28,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
return nil, err return nil, err
} }
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, _ := identity.UserIdentifier(c.SignedInUser.GetTypedID())
prefsQuery := pref.GetPreferenceWithDefaultsQuery{UserID: userID, OrgID: c.SignedInUser.GetOrgID(), Teams: c.Teams} prefsQuery := pref.GetPreferenceWithDefaultsQuery{UserID: userID, OrgID: c.SignedInUser.GetOrgID(), Teams: c.Teams}
prefs, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery) prefs, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery)
@ -166,10 +166,10 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
} }
func (hs *HTTPServer) buildUserAnalyticsSettings(c *contextmodel.ReqContext) dtos.AnalyticsSettings { func (hs *HTTPServer) buildUserAnalyticsSettings(c *contextmodel.ReqContext) dtos.AnalyticsSettings {
namespace, _ := c.SignedInUser.GetNamespacedID() namespace, _ := c.SignedInUser.GetTypedID()
// Anonymous users do not have an email or auth info // Anonymous users do not have an email or auth info
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
return dtos.AnalyticsSettings{Identifier: "@" + hs.Cfg.AppURL} return dtos.AnalyticsSettings{Identifier: "@" + hs.Cfg.AppURL}
} }

@ -257,7 +257,7 @@ func (hs *HTTPServer) Logout(c *contextmodel.ReqContext) {
return return
} }
_, id := c.SignedInUser.GetNamespacedID() _, id := c.SignedInUser.GetTypedID()
hs.log.Info("Successful Logout", "userID", id) hs.log.Info("Successful Logout", "userID", id)
c.Redirect(redirect.URL) c.Redirect(redirect.URL)
} }
@ -305,7 +305,7 @@ func (hs *HTTPServer) redirectURLWithErrorCookie(c *contextmodel.ReqContext, err
var userID int64 var userID int64
if c.SignedInUser != nil && !c.SignedInUser.IsNil() { if c.SignedInUser != nil && !c.SignedInUser.IsNil() {
var errID error var errID error
userID, errID = identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, errID = identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil { if errID != nil {
hs.log.Error("failed to retrieve user ID", "error", errID) hs.log.Error("failed to retrieve user ID", "error", errID)
} }

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
@ -331,7 +332,7 @@ func TestLoginPostRedirect(t *testing.T) {
HooksService: &hooks.HooksService{}, HooksService: &hooks.HooksService{},
License: &licensing.OSSLicensingService{}, License: &licensing.OSSLicensingService{},
authnService: &authntest.FakeService{ authnService: &authntest.FakeService{
ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:42"), SessionToken: &usertoken.UserToken{}}, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:42"), SessionToken: &usertoken.UserToken{}},
}, },
AuthTokenService: authtest.NewFakeUserAuthTokenService(), AuthTokenService: authtest.NewFakeUserAuthTokenService(),
Features: featuremgmt.WithFeatures(), Features: featuremgmt.WithFeatures(),

@ -132,8 +132,8 @@ func (hs *HTTPServer) CreateOrg(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
return response.Error(http.StatusForbidden, "Only users can create organizations", nil) return response.Error(http.StatusForbidden, "Only users can create organizations", nil)
} }

@ -101,9 +101,9 @@ func (hs *HTTPServer) AddOrgInvite(c *contextmodel.ReqContext) response.Response
cmd.Name = inviteDto.Name cmd.Name = inviteDto.Name
cmd.Status = tempuser.TmpUserInvitePending cmd.Status = tempuser.TmpUserInvitePending
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
var userID int64 var userID int64
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount { if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
var err error var err error
userID, err = strconv.ParseInt(identifier, 10, 64) userID, err = strconv.ParseInt(identifier, 10, 64)
if err != nil { if err != nil {

@ -6,14 +6,14 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@ -267,7 +267,7 @@ func TestAPIEndpoint_GetOrg(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
expectedIdentity := &authn.Identity{ expectedIdentity := &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
OrgID: 1, OrgID: 1,
Permissions: map[int64]map[string][]string{ Permissions: map[int64]map[string][]string{
0: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions), 0: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions),

@ -31,7 +31,6 @@ import (
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes" pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/authz/zanzana"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
@ -533,7 +532,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
&contextmodel.ReqContext{ &contextmodel.ReqContext{
SignedInUser: &user.SignedInUser{ SignedInUser: &user.SignedInUser{
Login: "test_user", Login: "test_user",
NamespacedID: authn.MustParseNamespaceID("user:1"), NamespacedID: identity.MustParseTypedID("user:1"),
}, },
}, },
&setting.Cfg{SendUserHeader: true}, &setting.Cfg{SendUserHeader: true},

@ -12,10 +12,10 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/authz/zanzana"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
@ -79,7 +79,7 @@ func TestPluginProxy(t *testing.T) {
&contextmodel.ReqContext{ &contextmodel.ReqContext{
SignedInUser: &user.SignedInUser{ SignedInUser: &user.SignedInUser{
Login: "test_user", Login: "test_user",
NamespacedID: authn.MustParseNamespaceID("user:1"), NamespacedID: identity.MustParseTypedID("user:1"),
}, },
Context: &web.Context{ Context: &web.Context{
Req: httpReq, Req: httpReq,

@ -22,7 +22,7 @@ func (hs *HTTPServer) SetHomeDashboard(c *contextmodel.ReqContext) response.Resp
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil { if errID != nil {
return response.Error(http.StatusInternalServerError, "Failed to set home dashboard", errID) return response.Error(http.StatusInternalServerError, "Failed to set home dashboard", errID)
} }
@ -64,7 +64,7 @@ func (hs *HTTPServer) SetHomeDashboard(c *contextmodel.ReqContext) response.Resp
// 401: unauthorisedError // 401: unauthorisedError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetUserPreferences(c *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) GetUserPreferences(c *contextmodel.ReqContext) response.Response {
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil { if errID != nil {
return response.Error(http.StatusInternalServerError, "Failed to get user preferences", errID) return response.Error(http.StatusInternalServerError, "Failed to get user preferences", errID)
} }
@ -89,7 +89,7 @@ func (hs *HTTPServer) UpdateUserPreferences(c *contextmodel.ReqContext) response
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil { if errID != nil {
return response.Error(http.StatusInternalServerError, "Failed to update user preferences", errID) return response.Error(http.StatusInternalServerError, "Failed to update user preferences", errID)
} }
@ -113,7 +113,7 @@ func (hs *HTTPServer) PatchUserPreferences(c *contextmodel.ReqContext) response.
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil { if errID != nil {
return response.Error(http.StatusInternalServerError, "Failed to update user preferences", errID) return response.Error(http.StatusInternalServerError, "Failed to update user preferences", errID)
} }

@ -65,7 +65,7 @@ func (hs *HTTPServer) RenderHandler(c *contextmodel.ReqContext) {
headers["Accept-Language"] = acceptLanguageHeader headers["Accept-Language"] = acceptLanguageHeader
} }
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil { if errID != nil {
hs.log.Error("Failed to parse user id", "err", errID) hs.log.Error("Failed to parse user id", "err", errID)
} }

@ -47,7 +47,7 @@ func (hs *HTTPServer) SignUp(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusUnprocessableEntity, "User with same email address already exists", nil) return response.Error(http.StatusUnprocessableEntity, "User with same email address already exists", nil)
} }
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil { if errID != nil {
hs.log.Error("Failed to parse user id", "err", errID) hs.log.Error("Failed to parse user id", "err", errID)
} }

@ -31,8 +31,8 @@ import (
// 404: notFoundError // 404: notFoundError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetSignedInUser(c *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) GetSignedInUser(c *contextmodel.ReqContext) response.Response {
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
return response.JSON(http.StatusOK, user.UserProfileDTO{ return response.JSON(http.StatusOK, user.UserProfileDTO{
IsGrafanaAdmin: c.SignedInUser.GetIsGrafanaAdmin(), IsGrafanaAdmin: c.SignedInUser.GetIsGrafanaAdmin(),
OrgID: c.SignedInUser.GetOrgID(), OrgID: c.SignedInUser.GetOrgID(),
@ -278,8 +278,8 @@ func (hs *HTTPServer) handleUpdateUser(ctx context.Context, cmd user.UpdateUserC
} }
func (hs *HTTPServer) StartEmailVerificaton(c *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) StartEmailVerificaton(c *contextmodel.ReqContext) response.Response {
namespace, id := c.SignedInUser.GetNamespacedID() namespace, id := c.SignedInUser.GetTypedID()
if !identity.IsNamespace(namespace, identity.NamespaceUser) { if !identity.IsIdentityType(namespace, identity.TypeUser) {
return response.Error(http.StatusBadRequest, "Only users can verify their email", nil) return response.Error(http.StatusBadRequest, "Only users can verify their email", nil)
} }
@ -506,8 +506,8 @@ func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *contextmodel.ReqContex
return return
} }
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
c.JsonApiErr(http.StatusForbidden, "Endpoint only available for users", nil) c.JsonApiErr(http.StatusForbidden, "Endpoint only available for users", nil)
return return
} }
@ -632,8 +632,8 @@ func (hs *HTTPServer) ClearHelpFlags(c *contextmodel.ReqContext) response.Respon
} }
func getUserID(c *contextmodel.ReqContext) (int64, *response.NormalResponse) { func getUserID(c *contextmodel.ReqContext) (int64, *response.NormalResponse) {
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
return 0, response.Error(http.StatusForbidden, "Endpoint only available for users", nil) return 0, response.Error(http.StatusForbidden, "Endpoint only available for users", nil)
} }

@ -32,8 +32,8 @@ import (
// 403: forbiddenError // 403: forbiddenError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) GetUserAuthTokens(c *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) GetUserAuthTokens(c *contextmodel.ReqContext) response.Response {
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil) return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil)
} }
@ -63,8 +63,8 @@ func (hs *HTTPServer) RevokeUserAuthToken(c *contextmodel.ReqContext) response.R
return response.Error(http.StatusBadRequest, "bad request data", err) return response.Error(http.StatusBadRequest, "bad request data", err)
} }
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil) return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil)
} }

@ -7,7 +7,7 @@ import (
) )
var ( var (
ErrInvalidNamespaceID = errutil.BadRequest("auth.identity.invalid-namespace-id") ErrInvalidTypedID = errutil.BadRequest("auth.identity.invalid-typed-id")
ErrNotIntIdentifier = errors.New("identifier is not an int64") ErrNotIntIdentifier = errors.New("identifier is not an int64")
ErrIdentifierNotInitialized = errors.New("identifier is not initialized") ErrIdentifierNotInitialized = errors.New("identifier is not initialized")
) )

@ -6,120 +6,131 @@ import (
"strings" "strings"
) )
type Namespace string type IdentityType string
const ( const (
NamespaceUser Namespace = "user" TypeUser IdentityType = "user"
NamespaceAPIKey Namespace = "api-key" TypeAPIKey IdentityType = "api-key"
NamespaceServiceAccount Namespace = "service-account" TypeServiceAccount IdentityType = "service-account"
NamespaceAnonymous Namespace = "anonymous" TypeAnonymous IdentityType = "anonymous"
NamespaceRenderService Namespace = "render" TypeRenderService IdentityType = "render"
NamespaceAccessPolicy Namespace = "access-policy" TypeAccessPolicy IdentityType = "access-policy"
NamespaceProvisioning Namespace = "provisioning" TypeProvisioning IdentityType = "provisioning"
NamespaceEmpty Namespace = "" TypeEmpty IdentityType = ""
) )
func (n Namespace) String() string { func (n IdentityType) String() string {
return string(n) return string(n)
} }
func ParseNamespace(str string) (Namespace, error) { func ParseType(str string) (IdentityType, error) {
switch str { switch str {
case string(NamespaceUser): case string(TypeUser):
return NamespaceUser, nil return TypeUser, nil
case string(NamespaceAPIKey): case string(TypeAPIKey):
return NamespaceAPIKey, nil return TypeAPIKey, nil
case string(NamespaceServiceAccount): case string(TypeServiceAccount):
return NamespaceServiceAccount, nil return TypeServiceAccount, nil
case string(NamespaceAnonymous): case string(TypeAnonymous):
return NamespaceAnonymous, nil return TypeAnonymous, nil
case string(NamespaceRenderService): case string(TypeRenderService):
return NamespaceRenderService, nil return TypeRenderService, nil
case string(NamespaceAccessPolicy): case string(TypeAccessPolicy):
return NamespaceAccessPolicy, nil return TypeAccessPolicy, nil
default: default:
return "", ErrInvalidNamespaceID.Errorf("got invalid namespace %s", str) return "", ErrInvalidTypedID.Errorf("got invalid identity type %s", str)
} }
} }
var AnonymousNamespaceID = NewNamespaceID(NamespaceAnonymous, 0) // IsIdentityType returns true if type matches any expected identity type
func IsIdentityType(typ IdentityType, expected ...IdentityType) bool {
for _, e := range expected {
if typ == e {
return true
}
}
return false
}
var AnonymousTypedID = NewTypedID(TypeAnonymous, 0)
func ParseNamespaceID(str string) (NamespaceID, error) { func ParseTypedID(str string) (TypedID, error) {
var namespaceID NamespaceID var typeID TypedID
parts := strings.Split(str, ":") parts := strings.Split(str, ":")
if len(parts) != 2 { if len(parts) != 2 {
return namespaceID, ErrInvalidNamespaceID.Errorf("expected namespace id to have 2 parts") return typeID, ErrInvalidTypedID.Errorf("expected typed id to have 2 parts")
} }
namespace, err := ParseNamespace(parts[0]) t, err := ParseType(parts[0])
if err != nil { if err != nil {
return namespaceID, err return typeID, err
} }
namespaceID.id = parts[1] typeID.id = parts[1]
namespaceID.namespace = namespace typeID.t = t
return namespaceID, nil return typeID, nil
} }
// MustParseNamespaceID parses namespace id, it will panic if it fails to do so. // MustParseTypedID parses namespace id, it will panic if it fails to do so.
// Suitable to use in tests or when we can guarantee that we pass a correct format. // Suitable to use in tests or when we can guarantee that we pass a correct format.
func MustParseNamespaceID(str string) NamespaceID { func MustParseTypedID(str string) TypedID {
namespaceID, err := ParseNamespaceID(str) typeID, err := ParseTypedID(str)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return namespaceID return typeID
} }
func NewNamespaceID(namespace Namespace, id int64) NamespaceID { func NewTypedID(t IdentityType, id int64) TypedID {
return NamespaceID{ return TypedID{
id: strconv.FormatInt(id, 10), id: strconv.FormatInt(id, 10),
namespace: namespace, t: t,
} }
} }
// NewNamespaceIDString creates a new NamespaceID with a string id // NewTypedIDString creates a new TypedID with a string id
func NewNamespaceIDString(namespace Namespace, id string) NamespaceID { func NewTypedIDString(t IdentityType, id string) TypedID {
return NamespaceID{ return TypedID{
id: id, id: id,
namespace: namespace, t: t,
} }
} }
// FIXME: use this instead of encoded string through the codebase // FIXME: use this instead of encoded string through the codebase
type NamespaceID struct { type TypedID struct {
id string id string
namespace Namespace t IdentityType
} }
func (ni NamespaceID) ID() string { func (ni TypedID) ID() string {
return ni.id return ni.id
} }
// UserID will try to parse and int64 identifier if namespace is either user or service-account. // UserID will try to parse and int64 identifier if namespace is either user or service-account.
// For all other namespaces '0' will be returned. // For all other namespaces '0' will be returned.
func (ni NamespaceID) UserID() (int64, error) { func (ni TypedID) UserID() (int64, error) {
if ni.IsNamespace(NamespaceUser, NamespaceServiceAccount) { if ni.IsType(TypeUser, TypeServiceAccount) {
return ni.ParseInt() return ni.ParseInt()
} }
return 0, nil return 0, nil
} }
// ParseInt will try to parse the id as an int64 identifier. // ParseInt will try to parse the id as an int64 identifier.
func (ni NamespaceID) ParseInt() (int64, error) { func (ni TypedID) ParseInt() (int64, error) {
return strconv.ParseInt(ni.id, 10, 64) return strconv.ParseInt(ni.id, 10, 64)
} }
func (ni NamespaceID) Namespace() Namespace { func (ni TypedID) Type() IdentityType {
return ni.namespace return ni.t
} }
func (ni NamespaceID) IsNamespace(expected ...Namespace) bool { func (ni TypedID) IsType(expected ...IdentityType) bool {
return IsNamespace(ni.namespace, expected...) return IsIdentityType(ni.t, expected...)
} }
func (ni NamespaceID) String() string { func (ni TypedID) String() string {
return fmt.Sprintf("%s:%s", ni.namespace, ni.id) return fmt.Sprintf("%s:%s", ni.t, ni.id)
} }

@ -7,13 +7,13 @@ import (
type Requester interface { type Requester interface {
// GetID returns namespaced id for the entity // GetID returns namespaced id for the entity
GetID() NamespaceID GetID() TypedID
// GetNamespacedID returns the namespace and ID of the active entity. // GetTypedID returns the namespace and ID of the active entity.
// The namespace is one of the constants defined in pkg/apimachinery/identity. // The namespace is one of the constants defined in pkg/apimachinery/identity.
// Deprecated: use GetID instead // Deprecated: use GetID instead
GetNamespacedID() (namespace Namespace, identifier string) GetTypedID() (kind IdentityType, identifier string)
// GetUID returns namespaced uid for the entity // GetUID returns namespaced uid for the entity
GetUID() NamespaceID GetUID() TypedID
// GetDisplayName returns the display name of the active entity. // GetDisplayName returns the display name of the active entity.
// The display name is the name if it is set, otherwise the login or email. // The display name is the name if it is set, otherwise the login or email.
GetDisplayName() string GetDisplayName() string
@ -68,25 +68,14 @@ type Requester interface {
GetIDToken() string GetIDToken() string
} }
// IsNamespace returns true if namespace matches any expected namespace
func IsNamespace(namespace Namespace, expected ...Namespace) bool {
for _, e := range expected {
if namespace == e {
return true
}
}
return false
}
// IntIdentifier converts a string identifier to an int64. // IntIdentifier converts a string identifier to an int64.
// Applicable for users, service accounts, api keys and renderer service. // Applicable for users, service accounts, api keys and renderer service.
// Errors if the identifier is not initialized or if namespace is not recognized. // Errors if the identifier is not initialized or if namespace is not recognized.
func IntIdentifier(namespace Namespace, identifier string) (int64, error) { func IntIdentifier(kind IdentityType, identifier string) (int64, error) {
if IsNamespace(namespace, NamespaceUser, NamespaceAPIKey, NamespaceServiceAccount, NamespaceRenderService) { if IsIdentityType(kind, TypeUser, TypeAPIKey, TypeServiceAccount, TypeRenderService) {
id, err := strconv.ParseInt(identifier, 10, 64) id, err := strconv.ParseInt(identifier, 10, 64)
if err != nil { if err != nil {
return 0, fmt.Errorf("unrecognized format for valid namespace %s: %w", namespace, err) return 0, fmt.Errorf("unrecognized format for valid type %s: %w", kind, err)
} }
if id < 1 { if id < 1 {
@ -102,14 +91,14 @@ func IntIdentifier(namespace Namespace, identifier string) (int64, error) {
// UserIdentifier converts a string identifier to an int64. // UserIdentifier converts a string identifier to an int64.
// Errors if the identifier is not initialized or if namespace is not recognized. // Errors if the identifier is not initialized or if namespace is not recognized.
// Returns 0 if the namespace is not user or service account // Returns 0 if the namespace is not user or service account
func UserIdentifier(namespace Namespace, identifier string) (int64, error) { func UserIdentifier(kind IdentityType, identifier string) (int64, error) {
userID, err := IntIdentifier(namespace, identifier) userID, err := IntIdentifier(kind, identifier)
if err != nil { if err != nil {
// FIXME: return this error once entity namespaces are handled by stores // FIXME: return this error once entity namespaces are handled by stores
return 0, nil return 0, nil
} }
if IsNamespace(namespace, NamespaceUser, NamespaceServiceAccount) { if IsIdentityType(kind, TypeUser, TypeServiceAccount) {
return userID, nil return userID, nil
} }

@ -9,7 +9,7 @@ var _ Requester = &StaticRequester{}
// This is mostly copied from: // This is mostly copied from:
// https://github.com/grafana/grafana/blob/v11.0.0/pkg/services/user/identity.go#L16 // https://github.com/grafana/grafana/blob/v11.0.0/pkg/services/user/identity.go#L16
type StaticRequester struct { type StaticRequester struct {
Namespace Namespace Type IdentityType
UserID int64 UserID int64
UserUID string UserUID string
OrgID int64 OrgID int64
@ -105,19 +105,19 @@ func (u *StaticRequester) HasUniqueId() bool {
} }
// GetID returns namespaced id for the entity // GetID returns namespaced id for the entity
func (u *StaticRequester) GetID() NamespaceID { func (u *StaticRequester) GetID() TypedID {
return NewNamespaceIDString(u.Namespace, fmt.Sprintf("%d", u.UserID)) return NewTypedIDString(u.Type, fmt.Sprintf("%d", u.UserID))
} }
// GetUID returns namespaced uid for the entity // GetUID returns namespaced uid for the entity
func (u *StaticRequester) GetUID() NamespaceID { func (u *StaticRequester) GetUID() TypedID {
return NewNamespaceIDString(u.Namespace, u.UserUID) return NewTypedIDString(u.Type, u.UserUID)
} }
// GetNamespacedID returns the namespace and ID of the active entity // GetTypedID returns the namespace and ID of the active entity
// The namespace is one of the constants defined in pkg/apimachinery/identity // The namespace is one of the constants defined in pkg/apimachinery/identity
func (u *StaticRequester) GetNamespacedID() (Namespace, string) { func (u *StaticRequester) GetTypedID() (IdentityType, string) {
return u.Namespace, fmt.Sprintf("%d", u.UserID) return u.Type, fmt.Sprintf("%d", u.UserID)
} }
func (u *StaticRequester) GetAuthID() string { func (u *StaticRequester) GetAuthID() string {

@ -26,7 +26,7 @@ func WithRequester(handler http.Handler) http.Handler {
if ok { if ok {
if info.GetName() == user.Anonymous { if info.GetName() == user.Anonymous {
requester = &identity.StaticRequester{ requester = &identity.StaticRequester{
Namespace: identity.NamespaceAnonymous, Type: identity.TypeAnonymous,
Name: info.GetName(), Name: info.GetName(),
Login: info.GetName(), Login: info.GetName(),
Permissions: map[int64]map[string][]string{}, Permissions: map[int64]map[string][]string{},
@ -37,7 +37,7 @@ func WithRequester(handler http.Handler) http.Handler {
slices.Contains(info.GetGroups(), user.SystemPrivilegedGroup) { slices.Contains(info.GetGroups(), user.SystemPrivilegedGroup) {
orgId := int64(1) orgId := int64(1)
requester = &identity.StaticRequester{ requester = &identity.StaticRequester{
Namespace: identity.NamespaceServiceAccount, // system:apiserver Type: identity.TypeServiceAccount, // system:apiserver
UserID: 1, UserID: 1,
OrgID: orgId, OrgID: orgId,
Name: info.GetName(), Name: info.GetName(),

@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log/logtest" "github.com/grafana/grafana/pkg/infra/log/logtest"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
@ -62,7 +63,7 @@ func TestAuth_Middleware(t *testing.T) {
desc: "ReqSignedIn should return 200 for anonymous user", desc: "ReqSignedIn should return 200 for anonymous user",
path: "/api/secure", path: "/api/secure",
authMiddleware: ReqSignedIn, authMiddleware: ReqSignedIn,
identity: &authn.Identity{ID: authn.AnonymousNamespaceID}, identity: &authn.Identity{ID: identity.AnonymousTypedID},
expecedReached: true, expecedReached: true,
expectedCode: http.StatusOK, expectedCode: http.StatusOK,
}, },
@ -70,7 +71,7 @@ func TestAuth_Middleware(t *testing.T) {
desc: "ReqSignedIn should return redirect anonymous user with forceLogin query string", desc: "ReqSignedIn should return redirect anonymous user with forceLogin query string",
path: "/secure?forceLogin=true", path: "/secure?forceLogin=true",
authMiddleware: ReqSignedIn, authMiddleware: ReqSignedIn,
identity: &authn.Identity{ID: authn.AnonymousNamespaceID}, identity: &authn.Identity{ID: identity.AnonymousTypedID},
expecedReached: false, expecedReached: false,
expectedCode: http.StatusFound, expectedCode: http.StatusFound,
}, },
@ -78,7 +79,7 @@ func TestAuth_Middleware(t *testing.T) {
desc: "ReqSignedIn should return redirect anonymous user when orgId in query string is different from currently used", desc: "ReqSignedIn should return redirect anonymous user when orgId in query string is different from currently used",
path: "/secure?orgId=2", path: "/secure?orgId=2",
authMiddleware: ReqSignedIn, authMiddleware: ReqSignedIn,
identity: &authn.Identity{ID: authn.AnonymousNamespaceID, OrgID: 1}, identity: &authn.Identity{ID: identity.AnonymousTypedID, OrgID: 1},
expecedReached: false, expecedReached: false,
expectedCode: http.StatusFound, expectedCode: http.StatusFound,
}, },
@ -86,7 +87,7 @@ func TestAuth_Middleware(t *testing.T) {
desc: "ReqSignedInNoAnonymous should return 401 for anonymous user", desc: "ReqSignedInNoAnonymous should return 401 for anonymous user",
path: "/api/secure", path: "/api/secure",
authMiddleware: ReqSignedInNoAnonymous, authMiddleware: ReqSignedInNoAnonymous,
identity: &authn.Identity{ID: authn.AnonymousNamespaceID}, identity: &authn.Identity{ID: identity.AnonymousTypedID},
expecedReached: false, expecedReached: false,
expectedCode: http.StatusUnauthorized, expectedCode: http.StatusUnauthorized,
}, },
@ -94,7 +95,7 @@ func TestAuth_Middleware(t *testing.T) {
desc: "ReqSignedInNoAnonymous should return 200 for authenticated user", desc: "ReqSignedInNoAnonymous should return 200 for authenticated user",
path: "/api/secure", path: "/api/secure",
authMiddleware: ReqSignedInNoAnonymous, authMiddleware: ReqSignedInNoAnonymous,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
expecedReached: true, expecedReached: true,
expectedCode: http.StatusOK, expectedCode: http.StatusOK,
}, },
@ -102,7 +103,7 @@ func TestAuth_Middleware(t *testing.T) {
desc: "snapshot public mode disabled should return 200 for authenticated user", desc: "snapshot public mode disabled should return 200 for authenticated user",
path: "/api/secure", path: "/api/secure",
authMiddleware: SnapshotPublicModeOrSignedIn(&setting.Cfg{SnapshotPublicMode: false}), authMiddleware: SnapshotPublicModeOrSignedIn(&setting.Cfg{SnapshotPublicMode: false}),
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
expecedReached: true, expecedReached: true,
expectedCode: http.StatusOK, expectedCode: http.StatusOK,
}, },

@ -5,6 +5,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/quota/quotatest"
@ -52,7 +53,7 @@ func TestMiddlewareQuota(t *testing.T) {
t.Run("with user logged in", func(t *testing.T) { t.Run("with user logged in", func(t *testing.T) {
setUp := func(sc *scenarioContext) { setUp := func(sc *scenarioContext) {
sc.withIdentity(&authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{UserId: 12}}) sc.withIdentity(&authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{UserId: 12}})
} }
middlewareScenario(t, "global datasource quota reached", func(t *testing.T, sc *scenarioContext) { middlewareScenario(t, "global datasource quota reached", func(t *testing.T, sc *scenarioContext) {

@ -333,9 +333,9 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
func getUserID(v sql.NullString) string { func getUserID(v sql.NullString) string {
if v.String == "" { if v.String == "" {
return identity.NewNamespaceIDString(identity.NamespaceProvisioning, "").String() return identity.NewTypedIDString(identity.TypeProvisioning, "").String()
} }
return identity.NewNamespaceIDString(identity.NamespaceUser, v.String).String() return identity.NewTypedIDString(identity.TypeUser, v.String).String()
} }
// DeleteDashboard implements DashboardAccess. // DeleteDashboard implements DashboardAccess.

@ -2,7 +2,6 @@ package accesscontrol
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
@ -84,7 +83,7 @@ type SearchOptions struct {
Action string Action string
ActionSets []string ActionSets []string
Scope string Scope string
NamespacedID string // ID of the identity (ex: user:3, service-account:4) TypedID identity.TypedID // ID of the identity (ex: user:3, service-account:4)
wildcards Wildcards // private field computed based on the Scope wildcards Wildcards // private field computed based on the Scope
RolePrefixes []string RolePrefixes []string
} }
@ -105,21 +104,17 @@ func (s *SearchOptions) Wildcards() []string {
} }
func (s *SearchOptions) ComputeUserID() (int64, error) { func (s *SearchOptions) ComputeUserID() (int64, error) {
if s.NamespacedID == "" { id, err := s.TypedID.ParseInt()
return 0, errors.New("namespacedID must be set")
}
id, err := identity.ParseNamespaceID(s.NamespacedID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
// Validate namespace type is user or service account // Validate namespace type is user or service account
if id.Namespace() != identity.NamespaceUser && id.Namespace() != identity.NamespaceServiceAccount { if s.TypedID.Type() != identity.TypeUser && s.TypedID.Type() != identity.TypeServiceAccount {
return 0, fmt.Errorf("invalid namespace: %s", id.Namespace()) return 0, fmt.Errorf("invalid type: %s", s.TypedID.Type())
} }
return id.ParseInt() return id, nil
} }
type SyncUserRolesCommand struct { type SyncUserRolesCommand struct {

@ -191,6 +191,6 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a
} }
func (a *AccessControl) debug(ctx context.Context, ident identity.Requester, msg string, eval accesscontrol.Evaluator) { func (a *AccessControl) debug(ctx context.Context, ident identity.Requester, msg string, eval accesscontrol.Evaluator) {
namespace, id := ident.GetNamespacedID() namespace, id := ident.GetTypedID()
a.log.FromContext(ctx).Debug(msg, "namespace", namespace, "id", id, "orgID", ident.GetOrgID(), "permissions", eval.GoString()) a.log.FromContext(ctx).Debug(msg, "namespace", namespace, "id", id, "orgID", ident.GetOrgID(), "permissions", eval.GoString())
} }

@ -25,7 +25,6 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/database" "github.com/grafana/grafana/pkg/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator" "github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils" "github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/authz/zanzana"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
@ -141,7 +140,7 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
permissions = append(permissions, SharedWithMeFolderPermission) permissions = append(permissions, SharedWithMeFolderPermission)
} }
userID, err := identity.UserIdentifier(user.GetNamespacedID()) userID, err := identity.UserIdentifier(user.GetTypedID())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -209,10 +208,10 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re
ctx, span := s.tracer.Start(ctx, "authz.getUserDirectPermissions") ctx, span := s.tracer.Start(ctx, "authz.getUserDirectPermissions")
defer span.End() defer span.End()
namespace, identifier := user.GetNamespacedID() namespace, identifier := user.GetTypedID()
var userID int64 var userID int64
if namespace == authn.NamespaceUser || namespace == authn.NamespaceServiceAccount { if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
var err error var err error
userID, err = strconv.ParseInt(identifier, 10, 64) userID, err = strconv.ParseInt(identifier, 10, 64)
if err != nil { if err != nil {
@ -483,7 +482,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
// Limit roles to available in OSS // Limit roles to available in OSS
options.RolePrefixes = OSSRolesPrefixes options.RolePrefixes = OSSRolesPrefixes
if options.NamespacedID != "" { if options.TypedID.Type() != "" {
userID, err := options.ComputeUserID() userID, err := options.ComputeUserID()
if err != nil { if err != nil {
s.log.Error("Failed to resolve user ID", "error", err) s.log.Error("Failed to resolve user ID", "error", err)
@ -598,7 +597,7 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
defer timer.ObserveDuration() defer timer.ObserveDuration()
if searchOptions.NamespacedID == "" { if searchOptions.TypedID.Type() == "" {
return nil, fmt.Errorf("expected namespaced ID to be specified") return nil, fmt.Errorf("expected namespaced ID to be specified")
} }

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
@ -261,7 +262,7 @@ func benchSearchUserWithAction(b *testing.B, usersCount, resourceCount int) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu, usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu,
accesscontrol.SearchOptions{Action: "resources:action2", NamespacedID: "user:14"}) accesscontrol.SearchOptions{Action: "resources:action2", TypedID: identity.NewTypedID(identity.TypeUser, 14)})
require.NoError(b, err) require.NoError(b, err)
require.Len(b, usersPermissions, 1) require.Len(b, usersPermissions, 1)
for _, permissions := range usersPermissions { for _, permissions := range usersPermissions {

@ -2,7 +2,6 @@ package acimpl
import ( import (
"context" "context"
"fmt"
"strings" "strings"
"testing" "testing"
@ -544,7 +543,7 @@ func TestService_SearchUsersPermissions(t *testing.T) {
// only the user's basic roles and the user's stored permissions // only the user's basic roles and the user's stored permissions
name: "check namespacedId filter works correctly", name: "check namespacedId filter works correctly",
siuPermissions: listAllPerms, siuPermissions: listAllPerms,
searchOption: accesscontrol.SearchOptions{NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceServiceAccount)}, searchOption: accesscontrol.SearchOptions{TypedID: identity.NewTypedID(identity.TypeServiceAccount, 1)},
ramRoles: map[string]*accesscontrol.RoleDTO{ ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{ string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
@ -616,7 +615,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "ram only", name: "ram only",
searchOption: accesscontrol.SearchOptions{ searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams", ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser), TypedID: identity.NewTypedID(identity.TypeUser, 2),
}, },
ramRoles: map[string]*accesscontrol.RoleDTO{ ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{ string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
@ -641,7 +640,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "stored only", name: "stored only",
searchOption: accesscontrol.SearchOptions{ searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams", ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser), TypedID: identity.NewTypedID(identity.TypeUser, 2),
}, },
storedPerms: map[int64][]accesscontrol.Permission{ storedPerms: map[int64][]accesscontrol.Permission{
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}}, 1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
@ -661,7 +660,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "ram and stored", name: "ram and stored",
searchOption: accesscontrol.SearchOptions{ searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams", ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser), TypedID: identity.NewTypedID(identity.TypeUser, 2),
}, },
ramRoles: map[string]*accesscontrol.RoleDTO{ ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{ string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
@ -691,7 +690,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "check action prefix filter works correctly", name: "check action prefix filter works correctly",
searchOption: accesscontrol.SearchOptions{ searchOption: accesscontrol.SearchOptions{
ActionPrefix: "teams", ActionPrefix: "teams",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser), TypedID: identity.NewTypedID(identity.TypeUser, 1),
}, },
ramRoles: map[string]*accesscontrol.RoleDTO{ ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{ string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
@ -713,7 +712,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "check action filter works correctly", name: "check action filter works correctly",
searchOption: accesscontrol.SearchOptions{ searchOption: accesscontrol.SearchOptions{
Action: accesscontrol.ActionTeamsRead, Action: accesscontrol.ActionTeamsRead,
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser), TypedID: identity.NewTypedID(identity.TypeUser, 1),
}, },
ramRoles: map[string]*accesscontrol.RoleDTO{ ramRoles: map[string]*accesscontrol.RoleDTO{
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{ string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
@ -735,7 +734,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "check action sets are correctly included if an action is specified", name: "check action sets are correctly included if an action is specified",
searchOption: accesscontrol.SearchOptions{ searchOption: accesscontrol.SearchOptions{
Action: "dashboards:read", Action: "dashboards:read",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser), TypedID: identity.NewTypedID(identity.TypeUser, 1),
}, },
withActionSets: true, withActionSets: true,
actionSets: map[string][]string{ actionSets: map[string][]string{
@ -768,7 +767,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
name: "check action sets are correctly included if an action prefix is specified", name: "check action sets are correctly included if an action prefix is specified",
searchOption: accesscontrol.SearchOptions{ searchOption: accesscontrol.SearchOptions{
ActionPrefix: "dashboards", ActionPrefix: "dashboards",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser), TypedID: identity.NewTypedID(identity.TypeUser, 1),
}, },
withActionSets: true, withActionSets: true,
actionSets: map[string][]string{ actionSets: map[string][]string{

@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/middleware/requestmeta" "github.com/grafana/grafana/pkg/middleware/requestmeta"
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
@ -71,16 +72,23 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext)
ActionPrefix: c.Query("actionPrefix"), ActionPrefix: c.Query("actionPrefix"),
Action: c.Query("action"), Action: c.Query("action"),
Scope: c.Query("scope"), Scope: c.Query("scope"),
NamespacedID: c.Query("namespacedId"),
} }
namespacedId := c.Query("namespacedId")
// Validate inputs // Validate inputs
if searchOptions.ActionPrefix != "" && searchOptions.Action != "" { if searchOptions.ActionPrefix != "" && searchOptions.Action != "" {
return response.JSON(http.StatusBadRequest, "'action' and 'actionPrefix' are mutually exclusive") return response.JSON(http.StatusBadRequest, "'action' and 'actionPrefix' are mutually exclusive")
} }
if searchOptions.NamespacedID == "" && searchOptions.ActionPrefix == "" && searchOptions.Action == "" { if namespacedId == "" && searchOptions.ActionPrefix == "" && searchOptions.Action == "" {
return response.JSON(http.StatusBadRequest, "at least one search option must be provided") return response.JSON(http.StatusBadRequest, "at least one search option must be provided")
} }
if namespacedId != "" {
var err error
searchOptions.TypedID, err = identity.ParseTypedID(namespacedId)
if err != nil {
return response.Error(http.StatusBadGateway, "invalid namespacedId", err)
}
}
// Compute metadata // Compute metadata
permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions) permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions)

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/accesscontrol/actest"
@ -186,7 +187,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil) req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil)
expectedIdentity := &authn.Identity{ expectedIdentity := &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, tc.ctxSignedInUser.UserID), ID: identity.NewTypedID(identity.TypeUser, tc.ctxSignedInUser.UserID),
OrgID: tc.targetOrgId, OrgID: tc.targetOrgId,
Permissions: map[int64]map[string][]string{}, Permissions: map[int64]map[string][]string{},
} }

@ -21,7 +21,7 @@ func TestPermissionCacheKey(t *testing.T) {
signedInUser: &user.SignedInUser{ signedInUser: &user.SignedInUser{
OrgID: 1, OrgID: 1,
UserID: 1, UserID: 1,
NamespacedID: identity.MustParseNamespaceID("user:1"), NamespacedID: identity.MustParseTypedID("user:1"),
}, },
expected: "rbac-permissions-1-user-1", expected: "rbac-permissions-1-user-1",
}, },
@ -31,7 +31,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1, OrgID: 1,
ApiKeyID: 1, ApiKeyID: 1,
IsServiceAccount: false, IsServiceAccount: false,
NamespacedID: identity.MustParseNamespaceID("user:1"), NamespacedID: identity.MustParseTypedID("user:1"),
}, },
expected: "rbac-permissions-1-api-key-1", expected: "rbac-permissions-1-api-key-1",
}, },
@ -41,7 +41,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1, OrgID: 1,
UserID: 1, UserID: 1,
IsServiceAccount: true, IsServiceAccount: true,
NamespacedID: identity.MustParseNamespaceID("service-account:1"), NamespacedID: identity.MustParseTypedID("service-account:1"),
}, },
expected: "rbac-permissions-1-service-account-1", expected: "rbac-permissions-1-service-account-1",
}, },
@ -51,7 +51,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1, OrgID: 1,
UserID: -1, UserID: -1,
IsServiceAccount: true, IsServiceAccount: true,
NamespacedID: identity.MustParseNamespaceID("service-account:-1"), NamespacedID: identity.MustParseTypedID("service-account:-1"),
}, },
expected: "rbac-permissions-1-service-account--1", expected: "rbac-permissions-1-service-account--1",
}, },
@ -60,7 +60,7 @@ func TestPermissionCacheKey(t *testing.T) {
signedInUser: &user.SignedInUser{ signedInUser: &user.SignedInUser{
OrgID: 1, OrgID: 1,
OrgRole: org.RoleNone, OrgRole: org.RoleNone,
NamespacedID: identity.MustParseNamespaceID("user:1"), NamespacedID: identity.MustParseTypedID("user:1"),
}, },
expected: "rbac-permissions-1-user-None", expected: "rbac-permissions-1-user-None",
}, },

@ -163,8 +163,8 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
} }
dbPerms := make([]UserRBACPermission, 0) dbPerms := make([]UserRBACPermission, 0)
var userID int64 userID := int64(-1)
if options.NamespacedID != "" { if options.TypedID.Type() != "" {
var err error var err error
userID, err = options.ComputeUserID() userID, err = options.ComputeUserID()
if err != nil { if err != nil {
@ -181,26 +181,26 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
params := []any{} params := []any{}
direct := userAssignsSQL direct := userAssignsSQL
if options.NamespacedID != "" { if userID >= 0 {
direct += " WHERE ur.user_id = ?" direct += " WHERE ur.user_id = ?"
params = append(params, userID) params = append(params, userID)
} }
team := teamAssignsSQL team := teamAssignsSQL
if options.NamespacedID != "" { if userID >= 0 {
team += " WHERE tm.user_id = ?" team += " WHERE tm.user_id = ?"
params = append(params, userID) params = append(params, userID)
} }
basic := basicRoleAssignsSQL basic := basicRoleAssignsSQL
if options.NamespacedID != "" { if userID >= 0 {
basic += " WHERE ou.user_id = ?" basic += " WHERE ou.user_id = ?"
params = append(params, userID) params = append(params, userID)
} }
grafanaAdmin := fmt.Sprintf(grafanaAdminAssignsSQL, s.sql.ReadReplica().Quote("user")) grafanaAdmin := fmt.Sprintf(grafanaAdminAssignsSQL, s.sql.ReadReplica().Quote("user"))
params = append(params, accesscontrol.RoleGrafanaAdmin) params = append(params, accesscontrol.RoleGrafanaAdmin)
if options.NamespacedID != "" { if userID >= 0 {
grafanaAdmin += " AND sa.user_id = ?" grafanaAdmin += " AND sa.user_id = ?"
params = append(params, userID) params = append(params, userID)
} }

@ -626,7 +626,7 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
}, },
options: accesscontrol.SearchOptions{ options: accesscontrol.SearchOptions{
ActionPrefix: "teams:", ActionPrefix: "teams:",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser), TypedID: identity.NewTypedID(identity.TypeUser, 1),
}, },
wantPerm: map[int64][]accesscontrol.Permission{ wantPerm: map[int64][]accesscontrol.Permission{
1: {{Action: "teams:read", Scope: "teams:id:1"}, {Action: "teams:read", Scope: "teams:id:10"}, 1: {{Action: "teams:read", Scope: "teams:id:1"}, {Action: "teams:read", Scope: "teams:id:10"},

@ -80,7 +80,7 @@ func deny(c *contextmodel.ReqContext, evaluator Evaluator, err error) {
if err != nil { if err != nil {
c.Logger.Error("Error from access control system", "error", err, "accessErrorID", id) c.Logger.Error("Error from access control system", "error", err, "accessErrorID", id)
} else { } else {
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
c.Logger.Info( c.Logger.Info(
"Access denied", "Access denied",
"namespace", namespace, "namespace", namespace,

@ -69,11 +69,11 @@ func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool {
return true return true
} }
func (a *Anonymous) Namespace() string { func (a *Anonymous) IdentityType() identity.IdentityType {
return authn.NamespaceAnonymous.String() return identity.TypeAnonymous
} }
func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.NamespaceID) (*authn.Identity, error) { func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
o, err := a.orgService.GetByName(ctx, &org.GetOrgByNameQuery{Name: a.cfg.AnonymousOrgName}) o, err := a.orgService.GetByName(ctx, &org.GetOrgByNameQuery{Name: a.cfg.AnonymousOrgName})
if err != nil { if err != nil {
return nil, err return nil, err
@ -84,7 +84,7 @@ func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceI
} }
// Anonymous identities should always have the same namespace id. // Anonymous identities should always have the same namespace id.
if namespaceID != authn.AnonymousNamespaceID { if namespaceID != identity.AnonymousTypedID {
return nil, errInvalidID return nil, errInvalidID
} }
@ -109,7 +109,7 @@ func (a *Anonymous) Priority() uint {
func (a *Anonymous) newAnonymousIdentity(o *org.Org) *authn.Identity { func (a *Anonymous) newAnonymousIdentity(o *org.Org) *authn.Identity {
return &authn.Identity{ return &authn.Identity{
ID: authn.AnonymousNamespaceID, ID: identity.AnonymousTypedID,
OrgID: o.ID, OrgID: o.ID,
OrgName: o.Name, OrgName: o.Name,
OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.AnonymousOrgRole)}, OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.AnonymousOrgRole)},

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/anonymous/anontest" "github.com/grafana/grafana/pkg/services/anonymous/anontest"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
@ -52,17 +53,17 @@ func TestAnonymous_Authenticate(t *testing.T) {
anonDeviceService: anontest.NewFakeService(), anonDeviceService: anontest.NewFakeService(),
} }
identity, err := c.Authenticate(context.Background(), &authn.Request{}) user, err := c.Authenticate(context.Background(), &authn.Request{})
if err != nil { if err != nil {
require.Error(t, err) require.Error(t, err)
require.Nil(t, identity) require.Nil(t, user)
} else { } else {
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, authn.AnonymousNamespaceID, identity.ID) assert.Equal(t, identity.AnonymousTypedID, user.ID)
assert.Equal(t, tt.org.ID, identity.OrgID) assert.Equal(t, tt.org.ID, user.OrgID)
assert.Equal(t, tt.org.Name, identity.OrgName) assert.Equal(t, tt.org.Name, user.OrgName)
assert.Equal(t, tt.cfg.AnonymousOrgRole, string(identity.GetOrgRole())) assert.Equal(t, tt.cfg.AnonymousOrgRole, string(user.GetOrgRole()))
} }
}) })
} }
@ -73,7 +74,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
desc string desc string
cfg *setting.Cfg cfg *setting.Cfg
orgID int64 orgID int64
namespaceID authn.NamespaceID namespaceID identity.TypedID
org *org.Org org *org.Org
orgErr error orgErr error
expectedErr error expectedErr error
@ -87,7 +88,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org", AnonymousOrgName: "some org",
}, },
orgID: 1, orgID: 1,
namespaceID: authn.AnonymousNamespaceID, namespaceID: identity.AnonymousTypedID,
expectedErr: errInvalidOrg, expectedErr: errInvalidOrg,
}, },
{ {
@ -97,7 +98,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org", AnonymousOrgName: "some org",
}, },
orgID: 1, orgID: 1,
namespaceID: authn.MustParseNamespaceID("anonymous:1"), namespaceID: identity.MustParseTypedID("anonymous:1"),
expectedErr: errInvalidID, expectedErr: errInvalidID,
}, },
{ {
@ -107,7 +108,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org", AnonymousOrgName: "some org",
}, },
orgID: 1, orgID: 1,
namespaceID: authn.AnonymousNamespaceID, namespaceID: identity.AnonymousTypedID,
}, },
} }

@ -147,7 +147,7 @@ func testSetup(t *testing.T, opts ...setupOption) (context.Context, storage.Inte
// Test with an admin identity // Test with an admin identity
ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{ ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{
Namespace: identity.NamespaceUser, Type: identity.TypeUser,
Login: "testuser", Login: "testuser",
UserID: 123, UserID: 123,
UserUID: "u123", UserUID: "u123",

@ -66,7 +66,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
cacheKey := prefixCacheKey(id.GetCacheKey()) cacheKey := prefixCacheKey(id.GetCacheKey())
result, err, _ := s.si.Do(cacheKey, func() (interface{}, error) { result, err, _ := s.si.Do(cacheKey, func() (interface{}, error) {
namespace, identifier := id.GetNamespacedID() namespace, identifier := id.GetTypedID()
cachedToken, err := s.cache.Get(ctx, cacheKey) cachedToken, err := s.cache.Get(ctx, cacheKey)
if err == nil { if err == nil {
@ -92,7 +92,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
}, },
} }
if identity.IsNamespace(namespace, identity.NamespaceUser) { if identity.IsIdentityType(namespace, identity.TypeUser) {
claims.Rest.Email = id.GetEmail() claims.Rest.Email = id.GetEmail()
claims.Rest.EmailVerified = id.IsEmailVerified() claims.Rest.EmailVerified = id.IsEmailVerified()
claims.Rest.AuthenticatedBy = id.GetAuthenticatedBy() claims.Rest.AuthenticatedBy = id.GetAuthenticatedBy()
@ -144,7 +144,7 @@ func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.R
token, err := s.SignIdentity(ctx, identity) token, err := s.SignIdentity(ctx, identity)
if err != nil { if err != nil {
if shouldLogErr(err) { if shouldLogErr(err) {
namespace, id := identity.GetNamespacedID() namespace, id := identity.GetTypedID()
s.logger.FromContext(ctx).Error("Failed to sign id token", "err", err, "namespace", namespace, "id", id) s.logger.FromContext(ctx).Error("Failed to sign id token", "err", err, "namespace", namespace, "id", id)
} }
// for now don't return error so we don't break authentication from this hook // for now don't return error so we don't break authentication from this hook

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/idtest" "github.com/grafana/grafana/pkg/services/auth/idtest"
@ -69,7 +70,7 @@ func TestService_SignIdentity(t *testing.T) {
featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding), featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding),
&authntest.FakeService{}, nil, &authntest.FakeService{}, nil,
) )
token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}) token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: identity.MustParseTypedID("user:1")})
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, token) require.NotEmpty(t, token)
}) })
@ -81,10 +82,10 @@ func TestService_SignIdentity(t *testing.T) {
&authntest.FakeService{}, nil, &authntest.FakeService{}, nil,
) )
token, err := s.SignIdentity(context.Background(), &authn.Identity{ token, err := s.SignIdentity(context.Background(), &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
AuthenticatedBy: login.AzureADAuthModule, AuthenticatedBy: login.AzureADAuthModule,
Login: "U1", Login: "U1",
UID: authn.NewNamespaceIDString(authn.NamespaceUser, "edpu3nnt61se8e")}) UID: identity.NewTypedIDString(identity.TypeUser, "edpu3nnt61se8e")})
require.NoError(t, err) require.NoError(t, err)
parsed, err := jwt.ParseSigned(token) parsed, err := jwt.ParseSigned(token)

@ -95,7 +95,7 @@ type Service interface {
// RegisterPreLogoutHook registers a hook that is called before a logout request. // RegisterPreLogoutHook registers a hook that is called before a logout request.
RegisterPreLogoutHook(hook PreLogoutHookFn, priority uint) RegisterPreLogoutHook(hook PreLogoutHookFn, priority uint)
// ResolveIdentity resolves an identity from org and namespace id. // ResolveIdentity resolves an identity from org and namespace id.
ResolveIdentity(ctx context.Context, orgID int64, namespaceID NamespaceID) (*Identity, error) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error)
// RegisterClient will register a new authn.Client that can be used for authentication // RegisterClient will register a new authn.Client that can be used for authentication
RegisterClient(c Client) RegisterClient(c Client)
@ -157,7 +157,7 @@ type RedirectClient interface {
// that should happen during logout and supports client specific redirect URL. // that should happen during logout and supports client specific redirect URL.
type LogoutClient interface { type LogoutClient interface {
Client Client
Logout(ctx context.Context, user Requester) (*Redirect, bool) Logout(ctx context.Context, user identity.Requester) (*Redirect, bool)
} }
type PasswordClient interface { type PasswordClient interface {
@ -179,8 +179,8 @@ type UsageStatClient interface {
// Clients that implements this interface can resolve an full identity from an orgID and namespaceID. // Clients that implements this interface can resolve an full identity from an orgID and namespaceID.
type IdentityResolverClient interface { type IdentityResolverClient interface {
Client Client
Namespace() string IdentityType() identity.IdentityType
ResolveIdentity(ctx context.Context, orgID int64, namespaceID NamespaceID) (*Identity, error) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error)
} }
type Request struct { type Request struct {

@ -13,6 +13,7 @@ import (
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/network" "github.com/grafana/grafana/pkg/infra/network"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
@ -216,9 +217,9 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i
} }
// Login is only supported for users // Login is only supported for users
if !id.ID.IsNamespace(authn.NamespaceUser) { if !id.ID.IsType(identity.TypeUser) {
s.metrics.failedLogin.WithLabelValues(client).Inc() s.metrics.failedLogin.WithLabelValues(client).Inc()
return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Namespace()) return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Type())
} }
userID, err := id.ID.ParseInt() userID, err := id.ID.ParseInt()
@ -271,7 +272,7 @@ func (s *Service) RegisterPreLogoutHook(hook authn.PreLogoutHookFn, priority uin
s.preLogoutHooks.insert(hook, priority) s.preLogoutHooks.insert(hook, priority)
} }
func (s *Service) Logout(ctx context.Context, user authn.Requester, sessionToken *auth.UserToken) (*authn.Redirect, error) { func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionToken *auth.UserToken) (*authn.Redirect, error) {
ctx, span := s.tracer.Start(ctx, "authn.Logout") ctx, span := s.tracer.Start(ctx, "authn.Logout")
defer span.End() defer span.End()
@ -280,7 +281,7 @@ func (s *Service) Logout(ctx context.Context, user authn.Requester, sessionToken
redirect.URL = s.cfg.SignoutRedirectUrl redirect.URL = s.cfg.SignoutRedirectUrl
} }
if !user.GetID().IsNamespace(authn.NamespaceUser) { if !user.GetID().IsType(identity.TypeUser) {
return redirect, nil return redirect, nil
} }
@ -327,7 +328,7 @@ Default:
return redirect, nil return redirect, nil
} }
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
ctx, span := s.tracer.Start(ctx, "authn.ResolveIdentity") ctx, span := s.tracer.Start(ctx, "authn.ResolveIdentity")
defer span.End() defer span.End()
@ -352,7 +353,7 @@ func (s *Service) RegisterClient(c authn.Client) {
} }
if rc, ok := c.(authn.IdentityResolverClient); ok { if rc, ok := c.(authn.IdentityResolverClient); ok {
s.idenityResolverClients[rc.Namespace()] = rc s.idenityResolverClients[rc.IdentityType().String()] = rc
} }
} }
@ -375,11 +376,11 @@ func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) er
return s.runPostAuthHooks(ctx, identity, r) return s.runPostAuthHooks(ctx, identity, r)
} }
func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
ctx, span := s.tracer.Start(ctx, "authn.resolveIdentity") ctx, span := s.tracer.Start(ctx, "authn.resolveIdentity")
defer span.End() defer span.End()
if namespaceID.IsNamespace(authn.NamespaceUser) { if namespaceID.IsType(identity.TypeUser) {
return &authn.Identity{ return &authn.Identity{
OrgID: orgID, OrgID: orgID,
ID: namespaceID, ID: namespaceID,
@ -390,7 +391,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a
}}, nil }}, nil
} }
if namespaceID.IsNamespace(authn.NamespaceServiceAccount) { if namespaceID.IsType(identity.TypeServiceAccount) {
return &authn.Identity{ return &authn.Identity{
ID: namespaceID, ID: namespaceID,
OrgID: orgID, OrgID: orgID,
@ -401,9 +402,9 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a
}}, nil }}, nil
} }
resolver, ok := s.idenityResolverClients[namespaceID.Namespace().String()] resolver, ok := s.idenityResolverClients[string(namespaceID.Type())]
if !ok { if !ok {
return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Namespace()) return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Type())
} }
return resolver.ResolveIdentity(ctx, orgID, namespaceID) return resolver.ResolveIdentity(ctx, orgID, namespaceID)
} }

@ -44,9 +44,9 @@ func TestService_Authenticate(t *testing.T) {
{ {
desc: "should succeed with authentication for configured client", desc: "should succeed with authentication for configured client",
clients: []authn.Client{ clients: []authn.Client{
&authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}}, &authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}},
}, },
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
}, },
{ {
desc: "should succeed with authentication for configured client for identity with fetch permissions params", desc: "should succeed with authentication for configured client for identity with fetch permissions params",
@ -54,7 +54,7 @@ func TestService_Authenticate(t *testing.T) {
&authntest.FakeClient{ &authntest.FakeClient{
ExpectedTest: true, ExpectedTest: true,
ExpectedIdentity: &authn.Identity{ ExpectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"), ID: identity.MustParseTypedID("user:2"),
ClientParams: authn.ClientParams{ ClientParams: authn.ClientParams{
FetchPermissionsParams: authn.FetchPermissionsParams{ FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: []string{ ActionsLookup: []string{
@ -70,7 +70,7 @@ func TestService_Authenticate(t *testing.T) {
}, },
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"), ID: identity.MustParseTypedID("user:2"),
ClientParams: authn.ClientParams{ ClientParams: authn.ClientParams{
FetchPermissionsParams: authn.FetchPermissionsParams{ FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: []string{ ActionsLookup: []string{
@ -92,19 +92,19 @@ func TestService_Authenticate(t *testing.T) {
ExpectedName: "2", ExpectedName: "2",
ExpectedPriority: 2, ExpectedPriority: 2,
ExpectedTest: true, ExpectedTest: true,
ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"}, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
}, },
}, },
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"}, expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
}, },
{ {
desc: "should succeed with authentication for third client when error happened in first", desc: "should succeed with authentication for third client when error happened in first",
clients: []authn.Client{ clients: []authn.Client{
&authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false}, &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false},
&authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: true, ExpectedErr: errors.New("some error")}, &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: true, ExpectedErr: errors.New("some error")},
&authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:3")}}, &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:3")}},
}, },
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:3")}, expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:3")},
}, },
{ {
desc: "should return error when no client could authenticate the request", desc: "should return error when no client could authenticate the request",
@ -315,10 +315,10 @@ func TestService_Login(t *testing.T) {
client: "fake", client: "fake",
expectedClientOK: true, expectedClientOK: true,
expectedClientIdentity: &authn.Identity{ expectedClientIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
SessionToken: &auth.UserToken{UserId: 1}, SessionToken: &auth.UserToken{UserId: 1},
}, },
}, },
@ -331,7 +331,7 @@ func TestService_Login(t *testing.T) {
desc: "should not login non user identity", desc: "should not login non user identity",
client: "fake", client: "fake",
expectedClientOK: true, expectedClientOK: true,
expectedClientIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1")}, expectedClientIdentity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1")},
expectedErr: authn.ErrUnsupportedIdentity, expectedErr: authn.ErrUnsupportedIdentity,
}, },
} }
@ -420,31 +420,31 @@ func TestService_Logout(t *testing.T) {
tests := []TestCase{ tests := []TestCase{
{ {
desc: "should redirect to default redirect url when identity is not a user", desc: "should redirect to default redirect url when identity is not a user",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceServiceAccount, 1)}, identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeServiceAccount, 1)},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
}, },
{ {
desc: "should redirect to default redirect url when no external provider was used to authenticate", desc: "should redirect to default redirect url when no external provider was used to authenticate",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1)}, identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1)},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
expectedTokenRevoked: true, expectedTokenRevoked: true,
}, },
{ {
desc: "should redirect to default redirect url when client is not found", desc: "should redirect to default redirect url when client is not found",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "notfound"}, identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "notfound"},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
expectedTokenRevoked: true, expectedTokenRevoked: true,
}, },
{ {
desc: "should redirect to default redirect url when client do not implement logout extension", desc: "should redirect to default redirect url when client do not implement logout extension",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"}, identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"}, client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"},
expectedTokenRevoked: true, expectedTokenRevoked: true,
}, },
{ {
desc: "should use signout redirect url if configured", desc: "should use signout redirect url if configured",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"}, identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "some-url"}, expectedRedirect: &authn.Redirect{URL: "some-url"},
client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"}, client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"},
signoutRedirectURL: "some-url", signoutRedirectURL: "some-url",
@ -452,7 +452,7 @@ func TestService_Logout(t *testing.T) {
}, },
{ {
desc: "should redirect to client specific url", desc: "should redirect to client specific url",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"}, identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"}, expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"},
client: &authntest.MockClient{ client: &authntest.MockClient{
NameFunc: func() string { return "auth.client.azuread" }, NameFunc: func() string { return "auth.client.azuread" },
@ -500,26 +500,26 @@ func TestService_Logout(t *testing.T) {
func TestService_ResolveIdentity(t *testing.T) { func TestService_ResolveIdentity(t *testing.T) {
t.Run("should return error for for unknown namespace", func(t *testing.T) { t.Run("should return error for for unknown namespace", func(t *testing.T) {
svc := setupTests(t) svc := setupTests(t)
_, err := svc.ResolveIdentity(context.Background(), 1, authn.NewNamespaceID("some", 1)) _, err := svc.ResolveIdentity(context.Background(), 1, identity.NewTypedID("some", 1))
assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity) assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity)
}) })
t.Run("should return error for for namespace that don't have a resolver", func(t *testing.T) { t.Run("should return error for for namespace that don't have a resolver", func(t *testing.T) {
svc := setupTests(t) svc := setupTests(t)
_, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("api-key:1")) _, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("api-key:1"))
assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity) assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity)
}) })
t.Run("should resolve for user", func(t *testing.T) { t.Run("should resolve for user", func(t *testing.T) {
svc := setupTests(t) svc := setupTests(t)
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("user:1")) identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("user:1"))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, identity) assert.NotNil(t, identity)
}) })
t.Run("should resolve for service account", func(t *testing.T) { t.Run("should resolve for service account", func(t *testing.T) {
svc := setupTests(t) svc := setupTests(t)
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("service-account:1")) identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("service-account:1"))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, identity) assert.NotNil(t, identity)
}) })
@ -527,14 +527,14 @@ func TestService_ResolveIdentity(t *testing.T) {
t.Run("should resolve for valid namespace if client is registered", func(t *testing.T) { t.Run("should resolve for valid namespace if client is registered", func(t *testing.T) {
svc := setupTests(t, func(svc *Service) { svc := setupTests(t, func(svc *Service) {
svc.RegisterClient(&authntest.MockClient{ svc.RegisterClient(&authntest.MockClient{
NamespaceFunc: func() string { return authn.NamespaceAPIKey.String() }, IdentityTypeFunc: func() identity.IdentityType { return identity.TypeAPIKey },
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
return &authn.Identity{}, nil return &authn.Identity{}, nil
}, },
}) })
}) })
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("api-key:1")) identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("api-key:1"))
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, identity) assert.NotNil(t, identity)
}) })

@ -8,6 +8,7 @@ import (
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
@ -36,42 +37,42 @@ type OAuthTokenSync struct {
tracer tracing.Tracer tracer tracing.Tracer
} }
func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "oauth.sync.SyncOauthTokenHook") ctx, span := s.tracer.Start(ctx, "oauth.sync.SyncOauthTokenHook")
defer span.End() defer span.End()
// only perform oauth token check if identity is a user // only perform oauth token check if identity is a user
if !identity.ID.IsNamespace(authn.NamespaceUser) { if !id.ID.IsType(identity.TypeUser) {
return nil return nil
} }
// Not authenticated through session tokens, so we can skip this hook. // Not authenticated through session tokens, so we can skip this hook.
if identity.SessionToken == nil { if id.SessionToken == nil {
return nil return nil
} }
// Not authenticated with a oauth provider, so we can skip this hook. // Not authenticated with a oauth provider, so we can skip this hook.
if !strings.HasPrefix(identity.GetAuthenticatedBy(), "oauth") { if !strings.HasPrefix(id.GetAuthenticatedBy(), "oauth") {
return nil return nil
} }
ctxLogger := s.log.FromContext(ctx).New("userID", identity.ID.ID()) ctxLogger := s.log.FromContext(ctx).New("userID", id.ID.ID())
_, err, _ := s.singleflightGroup.Do(identity.ID.String(), func() (interface{}, error) { _, err, _ := s.singleflightGroup.Do(id.ID.String(), func() (interface{}, error) {
ctxLogger.Debug("Singleflight request for OAuth token sync") ctxLogger.Debug("Singleflight request for OAuth token sync")
// FIXME: Consider using context.WithoutCancel instead of context.Background after Go 1.21 update // FIXME: Consider using context.WithoutCancel instead of context.Background after Go 1.21 update
updateCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second) updateCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel() defer cancel()
if refreshErr := s.service.TryTokenRefresh(updateCtx, identity); refreshErr != nil { if refreshErr := s.service.TryTokenRefresh(updateCtx, id); refreshErr != nil {
if errors.Is(refreshErr, context.Canceled) { if errors.Is(refreshErr, context.Canceled) {
return nil, nil return nil, nil
} }
token, _, err := s.service.HasOAuthEntry(ctx, identity) token, _, err := s.service.HasOAuthEntry(ctx, id)
if err != nil { if err != nil {
ctxLogger.Error("Failed to get OAuth entry for verifying if token has already been refreshed", "id", identity.ID, "error", err) ctxLogger.Error("Failed to get OAuth entry for verifying if token has already been refreshed", "id", id.ID, "error", err)
return nil, err return nil, err
} }
@ -81,14 +82,14 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn
return nil, nil return nil, nil
} }
ctxLogger.Error("Failed to refresh OAuth access token", "id", identity.ID, "error", refreshErr) ctxLogger.Error("Failed to refresh OAuth access token", "id", id.ID, "error", refreshErr)
if err := s.service.InvalidateOAuthTokens(ctx, token); err != nil { if err := s.service.InvalidateOAuthTokens(ctx, token); err != nil {
ctxLogger.Warn("Failed to invalidate OAuth tokens", "id", identity.ID, "error", err) ctxLogger.Warn("Failed to invalidate OAuth tokens", "id", id.ID, "error", err)
} }
if err := s.sessionService.RevokeToken(ctx, identity.SessionToken, false); err != nil { if err := s.sessionService.RevokeToken(ctx, id.SessionToken, false); err != nil {
ctxLogger.Warn("Failed to revoke session token", "id", identity.ID, "tokenId", identity.SessionToken.Id, "error", err) ctxLogger.Warn("Failed to revoke session token", "id", id.ID, "tokenId", id.SessionToken.Id, "error", err)
} }
return nil, refreshErr return nil, refreshErr

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
@ -41,17 +42,17 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
tests := []testCase{ tests := []testCase{
{ {
desc: "should skip sync when identity is not a user", desc: "should skip sync when identity is not a user",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("service-account:1")},
expectTryRefreshTokenCalled: false, expectTryRefreshTokenCalled: false,
}, },
{ {
desc: "should skip sync when identity is a user but is not authenticated with session token", desc: "should skip sync when identity is a user but is not authenticated with session token",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
expectTryRefreshTokenCalled: false, expectTryRefreshTokenCalled: false,
}, },
{ {
desc: "should invalidate access token and session token if token refresh fails", desc: "should invalidate access token and session token if token refresh fails",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: true, expectHasEntryCalled: true,
expectedTryRefreshErr: errors.New("some err"), expectedTryRefreshErr: errors.New("some err"),
expectTryRefreshTokenCalled: true, expectTryRefreshTokenCalled: true,
@ -62,7 +63,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
}, },
{ {
desc: "should refresh the token successfully", desc: "should refresh the token successfully",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: false, expectHasEntryCalled: false,
expectTryRefreshTokenCalled: true, expectTryRefreshTokenCalled: true,
expectInvalidateOauthTokensCalled: false, expectInvalidateOauthTokensCalled: false,
@ -70,7 +71,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
}, },
{ {
desc: "should not invalidate the token if the token has already been refreshed by another request (singleflight)", desc: "should not invalidate the token if the token has already been refreshed by another request (singleflight)",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: true, expectHasEntryCalled: true,
expectTryRefreshTokenCalled: true, expectTryRefreshTokenCalled: true,
expectInvalidateOauthTokensCalled: false, expectInvalidateOauthTokensCalled: false,
@ -92,7 +93,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
) )
service := &oauthtokentest.MockOauthTokenService{ service := &oauthtokentest.MockOauthTokenService{
HasOAuthEntryFunc: func(ctx context.Context, usr authn.Requester) (*login.UserAuth, bool, error) { HasOAuthEntryFunc: func(ctx context.Context, usr identity.Requester) (*login.UserAuth, bool, error) {
hasEntryCalled = true hasEntryCalled = true
return tt.expectedHasEntryToken, tt.expectedHasEntryToken != nil, nil return tt.expectedHasEntryToken, tt.expectedHasEntryToken != nil, nil
}, },
@ -100,7 +101,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
invalidateTokensCalled = true invalidateTokensCalled = true
return nil return nil
}, },
TryTokenRefreshFunc: func(ctx context.Context, usr authn.Requester) error { TryTokenRefreshFunc: func(ctx context.Context, usr identity.Requester) error {
tryRefreshCalled = true tryRefreshCalled = true
return tt.expectedTryRefreshErr return tt.expectedTryRefreshErr
}, },

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"sort" "sort"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
@ -38,14 +39,14 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a
ctxLogger := s.log.FromContext(ctx).New("id", id.ID, "login", id.Login) ctxLogger := s.log.FromContext(ctx).New("id", id.ID, "login", id.Login)
if !id.ID.IsNamespace(authn.NamespaceUser) { if !id.ID.IsType(identity.TypeUser) {
ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "namespace", id.ID.Namespace()) ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "type", id.ID.Type())
return nil return nil
} }
userID, err := id.ID.ParseInt() userID, err := id.ID.ParseInt()
if err != nil { if err != nil {
ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "namespace", id.ID.Namespace(), "err", err) ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "type", id.ID.Type(), "err", err)
return nil return nil
} }
@ -144,14 +145,14 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn.
ctxLogger := s.log.FromContext(ctx) ctxLogger := s.log.FromContext(ctx)
if !currentIdentity.ID.IsNamespace(authn.NamespaceUser) { if !currentIdentity.ID.IsType(identity.TypeUser) {
ctxLogger.Debug("Skipping default org sync, not a user", "namespace", currentIdentity.ID.Namespace()) ctxLogger.Debug("Skipping default org sync, not a user", "type", currentIdentity.ID.Type())
return return
} }
userID, err := currentIdentity.ID.ParseInt() userID, err := currentIdentity.ID.ParseInt()
if err != nil { if err != nil {
ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "namespace", currentIdentity.ID.Namespace(), "err", err) ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "type", currentIdentity.ID.Type(), "err", err)
return return
} }

@ -76,7 +76,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
id: &authn.Identity{ id: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
Login: "test", Login: "test",
Name: "test", Name: "test",
Email: "test", Email: "test",
@ -92,7 +92,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
}, },
}, },
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
Login: "test", Login: "test",
Name: "test", Name: "test",
Email: "test", Email: "test",
@ -139,7 +139,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{ {
name: "should set default org", name: "should set default org",
defaultOrgSetting: 2, defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
userService.On("Update", mock.Anything, mock.MatchedBy(func(cmd *user.UpdateUserCommand) bool { userService.On("Update", mock.Anything, mock.MatchedBy(func(cmd *user.UpdateUserCommand) bool {
return cmd.UserID == 1 && *cmd.OrgID == 2 return cmd.UserID == 1 && *cmd.OrgID == 2
@ -149,7 +149,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{ {
name: "should skip setting the default org when default org is not set", name: "should skip setting the default org when default org is not set",
defaultOrgSetting: -1, defaultOrgSetting: -1,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
}, },
{ {
name: "should skip setting the default org when identity is nil", name: "should skip setting the default org when identity is nil",
@ -159,28 +159,28 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{ {
name: "should skip setting the default org when input err is not nil", name: "should skip setting the default org when input err is not nil",
defaultOrgSetting: 2, defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
inputErr: fmt.Errorf("error"), inputErr: fmt.Errorf("error"),
}, },
{ {
name: "should skip setting the default org when identity is not a user", name: "should skip setting the default org when identity is not a user",
defaultOrgSetting: 2, defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("service-account:1")},
}, },
{ {
name: "should skip setting the default org when user id is not valid", name: "should skip setting the default org when user id is not valid",
defaultOrgSetting: 2, defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:invalid")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:invalid")},
}, },
{ {
name: "should skip setting the default org when user is not allowed to use the configured default org", name: "should skip setting the default org when user is not allowed to use the configured default org",
defaultOrgSetting: 3, defaultOrgSetting: 3,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
}, },
{ {
name: "should skip setting the default org when validateUsingOrg returns error", name: "should skip setting the default org when validateUsingOrg returns error",
defaultOrgSetting: 2, defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
orgService.ExpectedError = fmt.Errorf("error") orgService.ExpectedError = fmt.Errorf("error")
}, },
@ -188,7 +188,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{ {
name: "should skip the hook when the user org update was unsuccessful", name: "should skip the hook when the user org update was unsuccessful",
defaultOrgSetting: 2, defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
userService.On("Update", mock.Anything, mock.Anything).Return(fmt.Errorf("error")) userService.On("Update", mock.Anything, mock.Anything).Return(fmt.Errorf("error"))
}, },

@ -5,6 +5,7 @@ import (
"errors" "errors"
"github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
@ -146,7 +147,7 @@ func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r
return nil return nil
} }
if !ident.ID.IsNamespace(authn.NamespaceUser) { if !ident.ID.IsType(identity.TypeUser) {
s.log.FromContext(ctx).Debug("Skip syncing cloud role", "id", ident.ID) s.log.FromContext(ctx).Debug("Skip syncing cloud role", "id", ident.ID)
return nil return nil
} }

@ -4,6 +4,10 @@ import (
"context" "context"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
@ -11,8 +15,6 @@ import (
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestRBACSync_SyncPermission(t *testing.T) { func TestRBACSync_SyncPermission(t *testing.T) {
@ -24,14 +26,14 @@ func TestRBACSync_SyncPermission(t *testing.T) {
testCases := []testCase{ testCases := []testCase{
{ {
name: "enriches the identity successfully when SyncPermissions is true", name: "enriches the identity successfully when SyncPermissions is true",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
expectedPermissions: []accesscontrol.Permission{ expectedPermissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead}, {Action: accesscontrol.ActionUsersRead},
}, },
}, },
{ {
name: "does not load the permissions when SyncPermissions is false", name: "does not load the permissions when SyncPermissions is false",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, identity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
expectedPermissions: []accesscontrol.Permission{ expectedPermissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead}, {Action: accesscontrol.ActionUsersRead},
}, },
@ -65,7 +67,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has viewer role", desc: "should call sync when authenticated with grafana com and has viewer role",
module: login.GrafanaComAuthModule, module: login.GrafanaComAuthModule,
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
}, },
@ -76,7 +78,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has editor role", desc: "should call sync when authenticated with grafana com and has editor role",
module: login.GrafanaComAuthModule, module: login.GrafanaComAuthModule,
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
}, },
@ -87,7 +89,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has admin role", desc: "should call sync when authenticated with grafana com and has admin role",
module: login.GrafanaComAuthModule, module: login.GrafanaComAuthModule,
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
}, },
@ -98,7 +100,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should not call sync when authenticated with grafana com and has invalid role", desc: "should not call sync when authenticated with grafana com and has invalid role",
module: login.GrafanaComAuthModule, module: login.GrafanaComAuthModule,
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleType("something else")}, OrgRoles: map[int64]org.RoleType{1: org.RoleType("something else")},
}, },
@ -109,7 +111,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should not call sync when not authenticated with grafana com", desc: "should not call sync when not authenticated with grafana com",
module: login.LDAPAuthModule, module: login.LDAPAuthModule,
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
}, },
@ -155,7 +157,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{ {
desc: "should map Cloud Viewer to Grafana Cloud Viewer and Support ticket reader", desc: "should map Cloud Viewer to Grafana Cloud Viewer and Support ticket reader",
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
}, },
@ -174,7 +176,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{ {
desc: "should map Cloud Editor to Grafana Cloud Editor and Support ticket admin", desc: "should map Cloud Editor to Grafana Cloud Editor and Support ticket admin",
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
}, },
@ -193,7 +195,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{ {
desc: "should map Cloud Admin to Grafana Cloud Admin and Support ticket admin", desc: "should map Cloud Admin to Grafana Cloud Admin and Support ticket admin",
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
}, },
@ -212,7 +214,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{ {
desc: "should return an error for not supported role", desc: "should return an error for not supported role",
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleNone}, OrgRoles: map[int64]org.RoleType{1: org.RoleNone},
}, },
@ -234,7 +236,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
func setupTestEnv() *RBACSync { func setupTestEnv() *RBACSync {
acMock := &acmock.Mock{ acMock := &acmock.Mock{
GetUserPermissionsFunc: func(ctx context.Context, siu authn.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) { GetUserPermissionsFunc: func(ctx context.Context, siu identity.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) {
return []accesscontrol.Permission{ return []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead}, {Action: accesscontrol.ActionUsersRead},
}, nil }, nil

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
@ -109,21 +110,21 @@ func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *auth
return nil return nil
} }
func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error { func (s *UserSync) FetchSyncedUserHook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.FetchSyncedUserHook") ctx, span := s.tracer.Start(ctx, "user.sync.FetchSyncedUserHook")
defer span.End() defer span.End()
if !identity.ClientParams.FetchSyncedUser { if !id.ClientParams.FetchSyncedUser {
return nil return nil
} }
if !identity.ID.IsNamespace(authn.NamespaceUser, authn.NamespaceServiceAccount) { if !id.ID.IsType(identity.TypeUser, identity.TypeServiceAccount) {
return nil return nil
} }
userID, err := identity.ID.ParseInt() userID, err := id.ID.ParseInt()
if err != nil { if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err) s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil return nil
} }
@ -138,18 +139,18 @@ func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Iden
return errFetchingSignedInUser.Errorf("failed to resolve user: %w", err) return errFetchingSignedInUser.Errorf("failed to resolve user: %w", err)
} }
if identity.ClientParams.AllowGlobalOrg && identity.OrgID == authn.GlobalOrgID { if id.ClientParams.AllowGlobalOrg && id.OrgID == authn.GlobalOrgID {
usr.Teams = nil usr.Teams = nil
usr.OrgName = "" usr.OrgName = ""
usr.OrgRole = org.RoleNone usr.OrgRole = org.RoleNone
usr.OrgID = authn.GlobalOrgID usr.OrgID = authn.GlobalOrgID
} }
syncSignedInUserToIdentity(usr, identity) syncSignedInUserToIdentity(usr, id)
return nil return nil
} }
func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error { func (s *UserSync) SyncLastSeenHook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.SyncLastSeenHook") ctx, span := s.tracer.Start(ctx, "user.sync.SyncLastSeenHook")
defer span.End() defer span.End()
@ -158,13 +159,13 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit
return nil return nil
} }
if !identity.ID.IsNamespace(authn.NamespaceUser, authn.NamespaceServiceAccount) { if !id.ID.IsType(identity.TypeUser, identity.TypeServiceAccount) {
return nil return nil
} }
userID, err := identity.ID.ParseInt() userID, err := id.ID.ParseInt()
if err != nil { if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err) s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil return nil
} }
@ -186,21 +187,21 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit
return nil return nil
} }
func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { func (s *UserSync) EnableUserHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.EnableUserHook") ctx, span := s.tracer.Start(ctx, "user.sync.EnableUserHook")
defer span.End() defer span.End()
if !identity.ClientParams.EnableUser { if !id.ClientParams.EnableUser {
return nil return nil
} }
if !identity.ID.IsNamespace(authn.NamespaceUser) { if !id.ID.IsType(identity.TypeUser) {
return nil return nil
} }
userID, err := identity.ID.ParseInt() userID, err := id.ID.ParseInt()
if err != nil { if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err) s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil return nil
} }
@ -417,8 +418,8 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar
// syncUserToIdentity syncs a user to an identity. // syncUserToIdentity syncs a user to an identity.
// This is used to update the identity with the latest user information. // This is used to update the identity with the latest user information.
func syncUserToIdentity(usr *user.User, id *authn.Identity) { func syncUserToIdentity(usr *user.User, id *authn.Identity) {
id.ID = authn.NewNamespaceID(authn.NamespaceUser, usr.ID) id.ID = identity.NewTypedID(identity.TypeUser, usr.ID)
id.UID = authn.NewNamespaceIDString(authn.NamespaceUser, usr.UID) id.UID = identity.NewTypedIDString(identity.TypeUser, usr.UID)
id.Login = usr.Login id.Login = usr.Login
id.Email = usr.Email id.Email = usr.Email
id.Name = usr.Name id.Name = usr.Name
@ -427,25 +428,25 @@ func syncUserToIdentity(usr *user.User, id *authn.Identity) {
} }
// syncSignedInUserToIdentity syncs a user to an identity. // syncSignedInUserToIdentity syncs a user to an identity.
func syncSignedInUserToIdentity(usr *user.SignedInUser, identity *authn.Identity) { func syncSignedInUserToIdentity(usr *user.SignedInUser, id *authn.Identity) {
var ns authn.Namespace var ns identity.IdentityType
if identity.ID.IsNamespace(authn.NamespaceServiceAccount) { if id.ID.IsType(identity.TypeServiceAccount) {
ns = authn.NamespaceServiceAccount ns = identity.TypeServiceAccount
} else { } else {
ns = authn.NamespaceUser ns = identity.TypeUser
} }
identity.UID = authn.NewNamespaceIDString(ns, usr.UserUID) id.UID = identity.NewTypedIDString(ns, usr.UserUID)
identity.Name = usr.Name id.Name = usr.Name
identity.Login = usr.Login id.Login = usr.Login
identity.Email = usr.Email id.Email = usr.Email
identity.OrgID = usr.OrgID id.OrgID = usr.OrgID
identity.OrgName = usr.OrgName id.OrgName = usr.OrgName
identity.OrgRoles = map[int64]org.RoleType{identity.OrgID: usr.OrgRole} id.OrgRoles = map[int64]org.RoleType{id.OrgID: usr.OrgRole}
identity.HelpFlags1 = usr.HelpFlags1 id.HelpFlags1 = usr.HelpFlags1
identity.Teams = usr.Teams id.Teams = usr.Teams
identity.LastSeenAt = usr.LastSeenAt id.LastSeenAt = usr.LastSeenAt
identity.IsDisabled = usr.IsDisabled id.IsDisabled = usr.IsDisabled
identity.IsGrafanaAdmin = &usr.IsGrafanaAdmin id.IsGrafanaAdmin = &usr.IsGrafanaAdmin
identity.EmailVerified = usr.EmailVerified id.EmailVerified = usr.EmailVerified
} }

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
@ -163,8 +164,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
}, },
wantErr: false, wantErr: false,
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
UID: authn.MustParseNamespaceID("user:1"), UID: identity.MustParseTypedID("user:1"),
Login: "test", Login: "test",
Name: "test", Name: "test",
Email: "test", Email: "test",
@ -202,8 +203,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
}, },
wantErr: false, wantErr: false,
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
UID: authn.MustParseNamespaceID("user:1"), UID: identity.MustParseTypedID("user:1"),
Login: "test", Login: "test",
Name: "test", Name: "test",
Email: "test", Email: "test",
@ -243,8 +244,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
}, },
wantErr: false, wantErr: false,
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
UID: authn.MustParseNamespaceID("user:1"), UID: identity.MustParseTypedID("user:1"),
AuthID: "2032", AuthID: "2032",
AuthenticatedBy: "oauth", AuthenticatedBy: "oauth",
Login: "test", Login: "test",
@ -315,8 +316,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
}, },
wantErr: false, wantErr: false,
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"), ID: identity.MustParseTypedID("user:2"),
UID: authn.MustParseNamespaceID("user:2"), UID: identity.MustParseTypedID("user:2"),
Login: "test_create", Login: "test_create",
Name: "test_create", Name: "test_create",
Email: "test_create", Email: "test_create",
@ -361,8 +362,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
}, },
wantErr: false, wantErr: false,
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:3"), ID: identity.MustParseTypedID("user:3"),
UID: authn.MustParseNamespaceID("user:3"), UID: identity.MustParseTypedID("user:3"),
Login: "test_mod", Login: "test_mod",
Name: "test_mod", Name: "test_mod",
Email: "test_mod", Email: "test_mod",
@ -406,8 +407,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
}, },
wantErr: false, wantErr: false,
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:3"), ID: identity.MustParseTypedID("user:3"),
UID: authn.MustParseNamespaceID("user:3"), UID: identity.MustParseTypedID("user:3"),
Name: "test", Name: "test",
Login: "test", Login: "test",
Email: "test_mod@test.com", Email: "test_mod@test.com",
@ -457,7 +458,7 @@ func TestUserSync_FetchSyncedUserHook(t *testing.T) {
{ {
desc: "should skip hook when identity is not a user", desc: "should skip hook when identity is not a user",
req: &authn.Request{}, req: &authn.Request{},
identity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1"), ClientParams: authn.ClientParams{FetchSyncedUser: true}}, identity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1"), ClientParams: authn.ClientParams{FetchSyncedUser: true}},
}, },
} }
@ -483,7 +484,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{ {
desc: "should skip if correct flag is not set", desc: "should skip if correct flag is not set",
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
IsDisabled: true, IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: false}, ClientParams: authn.ClientParams{EnableUser: false},
}, },
@ -492,7 +493,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{ {
desc: "should skip if identity is not a user", desc: "should skip if identity is not a user",
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceAPIKey, 1), ID: identity.NewTypedID(identity.TypeAPIKey, 1),
IsDisabled: true, IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: true}, ClientParams: authn.ClientParams{EnableUser: true},
}, },
@ -501,7 +502,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{ {
desc: "should enabled disabled user", desc: "should enabled disabled user",
identity: &authn.Identity{ identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1), ID: identity.NewTypedID(identity.TypeUser, 1),
IsDisabled: true, IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: true}, ClientParams: authn.ClientParams{EnableUser: true},
}, },

@ -78,7 +78,7 @@ func (f *FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertok
panic("unimplemented") panic("unimplemented")
} }
func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if f.ExpectedIdentities != nil { if f.ExpectedIdentities != nil {
if f.CurrentIndex >= len(f.ExpectedIdentities) { if f.CurrentIndex >= len(f.ExpectedIdentities) {
panic("ExpectedIdentities is empty") panic("ExpectedIdentities is empty")

@ -54,7 +54,7 @@ func (*MockService) Logout(_ context.Context, _ identity.Requester, _ *usertoken
panic("unimplemented") panic("unimplemented")
} }
func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
panic("unimplemented") panic("unimplemented")
} }
@ -77,8 +77,8 @@ type MockClient struct {
PriorityFunc func() uint PriorityFunc func() uint
HookFunc func(ctx context.Context, identity *authn.Identity, r *authn.Request) error HookFunc func(ctx context.Context, identity *authn.Identity, r *authn.Request) error
LogoutFunc func(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) LogoutFunc func(ctx context.Context, user identity.Requester) (*authn.Redirect, bool)
NamespaceFunc func() string IdentityTypeFunc func() identity.IdentityType
ResolveIdentityFunc func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) ResolveIdentityFunc func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error)
} }
func (m MockClient) Name() string { func (m MockClient) Name() string {
@ -127,15 +127,15 @@ func (m *MockClient) Logout(ctx context.Context, user identity.Requester) (*auth
return nil, false return nil, false
} }
func (m *MockClient) Namespace() string { func (m *MockClient) IdentityType() identity.IdentityType {
if m.NamespaceFunc != nil { if m.IdentityTypeFunc != nil {
return m.NamespaceFunc() return m.IdentityTypeFunc()
} }
return "" return identity.TypeEmpty
} }
// ResolveIdentity implements authn.IdentityResolverClient. // ResolveIdentity implements authn.IdentityResolverClient.
func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if m.ResolveIdentityFunc != nil { if m.ResolveIdentityFunc != nil {
return m.ResolveIdentityFunc(ctx, orgID, namespaceID) return m.ResolveIdentityFunc(ctx, orgID, namespaceID)
} }

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/components/satokengen" "github.com/grafana/grafana/pkg/components/satokengen"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
@ -134,13 +135,13 @@ func (s *APIKey) Priority() uint {
return 30 return 30
} }
func (s *APIKey) Namespace() string { func (s *APIKey) IdentityType() identity.IdentityType {
return authn.NamespaceAPIKey.String() return identity.TypeAPIKey
} }
func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if !namespaceID.IsNamespace(authn.NamespaceAPIKey) { if !namespaceID.IsType(identity.TypeAPIKey) {
return nil, authn.ErrInvalidNamespaceID.Errorf("got unspected namespace: %s", namespaceID.Namespace()) return nil, identity.ErrInvalidTypedID.Errorf("got unspected namespace: %s", namespaceID.Type())
} }
apiKeyID, err := namespaceID.ParseInt() apiKeyID, err := namespaceID.ParseInt()
@ -160,7 +161,7 @@ func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID a
} }
if key.ServiceAccountId != nil && *key.ServiceAccountId >= 1 { if key.ServiceAccountId != nil && *key.ServiceAccountId >= 1 {
return nil, authn.ErrInvalidNamespaceID.Errorf("api key belongs to service account") return nil, identity.ErrInvalidTypedID.Errorf("api key belongs to service account")
} }
return newAPIKeyIdentity(key), nil return newAPIKeyIdentity(key), nil
@ -187,18 +188,18 @@ func (s *APIKey) Hook(ctx context.Context, identity *authn.Identity, r *authn.Re
return nil return nil
} }
func (s *APIKey) getAPIKeyID(ctx context.Context, identity *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) { func (s *APIKey) getAPIKeyID(ctx context.Context, id *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) {
id, err := identity.ID.ParseInt() internalId, err := id.ID.ParseInt()
if err != nil { if err != nil {
s.log.Warn("Failed to parse ID from identifier", "err", err) s.log.Warn("Failed to parse ID from identifier", "err", err)
return -1, false return -1, false
} }
if identity.ID.IsNamespace(authn.NamespaceAPIKey) { if id.ID.IsType(identity.TypeAPIKey) {
return id, true return internalId, true
} }
if identity.ID.IsNamespace(authn.NamespaceServiceAccount) { if id.ID.IsType(identity.TypeServiceAccount) {
// When the identity is service account, the ID in from the namespace is the service account ID. // When the identity is service account, the ID in from the namespace is the service account ID.
// We need to fetch the API key in this scenario, as we could use it to uniquely identify a service account token. // We need to fetch the API key in this scenario, as we could use it to uniquely identify a service account token.
apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r)) apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r))
@ -255,7 +256,7 @@ func validateApiKey(orgID int64, key *apikey.APIKey) error {
func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity { func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
return &authn.Identity{ return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceAPIKey, key.ID), ID: identity.NewTypedID(identity.TypeAPIKey, key.ID),
OrgID: key.OrgID, OrgID: key.OrgID,
OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role}, OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role},
ClientParams: authn.ClientParams{SyncPermissions: true}, ClientParams: authn.ClientParams{SyncPermissions: true},
@ -265,7 +266,7 @@ func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity { func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity {
return &authn.Identity{ return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceServiceAccount, *key.ServiceAccountId), ID: identity.NewTypedID(identity.TypeServiceAccount, *key.ServiceAccountId),
OrgID: key.OrgID, OrgID: key.OrgID,
AuthenticatedBy: login.APIKeyAuthModule, AuthenticatedBy: login.APIKeyAuthModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/components/satokengen" "github.com/grafana/grafana/pkg/components/satokengen"
"github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/apikey"
@ -47,7 +48,7 @@ func TestAPIKey_Authenticate(t *testing.T) {
Role: org.RoleAdmin, Role: org.RoleAdmin,
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("api-key:1"), ID: identity.MustParseTypedID("api-key:1"),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
ClientParams: authn.ClientParams{ ClientParams: authn.ClientParams{
@ -70,7 +71,7 @@ func TestAPIKey_Authenticate(t *testing.T) {
ServiceAccountId: intPtr(1), ServiceAccountId: intPtr(1),
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:1"), ID: identity.MustParseTypedID("service-account:1"),
OrgID: 1, OrgID: 1,
ClientParams: authn.ClientParams{ ClientParams: authn.ClientParams{
FetchSyncedUser: true, FetchSyncedUser: true,
@ -205,7 +206,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
ServiceAccountId: intPtr(1), ServiceAccountId: intPtr(1),
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:1"), ID: identity.MustParseTypedID("service-account:1"),
OrgID: 1, OrgID: 1,
Name: "test", Name: "test",
AuthenticatedBy: login.APIKeyAuthModule, AuthenticatedBy: login.APIKeyAuthModule,
@ -221,7 +222,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash, Key: hash,
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("api-key:2"), ID: identity.MustParseTypedID("api-key:2"),
OrgID: 1, OrgID: 1,
Name: "test", Name: "test",
AuthenticatedBy: login.APIKeyAuthModule, AuthenticatedBy: login.APIKeyAuthModule,
@ -237,7 +238,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash, Key: hash,
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"), ID: identity.MustParseTypedID("user:2"),
OrgID: 1, OrgID: 1,
Name: "test", Name: "test",
AuthenticatedBy: login.APIKeyAuthModule, AuthenticatedBy: login.APIKeyAuthModule,
@ -253,7 +254,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash, Key: hash,
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:2"), ID: identity.MustParseTypedID("service-account:2"),
OrgID: 1, OrgID: 1,
Name: "test", Name: "test",
AuthenticatedBy: login.APIKeyAuthModule, AuthenticatedBy: login.APIKeyAuthModule,
@ -286,7 +287,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
func TestAPIKey_ResolveIdentity(t *testing.T) { func TestAPIKey_ResolveIdentity(t *testing.T) {
type testCase struct { type testCase struct {
desc string desc string
namespaceID authn.NamespaceID namespaceID identity.TypedID
exptedApiKey *apikey.APIKey exptedApiKey *apikey.APIKey
@ -297,12 +298,12 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
tests := []testCase{ tests := []testCase{
{ {
desc: "should return error for invalid namespace", desc: "should return error for invalid namespace",
namespaceID: authn.MustParseNamespaceID("user:1"), namespaceID: identity.MustParseTypedID("user:1"),
expectedErr: authn.ErrInvalidNamespaceID, expectedErr: identity.ErrInvalidTypedID,
}, },
{ {
desc: "should return error when api key has expired", desc: "should return error when api key has expired",
namespaceID: authn.MustParseNamespaceID("api-key:1"), namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{ exptedApiKey: &apikey.APIKey{
ID: 1, ID: 1,
OrgID: 1, OrgID: 1,
@ -312,7 +313,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
}, },
{ {
desc: "should return error when api key is revoked", desc: "should return error when api key is revoked",
namespaceID: authn.MustParseNamespaceID("api-key:1"), namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{ exptedApiKey: &apikey.APIKey{
ID: 1, ID: 1,
OrgID: 1, OrgID: 1,
@ -322,17 +323,17 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
}, },
{ {
desc: "should return error when api key is connected to service account", desc: "should return error when api key is connected to service account",
namespaceID: authn.MustParseNamespaceID("api-key:1"), namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{ exptedApiKey: &apikey.APIKey{
ID: 1, ID: 1,
OrgID: 1, OrgID: 1,
ServiceAccountId: intPtr(1), ServiceAccountId: intPtr(1),
}, },
expectedErr: authn.ErrInvalidNamespaceID, expectedErr: identity.ErrInvalidTypedID,
}, },
{ {
desc: "should return error when api key is belongs to different org", desc: "should return error when api key is belongs to different org",
namespaceID: authn.MustParseNamespaceID("api-key:1"), namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{ exptedApiKey: &apikey.APIKey{
ID: 1, ID: 1,
OrgID: 2, OrgID: 2,
@ -342,7 +343,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
}, },
{ {
desc: "should return valid idenitty", desc: "should return valid idenitty",
namespaceID: authn.MustParseNamespaceID("api-key:1"), namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{ exptedApiKey: &apikey.APIKey{
ID: 1, ID: 1,
OrgID: 1, OrgID: 1,
@ -351,7 +352,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
expectedIdenity: &authn.Identity{ expectedIdenity: &authn.Identity{
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
ID: authn.MustParseNamespaceID("api-key:1"), ID: identity.MustParseTypedID("api-key:1"),
AuthenticatedBy: login.APIKeyAuthModule, AuthenticatedBy: login.APIKeyAuthModule,
ClientParams: authn.ClientParams{SyncPermissions: true}, ClientParams: authn.ClientParams{SyncPermissions: true},
}, },

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest" "github.com/grafana/grafana/pkg/services/authn/authntest"
) )
@ -24,8 +25,8 @@ func TestBasic_Authenticate(t *testing.T) {
{ {
desc: "should success when password client return identity", desc: "should success when password client return identity",
req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "password")}}}}, req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "password")}}}},
client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}}, client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
}, },
{ {
desc: "should fail when basic auth header could not be decoded", desc: "should fail when basic auth header could not be decoded",

@ -10,6 +10,7 @@ import (
authlib "github.com/grafana/authlib/authn" authlib "github.com/grafana/authlib/authn"
"github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
@ -108,21 +109,21 @@ func (s *ExtendedJWT) authenticateAsUser(
return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Rest.Namespace) return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Rest.Namespace)
} }
accessID, err := authn.ParseNamespaceID(accessTokenClaims.Subject) accessID, err := identity.ParseTypedID(accessTokenClaims.Subject)
if err != nil { if err != nil {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessID.String()) return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessID.String())
} }
if !accessID.IsNamespace(authn.NamespaceAccessPolicy) { if !accessID.IsType(identity.TypeAccessPolicy) {
return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessID.String()) return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessID.String())
} }
userID, err := authn.ParseNamespaceID(idTokenClaims.Subject) userID, err := identity.ParseTypedID(idTokenClaims.Subject)
if err != nil { if err != nil {
return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err) return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err)
} }
if !userID.IsNamespace(authn.NamespaceUser) { if !userID.IsType(identity.TypeUser) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", userID.String()) return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", userID.String())
} }
@ -154,12 +155,12 @@ func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.Acces
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", claims.Rest.Namespace) return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", claims.Rest.Namespace)
} }
id, err := authn.ParseNamespaceID(claims.Subject) id, err := identity.ParseTypedID(claims.Subject)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse access token subject: %w", err) return nil, fmt.Errorf("failed to parse access token subject: %w", err)
} }
if !id.IsNamespace(authn.NamespaceAccessPolicy) { if !id.IsType(identity.TypeAccessPolicy) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", id.String()) return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", id.String())
} }

@ -17,6 +17,7 @@ import (
authnlib "github.com/grafana/authlib/authn" authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -207,8 +208,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
accessToken: &validAccessTokenClaims, accessToken: &validAccessTokenClaims,
orgID: 1, orgID: 1,
want: &authn.Identity{ want: &authn.Identity{
ID: authn.MustParseNamespaceID("access-policy:this-uid"), ID: identity.MustParseTypedID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"), UID: identity.MustParseTypedID("access-policy:this-uid"),
OrgID: 1, OrgID: 1,
AllowedKubernetesNamespace: "default", AllowedKubernetesNamespace: "default",
AuthenticatedBy: "extendedjwt", AuthenticatedBy: "extendedjwt",
@ -223,8 +224,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
accessToken: &validAcessTokenClaimsWildcard, accessToken: &validAcessTokenClaimsWildcard,
orgID: 1, orgID: 1,
want: &authn.Identity{ want: &authn.Identity{
ID: authn.MustParseNamespaceID("access-policy:this-uid"), ID: identity.MustParseTypedID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"), UID: identity.MustParseTypedID("access-policy:this-uid"),
OrgID: 1, OrgID: 1,
AllowedKubernetesNamespace: "*", AllowedKubernetesNamespace: "*",
AuthenticatedBy: "extendedjwt", AuthenticatedBy: "extendedjwt",
@ -240,7 +241,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
idToken: &validIDTokenClaims, idToken: &validIDTokenClaims,
orgID: 1, orgID: 1,
want: &authn.Identity{ want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"), ID: identity.MustParseTypedID("user:2"),
OrgID: 1, OrgID: 1,
AllowedKubernetesNamespace: "default", AllowedKubernetesNamespace: "default",
AuthenticatedBy: "extendedjwt", AuthenticatedBy: "extendedjwt",
@ -260,7 +261,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
idToken: &validIDTokenClaims, idToken: &validIDTokenClaims,
orgID: 1, orgID: 1,
want: &authn.Identity{ want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"), ID: identity.MustParseTypedID("user:2"),
OrgID: 1, OrgID: 1,
AllowedKubernetesNamespace: "*", AllowedKubernetesNamespace: "*",
AuthenticatedBy: "extendedjwt", AuthenticatedBy: "extendedjwt",
@ -285,7 +286,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
}, },
}, },
want: &authn.Identity{ want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"), ID: identity.MustParseTypedID("user:2"),
OrgID: 1, OrgID: 1,
AllowedKubernetesNamespace: "stack-1234", AllowedKubernetesNamespace: "stack-1234",
AuthenticatedBy: "extendedjwt", AuthenticatedBy: "extendedjwt",

@ -6,6 +6,7 @@ import (
"errors" "errors"
"net/mail" "net/mail"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -105,7 +106,7 @@ func (c *Grafana) AuthenticatePassword(ctx context.Context, r *authn.Request, us
} }
return &authn.Identity{ return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, usr.ID), ID: identity.NewTypedID(identity.TypeUser, usr.ID),
OrgID: r.OrgID, OrgID: r.OrgID,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
AuthenticatedBy: login.PasswordAuthModule, AuthenticatedBy: login.PasswordAuthModule,

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -140,7 +141,7 @@ func TestGrafana_AuthenticatePassword(t *testing.T) {
password: "password", password: "password",
findUser: true, findUser: true,
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
OrgID: 1, OrgID: 1,
AuthenticatedBy: login.PasswordAuthModule, AuthenticatedBy: login.PasswordAuthModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},

@ -245,10 +245,10 @@ func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redir
}, nil }, nil
} }
func (c *OAuth) Logout(ctx context.Context, user authn.Requester) (*authn.Redirect, bool) { func (c *OAuth) Logout(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) {
token := c.oauthService.GetCurrentOAuthToken(ctx, user) token := c.oauthService.GetCurrentOAuthToken(ctx, user)
namespace, id := user.GetNamespacedID() namespace, id := user.GetTypedID()
userID, err := identity.UserIdentifier(namespace, id) userID, err := identity.UserIdentifier(namespace, id)
if err != nil { if err != nil {
c.log.FromContext(ctx).Error("Failed to parse user id", "namespace", namespace, "id", id, "error", err) c.log.FromContext(ctx).Error("Failed to parse user id", "namespace", namespace, "id", id, "error", err)
@ -260,7 +260,7 @@ func (c *OAuth) Logout(ctx context.Context, user authn.Requester) (*authn.Redire
AuthId: user.GetAuthID(), AuthId: user.GetAuthID(),
AuthModule: user.GetAuthenticatedBy(), AuthModule: user.GetAuthenticatedBy(),
}); err != nil { }); err != nil {
namespace, id := user.GetNamespacedID() namespace, id := user.GetTypedID()
c.log.FromContext(ctx).Error("Failed to invalidate tokens", "namespace", namespace, "id", id, "error", err) c.log.FromContext(ctx).Error("Failed to invalidate tokens", "namespace", namespace, "id", id, "error", err)
} }

@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest" "github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/services/loginattempt/loginattempttest" "github.com/grafana/grafana/pkg/services/loginattempt/loginattempttest"
@ -29,16 +30,16 @@ func TestPassword_AuthenticatePassword(t *testing.T) {
username: "test", username: "test",
password: "test", password: "test",
req: &authn.Request{}, req: &authn.Request{},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}}}, clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
}, },
{ {
desc: "should success when found in second client", desc: "should success when found in second client",
username: "test", username: "test",
password: "test", password: "test",
req: &authn.Request{}, req: &authn.Request{},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}}}, clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2")}}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}, expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2")},
}, },
{ {
desc: "should fail for empty password", desc: "should fail for empty password",

@ -13,6 +13,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
@ -124,7 +125,7 @@ func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *aut
} }
return &authn.Identity{ return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, uid), ID: identity.NewTypedID(identity.TypeUser, uid),
OrgID: r.OrgID, OrgID: r.OrgID,
// FIXME: This does not match the actual auth module used, but should not have any impact // FIXME: This does not match the actual auth module used, but should not have any impact
// Maybe caching the auth module used with the user ID would be a good idea // Maybe caching the auth module used with the user ID would be a good idea
@ -144,18 +145,18 @@ func (c *Proxy) Priority() uint {
return 50 return 50
} }
func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Request) error { func (c *Proxy) Hook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
if identity.ClientParams.CacheAuthProxyKey == "" { if id.ClientParams.CacheAuthProxyKey == "" {
return nil return nil
} }
if !identity.ID.IsNamespace(authn.NamespaceUser) { if !id.ID.IsType(identity.TypeUser) {
return nil return nil
} }
id, err := identity.ID.ParseInt() internalId, err := id.ID.ParseInt()
if err != nil { if err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", identity.ID.ID(), "err", err) c.log.Warn("Failed to cache proxy user", "error", err, "userId", id.ID.ID(), "err", err)
return nil return nil
} }
@ -175,15 +176,15 @@ func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Req
} }
} }
c.log.FromContext(ctx).Debug("Cache proxy user", "userId", id) c.log.FromContext(ctx).Debug("Cache proxy user", "userId", internalId)
bytes := []byte(strconv.FormatInt(id, 10)) bytes := []byte(strconv.FormatInt(internalId, 10))
duration := time.Duration(c.cfg.AuthProxy.SyncTTL) * time.Minute duration := time.Duration(c.cfg.AuthProxy.SyncTTL) * time.Minute
if err := c.cache.Set(ctx, identity.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil { if err := c.cache.Set(ctx, id.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", id) c.log.Warn("Failed to cache proxy user", "error", err, "userId", internalId)
} }
// store current cacheKey for the user // store current cacheKey for the user
return c.cache.Set(ctx, userKey, []byte(identity.ClientParams.CacheAuthProxyKey), duration) return c.cache.Set(ctx, userKey, []byte(id.ClientParams.CacheAuthProxyKey), duration)
} }
func (c *Proxy) isAllowedIP(r *authn.Request) bool { func (c *Proxy) isAllowedIP(r *authn.Request) bool {

@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest" "github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -203,7 +204,7 @@ func TestProxy_Hook(t *testing.T) {
} }
cache := &fakeCache{data: make(map[string][]byte)} cache := &fakeCache{data: make(map[string][]byte)}
userId := int64(1) userId := int64(1)
userID := authn.NewNamespaceID(authn.NamespaceUser, userId) userID := identity.NewTypedID(identity.TypeUser, userId)
// withRole creates a test case for a user with a specific role. // withRole creates a test case for a user with a specific role.
withRole := func(role string) func(t *testing.T) { withRole := func(role string) func(t *testing.T) {

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -42,7 +43,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
if renderUsr.UserID <= 0 { if renderUsr.UserID <= 0 {
return &authn.Identity{ return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceRenderService, 0), ID: identity.NewTypedID(identity.TypeRenderService, 0),
OrgID: renderUsr.OrgID, OrgID: renderUsr.OrgID,
OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)}, OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)},
ClientParams: authn.ClientParams{SyncPermissions: true}, ClientParams: authn.ClientParams{SyncPermissions: true},
@ -52,7 +53,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
} }
return &authn.Identity{ return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, renderUsr.UserID), ID: identity.NewTypedID(identity.TypeUser, renderUsr.UserID),
LastSeenAt: time.Now(), LastSeenAt: time.Now(),
AuthenticatedBy: login.RenderModule, AuthenticatedBy: login.RenderModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},

@ -9,6 +9,7 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -35,7 +36,7 @@ func TestRender_Authenticate(t *testing.T) {
}, },
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("render:0"), ID: identity.MustParseTypedID("render:0"),
OrgID: 1, OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
AuthenticatedBy: login.RenderModule, AuthenticatedBy: login.RenderModule,
@ -56,7 +57,7 @@ func TestRender_Authenticate(t *testing.T) {
}, },
}, },
expectedIdentity: &authn.Identity{ expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
AuthenticatedBy: login.RenderModule, AuthenticatedBy: login.RenderModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
}, },

@ -6,6 +6,7 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
@ -57,7 +58,7 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
} }
ident := &authn.Identity{ ident := &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, token.UserId), ID: identity.NewTypedID(identity.TypeUser, token.UserId),
SessionToken: token, SessionToken: token,
ClientParams: authn.ClientParams{ ClientParams: authn.ClientParams{
FetchSyncedUser: true, FetchSyncedUser: true,

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/authtest" "github.com/grafana/grafana/pkg/services/auth/authtest"
@ -96,7 +97,7 @@ func TestSession_Authenticate(t *testing.T) {
}, },
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken, SessionToken: validToken,
ClientParams: authn.ClientParams{ ClientParams: authn.ClientParams{
SyncPermissions: true, SyncPermissions: true,
@ -129,7 +130,7 @@ func TestSession_Authenticate(t *testing.T) {
}, },
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken, SessionToken: validToken,
ClientParams: authn.ClientParams{ ClientParams: authn.ClientParams{
SyncPermissions: true, SyncPermissions: true,
@ -148,7 +149,7 @@ func TestSession_Authenticate(t *testing.T) {
}, },
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
AuthID: "1", AuthID: "1",
AuthenticatedBy: "oauth_azuread", AuthenticatedBy: "oauth_azuread",
SessionToken: validToken, SessionToken: validToken,
@ -170,7 +171,7 @@ func TestSession_Authenticate(t *testing.T) {
}, },
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{ wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"), ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken, SessionToken: validToken,
ClientParams: authn.ClientParams{ ClientParams: authn.ClientParams{

@ -15,16 +15,14 @@ import (
const GlobalOrgID = int64(0) const GlobalOrgID = int64(0)
type Requester = identity.Requester var _ identity.Requester = (*Identity)(nil)
var _ Requester = (*Identity)(nil)
type Identity struct { type Identity struct {
// ID is the unique identifier for the entity in the Grafana database. // ID is the unique identifier for the entity in the Grafana database.
// If the entity is not found in the DB or this entity is non-persistent, this field will be empty. // If the entity is not found in the DB or this entity is non-persistent, this field will be empty.
ID NamespaceID ID identity.TypedID
// UID is a unique identifier stored for the entity in Grafana database. Not all entities support uid so it can be empty. // UID is a unique identifier stored for the entity in Grafana database. Not all entities support uid so it can be empty.
UID NamespaceID UID identity.TypedID
// OrgID is the active organization for the entity. // OrgID is the active organization for the entity.
OrgID int64 OrgID int64
// OrgName is the name of the active organization. // OrgName is the name of the active organization.
@ -74,15 +72,15 @@ type Identity struct {
IDToken string IDToken string
} }
func (i *Identity) GetID() NamespaceID { func (i *Identity) GetID() identity.TypedID {
return i.ID return i.ID
} }
func (i *Identity) GetNamespacedID() (namespace identity.Namespace, identifier string) { func (i *Identity) GetTypedID() (namespace identity.IdentityType, identifier string) {
return i.ID.Namespace(), i.ID.ID() return i.ID.Type(), i.ID.ID()
} }
func (i *Identity) GetUID() NamespaceID { func (i *Identity) GetUID() identity.TypedID {
return i.UID return i.UID
} }
@ -95,7 +93,7 @@ func (i *Identity) GetAuthenticatedBy() string {
} }
func (i *Identity) GetCacheKey() string { func (i *Identity) GetCacheKey() string {
namespace, id := i.GetNamespacedID() namespace, id := i.GetTypedID()
if !i.HasUniqueId() { if !i.HasUniqueId() {
// Hack use the org role as id for identities that do not have a unique id // Hack use the org role as id for identities that do not have a unique id
// e.g. anonymous and render key. // e.g. anonymous and render key.
@ -191,8 +189,10 @@ func (i *Identity) HasRole(role org.RoleType) bool {
} }
func (i *Identity) HasUniqueId() bool { func (i *Identity) HasUniqueId() bool {
namespace, _ := i.GetNamespacedID() namespace, _ := i.GetTypedID()
return namespace == NamespaceUser || namespace == NamespaceServiceAccount || namespace == NamespaceAPIKey return namespace == identity.TypeUser ||
namespace == identity.TypeServiceAccount ||
namespace == identity.TypeAPIKey
} }
func (i *Identity) IsAuthenticatedBy(providers ...string) bool { func (i *Identity) IsAuthenticatedBy(providers ...string) bool {
@ -220,7 +220,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
AuthID: i.AuthID, AuthID: i.AuthID,
AuthenticatedBy: i.AuthenticatedBy, AuthenticatedBy: i.AuthenticatedBy,
IsGrafanaAdmin: i.GetIsGrafanaAdmin(), IsGrafanaAdmin: i.GetIsGrafanaAdmin(),
IsAnonymous: i.ID.IsNamespace(NamespaceAnonymous), IsAnonymous: i.ID.IsType(identity.TypeAnonymous),
IsDisabled: i.IsDisabled, IsDisabled: i.IsDisabled,
HelpFlags1: i.HelpFlags1, HelpFlags1: i.HelpFlags1,
LastSeenAt: i.LastSeenAt, LastSeenAt: i.LastSeenAt,
@ -230,14 +230,14 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
NamespacedID: i.ID, NamespacedID: i.ID,
} }
if i.ID.IsNamespace(NamespaceAPIKey) { if i.ID.IsType(identity.TypeAPIKey) {
id, _ := i.ID.ParseInt() id, _ := i.ID.ParseInt()
u.ApiKeyID = id u.ApiKeyID = id
} else { } else {
id, _ := i.ID.UserID() id, _ := i.ID.UserID()
u.UserID = id u.UserID = id
u.UserUID = i.UID.ID() u.UserUID = i.UID.ID()
u.IsServiceAccount = i.ID.IsNamespace(NamespaceServiceAccount) u.IsServiceAccount = i.ID.IsType(identity.TypeServiceAccount)
} }
return u return u

@ -1,27 +0,0 @@
package authn
import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
)
const (
NamespaceUser = identity.NamespaceUser
NamespaceAPIKey = identity.NamespaceAPIKey
NamespaceServiceAccount = identity.NamespaceServiceAccount
NamespaceAnonymous = identity.NamespaceAnonymous
NamespaceRenderService = identity.NamespaceRenderService
NamespaceAccessPolicy = identity.NamespaceAccessPolicy
)
var AnonymousNamespaceID = NewNamespaceID(NamespaceAnonymous, 0)
type Namespace = identity.Namespace
type NamespaceID = identity.NamespaceID
var (
ParseNamespaceID = identity.ParseNamespaceID
MustParseNamespaceID = identity.MustParseNamespaceID
NewNamespaceID = identity.NewNamespaceID
NewNamespaceIDString = identity.NewNamespaceIDString
ErrInvalidNamespaceID = identity.ErrInvalidNamespaceID
)

@ -5,6 +5,7 @@ import (
authzv1 "github.com/grafana/authlib/authz/proto/v1" authzv1 "github.com/grafana/authlib/authz/proto/v1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
@ -54,7 +55,16 @@ func (s *legacyServer) Read(ctx context.Context, req *authzv1.ReadRequest) (*aut
ctxLogger := s.logger.FromContext(ctx) ctxLogger := s.logger.FromContext(ctx)
ctxLogger.Debug("Read", "action", action, "subject", subject, "stackID", stackID) ctxLogger.Debug("Read", "action", action, "subject", subject, "stackID", stackID)
permissions, err := s.acSvc.SearchUserPermissions(ctx, stackID, accesscontrol.SearchOptions{NamespacedID: subject, Action: action}) var err error
opts := accesscontrol.SearchOptions{Action: action}
if subject != "" {
opts.TypedID, err = identity.ParseTypedID(subject)
if err != nil {
return nil, err
}
}
permissions, err := s.acSvc.SearchUserPermissions(ctx, stackID, opts)
if err != nil { if err != nil {
ctxLogger.Error("failed to search user permissions", "error", err) ctxLogger.Error("failed to search user permissions", "error", err)
return nil, tracing.Errorf(span, "failed to search user permissions: %w", err) return nil, tracing.Errorf(span, "failed to search user permissions: %w", err)

@ -6,10 +6,11 @@ import (
"fmt" "fmt"
"net/http" "net/http"
authnClients "github.com/grafana/grafana/pkg/services/authn/clients"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
authnClients "github.com/grafana/grafana/pkg/services/authn/clients"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
@ -153,12 +154,12 @@ func (h *ContextHandler) addIDHeaderEndOfRequestFunc(ident identity.Requester) w
return return
} }
namespace, id := ident.GetNamespacedID() namespace, id := ident.GetTypedID()
if !identity.IsNamespace( if !identity.IsIdentityType(
namespace, namespace,
identity.NamespaceUser, identity.TypeUser,
identity.NamespaceServiceAccount, identity.TypeServiceAccount,
identity.NamespaceAPIKey, identity.TypeAPIKey,
) || id == "0" { ) || id == "0" {
return return
} }

@ -43,7 +43,7 @@ func TestContextHandler(t *testing.T) {
}) })
t.Run("should set identity on successful authentication", func(t *testing.T) { t.Run("should set identity on successful authentication", func(t *testing.T) {
id := &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), OrgID: 1} id := &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), OrgID: 1}
handler := contexthandler.ProvideService( handler := contexthandler.ProvideService(
setting.NewCfg(), setting.NewCfg(),
tracing.InitializeTracerForTest(), tracing.InitializeTracerForTest(),
@ -68,7 +68,7 @@ func TestContextHandler(t *testing.T) {
}) })
t.Run("should not set IsSignedIn on anonymous identity", func(t *testing.T) { t.Run("should not set IsSignedIn on anonymous identity", func(t *testing.T) {
identity := &authn.Identity{ID: authn.AnonymousNamespaceID, OrgID: 1} identity := &authn.Identity{ID: identity.AnonymousTypedID, OrgID: 1}
handler := contexthandler.ProvideService( handler := contexthandler.ProvideService(
setting.NewCfg(), setting.NewCfg(),
tracing.InitializeTracerForTest(), tracing.InitializeTracerForTest(),
@ -148,7 +148,7 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService( handler := contexthandler.ProvideService(
cfg, cfg,
tracing.InitializeTracerForTest(), tracing.InitializeTracerForTest(),
&authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID(id)}}, &authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID(id)}},
) )
server := webtest.NewServer(t, routing.NewRouteRegister()) server := webtest.NewServer(t, routing.NewRouteRegister())

@ -109,10 +109,10 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb
req.FolderUid = folder.UID req.FolderUid = folder.UID
} }
namespace, identifier := req.User.GetNamespacedID() namespace, identifier := req.User.GetTypedID()
userID := int64(0) userID := int64(0)
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount { if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, _ = identity.IntIdentifier(namespace, identifier) userID, _ = identity.IntIdentifier(namespace, identifier)
} }

@ -83,7 +83,7 @@ func TestImportDashboardService(t *testing.T) {
require.NotNil(t, resp) require.NotNil(t, resp)
require.Equal(t, "UDdpyzz7z", resp.UID) require.Equal(t, "UDdpyzz7z", resp.UID)
userID, err := identity.IntIdentifier(importDashboardArg.User.GetNamespacedID()) userID, err := identity.IntIdentifier(importDashboardArg.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, importDashboardArg) require.NotNil(t, importDashboardArg)
@ -149,7 +149,7 @@ func TestImportDashboardService(t *testing.T) {
require.NotNil(t, resp) require.NotNil(t, resp)
require.Equal(t, "UDdpyzz7z", resp.UID) require.Equal(t, "UDdpyzz7z", resp.UID)
userID, err := identity.IntIdentifier(importDashboardArg.User.GetNamespacedID()) userID, err := identity.IntIdentifier(importDashboardArg.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, importDashboardArg) require.NotNil(t, importDashboardArg)

@ -878,7 +878,7 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F
} }
// only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users
if query.SignedInUser == nil || query.SignedInUser.GetID().Namespace() != identity.NamespaceServiceAccount { if query.SignedInUser == nil || query.SignedInUser.GetID().Type() != identity.TypeServiceAccount {
filters = append(filters, searchstore.K6FolderFilter{}) filters = append(filters, searchstore.K6FolderFilter{})
} }

@ -234,8 +234,8 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
func resolveUserID(user identity.Requester, log log.Logger) (int64, error) { func resolveUserID(user identity.Requester, log log.Logger) (int64, error) {
userID := int64(0) userID := int64(0)
namespaceID, identifier := user.GetNamespacedID() namespaceID, identifier := user.GetTypedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
log.Debug("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", identifier) log.Debug("User does not belong to a user or service account namespace", "namespaceID", namespaceID, "userID", identifier)
} else { } else {
var err error var err error
@ -503,12 +503,12 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto *
var permissions []accesscontrol.SetResourcePermissionCommand var permissions []accesscontrol.SetResourcePermissionCommand
if !provisioned { if !provisioned {
namespaceID, userIDstr := dto.User.GetNamespacedID() namespaceID, userIDstr := dto.User.GetTypedID()
userID, err := identity.IntIdentifier(namespaceID, userIDstr) userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil { if err != nil {
dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err) dr.log.Error("Could not make user admin", "dashboard", dash.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
} else if namespaceID == identity.NamespaceUser && userID > 0 { } else if namespaceID == identity.TypeUser && userID > 0 {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
}) })
@ -541,12 +541,12 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context,
var permissions []accesscontrol.SetResourcePermissionCommand var permissions []accesscontrol.SetResourcePermissionCommand
if !provisioned { if !provisioned {
namespaceID, userIDstr := cmd.SignedInUser.GetNamespacedID() namespaceID, userIDstr := cmd.SignedInUser.GetTypedID()
userID, err := identity.IntIdentifier(namespaceID, userIDstr) userID, err := identity.IntIdentifier(namespaceID, userIDstr)
if err != nil { if err != nil {
dr.log.Error("Could not make user admin", "folder", cmd.Title, "namespaceID", namespaceID, "userID", userID, "error", err) dr.log.Error("Could not make user admin", "folder", cmd.Title, "namespaceID", namespaceID, "userID", userID, "error", err)
} else if namespaceID == identity.NamespaceUser && userID > 0 { } else if namespaceID == identity.TypeUser && userID > 0 {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{
UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(),
}) })

@ -116,7 +116,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sqlStore) err := callSaveWithError(t, cmd, sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "", sc.dashboardGuardianMock.DashUID) assert.Equal(t, "", sc.dashboardGuardianMock.DashUID)
@ -139,7 +139,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.otherSavedFolder.ID, sc.dashboardGuardianMock.DashID) assert.Equal(t, sc.otherSavedFolder.ID, sc.dashboardGuardianMock.DashID)
@ -162,7 +162,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -186,7 +186,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -210,7 +210,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -234,7 +234,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -258,7 +258,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -282,7 +282,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -306,7 +306,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -330,7 +330,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore) err := callSaveWithError(t, cmd, sc.sqlStore)
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetNamespacedID()) userID, err := identity.IntIdentifier(sc.dashboardGuardianMock.User.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID) assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)

@ -135,10 +135,10 @@ func (d *DashboardSnapshotStore) SearchDashboardSnapshots(ctx context.Context, q
sess.Where("name LIKE ?", query.Name) sess.Where("name LIKE ?", query.Name)
} }
namespace, id := query.SignedInUser.GetNamespacedID() namespace, id := query.SignedInUser.GetTypedID()
var userID int64 var userID int64
if namespace == identity.NamespaceServiceAccount || namespace == identity.NamespaceUser { if namespace == identity.TypeServiceAccount || namespace == identity.TypeUser {
var err error var err error
userID, err = identity.IntIdentifier(namespace, id) userID, err = identity.IntIdentifier(namespace, id)
if err != nil { if err != nil {
@ -150,7 +150,7 @@ func (d *DashboardSnapshotStore) SearchDashboardSnapshots(ctx context.Context, q
switch { switch {
case query.SignedInUser.GetOrgRole() == org.RoleAdmin: case query.SignedInUser.GetOrgRole() == org.RoleAdmin:
sess.Where("org_id = ?", query.SignedInUser.GetOrgID()) sess.Where("org_id = ?", query.SignedInUser.GetOrgID())
case namespace != identity.NamespaceAnonymous: case namespace != identity.TypeAnonymous:
sess.Where("org_id = ? AND user_id = ?", query.OrgID, userID) sess.Where("org_id = ? AND user_id = ?", query.OrgID, userID)
default: default:
queryResult = snapshots queryResult = snapshots

@ -58,7 +58,7 @@ func CreateDashboardSnapshot(c *contextmodel.ReqContext, cfg dashboardsnapshot.S
cmd.DashboardCreateCommand.Name = "Unnamed snapshot" cmd.DashboardCreateCommand.Name = "Unnamed snapshot"
} }
userID, err := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, err := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if err != nil { if err != nil {
c.JsonApiErr(http.StatusInternalServerError, c.JsonApiErr(http.StatusInternalServerError,
"Failed to create external snapshot", err) "Failed to create external snapshot", err)

@ -580,8 +580,8 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
userID := int64(0) userID := int64(0)
var err error var err error
namespaceID, userIDstr := user.GetNamespacedID() namespaceID, userIDstr := user.GetTypedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
s.log.Debug("User does not belong to a user or service account namespace, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr) s.log.Debug("User does not belong to a user or service account namespace, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr)
} else { } else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr) userID, err = identity.IntIdentifier(namespaceID, userIDstr)
@ -668,7 +668,7 @@ func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (
} }
if cmd.NewTitle != nil { if cmd.NewTitle != nil {
namespace, id := cmd.SignedInUser.GetNamespacedID() namespace, id := cmd.SignedInUser.GetTypedID()
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc() metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc()
if err := s.bus.Publish(ctx, &events.FolderTitleUpdated{ if err := s.bus.Publish(ctx, &events.FolderTitleUpdated{
@ -725,8 +725,8 @@ func (s *Service) legacyUpdate(ctx context.Context, cmd *folder.UpdateFolderComm
} }
var userID int64 var userID int64
namespace, id := cmd.SignedInUser.GetNamespacedID() namespace, id := cmd.SignedInUser.GetTypedID()
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount { if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespace, id) userID, err = identity.IntIdentifier(namespace, id)
if err != nil { if err != nil {
s.log.ErrorContext(ctx, "failed to parse user ID", "namespace", namespace, "userID", id, "error", err) s.log.ErrorContext(ctx, "failed to parse user ID", "namespace", namespace, "userID", id, "error", err)
@ -1142,8 +1142,8 @@ func (s *Service) buildSaveDashboardCommand(ctx context.Context, dto *dashboards
} }
userID := int64(0) userID := int64(0)
namespaceID, userIDstr := dto.User.GetNamespacedID() namespaceID, userIDstr := dto.User.GetTypedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount { if namespaceID != identity.TypeUser && namespaceID != identity.TypeServiceAccount {
s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr) s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "namespaceID", namespaceID, "userID", userIDstr)
} else { } else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr) userID, err = identity.IntIdentifier(namespaceID, userIDstr)

@ -323,7 +323,7 @@ func (ss *sqlStore) GetChildren(ctx context.Context, q folder.GetChildrenQuery)
} }
// only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users
if q.SignedInUser == nil || q.SignedInUser.GetID().Namespace() != identity.NamespaceServiceAccount { if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != identity.TypeServiceAccount {
sql.WriteString(" AND uid != ?") sql.WriteString(" AND uid != ?")
args = append(args, accesscontrol.K6FolderUID) args = append(args, accesscontrol.K6FolderUID)
} }
@ -484,7 +484,7 @@ func (ss *sqlStore) GetFolders(ctx context.Context, q getFoldersQuery) ([]*folde
} }
// only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users
if q.SignedInUser == nil || q.SignedInUser.GetID().Namespace() != identity.NamespaceServiceAccount { if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != identity.TypeServiceAccount {
s.WriteString(" AND f0.uid != ? AND (f0.parent_uid != ? OR f0.parent_uid IS NULL)") s.WriteString(" AND f0.uid != ? AND (f0.parent_uid != ? OR f0.parent_uid IS NULL)")
args = append(args, accesscontrol.K6FolderUID, accesscontrol.K6FolderUID) args = append(args, accesscontrol.K6FolderUID, accesscontrol.K6FolderUID)
} }

@ -309,7 +309,7 @@ func (a *accessControlFolderGuardian) CanCreate(folderID int64, isFolder bool) (
func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) { func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator) ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
namespaceID, userID := a.user.GetNamespacedID() namespaceID, userID := a.user.GetTypedID()
if err != nil { if err != nil {
id := 0 id := 0
if a.dashboard != nil { if a.dashboard != nil {
@ -331,7 +331,7 @@ func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evalua
func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) { func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator) ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
namespaceID, userID := a.user.GetNamespacedID() namespaceID, userID := a.user.GetTypedID()
if err != nil { if err != nil {
uid := "" uid := ""
orgID := 0 orgID := 0

@ -139,8 +139,8 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn
} }
userID := int64(0) userID := int64(0)
namespaceID, identifier := signedInUser.GetNamespacedID() namespaceID, identifier := signedInUser.GetTypedID()
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount { if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespaceID, identifier) userID, err = identity.IntIdentifier(namespaceID, identifier)
if err != nil { if err != nil {
l.log.Warn("Error while parsing userID", "namespaceID", namespaceID, "userID", identifier) l.log.Warn("Error while parsing userID", "namespaceID", namespaceID, "userID", identifier)
@ -593,8 +593,8 @@ func (l *LibraryElementService) patchLibraryElement(c context.Context, signedInU
} }
var userID int64 var userID int64
namespaceID, identifier := signedInUser.GetNamespacedID() namespaceID, identifier := signedInUser.GetTypedID()
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount { if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
var errID error var errID error
userID, errID = identity.IntIdentifier(namespaceID, identifier) userID, errID = identity.IntIdentifier(namespaceID, identifier)
if errID != nil { if errID != nil {
@ -800,9 +800,9 @@ func (l *LibraryElementService) connectElementsToDashboardID(c context.Context,
return err return err
} }
namespaceID, identifier := signedInUser.GetNamespacedID() namespaceID, identifier := signedInUser.GetTypedID()
userID := int64(0) userID := int64(0)
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount { if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespaceID, identifier) userID, err = identity.IntIdentifier(namespaceID, identifier)
if err != nil { if err != nil {
l.log.Warn("Failed to parse user ID from namespace identifier", "namespace", namespaceID, "identifier", identifier, "error", err) l.log.Warn("Failed to parse user ID from namespace identifier", "namespace", namespaceID, "identifier", identifier, "error", err)

@ -38,7 +38,7 @@ type userDisplayDTO struct {
// Static function to parse a requester into a userDisplayDTO // Static function to parse a requester into a userDisplayDTO
func newUserDisplayDTOFromRequester(requester identity.Requester) *userDisplayDTO { func newUserDisplayDTOFromRequester(requester identity.Requester) *userDisplayDTO {
uid := "" uid := ""
if requester.GetUID().IsNamespace(identity.NamespaceUser, identity.NamespaceServiceAccount) { if requester.GetUID().IsType(identity.TypeUser, identity.TypeServiceAccount) {
uid = requester.GetUID().ID() uid = requester.GetUID().ID()
} }

@ -284,7 +284,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r
g.websocketHandler = func(ctx *contextmodel.ReqContext) { g.websocketHandler = func(ctx *contextmodel.ReqContext) {
user := ctx.SignedInUser user := ctx.SignedInUser
_, identifier := user.GetNamespacedID() _, identifier := user.GetTypedID()
// Centrifuge expects Credentials in context with a current user ID. // Centrifuge expects Credentials in context with a current user ID.
cred := &centrifuge.Credentials{ cred := &centrifuge.Credentials{
@ -955,7 +955,7 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R
return response.Error(http.StatusBadRequest, "invalid channel ID", nil) return response.Error(http.StatusBadRequest, "invalid channel ID", nil)
} }
namespaceID, userID := ctx.SignedInUser.GetNamespacedID() namespaceID, userID := ctx.SignedInUser.GetTypedID()
logger.Debug("Publish API cmd", "namespaceID", namespaceID, "userID", userID, "channel", cmd.Channel) logger.Debug("Publish API cmd", "namespaceID", namespaceID, "userID", userID, "channel", cmd.Channel)
user := ctx.SignedInUser user := ctx.SignedInUser
channel := cmd.Channel channel := cmd.Channel

@ -73,7 +73,7 @@ func TestStreamManager_SubmitStream_Send(t *testing.T) {
} }
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID()) userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(2), userID) require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID()) require.Equal(t, int64(1), user.GetOrgID())
@ -258,7 +258,7 @@ func TestStreamManager_SubmitStream_ErrorRestartsRunStream(t *testing.T) {
} }
mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID()) userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(2), userID) require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID()) require.Equal(t, int64(1), user.GetOrgID())
@ -343,7 +343,7 @@ func TestStreamManager_HandleDatasourceUpdate(t *testing.T) {
} }
mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { mockContextGetter.EXPECT().GetPluginContext(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID()) userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(2), userID) require.Equal(t, int64(2), userID)
@ -412,7 +412,7 @@ func TestStreamManager_HandleDatasourceDelete(t *testing.T) {
} }
mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) { mockContextGetter.EXPECT().GetPluginContext(context.Background(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, user identity.Requester, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
userID, err := identity.IntIdentifier(user.GetNamespacedID()) userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(2), userID) require.Equal(t, int64(2), userID)
require.Equal(t, int64(1), user.GetOrgID()) require.Equal(t, int64(1), user.GetOrgID())

@ -297,7 +297,7 @@ func (s *ServiceImpl) getProfileNode(c *contextmodel.ReqContext) *navtree.NavLin
func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]*navtree.NavLink, error) { func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]*navtree.NavLink, error) {
starredItemsChildNavs := []*navtree.NavLink{} starredItemsChildNavs := []*navtree.NavLink{}
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, _ := identity.UserIdentifier(c.SignedInUser.GetTypedID())
query := star.GetUserStarsQuery{ query := star.GetUserStarsQuery{
UserID: userID, UserID: userID,
} }

@ -76,7 +76,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU
return toNamespaceErrorResponse(err) return toNamespaceErrorResponse(err)
} }
userNamespace, id := c.SignedInUser.GetNamespacedID() userNamespace, id := c.SignedInUser.GetTypedID()
var loggerCtx = []any{ var loggerCtx = []any{
"userId", "userId",
id, id,
@ -283,7 +283,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res
for groupKey, rules := range configs { for groupKey, rules := range configs {
folder, ok := namespaceMap[groupKey.NamespaceUID] folder, ok := namespaceMap[groupKey.NamespaceUID]
if !ok { if !ok {
userNamespace, id := c.SignedInUser.GetNamespacedID() userNamespace, id := c.SignedInUser.GetTypedID()
srv.log.Error("Namespace not visible to the user", "user", id, "userNamespace", userNamespace, "namespace", groupKey.NamespaceUID) srv.log.Error("Namespace not visible to the user", "user", id, "userNamespace", userNamespace, "namespace", groupKey.NamespaceUID)
continue continue
} }
@ -359,7 +359,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
var finalChanges *store.GroupDelta var finalChanges *store.GroupDelta
var dbConfig *ngmodels.AlertConfiguration var dbConfig *ngmodels.AlertConfiguration
err := srv.xactManager.InTransaction(c.Req.Context(), func(tranCtx context.Context) error { err := srv.xactManager.InTransaction(c.Req.Context(), func(tranCtx context.Context) error {
userNamespace, id := c.SignedInUser.GetNamespacedID() userNamespace, id := c.SignedInUser.GetTypedID()
logger := srv.log.New("namespace_uid", groupKey.NamespaceUID, "group", logger := srv.log.New("namespace_uid", groupKey.NamespaceUID, "group",
groupKey.RuleGroup, "org_id", groupKey.OrgID, "user_id", id, "userNamespace", userNamespace) groupKey.RuleGroup, "org_id", groupKey.OrgID, "user_id", id, "userNamespace", userNamespace)
groupChanges, err := store.CalculateChanges(tranCtx, srv.store, groupKey, rules) groupChanges, err := store.CalculateChanges(tranCtx, srv.store, groupKey, rules)
@ -454,7 +454,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
} }
if len(finalChanges.New) > 0 { if len(finalChanges.New) > 0 {
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID()) userID, _ := identity.UserIdentifier(c.SignedInUser.GetTypedID())
limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, ngmodels.QuotaTargetSrv, &quota.ScopeParameters{ limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, ngmodels.QuotaTargetSrv, &quota.ScopeParameters{
OrgID: c.SignedInUser.GetOrgID(), OrgID: c.SignedInUser.GetOrgID(),
UserID: userID, UserID: userID,

@ -660,7 +660,7 @@ func (service *AlertRuleService) DeleteAlertRule(ctx context.Context, user ident
func (service *AlertRuleService) checkLimitsTransactionCtx(ctx context.Context, user identity.Requester) error { func (service *AlertRuleService) checkLimitsTransactionCtx(ctx context.Context, user identity.Requester) error {
// default to 0 if there is no user // default to 0 if there is no user
userID := int64(0) userID := int64(0)
u, err := identity.UserIdentifier(user.GetNamespacedID()) u, err := identity.UserIdentifier(user.GetTypedID())
if err != nil { if err != nil {
return fmt.Errorf("failed to check alert rule quota: %w", err) return fmt.Errorf("failed to check alert rule quota: %w", err)
} }

@ -94,8 +94,8 @@ func (o *Service) HasOAuthEntry(ctx context.Context, usr identity.Requester) (*l
return nil, false, nil return nil, false, nil
} }
namespace, id := usr.GetNamespacedID() namespace, id := usr.GetTypedID()
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
// Not a user, therefore no token. // Not a user, therefore no token.
return nil, false, nil return nil, false, nil
} }
@ -136,8 +136,8 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) e
return nil return nil
} }
namespace, id := usr.GetNamespacedID() namespace, id := usr.GetTypedID()
if namespace != identity.NamespaceUser { if namespace != identity.TypeUser {
// Not a user, therefore no token. // Not a user, therefore no token.
logger.Warn("Can only refresh OAuth tokens for users", "namespace", namespace, "userId", id) logger.Warn("Can only refresh OAuth tokens for users", "namespace", namespace, "userId", id)
return nil return nil

@ -174,13 +174,13 @@ func TestService_TryTokenRefresh(t *testing.T) {
{ {
desc: "should skip sync when identity is not a user", desc: "should skip sync when identity is not a user",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("service-account:1")}
}, },
}, },
{ {
desc: "should skip token refresh and return nil if namespace and id cannot be converted to user ID", desc: "should skip token refresh and return nil if namespace and id cannot be converted to user ID",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:invalidIdentifierFormat")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:invalidIdentifierFormat")}
}, },
}, },
{ {
@ -203,28 +203,28 @@ func TestService_TryTokenRefresh(t *testing.T) {
env.identity = &authn.Identity{ env.identity = &authn.Identity{
AuthenticatedBy: login.GenericOAuthModule, AuthenticatedBy: login.GenericOAuthModule,
ID: authn.MustParseNamespaceID("user:1234"), ID: identity.MustParseTypedID("user:1234"),
} }
}, },
}, },
{ {
desc: "should skip token refresh if the expiration check has already been cached", desc: "should skip token refresh if the expiration check has already been cached",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.cache.Set("oauth-refresh-token-1234", true, 1*time.Minute) env.cache.Set("oauth-refresh-token-1234", true, 1*time.Minute)
}, },
}, },
{ {
desc: "should skip token refresh if there's an unexpected error while looking up the user oauth entry, additionally, no error should be returned", desc: "should skip token refresh if there's an unexpected error while looking up the user oauth entry, additionally, no error should be returned",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedError = errors.New("some error") env.authInfoService.ExpectedError = errors.New("some error")
}, },
}, },
{ {
desc: "should skip token refresh if the user doesn't have an oauth entry", desc: "should skip token refresh if the user doesn't have an oauth entry",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{ env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.SAMLAuthModule, AuthModule: login.SAMLAuthModule,
} }
@ -233,7 +233,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{ {
desc: "should do token refresh if access token or id token have not expired yet", desc: "should do token refresh if access token or id token have not expired yet",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{ env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.GenericOAuthModule, AuthModule: login.GenericOAuthModule,
} }
@ -242,7 +242,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{ {
desc: "should skip token refresh when no oauth provider was found", desc: "should skip token refresh when no oauth provider was found",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{ env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.GenericOAuthModule, AuthModule: login.GenericOAuthModule,
OAuthIdToken: EXPIRED_JWT, OAuthIdToken: EXPIRED_JWT,
@ -252,7 +252,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{ {
desc: "should skip token refresh when oauth provider token handling is disabled (UseRefreshToken is false)", desc: "should skip token refresh when oauth provider token handling is disabled (UseRefreshToken is false)",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{ env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.GenericOAuthModule, AuthModule: login.GenericOAuthModule,
OAuthIdToken: EXPIRED_JWT, OAuthIdToken: EXPIRED_JWT,
@ -265,7 +265,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{ {
desc: "should skip token refresh when there is no refresh token", desc: "should skip token refresh when there is no refresh token",
setup: func(env *environment) { setup: func(env *environment) {
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.authInfoService.ExpectedUserAuth = &login.UserAuth{ env.authInfoService.ExpectedUserAuth = &login.UserAuth{
AuthModule: login.GenericOAuthModule, AuthModule: login.GenericOAuthModule,
OAuthIdToken: EXPIRED_JWT, OAuthIdToken: EXPIRED_JWT,
@ -285,7 +285,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
Expiry: time.Now().Add(-time.Hour), Expiry: time.Now().Add(-time.Hour),
TokenType: "Bearer", TokenType: "Bearer",
} }
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{
UseRefreshToken: true, UseRefreshToken: true,
} }
@ -310,7 +310,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
Expiry: time.Now().Add(time.Hour), Expiry: time.Now().Add(time.Hour),
TokenType: "Bearer", TokenType: "Bearer",
} }
env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")}
env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{
UseRefreshToken: true, UseRefreshToken: true,
} }

@ -35,8 +35,8 @@ func (m *UserHeaderMiddleware) applyUserHeader(ctx context.Context, h backend.Fo
} }
h.DeleteHTTPHeader(proxyutil.UserHeaderName) h.DeleteHTTPHeader(proxyutil.UserHeaderName)
namespace, _ := reqCtx.SignedInUser.GetNamespacedID() namespace, _ := reqCtx.SignedInUser.GetTypedID()
if namespace != identity.NamespaceAnonymous { if namespace != identity.TypeAnonymous {
h.SetHTTPHeader(proxyutil.UserHeaderName, reqCtx.SignedInUser.GetLogin()) h.SetHTTPHeader(proxyutil.UserHeaderName, reqCtx.SignedInUser.GetLogin())
} }
} }

@ -7,10 +7,10 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/middleware/requestmeta" "github.com/grafana/grafana/pkg/middleware/requestmeta"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
@ -99,7 +99,8 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *contextmodel.ReqContext)
} }
if api.cfg.RBAC.PermissionsOnCreation("service-account") { if api.cfg.RBAC.PermissionsOnCreation("service-account") {
if c.SignedInUser.GetID().IsNamespace(authn.NamespaceUser) { t, _ := c.SignedInUser.GetTypedID()
if t == identity.TypeUser {
userID, err := c.SignedInUser.GetID().ParseInt() userID, err := c.SignedInUser.GetID().ParseInt()
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to parse user id", err) return response.Error(http.StatusInternalServerError, "Failed to parse user id", err)

@ -166,8 +166,8 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix) folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix)
userID := int64(0) userID := int64(0)
namespace, identifier := f.user.GetNamespacedID() namespace, identifier := f.user.GetTypedID()
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount { if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, _ = identity.IntIdentifier(namespace, identifier) userID, _ = identity.IntIdentifier(namespace, identifier)
} }

@ -34,8 +34,8 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix) folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix)
userID := int64(0) userID := int64(0)
namespace, identifier := f.user.GetNamespacedID() namespace, identifier := f.user.GetTypedID()
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount { if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, _ = identity.IntIdentifier(namespace, identifier) userID, _ = identity.IntIdentifier(namespace, identifier)
} }

@ -47,8 +47,8 @@ func (api *API) getDashboardHelper(ctx context.Context, orgID int64, id int64, u
} }
func (api *API) GetStars(c *contextmodel.ReqContext) response.Response { func (api *API) GetStars(c *contextmodel.ReqContext) response.Response {
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount { if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil) return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
} }
@ -101,8 +101,8 @@ func (api *API) GetStars(c *contextmodel.ReqContext) response.Response {
// 403: forbiddenError // 403: forbiddenError
// 500: internalServerError // 500: internalServerError
func (api *API) StarDashboard(c *contextmodel.ReqContext) response.Response { func (api *API) StarDashboard(c *contextmodel.ReqContext) response.Response {
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount { if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil) return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
} }
@ -146,8 +146,8 @@ func (api *API) StarDashboardByUID(c *contextmodel.ReqContext) response.Response
return response.Error(http.StatusBadRequest, "Invalid dashboard UID", nil) return response.Error(http.StatusBadRequest, "Invalid dashboard UID", nil)
} }
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount { if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil) return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
} }
@ -193,8 +193,8 @@ func (api *API) UnstarDashboard(c *contextmodel.ReqContext) response.Response {
return response.Error(http.StatusBadRequest, "Invalid dashboard ID", nil) return response.Error(http.StatusBadRequest, "Invalid dashboard ID", nil)
} }
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount { if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil) return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
} }
@ -233,8 +233,8 @@ func (api *API) UnstarDashboardByUID(c *contextmodel.ReqContext) response.Respon
return response.Error(http.StatusBadRequest, "Invalid dashboard UID", nil) return response.Error(http.StatusBadRequest, "Invalid dashboard UID", nil)
} }
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount { if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil) return response.Error(http.StatusBadRequest, "Only users and service accounts can star dashboards", nil)
} }

@ -49,9 +49,9 @@ func (tapi *TeamAPI) createTeam(c *contextmodel.ReqContext) response.Response {
// if the request is authenticated using API tokens // if the request is authenticated using API tokens
// the SignedInUser is an empty struct therefore // the SignedInUser is an empty struct therefore
// an additional check whether it is an actual user is required // an additional check whether it is an actual user is required
namespace, identifier := c.SignedInUser.GetNamespacedID() namespace, identifier := c.SignedInUser.GetTypedID()
switch namespace { switch namespace {
case identity.NamespaceUser: case identity.TypeUser:
userID, err := strconv.ParseInt(identifier, 10, 64) userID, err := strconv.ParseInt(identifier, 10, 64)
if err != nil { if err != nil {
c.Logger.Error("Could not add creator to team because user id is not a number", "error", err) c.Logger.Error("Could not add creator to team because user id is not a number", "error", err)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save