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

This commit is contained in:
Ryan McKinley
2024-07-25 02:52:14 -07:00
committed by GitHub
parent 8cdf5ee824
commit 9db3bc926e
109 changed files with 649 additions and 632 deletions

View File

@@ -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)
}

View File

@@ -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{}}},
)
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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}
}

View File

@@ -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)
}

View File

@@ -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(),

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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),

View File

@@ -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},

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")
)

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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",

View File

@@ -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,
},

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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())
}

View File

@@ -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")
}

View File

@@ -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 {

View File

@@ -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{

View File

@@ -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)

View File

@@ -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{},
}

View File

@@ -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",
},

View File

@@ -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)
}

View File

@@ -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"},

View File

@@ -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,

View File

@@ -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)},

View File

@@ -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,
},
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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)
})

View File

@@ -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

View File

@@ -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
},

View File

@@ -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
}

View File

@@ -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"))
},

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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},
},

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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},

View File

@@ -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},
},

View File

@@ -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",

View File

@@ -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())
}

View File

@@ -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",

View File

@@ -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,

View File

@@ -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},

View File

@@ -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)
}

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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},

View File

@@ -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},
},

View File

@@ -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,

View File

@@ -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{

View File

@@ -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

View File

@@ -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
)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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())

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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{})
}

View File

@@ -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(),
})

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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()
}

View File

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

View File

@@ -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())

View File

@@ -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,
}

View File

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

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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())
}
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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