mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Identity: Rename "namespace" to "type" in the requester interface (#90567)
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
func ParseNamespaceID(str string) (NamespaceID, error) {
|
||||
var namespaceID NamespaceID
|
||||
return false
|
||||
}
|
||||
|
||||
var AnonymousTypedID = NewTypedID(TypeAnonymous, 0)
|
||||
|
||||
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
|
||||
ns = identity.TypeUser
|
||||
}
|
||||
identity.UID = authn.NewNamespaceIDString(ns, usr.UserUID)
|
||||
id.UID = identity.NewTypedIDString(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
|
||||
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 := ¢rifuge.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, "a.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
Reference in New Issue
Block a user