Identity: Use typed namespace id (#87121)

* Use typed namespace id
This commit is contained in:
Karl Persson 2024-05-02 14:50:56 +02:00 committed by GitHub
parent 4fd2cb6014
commit d8fbbdefea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 35 additions and 43 deletions

View File

@ -30,6 +30,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
@ -514,7 +515,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
&contextmodel.ReqContext{
SignedInUser: &user.SignedInUser{
Login: "test_user",
NamespacedID: "user:1",
NamespacedID: authn.MustParseNamespaceID("user:1"),
},
},
&setting.Cfg{SendUserHeader: true},

View File

@ -15,6 +15,7 @@ import (
"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"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
@ -77,7 +78,7 @@ func TestPluginProxy(t *testing.T) {
&contextmodel.ReqContext{
SignedInUser: &user.SignedInUser{
Login: "test_user",
NamespacedID: "user:1",
NamespacedID: authn.MustParseNamespaceID("user:1"),
},
Context: &web.Context{
Req: httpReq,

View File

@ -756,7 +756,7 @@ func TestPermissionCacheKey(t *testing.T) {
signedInUser: &user.SignedInUser{
OrgID: 1,
UserID: 1,
NamespacedID: "user:1",
NamespacedID: identity.MustParseNamespaceID("user:1"),
},
expected: "rbac-permissions-1-user-1",
},
@ -766,7 +766,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
ApiKeyID: 1,
IsServiceAccount: false,
NamespacedID: "user:1",
NamespacedID: identity.MustParseNamespaceID("user:1"),
},
expected: "rbac-permissions-1-api-key-1",
},
@ -776,7 +776,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
UserID: 1,
IsServiceAccount: true,
NamespacedID: "serviceaccount:1",
NamespacedID: identity.MustParseNamespaceID("service-account:1"),
},
expected: "rbac-permissions-1-service-account-1",
},
@ -786,7 +786,7 @@ func TestPermissionCacheKey(t *testing.T) {
OrgID: 1,
UserID: -1,
IsServiceAccount: true,
NamespacedID: "serviceaccount:-1",
NamespacedID: identity.MustParseNamespaceID("service-account:-1"),
},
expected: "rbac-permissions-1-service-account--1",
},
@ -795,7 +795,7 @@ func TestPermissionCacheKey(t *testing.T) {
signedInUser: &user.SignedInUser{
OrgID: 1,
OrgRole: org.RoleNone,
NamespacedID: "user:1",
NamespacedID: identity.MustParseNamespaceID("user:1"),
},
expected: "rbac-permissions-1-user-None",
},

View File

@ -9,7 +9,7 @@ import (
type Requester interface {
// GetID returns namespaced id for the entity
GetID() string
GetID() NamespaceID
// GetNamespacedID returns the namespace and ID of the active entity.
// The namespace is one of the constants defined in pkg/services/auth/identity.
GetNamespacedID() (namespace string, identifier string)

View File

@ -90,7 +90,7 @@ type Service interface {
Logout(ctx context.Context, user identity.Requester, sessionToken *usertoken.UserToken) (*Redirect, error)
// ResolveIdentity resolves an identity from org and namespace id.
ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*Identity, error)
ResolveIdentity(ctx context.Context, orgID int64, namespaceID NamespaceID) (*Identity, error)
// RegisterClient will register a new authn.Client that can be used for authentication
RegisterClient(c Client)

View File

@ -308,18 +308,13 @@ Default:
return redirect, nil
}
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) {
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
r := &authn.Request{}
r.OrgID = orgID
// hack to not update last seen
r.SetMeta(authn.MetaKeyIsLogin, "true")
id, err := authn.ParseNamespaceID(namespaceID)
if err != nil {
return nil, err
}
identity, err := s.resolveIdenity(ctx, orgID, id)
identity, err := s.resolveIdenity(ctx, orgID, namespaceID)
if err != nil {
return nil, err
}

View File

@ -487,26 +487,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, "some:1")
assert.ErrorIs(t, err, authn.ErrInvalidNamespaceID)
_, err := svc.ResolveIdentity(context.Background(), 1, authn.NewNamespaceIDUnchecked("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, "api-key:1")
_, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("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, "user:1")
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("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, "service-account:1")
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("service-account:1"))
assert.NoError(t, err)
assert.NotNil(t, identity)
})
@ -521,7 +521,7 @@ func TestService_ResolveIdentity(t *testing.T) {
})
})
identity, err := svc.ResolveIdentity(context.Background(), 1, "api-key:1")
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("api-key:1"))
assert.NoError(t, err)
assert.NotNil(t, identity)
})

View File

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

View File

@ -50,7 +50,7 @@ func (*MockService) Logout(_ context.Context, _ identity.Requester, _ *usertoken
panic("unimplemented")
}
func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) {
func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
panic("unimplemented")
}

View File

@ -73,8 +73,8 @@ type Identity struct {
IDToken string
}
func (i *Identity) GetID() string {
return i.ID.String()
func (i *Identity) GetID() NamespaceID {
return i.ID
}
func (i *Identity) GetNamespacedID() (namespace string, identifier string) {
@ -220,7 +220,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
Teams: i.Teams,
Permissions: i.Permissions,
IDToken: i.IDToken,
NamespacedID: i.ID.String(),
NamespacedID: i.ID,
}
if namespace == NamespaceAPIKey {

View File

@ -2,7 +2,6 @@ package user
import (
"fmt"
"strings"
"time"
"github.com/grafana/grafana/pkg/models/roletype"
@ -40,7 +39,7 @@ type SignedInUser struct {
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
IDToken string `json:"-" xorm:"-"`
NamespacedID string
NamespacedID identity.NamespaceID
}
func (u *SignedInUser) ShouldUpdateLastSeenAt() bool {
@ -196,7 +195,7 @@ func (u *SignedInUser) GetOrgRole() roletype.RoleType {
}
// GetID returns namespaced id for the entity
func (u *SignedInUser) GetID() string {
func (u *SignedInUser) GetID() identity.NamespaceID {
switch {
case u.ApiKeyID != 0:
return namespacedID(identity.NamespaceAPIKey, u.ApiKeyID)
@ -205,7 +204,7 @@ func (u *SignedInUser) GetID() string {
case u.UserID > 0:
return namespacedID(identity.NamespaceUser, u.UserID)
case u.IsAnonymous:
return identity.NamespaceAnonymous + ":"
return namespacedID(identity.NamespaceAnonymous, 0)
case u.AuthenticatedBy == "render" && u.UserID == 0:
return namespacedID(identity.NamespaceRenderService, 0)
}
@ -216,13 +215,8 @@ func (u *SignedInUser) GetID() string {
// GetNamespacedID returns the namespace and ID of the active entity
// The namespace is one of the constants defined in pkg/services/auth/identity
func (u *SignedInUser) GetNamespacedID() (string, string) {
parts := strings.Split(u.GetID(), ":")
// Safety: GetID always returns a ':' separated string
if len(parts) != 2 {
return "", ""
}
return parts[0], parts[1]
id := u.GetID()
return id.Namespace(), id.ID()
}
func (u *SignedInUser) GetAuthID() string {
@ -267,6 +261,6 @@ func (u *SignedInUser) GetIDToken() string {
return u.IDToken
}
func namespacedID(namespace string, id int64) string {
return fmt.Sprintf("%s:%d", namespace, id)
func namespacedID(namespace string, id int64) identity.NamespaceID {
return identity.NewNamespaceIDUnchecked(namespace, id)
}

View File

@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/user"
)
@ -174,7 +175,7 @@ func TestApplyUserHeader(t *testing.T) {
require.NoError(t, err)
req.Header.Set("X-Grafana-User", "admin")
ApplyUserHeader(false, req, &user.SignedInUser{Login: "admin", NamespacedID: "user:1"})
ApplyUserHeader(false, req, &user.SignedInUser{Login: "admin", NamespacedID: identity.MustParseNamespaceID("user:1")})
require.NotContains(t, req.Header, "X-Grafana-User")
})
@ -191,7 +192,7 @@ func TestApplyUserHeader(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "/", nil)
require.NoError(t, err)
ApplyUserHeader(true, req, &user.SignedInUser{IsAnonymous: true, NamespacedID: "anonymous:1"})
ApplyUserHeader(true, req, &user.SignedInUser{IsAnonymous: true, NamespacedID: identity.MustParseNamespaceID("anonymous:0")})
require.NotContains(t, req.Header, "X-Grafana-User")
})
@ -199,7 +200,7 @@ func TestApplyUserHeader(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "/", nil)
require.NoError(t, err)
ApplyUserHeader(true, req, &user.SignedInUser{Login: "admin", NamespacedID: "user:1"})
ApplyUserHeader(true, req, &user.SignedInUser{Login: "admin", NamespacedID: identity.MustParseNamespaceID("user:1")})
require.Equal(t, "admin", req.Header.Get("X-Grafana-User"))
})
}