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. 129
      pkg/apimachinery/identity/namespace.go
  21. 31
      pkg/apimachinery/identity/requester.go
  22. 16
      pkg/apimachinery/identity/static.go
  23. 14
      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. 17
      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. 21
      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/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/login"
"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)
}
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)
}

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"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/fs"
"github.com/grafana/grafana/pkg/infra/tracing"
@ -189,7 +190,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa
return contexthandler.ProvideService(
cfg,
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
}
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID()
namespaceID, userIDstr := c.SignedInUser.GetTypedID()
if namespaceID != identity.NamespaceUser {
if namespaceID != identity.TypeUser {
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)
}
namespaceID, userIDStr := c.SignedInUser.GetNamespacedID()
namespaceID, userIDStr := c.SignedInUser.GetTypedID()
// disconnect all library elements for this dashboard
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
userID := int64(0)
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
namespaceID, userIDstr := c.SignedInUser.GetTypedID()
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)
} else {
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 {
userID := int64(0)
var err error
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
namespaceID, userIDstr := c.SignedInUser.GetTypedID()
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)
} else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr)
@ -1083,8 +1083,8 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
}
userID := int64(0)
namespaceID, userIDstr := c.SignedInUser.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
namespaceID, userIDstr := c.SignedInUser.GetTypedID()
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)
} else {
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)
}
userID, err := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, err := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if err != nil {
return response.Error(http.StatusInternalServerError,
"Failed to add datasource", err)

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

@ -28,7 +28,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
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}
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 {
namespace, _ := c.SignedInUser.GetNamespacedID()
namespace, _ := c.SignedInUser.GetTypedID()
// 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}
}

@ -257,7 +257,7 @@ func (hs *HTTPServer) Logout(c *contextmodel.ReqContext) {
return
}
_, id := c.SignedInUser.GetNamespacedID()
_, id := c.SignedInUser.GetTypedID()
hs.log.Info("Successful Logout", "userID", id)
c.Redirect(redirect.URL)
}
@ -305,7 +305,7 @@ func (hs *HTTPServer) redirectURLWithErrorCookie(c *contextmodel.ReqContext, err
var userID int64
if c.SignedInUser != nil && !c.SignedInUser.IsNil() {
var errID error
userID, errID = identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, errID = identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil {
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/response"
"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/infra/log"
"github.com/grafana/grafana/pkg/login/social"
@ -331,7 +332,7 @@ func TestLoginPostRedirect(t *testing.T) {
HooksService: &hooks.HooksService{},
License: &licensing.OSSLicensingService{},
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(),
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)
}
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser {
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.Status = tempuser.TmpUserInvitePending
namespace, identifier := c.SignedInUser.GetNamespacedID()
namespace, identifier := c.SignedInUser.GetTypedID()
var userID int64
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount {
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
var err error
userID, err = strconv.ParseInt(identifier, 10, 64)
if err != nil {

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

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

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

@ -22,7 +22,7 @@ func (hs *HTTPServer) SetHomeDashboard(c *contextmodel.ReqContext) response.Resp
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 {
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
// 500: internalServerError
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 {
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)
}
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil {
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)
}
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil {
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
}
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil {
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)
}
userID, errID := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, errID := identity.UserIdentifier(c.SignedInUser.GetTypedID())
if errID != nil {
hs.log.Error("Failed to parse user id", "err", errID)
}

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

@ -32,8 +32,8 @@ import (
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) GetUserAuthTokens(c *contextmodel.ReqContext) response.Response {
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser {
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)
}
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser {
return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil)
}

@ -7,7 +7,7 @@ import (
)
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")
ErrIdentifierNotInitialized = errors.New("identifier is not initialized")
)

@ -6,120 +6,131 @@ import (
"strings"
)
type Namespace string
type IdentityType string
const (
NamespaceUser Namespace = "user"
NamespaceAPIKey Namespace = "api-key"
NamespaceServiceAccount Namespace = "service-account"
NamespaceAnonymous Namespace = "anonymous"
NamespaceRenderService Namespace = "render"
NamespaceAccessPolicy Namespace = "access-policy"
NamespaceProvisioning Namespace = "provisioning"
NamespaceEmpty Namespace = ""
TypeUser IdentityType = "user"
TypeAPIKey IdentityType = "api-key"
TypeServiceAccount IdentityType = "service-account"
TypeAnonymous IdentityType = "anonymous"
TypeRenderService IdentityType = "render"
TypeAccessPolicy IdentityType = "access-policy"
TypeProvisioning IdentityType = "provisioning"
TypeEmpty IdentityType = ""
)
func (n Namespace) String() string {
func (n IdentityType) String() string {
return string(n)
}
func ParseNamespace(str string) (Namespace, error) {
func ParseType(str string) (IdentityType, error) {
switch str {
case string(NamespaceUser):
return NamespaceUser, nil
case string(NamespaceAPIKey):
return NamespaceAPIKey, nil
case string(NamespaceServiceAccount):
return NamespaceServiceAccount, nil
case string(NamespaceAnonymous):
return NamespaceAnonymous, nil
case string(NamespaceRenderService):
return NamespaceRenderService, nil
case string(NamespaceAccessPolicy):
return NamespaceAccessPolicy, nil
case string(TypeUser):
return TypeUser, nil
case string(TypeAPIKey):
return TypeAPIKey, nil
case string(TypeServiceAccount):
return TypeServiceAccount, nil
case string(TypeAnonymous):
return TypeAnonymous, nil
case string(TypeRenderService):
return TypeRenderService, nil
case string(TypeAccessPolicy):
return TypeAccessPolicy, nil
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) {
var namespaceID NamespaceID
func ParseTypedID(str string) (TypedID, error) {
var typeID TypedID
parts := strings.Split(str, ":")
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 {
return namespaceID, err
return typeID, err
}
namespaceID.id = parts[1]
namespaceID.namespace = namespace
typeID.id = parts[1]
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.
func MustParseNamespaceID(str string) NamespaceID {
namespaceID, err := ParseNamespaceID(str)
func MustParseTypedID(str string) TypedID {
typeID, err := ParseTypedID(str)
if err != nil {
panic(err)
}
return namespaceID
return typeID
}
func NewNamespaceID(namespace Namespace, id int64) NamespaceID {
return NamespaceID{
id: strconv.FormatInt(id, 10),
namespace: namespace,
func NewTypedID(t IdentityType, id int64) TypedID {
return TypedID{
id: strconv.FormatInt(id, 10),
t: t,
}
}
// NewNamespaceIDString creates a new NamespaceID with a string id
func NewNamespaceIDString(namespace Namespace, id string) NamespaceID {
return NamespaceID{
id: id,
namespace: namespace,
// NewTypedIDString creates a new TypedID with a string id
func NewTypedIDString(t IdentityType, id string) TypedID {
return TypedID{
id: id,
t: t,
}
}
// FIXME: use this instead of encoded string through the codebase
type NamespaceID struct {
id string
namespace Namespace
type TypedID struct {
id string
t IdentityType
}
func (ni NamespaceID) ID() string {
func (ni TypedID) ID() string {
return ni.id
}
// UserID will try to parse and int64 identifier if namespace is either user or service-account.
// For all other namespaces '0' will be returned.
func (ni NamespaceID) UserID() (int64, error) {
if ni.IsNamespace(NamespaceUser, NamespaceServiceAccount) {
func (ni TypedID) UserID() (int64, error) {
if ni.IsType(TypeUser, TypeServiceAccount) {
return ni.ParseInt()
}
return 0, nil
}
// 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)
}
func (ni NamespaceID) Namespace() Namespace {
return ni.namespace
func (ni TypedID) Type() IdentityType {
return ni.t
}
func (ni NamespaceID) IsNamespace(expected ...Namespace) bool {
return IsNamespace(ni.namespace, expected...)
func (ni TypedID) IsType(expected ...IdentityType) bool {
return IsIdentityType(ni.t, expected...)
}
func (ni NamespaceID) String() string {
return fmt.Sprintf("%s:%s", ni.namespace, ni.id)
func (ni TypedID) String() string {
return fmt.Sprintf("%s:%s", ni.t, ni.id)
}

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

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

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

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

@ -5,6 +5,7 @@ import (
"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/authn"
"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) {
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) {

@ -333,9 +333,9 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
func getUserID(v sql.NullString) 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.

@ -2,7 +2,6 @@ package accesscontrol
import (
"context"
"errors"
"fmt"
"strings"
@ -84,8 +83,8 @@ type SearchOptions struct {
Action string
ActionSets []string
Scope string
NamespacedID string // ID of the identity (ex: user:3, service-account:4)
wildcards Wildcards // private field computed based on the Scope
TypedID identity.TypedID // ID of the identity (ex: user:3, service-account:4)
wildcards Wildcards // private field computed based on the Scope
RolePrefixes []string
}
@ -105,21 +104,17 @@ func (s *SearchOptions) Wildcards() []string {
}
func (s *SearchOptions) ComputeUserID() (int64, error) {
if s.NamespacedID == "" {
return 0, errors.New("namespacedID must be set")
}
id, err := identity.ParseNamespaceID(s.NamespacedID)
id, err := s.TypedID.ParseInt()
if err != nil {
return 0, err
}
// Validate namespace type is user or service account
if id.Namespace() != identity.NamespaceUser && id.Namespace() != identity.NamespaceServiceAccount {
return 0, fmt.Errorf("invalid namespace: %s", id.Namespace())
if s.TypedID.Type() != identity.TypeUser && s.TypedID.Type() != identity.TypeServiceAccount {
return 0, fmt.Errorf("invalid type: %s", s.TypedID.Type())
}
return id.ParseInt()
return id, nil
}
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) {
namespace, id := ident.GetNamespacedID()
namespace, id := ident.GetTypedID()
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/migrator"
"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/dashboards"
"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)
}
userID, err := identity.UserIdentifier(user.GetNamespacedID())
userID, err := identity.UserIdentifier(user.GetTypedID())
if err != nil {
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")
defer span.End()
namespace, identifier := user.GetNamespacedID()
namespace, identifier := user.GetTypedID()
var userID int64
if namespace == authn.NamespaceUser || namespace == authn.NamespaceServiceAccount {
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
var err error
userID, err = strconv.ParseInt(identifier, 10, 64)
if err != nil {
@ -483,7 +482,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
// Limit roles to available in OSS
options.RolePrefixes = OSSRolesPrefixes
if options.NamespacedID != "" {
if options.TypedID.Type() != "" {
userID, err := options.ComputeUserID()
if err != nil {
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)
defer timer.ObserveDuration()
if searchOptions.NamespacedID == "" {
if searchOptions.TypedID.Type() == "" {
return nil, fmt.Errorf("expected namespaced ID to be specified")
}

@ -8,6 +8,7 @@ import (
"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/localcache"
"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++ {
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.Len(b, usersPermissions, 1)
for _, permissions := range usersPermissions {

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

@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"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/requestmeta"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
@ -71,16 +72,23 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext)
ActionPrefix: c.Query("actionPrefix"),
Action: c.Query("action"),
Scope: c.Query("scope"),
NamespacedID: c.Query("namespacedId"),
}
namespacedId := c.Query("namespacedId")
// Validate inputs
if searchOptions.ActionPrefix != "" && searchOptions.Action != "" {
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")
}
if namespacedId != "" {
var err error
searchOptions.TypedID, err = identity.ParseTypedID(namespacedId)
if err != nil {
return response.Error(http.StatusBadGateway, "invalid namespacedId", err)
}
}
// Compute metadata
permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions)

@ -9,6 +9,7 @@ import (
"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/acimpl"
"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)
expectedIdentity := &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, tc.ctxSignedInUser.UserID),
ID: identity.NewTypedID(identity.TypeUser, tc.ctxSignedInUser.UserID),
OrgID: tc.targetOrgId,
Permissions: map[int64]map[string][]string{},
}

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

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

@ -626,7 +626,7 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
},
options: accesscontrol.SearchOptions{
ActionPrefix: "teams:",
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
TypedID: identity.NewTypedID(identity.TypeUser, 1),
},
wantPerm: map[int64][]accesscontrol.Permission{
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 {
c.Logger.Error("Error from access control system", "error", err, "accessErrorID", id)
} else {
namespace, identifier := c.SignedInUser.GetNamespacedID()
namespace, identifier := c.SignedInUser.GetTypedID()
c.Logger.Info(
"Access denied",
"namespace", namespace,

@ -69,11 +69,11 @@ func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool {
return true
}
func (a *Anonymous) Namespace() string {
return authn.NamespaceAnonymous.String()
func (a *Anonymous) IdentityType() identity.IdentityType {
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})
if err != nil {
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.
if namespaceID != authn.AnonymousNamespaceID {
if namespaceID != identity.AnonymousTypedID {
return nil, errInvalidID
}
@ -109,7 +109,7 @@ func (a *Anonymous) Priority() uint {
func (a *Anonymous) newAnonymousIdentity(o *org.Org) *authn.Identity {
return &authn.Identity{
ID: authn.AnonymousNamespaceID,
ID: identity.AnonymousTypedID,
OrgID: o.ID,
OrgName: o.Name,
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/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/anonymous/anontest"
"github.com/grafana/grafana/pkg/services/authn"
@ -52,17 +53,17 @@ func TestAnonymous_Authenticate(t *testing.T) {
anonDeviceService: anontest.NewFakeService(),
}
identity, err := c.Authenticate(context.Background(), &authn.Request{})
user, err := c.Authenticate(context.Background(), &authn.Request{})
if err != nil {
require.Error(t, err)
require.Nil(t, identity)
require.Nil(t, user)
} else {
require.Nil(t, err)
assert.Equal(t, authn.AnonymousNamespaceID, identity.ID)
assert.Equal(t, tt.org.ID, identity.OrgID)
assert.Equal(t, tt.org.Name, identity.OrgName)
assert.Equal(t, tt.cfg.AnonymousOrgRole, string(identity.GetOrgRole()))
assert.Equal(t, identity.AnonymousTypedID, user.ID)
assert.Equal(t, tt.org.ID, user.OrgID)
assert.Equal(t, tt.org.Name, user.OrgName)
assert.Equal(t, tt.cfg.AnonymousOrgRole, string(user.GetOrgRole()))
}
})
}
@ -73,7 +74,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
desc string
cfg *setting.Cfg
orgID int64
namespaceID authn.NamespaceID
namespaceID identity.TypedID
org *org.Org
orgErr error
expectedErr error
@ -87,7 +88,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org",
},
orgID: 1,
namespaceID: authn.AnonymousNamespaceID,
namespaceID: identity.AnonymousTypedID,
expectedErr: errInvalidOrg,
},
{
@ -97,7 +98,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org",
},
orgID: 1,
namespaceID: authn.MustParseNamespaceID("anonymous:1"),
namespaceID: identity.MustParseTypedID("anonymous:1"),
expectedErr: errInvalidID,
},
{
@ -107,7 +108,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) {
AnonymousOrgName: "some org",
},
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
ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{
Namespace: identity.NamespaceUser,
Type: identity.TypeUser,
Login: "testuser",
UserID: 123,
UserUID: "u123",

@ -66,7 +66,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
cacheKey := prefixCacheKey(id.GetCacheKey())
result, err, _ := s.si.Do(cacheKey, func() (interface{}, error) {
namespace, identifier := id.GetNamespacedID()
namespace, identifier := id.GetTypedID()
cachedToken, err := s.cache.Get(ctx, cacheKey)
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.EmailVerified = id.IsEmailVerified()
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)
if err != nil {
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)
}
// 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/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/idtest"
@ -69,7 +70,7 @@ func TestService_SignIdentity(t *testing.T) {
featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding),
&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.NotEmpty(t, token)
})
@ -81,10 +82,10 @@ func TestService_SignIdentity(t *testing.T) {
&authntest.FakeService{}, nil,
)
token, err := s.SignIdentity(context.Background(), &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
AuthenticatedBy: login.AzureADAuthModule,
Login: "U1",
UID: authn.NewNamespaceIDString(authn.NamespaceUser, "edpu3nnt61se8e")})
UID: identity.NewTypedIDString(identity.TypeUser, "edpu3nnt61se8e")})
require.NoError(t, err)
parsed, err := jwt.ParseSigned(token)

@ -95,7 +95,7 @@ type Service interface {
// RegisterPreLogoutHook registers a hook that is called before a logout request.
RegisterPreLogoutHook(hook PreLogoutHookFn, priority uint)
// 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(c Client)
@ -157,7 +157,7 @@ type RedirectClient interface {
// that should happen during logout and supports client specific redirect URL.
type LogoutClient interface {
Client
Logout(ctx context.Context, user Requester) (*Redirect, bool)
Logout(ctx context.Context, user identity.Requester) (*Redirect, bool)
}
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.
type IdentityResolverClient interface {
Client
Namespace() string
ResolveIdentity(ctx context.Context, orgID int64, namespaceID NamespaceID) (*Identity, error)
IdentityType() identity.IdentityType
ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error)
}
type Request struct {

@ -13,6 +13,7 @@ import (
"go.opentelemetry.io/otel/trace"
"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/network"
"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
if !id.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
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()
@ -271,7 +272,7 @@ func (s *Service) RegisterPreLogoutHook(hook authn.PreLogoutHookFn, priority uin
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")
defer span.End()
@ -280,7 +281,7 @@ func (s *Service) Logout(ctx context.Context, user authn.Requester, sessionToken
redirect.URL = s.cfg.SignoutRedirectUrl
}
if !user.GetID().IsNamespace(authn.NamespaceUser) {
if !user.GetID().IsType(identity.TypeUser) {
return redirect, nil
}
@ -327,7 +328,7 @@ Default:
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")
defer span.End()
@ -352,7 +353,7 @@ func (s *Service) RegisterClient(c authn.Client) {
}
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)
}
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")
defer span.End()
if namespaceID.IsNamespace(authn.NamespaceUser) {
if namespaceID.IsType(identity.TypeUser) {
return &authn.Identity{
OrgID: orgID,
ID: namespaceID,
@ -390,7 +391,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a
}}, nil
}
if namespaceID.IsNamespace(authn.NamespaceServiceAccount) {
if namespaceID.IsType(identity.TypeServiceAccount) {
return &authn.Identity{
ID: namespaceID,
OrgID: orgID,
@ -401,9 +402,9 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a
}}, nil
}
resolver, ok := s.idenityResolverClients[namespaceID.Namespace().String()]
resolver, ok := s.idenityResolverClients[string(namespaceID.Type())]
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)
}

@ -44,9 +44,9 @@ func TestService_Authenticate(t *testing.T) {
{
desc: "should succeed with authentication for configured 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",
@ -54,7 +54,7 @@ func TestService_Authenticate(t *testing.T) {
&authntest.FakeClient{
ExpectedTest: true,
ExpectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
ClientParams: authn.ClientParams{
FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: []string{
@ -70,7 +70,7 @@ func TestService_Authenticate(t *testing.T) {
},
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
ClientParams: authn.ClientParams{
FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: []string{
@ -92,19 +92,19 @@ func TestService_Authenticate(t *testing.T) {
ExpectedName: "2",
ExpectedPriority: 2,
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",
clients: []authn.Client{
&authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false},
&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",
@ -315,10 +315,10 @@ func TestService_Login(t *testing.T) {
client: "fake",
expectedClientOK: true,
expectedClientIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: &auth.UserToken{UserId: 1},
},
},
@ -331,7 +331,7 @@ func TestService_Login(t *testing.T) {
desc: "should not login non user identity",
client: "fake",
expectedClientOK: true,
expectedClientIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1")},
expectedClientIdentity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1")},
expectedErr: authn.ErrUnsupportedIdentity,
},
}
@ -420,31 +420,31 @@ func TestService_Logout(t *testing.T) {
tests := []TestCase{
{
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"},
},
{
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"},
expectedTokenRevoked: true,
},
{
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"},
expectedTokenRevoked: true,
},
{
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"},
client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"},
expectedTokenRevoked: true,
},
{
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"},
client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"},
signoutRedirectURL: "some-url",
@ -452,7 +452,7 @@ func TestService_Logout(t *testing.T) {
},
{
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"},
client: &authntest.MockClient{
NameFunc: func() string { return "auth.client.azuread" },
@ -500,26 +500,26 @@ func TestService_Logout(t *testing.T) {
func TestService_ResolveIdentity(t *testing.T) {
t.Run("should return error for for unknown namespace", func(t *testing.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)
})
t.Run("should return error for for namespace that don't have a resolver", func(t *testing.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)
})
t.Run("should resolve for user", func(t *testing.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.NotNil(t, identity)
})
t.Run("should resolve for service account", func(t *testing.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.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) {
svc := setupTests(t, func(svc *Service) {
svc.RegisterClient(&authntest.MockClient{
NamespaceFunc: func() string { return authn.NamespaceAPIKey.String() },
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
IdentityTypeFunc: func() identity.IdentityType { return identity.TypeAPIKey },
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
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.NotNil(t, identity)
})

@ -8,6 +8,7 @@ import (
"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/tracing"
"github.com/grafana/grafana/pkg/login/social"
@ -36,42 +37,42 @@ type OAuthTokenSync struct {
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")
defer span.End()
// only perform oauth token check if identity is a user
if !identity.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
return nil
}
// Not authenticated through session tokens, so we can skip this hook.
if identity.SessionToken == nil {
if id.SessionToken == nil {
return nil
}
// 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
}
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")
// FIXME: Consider using context.WithoutCancel instead of context.Background after Go 1.21 update
updateCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
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) {
return nil, nil
}
token, _, err := s.service.HasOAuthEntry(ctx, identity)
token, _, err := s.service.HasOAuthEntry(ctx, id)
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
}
@ -81,14 +82,14 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn
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 {
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 {
ctxLogger.Warn("Failed to revoke session token", "id", identity.ID, "tokenId", identity.SessionToken.Id, "error", err)
if err := s.sessionService.RevokeToken(ctx, id.SessionToken, false); err != nil {
ctxLogger.Warn("Failed to revoke session token", "id", id.ID, "tokenId", id.SessionToken.Id, "error", err)
}
return nil, refreshErr

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"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/tracing"
"github.com/grafana/grafana/pkg/login/social"
@ -41,17 +42,17 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
tests := []testCase{
{
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,
},
{
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,
},
{
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,
expectedTryRefreshErr: errors.New("some err"),
expectTryRefreshTokenCalled: true,
@ -62,7 +63,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
},
{
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,
expectTryRefreshTokenCalled: true,
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)",
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,
expectTryRefreshTokenCalled: true,
expectInvalidateOauthTokensCalled: false,
@ -92,7 +93,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
)
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
return tt.expectedHasEntryToken, tt.expectedHasEntryToken != nil, nil
},
@ -100,7 +101,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
invalidateTokensCalled = true
return nil
},
TryTokenRefreshFunc: func(ctx context.Context, usr authn.Requester) error {
TryTokenRefreshFunc: func(ctx context.Context, usr identity.Requester) error {
tryRefreshCalled = true
return tt.expectedTryRefreshErr
},

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

@ -76,7 +76,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
args: args{
ctx: context.Background(),
id: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@ -92,7 +92,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
},
},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@ -139,7 +139,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should set default org",
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) {
userService.On("Update", mock.Anything, mock.MatchedBy(func(cmd *user.UpdateUserCommand) bool {
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",
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",
@ -159,28 +159,28 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should skip setting the default org when input err is not nil",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
inputErr: fmt.Errorf("error"),
},
{
name: "should skip setting the default org when identity is not a user",
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",
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",
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",
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) {
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",
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) {
userService.On("Update", mock.Anything, mock.Anything).Return(fmt.Errorf("error"))
},

@ -5,6 +5,7 @@ import (
"errors"
"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/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@ -146,7 +147,7 @@ func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r
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)
return nil
}

@ -4,6 +4,10 @@ import (
"context"
"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/tracing"
"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/login"
"github.com/grafana/grafana/pkg/services/org"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRBACSync_SyncPermission(t *testing.T) {
@ -24,14 +26,14 @@ func TestRBACSync_SyncPermission(t *testing.T) {
testCases := []testCase{
{
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{
{Action: accesscontrol.ActionUsersRead},
},
},
{
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{
{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",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
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",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
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",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
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",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
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",
module: login.LDAPAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
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",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
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",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
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",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
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",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleNone},
},
@ -234,7 +236,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
func setupTestEnv() *RBACSync {
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{
{Action: accesscontrol.ActionUsersRead},
}, nil

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

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

@ -78,7 +78,7 @@ func (f *FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertok
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.CurrentIndex >= len(f.ExpectedIdentities) {
panic("ExpectedIdentities is empty")

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

@ -7,6 +7,7 @@ import (
"time"
"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/satokengen"
"github.com/grafana/grafana/pkg/infra/log"
@ -134,13 +135,13 @@ func (s *APIKey) Priority() uint {
return 30
}
func (s *APIKey) Namespace() string {
return authn.NamespaceAPIKey.String()
func (s *APIKey) IdentityType() identity.IdentityType {
return identity.TypeAPIKey
}
func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
if !namespaceID.IsNamespace(authn.NamespaceAPIKey) {
return nil, authn.ErrInvalidNamespaceID.Errorf("got unspected namespace: %s", namespaceID.Namespace())
func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if !namespaceID.IsType(identity.TypeAPIKey) {
return nil, identity.ErrInvalidTypedID.Errorf("got unspected namespace: %s", namespaceID.Type())
}
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 {
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
@ -187,18 +188,18 @@ func (s *APIKey) Hook(ctx context.Context, identity *authn.Identity, r *authn.Re
return nil
}
func (s *APIKey) getAPIKeyID(ctx context.Context, identity *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) {
id, err := identity.ID.ParseInt()
func (s *APIKey) getAPIKeyID(ctx context.Context, id *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) {
internalId, err := id.ID.ParseInt()
if err != nil {
s.log.Warn("Failed to parse ID from identifier", "err", err)
return -1, false
}
if identity.ID.IsNamespace(authn.NamespaceAPIKey) {
return id, true
if id.ID.IsType(identity.TypeAPIKey) {
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.
// 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))
@ -255,7 +256,7 @@ func validateApiKey(orgID int64, key *apikey.APIKey) error {
func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceAPIKey, key.ID),
ID: identity.NewTypedID(identity.TypeAPIKey, key.ID),
OrgID: key.OrgID,
OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role},
ClientParams: authn.ClientParams{SyncPermissions: true},
@ -265,7 +266,7 @@ func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity {
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceServiceAccount, *key.ServiceAccountId),
ID: identity.NewTypedID(identity.TypeServiceAccount, *key.ServiceAccountId),
OrgID: key.OrgID,
AuthenticatedBy: login.APIKeyAuthModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},

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

@ -7,6 +7,7 @@ import (
"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/authntest"
)
@ -24,8 +25,8 @@ func TestBasic_Authenticate(t *testing.T) {
{
desc: "should success when password client return identity",
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")}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
desc: "should fail when basic auth header could not be decoded",

@ -10,6 +10,7 @@ import (
authlib "github.com/grafana/authlib/authn"
"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/services/apiserver/endpoints/request"
"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)
}
accessID, err := authn.ParseNamespaceID(accessTokenClaims.Subject)
accessID, err := identity.ParseTypedID(accessTokenClaims.Subject)
if err != nil {
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())
}
userID, err := authn.ParseNamespaceID(idTokenClaims.Subject)
userID, err := identity.ParseTypedID(idTokenClaims.Subject)
if err != nil {
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())
}
@ -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)
}
id, err := authn.ParseNamespaceID(claims.Subject)
id, err := identity.ParseTypedID(claims.Subject)
if err != nil {
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())
}

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

@ -6,6 +6,7 @@ import (
"errors"
"net/mail"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"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{
ID: authn.NewNamespaceID(authn.NamespaceUser, usr.ID),
ID: identity.NewTypedID(identity.TypeUser, usr.ID),
OrgID: r.OrgID,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
AuthenticatedBy: login.PasswordAuthModule,

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

@ -245,10 +245,10 @@ func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redir
}, 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)
namespace, id := user.GetNamespacedID()
namespace, id := user.GetTypedID()
userID, err := identity.UserIdentifier(namespace, id)
if err != nil {
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(),
AuthModule: user.GetAuthenticatedBy(),
}); 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)
}

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

@ -13,6 +13,7 @@ import (
"time"
"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/remotecache"
"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{
ID: authn.NewNamespaceID(authn.NamespaceUser, uid),
ID: identity.NewTypedID(identity.TypeUser, uid),
OrgID: r.OrgID,
// 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
@ -144,18 +145,18 @@ func (c *Proxy) Priority() uint {
return 50
}
func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
if identity.ClientParams.CacheAuthProxyKey == "" {
func (c *Proxy) Hook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
if id.ClientParams.CacheAuthProxyKey == "" {
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
return nil
}
id, err := identity.ID.ParseInt()
internalId, err := id.ID.ParseInt()
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
}
@ -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)
bytes := []byte(strconv.FormatInt(id, 10))
c.log.FromContext(ctx).Debug("Cache proxy user", "userId", internalId)
bytes := []byte(strconv.FormatInt(internalId, 10))
duration := time.Duration(c.cfg.AuthProxy.SyncTTL) * time.Minute
if err := c.cache.Set(ctx, identity.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", id)
if err := c.cache.Set(ctx, id.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", internalId)
}
// 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 {

@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"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/authntest"
"github.com/grafana/grafana/pkg/setting"
@ -203,7 +204,7 @@ func TestProxy_Hook(t *testing.T) {
}
cache := &fakeCache{data: make(map[string][]byte)}
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 := func(role string) func(t *testing.T) {

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

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

@ -6,6 +6,7 @@ import (
"net/url"
"time"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth"
"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{
ID: authn.NewNamespaceID(authn.NamespaceUser, token.UserId),
ID: identity.NewTypedID(identity.TypeUser, token.UserId),
SessionToken: token,
ClientParams: authn.ClientParams{
FetchSyncedUser: true,

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

@ -15,16 +15,14 @@ import (
const GlobalOrgID = int64(0)
type Requester = identity.Requester
var _ Requester = (*Identity)(nil)
var _ identity.Requester = (*Identity)(nil)
type Identity struct {
// 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.
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 NamespaceID
UID identity.TypedID
// OrgID is the active organization for the entity.
OrgID int64
// OrgName is the name of the active organization.
@ -74,15 +72,15 @@ type Identity struct {
IDToken string
}
func (i *Identity) GetID() NamespaceID {
func (i *Identity) GetID() identity.TypedID {
return i.ID
}
func (i *Identity) GetNamespacedID() (namespace identity.Namespace, identifier string) {
return i.ID.Namespace(), i.ID.ID()
func (i *Identity) GetTypedID() (namespace identity.IdentityType, identifier string) {
return i.ID.Type(), i.ID.ID()
}
func (i *Identity) GetUID() NamespaceID {
func (i *Identity) GetUID() identity.TypedID {
return i.UID
}
@ -95,7 +93,7 @@ func (i *Identity) GetAuthenticatedBy() string {
}
func (i *Identity) GetCacheKey() string {
namespace, id := i.GetNamespacedID()
namespace, id := i.GetTypedID()
if !i.HasUniqueId() {
// Hack use the org role as id for identities that do not have a unique id
// e.g. anonymous and render key.
@ -191,8 +189,10 @@ func (i *Identity) HasRole(role org.RoleType) bool {
}
func (i *Identity) HasUniqueId() bool {
namespace, _ := i.GetNamespacedID()
return namespace == NamespaceUser || namespace == NamespaceServiceAccount || namespace == NamespaceAPIKey
namespace, _ := i.GetTypedID()
return namespace == identity.TypeUser ||
namespace == identity.TypeServiceAccount ||
namespace == identity.TypeAPIKey
}
func (i *Identity) IsAuthenticatedBy(providers ...string) bool {
@ -220,7 +220,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
AuthID: i.AuthID,
AuthenticatedBy: i.AuthenticatedBy,
IsGrafanaAdmin: i.GetIsGrafanaAdmin(),
IsAnonymous: i.ID.IsNamespace(NamespaceAnonymous),
IsAnonymous: i.ID.IsType(identity.TypeAnonymous),
IsDisabled: i.IsDisabled,
HelpFlags1: i.HelpFlags1,
LastSeenAt: i.LastSeenAt,
@ -230,14 +230,14 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
NamespacedID: i.ID,
}
if i.ID.IsNamespace(NamespaceAPIKey) {
if i.ID.IsType(identity.TypeAPIKey) {
id, _ := i.ID.ParseInt()
u.ApiKeyID = id
} else {
id, _ := i.ID.UserID()
u.UserID = id
u.UserUID = i.UID.ID()
u.IsServiceAccount = i.ID.IsNamespace(NamespaceServiceAccount)
u.IsServiceAccount = i.ID.IsType(identity.TypeServiceAccount)
}
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"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"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.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 {
ctxLogger.Error("failed to search user permissions", "error", err)
return nil, tracing.Errorf(span, "failed to search user permissions: %w", err)

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

@ -43,7 +43,7 @@ func TestContextHandler(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(
setting.NewCfg(),
tracing.InitializeTracerForTest(),
@ -68,7 +68,7 @@ func TestContextHandler(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(
setting.NewCfg(),
tracing.InitializeTracerForTest(),
@ -148,7 +148,7 @@ func TestContextHandler(t *testing.T) {
handler := contexthandler.ProvideService(
cfg,
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())

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

@ -83,7 +83,7 @@ func TestImportDashboardService(t *testing.T) {
require.NotNil(t, resp)
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.NotNil(t, importDashboardArg)
@ -149,7 +149,7 @@ func TestImportDashboardService(t *testing.T) {
require.NotNil(t, resp)
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.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
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{})
}

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

@ -116,7 +116,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sqlStore)
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)
assert.Equal(t, "", sc.dashboardGuardianMock.DashUID)
@ -139,7 +139,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
assert.Equal(t, sc.otherSavedFolder.ID, sc.dashboardGuardianMock.DashID)
@ -162,7 +162,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -186,7 +186,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -210,7 +210,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -234,7 +234,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -258,7 +258,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -282,7 +282,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
assert.Equal(t, sc.savedDashInFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -306,7 +306,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
assert.Equal(t, sc.savedDashInGeneralFolder.UID, sc.dashboardGuardianMock.DashUID)
@ -330,7 +330,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
err := callSaveWithError(t, cmd, sc.sqlStore)
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)
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)
}
namespace, id := query.SignedInUser.GetNamespacedID()
namespace, id := query.SignedInUser.GetTypedID()
var userID int64
if namespace == identity.NamespaceServiceAccount || namespace == identity.NamespaceUser {
if namespace == identity.TypeServiceAccount || namespace == identity.TypeUser {
var err error
userID, err = identity.IntIdentifier(namespace, id)
if err != nil {
@ -150,7 +150,7 @@ func (d *DashboardSnapshotStore) SearchDashboardSnapshots(ctx context.Context, q
switch {
case query.SignedInUser.GetOrgRole() == org.RoleAdmin:
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)
default:
queryResult = snapshots

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

@ -580,8 +580,8 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (
userID := int64(0)
var err error
namespaceID, userIDstr := user.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
namespaceID, userIDstr := user.GetTypedID()
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)
} else {
userID, err = identity.IntIdentifier(namespaceID, userIDstr)
@ -668,7 +668,7 @@ func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (
}
if cmd.NewTitle != nil {
namespace, id := cmd.SignedInUser.GetNamespacedID()
namespace, id := cmd.SignedInUser.GetTypedID()
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc()
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
namespace, id := cmd.SignedInUser.GetNamespacedID()
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount {
namespace, id := cmd.SignedInUser.GetTypedID()
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespace, id)
if err != nil {
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)
namespaceID, userIDstr := dto.User.GetNamespacedID()
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
namespaceID, userIDstr := dto.User.GetTypedID()
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)
} else {
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
if q.SignedInUser == nil || q.SignedInUser.GetID().Namespace() != identity.NamespaceServiceAccount {
if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != identity.TypeServiceAccount {
sql.WriteString(" AND uid != ?")
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
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)")
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) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
namespaceID, userID := a.user.GetNamespacedID()
namespaceID, userID := a.user.GetTypedID()
if err != nil {
id := 0
if a.dashboard != nil {
@ -331,7 +331,7 @@ func (a *accessControlDashboardGuardian) evaluate(evaluator accesscontrol.Evalua
func (a *accessControlFolderGuardian) evaluate(evaluator accesscontrol.Evaluator) (bool, error) {
ok, err := a.ac.Evaluate(a.ctx, a.user, evaluator)
namespaceID, userID := a.user.GetNamespacedID()
namespaceID, userID := a.user.GetTypedID()
if err != nil {
uid := ""
orgID := 0

@ -139,8 +139,8 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn
}
userID := int64(0)
namespaceID, identifier := signedInUser.GetNamespacedID()
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount {
namespaceID, identifier := signedInUser.GetTypedID()
if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespaceID, identifier)
if err != nil {
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
namespaceID, identifier := signedInUser.GetNamespacedID()
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount {
namespaceID, identifier := signedInUser.GetTypedID()
if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
var errID error
userID, errID = identity.IntIdentifier(namespaceID, identifier)
if errID != nil {
@ -800,9 +800,9 @@ func (l *LibraryElementService) connectElementsToDashboardID(c context.Context,
return err
}
namespaceID, identifier := signedInUser.GetNamespacedID()
namespaceID, identifier := signedInUser.GetTypedID()
userID := int64(0)
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount {
if namespaceID == identity.TypeUser || namespaceID == identity.TypeServiceAccount {
userID, err = identity.IntIdentifier(namespaceID, identifier)
if err != nil {
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
func newUserDisplayDTOFromRequester(requester identity.Requester) *userDisplayDTO {
uid := ""
if requester.GetUID().IsNamespace(identity.NamespaceUser, identity.NamespaceServiceAccount) {
if requester.GetUID().IsType(identity.TypeUser, identity.TypeServiceAccount) {
uid = requester.GetUID().ID()
}

@ -284,7 +284,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r
g.websocketHandler = func(ctx *contextmodel.ReqContext) {
user := ctx.SignedInUser
_, identifier := user.GetNamespacedID()
_, identifier := user.GetTypedID()
// Centrifuge expects Credentials in context with a current user ID.
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)
}
namespaceID, userID := ctx.SignedInUser.GetNamespacedID()
namespaceID, userID := ctx.SignedInUser.GetTypedID()
logger.Debug("Publish API cmd", "namespaceID", namespaceID, "userID", userID, "channel", cmd.Channel)
user := ctx.SignedInUser
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) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
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) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
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) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err)
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) {
userID, err := identity.IntIdentifier(user.GetNamespacedID())
userID, err := identity.IntIdentifier(user.GetTypedID())
require.NoError(t, err)
require.Equal(t, int64(2), userID)
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) {
starredItemsChildNavs := []*navtree.NavLink{}
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, _ := identity.UserIdentifier(c.SignedInUser.GetTypedID())
query := star.GetUserStarsQuery{
UserID: userID,
}

@ -76,7 +76,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU
return toNamespaceErrorResponse(err)
}
userNamespace, id := c.SignedInUser.GetNamespacedID()
userNamespace, id := c.SignedInUser.GetTypedID()
var loggerCtx = []any{
"userId",
id,
@ -283,7 +283,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res
for groupKey, rules := range configs {
folder, ok := namespaceMap[groupKey.NamespaceUID]
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)
continue
}
@ -359,7 +359,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
var finalChanges *store.GroupDelta
var dbConfig *ngmodels.AlertConfiguration
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",
groupKey.RuleGroup, "org_id", groupKey.OrgID, "user_id", id, "userNamespace", userNamespace)
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 {
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
userID, _ := identity.UserIdentifier(c.SignedInUser.GetTypedID())
limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, ngmodels.QuotaTargetSrv, &quota.ScopeParameters{
OrgID: c.SignedInUser.GetOrgID(),
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 {
// default to 0 if there is no user
userID := int64(0)
u, err := identity.UserIdentifier(user.GetNamespacedID())
u, err := identity.UserIdentifier(user.GetTypedID())
if err != nil {
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
}
namespace, id := usr.GetNamespacedID()
if namespace != identity.NamespaceUser {
namespace, id := usr.GetTypedID()
if namespace != identity.TypeUser {
// Not a user, therefore no token.
return nil, false, nil
}
@ -136,8 +136,8 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) e
return nil
}
namespace, id := usr.GetNamespacedID()
if namespace != identity.NamespaceUser {
namespace, id := usr.GetTypedID()
if namespace != identity.TypeUser {
// Not a user, therefore no token.
logger.Warn("Can only refresh OAuth tokens for users", "namespace", namespace, "userId", id)
return nil

@ -174,13 +174,13 @@ func TestService_TryTokenRefresh(t *testing.T) {
{
desc: "should skip sync when identity is not a user",
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",
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{
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",
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)
},
},
{
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) {
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")
},
},
{
desc: "should skip token refresh if the user doesn't have an oauth entry",
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{
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",
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{
AuthModule: login.GenericOAuthModule,
}
@ -242,7 +242,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{
desc: "should skip token refresh when no oauth provider was found",
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{
AuthModule: login.GenericOAuthModule,
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)",
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{
AuthModule: login.GenericOAuthModule,
OAuthIdToken: EXPIRED_JWT,
@ -265,7 +265,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
{
desc: "should skip token refresh when there is no refresh token",
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{
AuthModule: login.GenericOAuthModule,
OAuthIdToken: EXPIRED_JWT,
@ -285,7 +285,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
Expiry: time.Now().Add(-time.Hour),
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{
UseRefreshToken: true,
}
@ -310,7 +310,7 @@ func TestService_TryTokenRefresh(t *testing.T) {
Expiry: time.Now().Add(time.Hour),
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{
UseRefreshToken: true,
}

@ -35,8 +35,8 @@ func (m *UserHeaderMiddleware) applyUserHeader(ctx context.Context, h backend.Fo
}
h.DeleteHTTPHeader(proxyutil.UserHeaderName)
namespace, _ := reqCtx.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceAnonymous {
namespace, _ := reqCtx.SignedInUser.GetTypedID()
if namespace != identity.TypeAnonymous {
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/response"
"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/middleware/requestmeta"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"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 c.SignedInUser.GetID().IsNamespace(authn.NamespaceUser) {
t, _ := c.SignedInUser.GetTypedID()
if t == identity.TypeUser {
userID, err := c.SignedInUser.GetID().ParseInt()
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to parse user id", err)

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

@ -34,8 +34,8 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
folderWildcards := accesscontrol.WildcardsFromPrefix(dashboards.ScopeFoldersPrefix)
userID := int64(0)
namespace, identifier := f.user.GetNamespacedID()
if namespace == identity.NamespaceUser || namespace == identity.NamespaceServiceAccount {
namespace, identifier := f.user.GetTypedID()
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
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 {
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
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
// 500: internalServerError
func (api *API) StarDashboard(c *contextmodel.ReqContext) response.Response {
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
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)
}
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
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)
}
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
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)
}
namespace, identifier := c.SignedInUser.GetNamespacedID()
if namespace != identity.NamespaceUser && namespace != identity.NamespaceServiceAccount {
namespace, identifier := c.SignedInUser.GetTypedID()
if namespace != identity.TypeUser && namespace != identity.TypeServiceAccount {
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
// the SignedInUser is an empty struct therefore
// an additional check whether it is an actual user is required
namespace, identifier := c.SignedInUser.GetNamespacedID()
namespace, identifier := c.SignedInUser.GetTypedID()
switch namespace {
case identity.NamespaceUser:
case identity.TypeUser:
userID, err := strconv.ParseInt(identifier, 10, 64)
if err != nil {
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