Identity: Rename "namespace" to "type" in the requester interface (#90567)
This commit is contained in:
@@ -2,7 +2,6 @@ package accesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -84,8 +83,8 @@ type SearchOptions struct {
|
||||
Action string
|
||||
ActionSets []string
|
||||
Scope string
|
||||
NamespacedID string // ID of the identity (ex: user:3, service-account:4)
|
||||
wildcards Wildcards // private field computed based on the Scope
|
||||
TypedID identity.TypedID // ID of the identity (ex: user:3, service-account:4)
|
||||
wildcards Wildcards // private field computed based on the Scope
|
||||
RolePrefixes []string
|
||||
}
|
||||
|
||||
@@ -105,21 +104,17 @@ func (s *SearchOptions) Wildcards() []string {
|
||||
}
|
||||
|
||||
func (s *SearchOptions) ComputeUserID() (int64, error) {
|
||||
if s.NamespacedID == "" {
|
||||
return 0, errors.New("namespacedID must be set")
|
||||
}
|
||||
|
||||
id, err := identity.ParseNamespaceID(s.NamespacedID)
|
||||
id, err := s.TypedID.ParseInt()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Validate namespace type is user or service account
|
||||
if id.Namespace() != identity.NamespaceUser && id.Namespace() != identity.NamespaceServiceAccount {
|
||||
return 0, fmt.Errorf("invalid namespace: %s", id.Namespace())
|
||||
if s.TypedID.Type() != identity.TypeUser && s.TypedID.Type() != identity.TypeServiceAccount {
|
||||
return 0, fmt.Errorf("invalid type: %s", s.TypedID.Type())
|
||||
}
|
||||
|
||||
return id.ParseInt()
|
||||
return id, nil
|
||||
}
|
||||
|
||||
type SyncUserRolesCommand struct {
|
||||
|
||||
@@ -191,6 +191,6 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a
|
||||
}
|
||||
|
||||
func (a *AccessControl) debug(ctx context.Context, ident identity.Requester, msg string, eval accesscontrol.Evaluator) {
|
||||
namespace, id := ident.GetNamespacedID()
|
||||
namespace, id := ident.GetTypedID()
|
||||
a.log.FromContext(ctx).Debug(msg, "namespace", namespace, "id", id, "orgID", ident.GetOrgID(), "permissions", eval.GoString())
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@@ -141,7 +140,7 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
|
||||
permissions = append(permissions, SharedWithMeFolderPermission)
|
||||
}
|
||||
|
||||
userID, err := identity.UserIdentifier(user.GetNamespacedID())
|
||||
userID, err := identity.UserIdentifier(user.GetTypedID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -209,10 +208,10 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re
|
||||
ctx, span := s.tracer.Start(ctx, "authz.getUserDirectPermissions")
|
||||
defer span.End()
|
||||
|
||||
namespace, identifier := user.GetNamespacedID()
|
||||
namespace, identifier := user.GetTypedID()
|
||||
|
||||
var userID int64
|
||||
if namespace == authn.NamespaceUser || namespace == authn.NamespaceServiceAccount {
|
||||
if namespace == identity.TypeUser || namespace == identity.TypeServiceAccount {
|
||||
var err error
|
||||
userID, err = strconv.ParseInt(identifier, 10, 64)
|
||||
if err != nil {
|
||||
@@ -483,7 +482,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque
|
||||
|
||||
// Limit roles to available in OSS
|
||||
options.RolePrefixes = OSSRolesPrefixes
|
||||
if options.NamespacedID != "" {
|
||||
if options.TypedID.Type() != "" {
|
||||
userID, err := options.ComputeUserID()
|
||||
if err != nil {
|
||||
s.log.Error("Failed to resolve user ID", "error", err)
|
||||
@@ -598,7 +597,7 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search
|
||||
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
if searchOptions.NamespacedID == "" {
|
||||
if searchOptions.TypedID.Type() == "" {
|
||||
return nil, fmt.Errorf("expected namespaced ID to be specified")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -261,7 +262,7 @@ func benchSearchUserWithAction(b *testing.B, usersCount, resourceCount int) {
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu,
|
||||
accesscontrol.SearchOptions{Action: "resources:action2", NamespacedID: "user:14"})
|
||||
accesscontrol.SearchOptions{Action: "resources:action2", TypedID: identity.NewTypedID(identity.TypeUser, 14)})
|
||||
require.NoError(b, err)
|
||||
require.Len(b, usersPermissions, 1)
|
||||
for _, permissions := range usersPermissions {
|
||||
|
||||
@@ -2,7 +2,6 @@ package acimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -544,7 +543,7 @@ func TestService_SearchUsersPermissions(t *testing.T) {
|
||||
// only the user's basic roles and the user's stored permissions
|
||||
name: "check namespacedId filter works correctly",
|
||||
siuPermissions: listAllPerms,
|
||||
searchOption: accesscontrol.SearchOptions{NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceServiceAccount)},
|
||||
searchOption: accesscontrol.SearchOptions{TypedID: identity.NewTypedID(identity.TypeServiceAccount, 1)},
|
||||
ramRoles: map[string]*accesscontrol.RoleDTO{
|
||||
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
|
||||
@@ -616,7 +615,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
|
||||
name: "ram only",
|
||||
searchOption: accesscontrol.SearchOptions{
|
||||
ActionPrefix: "teams",
|
||||
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
|
||||
TypedID: identity.NewTypedID(identity.TypeUser, 2),
|
||||
},
|
||||
ramRoles: map[string]*accesscontrol.RoleDTO{
|
||||
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
|
||||
@@ -641,7 +640,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
|
||||
name: "stored only",
|
||||
searchOption: accesscontrol.SearchOptions{
|
||||
ActionPrefix: "teams",
|
||||
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
|
||||
TypedID: identity.NewTypedID(identity.TypeUser, 2),
|
||||
},
|
||||
storedPerms: map[int64][]accesscontrol.Permission{
|
||||
1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}},
|
||||
@@ -661,7 +660,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
|
||||
name: "ram and stored",
|
||||
searchOption: accesscontrol.SearchOptions{
|
||||
ActionPrefix: "teams",
|
||||
NamespacedID: fmt.Sprintf("%s:2", identity.NamespaceUser),
|
||||
TypedID: identity.NewTypedID(identity.TypeUser, 2),
|
||||
},
|
||||
ramRoles: map[string]*accesscontrol.RoleDTO{
|
||||
string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{
|
||||
@@ -691,7 +690,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
|
||||
name: "check action prefix filter works correctly",
|
||||
searchOption: accesscontrol.SearchOptions{
|
||||
ActionPrefix: "teams",
|
||||
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
|
||||
TypedID: identity.NewTypedID(identity.TypeUser, 1),
|
||||
},
|
||||
ramRoles: map[string]*accesscontrol.RoleDTO{
|
||||
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
|
||||
@@ -712,8 +711,8 @@ func TestService_SearchUserPermissions(t *testing.T) {
|
||||
{
|
||||
name: "check action filter works correctly",
|
||||
searchOption: accesscontrol.SearchOptions{
|
||||
Action: accesscontrol.ActionTeamsRead,
|
||||
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
|
||||
Action: accesscontrol.ActionTeamsRead,
|
||||
TypedID: identity.NewTypedID(identity.TypeUser, 1),
|
||||
},
|
||||
ramRoles: map[string]*accesscontrol.RoleDTO{
|
||||
string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{
|
||||
@@ -734,8 +733,8 @@ func TestService_SearchUserPermissions(t *testing.T) {
|
||||
{
|
||||
name: "check action sets are correctly included if an action is specified",
|
||||
searchOption: accesscontrol.SearchOptions{
|
||||
Action: "dashboards:read",
|
||||
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
|
||||
Action: "dashboards:read",
|
||||
TypedID: identity.NewTypedID(identity.TypeUser, 1),
|
||||
},
|
||||
withActionSets: true,
|
||||
actionSets: map[string][]string{
|
||||
@@ -768,7 +767,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
|
||||
name: "check action sets are correctly included if an action prefix is specified",
|
||||
searchOption: accesscontrol.SearchOptions{
|
||||
ActionPrefix: "dashboards",
|
||||
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
|
||||
TypedID: identity.NewTypedID(identity.TypeUser, 1),
|
||||
},
|
||||
withActionSets: true,
|
||||
actionSets: map[string][]string{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
@@ -71,16 +72,23 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext)
|
||||
ActionPrefix: c.Query("actionPrefix"),
|
||||
Action: c.Query("action"),
|
||||
Scope: c.Query("scope"),
|
||||
NamespacedID: c.Query("namespacedId"),
|
||||
}
|
||||
namespacedId := c.Query("namespacedId")
|
||||
|
||||
// Validate inputs
|
||||
if searchOptions.ActionPrefix != "" && searchOptions.Action != "" {
|
||||
return response.JSON(http.StatusBadRequest, "'action' and 'actionPrefix' are mutually exclusive")
|
||||
}
|
||||
if searchOptions.NamespacedID == "" && searchOptions.ActionPrefix == "" && searchOptions.Action == "" {
|
||||
if namespacedId == "" && searchOptions.ActionPrefix == "" && searchOptions.Action == "" {
|
||||
return response.JSON(http.StatusBadRequest, "at least one search option must be provided")
|
||||
}
|
||||
if namespacedId != "" {
|
||||
var err error
|
||||
searchOptions.TypedID, err = identity.ParseTypedID(namespacedId)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadGateway, "invalid namespacedId", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Compute metadata
|
||||
permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
@@ -186,7 +187,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil)
|
||||
|
||||
expectedIdentity := &authn.Identity{
|
||||
ID: authn.NewNamespaceID(authn.NamespaceUser, tc.ctxSignedInUser.UserID),
|
||||
ID: identity.NewTypedID(identity.TypeUser, tc.ctxSignedInUser.UserID),
|
||||
OrgID: tc.targetOrgId,
|
||||
Permissions: map[int64]map[string][]string{},
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
||||
signedInUser: &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
UserID: 1,
|
||||
NamespacedID: identity.MustParseNamespaceID("user:1"),
|
||||
NamespacedID: identity.MustParseTypedID("user:1"),
|
||||
},
|
||||
expected: "rbac-permissions-1-user-1",
|
||||
},
|
||||
@@ -31,7 +31,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
||||
OrgID: 1,
|
||||
ApiKeyID: 1,
|
||||
IsServiceAccount: false,
|
||||
NamespacedID: identity.MustParseNamespaceID("user:1"),
|
||||
NamespacedID: identity.MustParseTypedID("user:1"),
|
||||
},
|
||||
expected: "rbac-permissions-1-api-key-1",
|
||||
},
|
||||
@@ -41,7 +41,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
||||
OrgID: 1,
|
||||
UserID: 1,
|
||||
IsServiceAccount: true,
|
||||
NamespacedID: identity.MustParseNamespaceID("service-account:1"),
|
||||
NamespacedID: identity.MustParseTypedID("service-account:1"),
|
||||
},
|
||||
expected: "rbac-permissions-1-service-account-1",
|
||||
},
|
||||
@@ -51,7 +51,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
||||
OrgID: 1,
|
||||
UserID: -1,
|
||||
IsServiceAccount: true,
|
||||
NamespacedID: identity.MustParseNamespaceID("service-account:-1"),
|
||||
NamespacedID: identity.MustParseTypedID("service-account:-1"),
|
||||
},
|
||||
expected: "rbac-permissions-1-service-account--1",
|
||||
},
|
||||
@@ -60,7 +60,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
||||
signedInUser: &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
OrgRole: org.RoleNone,
|
||||
NamespacedID: identity.MustParseNamespaceID("user:1"),
|
||||
NamespacedID: identity.MustParseTypedID("user:1"),
|
||||
},
|
||||
expected: "rbac-permissions-1-user-None",
|
||||
},
|
||||
|
||||
@@ -163,8 +163,8 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
|
||||
}
|
||||
dbPerms := make([]UserRBACPermission, 0)
|
||||
|
||||
var userID int64
|
||||
if options.NamespacedID != "" {
|
||||
userID := int64(-1)
|
||||
if options.TypedID.Type() != "" {
|
||||
var err error
|
||||
userID, err = options.ComputeUserID()
|
||||
if err != nil {
|
||||
@@ -181,26 +181,26 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
|
||||
params := []any{}
|
||||
|
||||
direct := userAssignsSQL
|
||||
if options.NamespacedID != "" {
|
||||
if userID >= 0 {
|
||||
direct += " WHERE ur.user_id = ?"
|
||||
params = append(params, userID)
|
||||
}
|
||||
|
||||
team := teamAssignsSQL
|
||||
if options.NamespacedID != "" {
|
||||
if userID >= 0 {
|
||||
team += " WHERE tm.user_id = ?"
|
||||
params = append(params, userID)
|
||||
}
|
||||
|
||||
basic := basicRoleAssignsSQL
|
||||
if options.NamespacedID != "" {
|
||||
if userID >= 0 {
|
||||
basic += " WHERE ou.user_id = ?"
|
||||
params = append(params, userID)
|
||||
}
|
||||
|
||||
grafanaAdmin := fmt.Sprintf(grafanaAdminAssignsSQL, s.sql.ReadReplica().Quote("user"))
|
||||
params = append(params, accesscontrol.RoleGrafanaAdmin)
|
||||
if options.NamespacedID != "" {
|
||||
if userID >= 0 {
|
||||
grafanaAdmin += " AND sa.user_id = ?"
|
||||
params = append(params, userID)
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
|
||||
},
|
||||
options: accesscontrol.SearchOptions{
|
||||
ActionPrefix: "teams:",
|
||||
NamespacedID: fmt.Sprintf("%s:1", identity.NamespaceUser),
|
||||
TypedID: identity.NewTypedID(identity.TypeUser, 1),
|
||||
},
|
||||
wantPerm: map[int64][]accesscontrol.Permission{
|
||||
1: {{Action: "teams:read", Scope: "teams:id:1"}, {Action: "teams:read", Scope: "teams:id:10"},
|
||||
|
||||
@@ -80,7 +80,7 @@ func deny(c *contextmodel.ReqContext, evaluator Evaluator, err error) {
|
||||
if err != nil {
|
||||
c.Logger.Error("Error from access control system", "error", err, "accessErrorID", id)
|
||||
} else {
|
||||
namespace, identifier := c.SignedInUser.GetNamespacedID()
|
||||
namespace, identifier := c.SignedInUser.GetTypedID()
|
||||
c.Logger.Info(
|
||||
"Access denied",
|
||||
"namespace", namespace,
|
||||
|
||||
Reference in New Issue
Block a user