mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 00:25:30 +08:00
Identity: extend k8s user.Info (#90937)
This commit is contained in:
@ -140,7 +140,8 @@ func (hs *HTTPServer) PostAnnotation(c *contextmodel.ReqContext) response.Respon
|
|||||||
return response.Error(http.StatusBadRequest, "Failed to save annotation", err)
|
return response.Error(http.StatusBadRequest, "Failed to save annotation", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, err := c.SignedInUser.GetID().UserID()
|
// nolint:staticcheck
|
||||||
|
userID, err := c.SignedInUser.GetInternalID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, "Failed to save annotation", err)
|
return response.Error(http.StatusInternalServerError, "Failed to save annotation", err)
|
||||||
}
|
}
|
||||||
@ -227,7 +228,8 @@ func (hs *HTTPServer) PostGraphiteAnnotation(c *contextmodel.ReqContext) respons
|
|||||||
return response.Error(http.StatusBadRequest, "Failed to save Graphite annotation", err)
|
return response.Error(http.StatusBadRequest, "Failed to save Graphite annotation", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, err := c.SignedInUser.GetID().UserID()
|
// nolint:staticcheck
|
||||||
|
userID, err := c.SignedInUser.GetInternalID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, "Failed to save Graphite annotation", err)
|
return response.Error(http.StatusInternalServerError, "Failed to save Graphite annotation", err)
|
||||||
}
|
}
|
||||||
@ -284,7 +286,8 @@ func (hs *HTTPServer) UpdateAnnotation(c *contextmodel.ReqContext) response.Resp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, err := c.SignedInUser.GetID().UserID()
|
// nolint:staticcheck
|
||||||
|
userID, err := c.SignedInUser.GetInternalID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, "Failed to update annotation", err)
|
return response.Error(http.StatusInternalServerError, "Failed to update annotation", err)
|
||||||
}
|
}
|
||||||
@ -346,7 +349,8 @@ func (hs *HTTPServer) PatchAnnotation(c *contextmodel.ReqContext) response.Respo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, err := c.SignedInUser.GetID().UserID()
|
// nolint:staticcheck
|
||||||
|
userID, err := c.SignedInUser.GetInternalID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, "Failed to update annotation", err)
|
return response.Error(http.StatusInternalServerError, "Failed to update annotation", err)
|
||||||
}
|
}
|
||||||
|
@ -586,7 +586,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
|||||||
&contextmodel.ReqContext{
|
&contextmodel.ReqContext{
|
||||||
SignedInUser: &user.SignedInUser{
|
SignedInUser: &user.SignedInUser{
|
||||||
Login: "test_user",
|
Login: "test_user",
|
||||||
NamespacedID: identity.MustParseTypedID("user:1"),
|
FallbackType: identity.TypeUser,
|
||||||
|
UserID: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&setting.Cfg{SendUserHeader: true},
|
&setting.Cfg{SendUserHeader: true},
|
||||||
|
@ -79,7 +79,8 @@ func TestPluginProxy(t *testing.T) {
|
|||||||
&contextmodel.ReqContext{
|
&contextmodel.ReqContext{
|
||||||
SignedInUser: &user.SignedInUser{
|
SignedInUser: &user.SignedInUser{
|
||||||
Login: "test_user",
|
Login: "test_user",
|
||||||
NamespacedID: identity.MustParseTypedID("user:1"),
|
FallbackType: identity.TypeUser,
|
||||||
|
UserID: 1,
|
||||||
},
|
},
|
||||||
Context: &web.Context{
|
Context: &web.Context{
|
||||||
Req: httpReq,
|
Req: httpReq,
|
||||||
|
@ -3,17 +3,28 @@ package identity
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Requester interface {
|
type Requester interface {
|
||||||
// GetID returns namespaced id for the entity
|
user.Info
|
||||||
|
|
||||||
|
// GetIdentityType returns the type for the requester
|
||||||
|
GetIdentityType() IdentityType
|
||||||
|
// GetRawIdentifier returns only the identifier part of the UID, excluding the type
|
||||||
|
GetRawIdentifier() string
|
||||||
|
// Deprecated: use GetUID instead
|
||||||
|
GetInternalID() (int64, error)
|
||||||
|
|
||||||
|
// GetID returns namespaced internalID for the entity
|
||||||
|
// Deprecated: use GetUID instead
|
||||||
GetID() TypedID
|
GetID() TypedID
|
||||||
// GetTypedID 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.
|
// The namespace is one of the constants defined in pkg/apimachinery/identity.
|
||||||
// Deprecated: use GetID instead
|
// Deprecated: use GetID instead
|
||||||
GetTypedID() (kind IdentityType, identifier string)
|
GetTypedID() (kind IdentityType, identifier string)
|
||||||
// GetUID returns namespaced uid for the entity
|
|
||||||
GetUID() TypedID
|
|
||||||
// GetDisplayName returns the display name of the active entity.
|
// GetDisplayName returns the display name of the active entity.
|
||||||
// The display name is the name if it is set, otherwise the login or email.
|
// The display name is the name if it is set, otherwise the login or email.
|
||||||
GetDisplayName() string
|
GetDisplayName() string
|
||||||
|
@ -30,6 +30,41 @@ type StaticRequester struct {
|
|||||||
CacheKey string
|
CacheKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRawIdentifier implements Requester.
|
||||||
|
func (u *StaticRequester) GetUID() string {
|
||||||
|
return fmt.Sprintf("%s:%s", u.Type, u.UserUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawIdentifier implements Requester.
|
||||||
|
func (u *StaticRequester) GetRawIdentifier() string {
|
||||||
|
return u.UserUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInternalID implements Requester.
|
||||||
|
func (u *StaticRequester) GetInternalID() (int64, error) {
|
||||||
|
return u.UserID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdentityType implements Requester.
|
||||||
|
func (u *StaticRequester) GetIdentityType() IdentityType {
|
||||||
|
return u.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtra implements Requester.
|
||||||
|
func (u *StaticRequester) GetExtra() map[string][]string {
|
||||||
|
return map[string][]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroups implements Requester.
|
||||||
|
func (u *StaticRequester) GetGroups() []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName implements Requester.
|
||||||
|
func (u *StaticRequester) GetName() string {
|
||||||
|
return u.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
func (u *StaticRequester) HasRole(role RoleType) bool {
|
func (u *StaticRequester) HasRole(role RoleType) bool {
|
||||||
if u.IsGrafanaAdmin {
|
if u.IsGrafanaAdmin {
|
||||||
return true
|
return true
|
||||||
@ -109,11 +144,6 @@ func (u *StaticRequester) GetID() TypedID {
|
|||||||
return NewTypedIDString(u.Type, fmt.Sprintf("%d", u.UserID))
|
return NewTypedIDString(u.Type, fmt.Sprintf("%d", u.UserID))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUID returns namespaced uid for the entity
|
|
||||||
func (u *StaticRequester) GetUID() TypedID {
|
|
||||||
return NewTypedIDString(u.Type, u.UserUID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTypedID 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
|
// The namespace is one of the constants defined in pkg/apimachinery/identity
|
||||||
func (u *StaticRequester) GetTypedID() (IdentityType, string) {
|
func (u *StaticRequester) GetTypedID() (IdentityType, string) {
|
||||||
|
@ -108,7 +108,7 @@ func (a *AccessControl) evaluateZanzana(ctx context.Context, user identity.Reque
|
|||||||
|
|
||||||
return eval.EvaluateCustom(func(action, scope string) (bool, error) {
|
return eval.EvaluateCustom(func(action, scope string) (bool, error) {
|
||||||
kind, _, identifier := accesscontrol.SplitScope(scope)
|
kind, _, identifier := accesscontrol.SplitScope(scope)
|
||||||
key, ok := zanzana.TranslateToTuple(user.GetUID().String(), action, kind, identifier, user.GetOrgID())
|
key, ok := zanzana.TranslateToTuple(user.GetUID(), action, kind, identifier, user.GetOrgID())
|
||||||
if !ok {
|
if !ok {
|
||||||
// unsupported translation
|
// unsupported translation
|
||||||
return false, errAccessNotImplemented
|
return false, errAccessNotImplemented
|
||||||
|
@ -21,7 +21,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
|||||||
signedInUser: &user.SignedInUser{
|
signedInUser: &user.SignedInUser{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
UserID: 1,
|
UserID: 1,
|
||||||
NamespacedID: identity.MustParseTypedID("user:1"),
|
FallbackType: identity.TypeUser,
|
||||||
},
|
},
|
||||||
expected: "rbac-permissions-1-user-1",
|
expected: "rbac-permissions-1-user-1",
|
||||||
},
|
},
|
||||||
@ -31,7 +31,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
|||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
ApiKeyID: 1,
|
ApiKeyID: 1,
|
||||||
IsServiceAccount: false,
|
IsServiceAccount: false,
|
||||||
NamespacedID: identity.MustParseTypedID("user:1"),
|
FallbackType: identity.TypeUser,
|
||||||
},
|
},
|
||||||
expected: "rbac-permissions-1-api-key-1",
|
expected: "rbac-permissions-1-api-key-1",
|
||||||
},
|
},
|
||||||
@ -41,7 +41,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
|||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
UserID: 1,
|
UserID: 1,
|
||||||
IsServiceAccount: true,
|
IsServiceAccount: true,
|
||||||
NamespacedID: identity.MustParseTypedID("service-account:1"),
|
FallbackType: identity.TypeUser,
|
||||||
},
|
},
|
||||||
expected: "rbac-permissions-1-service-account-1",
|
expected: "rbac-permissions-1-service-account-1",
|
||||||
},
|
},
|
||||||
@ -51,7 +51,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
|||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
UserID: -1,
|
UserID: -1,
|
||||||
IsServiceAccount: true,
|
IsServiceAccount: true,
|
||||||
NamespacedID: identity.MustParseTypedID("service-account:-1"),
|
FallbackType: identity.TypeUser, // NOTE, this is still a service account!
|
||||||
},
|
},
|
||||||
expected: "rbac-permissions-1-service-account--1",
|
expected: "rbac-permissions-1-service-account--1",
|
||||||
},
|
},
|
||||||
@ -60,7 +60,7 @@ func TestPermissionCacheKey(t *testing.T) {
|
|||||||
signedInUser: &user.SignedInUser{
|
signedInUser: &user.SignedInUser{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
OrgRole: org.RoleNone,
|
OrgRole: org.RoleNone,
|
||||||
NamespacedID: identity.MustParseTypedID("user:1"),
|
FallbackType: identity.TypeUser,
|
||||||
},
|
},
|
||||||
expected: "rbac-permissions-1-user-None",
|
expected: "rbac-permissions-1-user-None",
|
||||||
},
|
},
|
||||||
|
@ -2,13 +2,10 @@ package authenticator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
k8suser "k8s.io/apiserver/pkg/authentication/user"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ authenticator.RequestFunc = signedInUserAuthenticator
|
var _ authenticator.RequestFunc = signedInUserAuthenticator
|
||||||
@ -21,28 +18,7 @@ func signedInUserAuthenticator(req *http.Request) (*authenticator.Response, bool
|
|||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userInfo := &k8suser.DefaultInfo{
|
|
||||||
Name: signedInUser.GetLogin(),
|
|
||||||
UID: signedInUser.GetUID().ID(),
|
|
||||||
Groups: []string{},
|
|
||||||
// In order to faithfully round-trip through an impersonation flow, Extra keys MUST be lowercase.
|
|
||||||
// see: https://pkg.go.dev/k8s.io/apiserver@v0.27.1/pkg/authentication/user#Info
|
|
||||||
Extra: map[string][]string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range signedInUser.GetTeams() {
|
|
||||||
userInfo.Groups = append(userInfo.Groups, strconv.FormatInt(v, 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
if signedInUser.GetIDToken() != "" {
|
|
||||||
userInfo.Extra["id-token"] = []string{signedInUser.GetIDToken()}
|
|
||||||
}
|
|
||||||
if signedInUser.GetOrgRole().IsValid() {
|
|
||||||
userInfo.Extra["user-instance-role"] = []string{string(signedInUser.GetOrgRole())}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &authenticator.Response{
|
return &authenticator.Response{
|
||||||
User: userInfo,
|
User: signedInUser,
|
||||||
}, true, nil
|
}, true, nil
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,9 @@ func TestSignedInUser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("should set user and group", func(t *testing.T) {
|
t.Run("should set user and group", func(t *testing.T) {
|
||||||
u := &user.SignedInUser{
|
u := &user.SignedInUser{
|
||||||
Login: "admin",
|
Name: "admin",
|
||||||
UserID: 1,
|
UserID: 1,
|
||||||
UserUID: uuid.New().String(),
|
UserUID: "xyz",
|
||||||
Teams: []int64{1, 2},
|
Teams: []int64{1, 2},
|
||||||
}
|
}
|
||||||
ctx := identity.WithRequester(context.Background(), u)
|
ctx := identity.WithRequester(context.Background(), u)
|
||||||
@ -44,15 +44,15 @@ func TestSignedInUser(t *testing.T) {
|
|||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.False(t, mockAuthenticator.called)
|
require.False(t, mockAuthenticator.called)
|
||||||
|
|
||||||
require.Equal(t, u.Login, res.User.GetName())
|
require.Equal(t, u.GetName(), res.User.GetName())
|
||||||
require.Equal(t, u.UserUID, res.User.GetUID())
|
require.Equal(t, u.GetUID(), res.User.GetUID())
|
||||||
require.Equal(t, []string{"1", "2"}, res.User.GetGroups())
|
require.Equal(t, []string{"1", "2"}, res.User.GetGroups())
|
||||||
require.Empty(t, res.User.GetExtra()["id-token"])
|
require.Empty(t, res.User.GetExtra()["id-token"])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should set ID token when available", func(t *testing.T) {
|
t.Run("should set ID token when available", func(t *testing.T) {
|
||||||
u := &user.SignedInUser{
|
u := &user.SignedInUser{
|
||||||
Login: "admin",
|
Name: "admin",
|
||||||
UserID: 1,
|
UserID: 1,
|
||||||
UserUID: uuid.New().String(),
|
UserUID: uuid.New().String(),
|
||||||
Teams: []int64{1, 2},
|
Teams: []int64{1, 2},
|
||||||
@ -69,8 +69,8 @@ func TestSignedInUser(t *testing.T) {
|
|||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
require.False(t, mockAuthenticator.called)
|
require.False(t, mockAuthenticator.called)
|
||||||
require.Equal(t, u.Login, res.User.GetName())
|
require.Equal(t, u.GetName(), res.User.GetName())
|
||||||
require.Equal(t, u.UserUID, res.User.GetUID())
|
require.Equal(t, u.GetUID(), res.User.GetUID())
|
||||||
require.Equal(t, []string{"1", "2"}, res.User.GetGroups())
|
require.Equal(t, []string{"1", "2"}, res.User.GetGroups())
|
||||||
require.Equal(t, "test-id-token", res.User.GetExtra()["id-token"][0])
|
require.Equal(t, "test-id-token", res.User.GetExtra()["id-token"][0])
|
||||||
})
|
})
|
||||||
|
@ -4,12 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ authorizer.Authorizer = &orgIDAuthorizer{}
|
var _ authorizer.Authorizer = &orgIDAuthorizer{}
|
||||||
@ -56,7 +55,8 @@ func (auth orgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has access to the specified org
|
// Check if the user has access to the specified org
|
||||||
userId, err := signedInUser.GetID().UserID()
|
// nolint:staticcheck
|
||||||
|
userId, err := signedInUser.GetInternalID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return authorizer.DecisionDeny, "unable to get userId", err
|
return authorizer.DecisionDeny, "unable to get userId", err
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ authorizer.Authorizer = &orgRoleAuthorizer{}
|
var _ authorizer.Authorizer = &orgRoleAuthorizer{}
|
||||||
|
@ -3,14 +3,13 @@ package authorizer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
orgsvc "github.com/grafana/grafana/pkg/services/org"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
k8suser "k8s.io/apiserver/pkg/authentication/user"
|
k8suser "k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
||||||
"k8s.io/apiserver/pkg/authorization/union"
|
"k8s.io/apiserver/pkg/authorization/union"
|
||||||
|
|
||||||
orgsvc "github.com/grafana/grafana/pkg/services/org"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ authorizer.Authorizer = (*GrafanaAuthorizer)(nil)
|
var _ authorizer.Authorizer = (*GrafanaAuthorizer)(nil)
|
||||||
|
@ -4,12 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ authorizer.Authorizer = &stackIDAuthorizer{}
|
var _ authorizer.Authorizer = &stackIDAuthorizer{}
|
||||||
|
@ -97,7 +97,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
|
|||||||
claims.Rest.EmailVerified = id.IsEmailVerified()
|
claims.Rest.EmailVerified = id.IsEmailVerified()
|
||||||
claims.Rest.AuthenticatedBy = id.GetAuthenticatedBy()
|
claims.Rest.AuthenticatedBy = id.GetAuthenticatedBy()
|
||||||
claims.Rest.Username = id.GetLogin()
|
claims.Rest.Username = id.GetLogin()
|
||||||
claims.Rest.UID = id.GetUID().String()
|
claims.Rest.UID = id.GetUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := s.signer.SignIDToken(ctx, claims)
|
token, err := s.signer.SignIDToken(ctx, claims)
|
||||||
|
@ -72,6 +72,43 @@ type Identity struct {
|
|||||||
IDToken string
|
IDToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRawIdentifier implements Requester.
|
||||||
|
func (i *Identity) GetRawIdentifier() string {
|
||||||
|
return i.UID.ID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInternalID implements Requester.
|
||||||
|
func (i *Identity) GetInternalID() (int64, error) {
|
||||||
|
return i.ID.UserID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdentityType implements Requester.
|
||||||
|
func (i *Identity) GetIdentityType() identity.IdentityType {
|
||||||
|
return i.UID.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtra implements identity.Requester.
|
||||||
|
func (i *Identity) GetExtra() map[string][]string {
|
||||||
|
extra := map[string][]string{}
|
||||||
|
if i.IDToken != "" {
|
||||||
|
extra["id-token"] = []string{i.IDToken}
|
||||||
|
}
|
||||||
|
if i.GetOrgRole().IsValid() {
|
||||||
|
extra["user-instance-role"] = []string{string(i.GetOrgRole())}
|
||||||
|
}
|
||||||
|
return extra
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroups implements identity.Requester.
|
||||||
|
func (i *Identity) GetGroups() []string {
|
||||||
|
return []string{} // teams?
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName implements identity.Requester.
|
||||||
|
func (i *Identity) GetName() string {
|
||||||
|
return i.Name
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Identity) GetID() identity.TypedID {
|
func (i *Identity) GetID() identity.TypedID {
|
||||||
return i.ID
|
return i.ID
|
||||||
}
|
}
|
||||||
@ -80,8 +117,8 @@ func (i *Identity) GetTypedID() (namespace identity.IdentityType, identifier str
|
|||||||
return i.ID.Type(), i.ID.ID()
|
return i.ID.Type(), i.ID.ID()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Identity) GetUID() identity.TypedID {
|
func (i *Identity) GetUID() string {
|
||||||
return i.UID
|
return i.UID.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Identity) GetAuthID() string {
|
func (i *Identity) GetAuthID() string {
|
||||||
@ -227,7 +264,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
|
|||||||
Teams: i.Teams,
|
Teams: i.Teams,
|
||||||
Permissions: i.Permissions,
|
Permissions: i.Permissions,
|
||||||
IDToken: i.IDToken,
|
IDToken: i.IDToken,
|
||||||
NamespacedID: i.ID,
|
FallbackType: i.ID.Type(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.ID.IsType(identity.TypeAPIKey) {
|
if i.ID.IsType(identity.TypeAPIKey) {
|
||||||
|
@ -37,15 +37,11 @@ type userDisplayDTO struct {
|
|||||||
|
|
||||||
// Static function to parse a requester into a userDisplayDTO
|
// Static function to parse a requester into a userDisplayDTO
|
||||||
func newUserDisplayDTOFromRequester(requester identity.Requester) *userDisplayDTO {
|
func newUserDisplayDTOFromRequester(requester identity.Requester) *userDisplayDTO {
|
||||||
uid := ""
|
// nolint:staticcheck
|
||||||
if requester.GetUID().IsType(identity.TypeUser, identity.TypeServiceAccount) {
|
userID, _ := requester.GetInternalID()
|
||||||
uid = requester.GetUID().ID()
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, _ := requester.GetID().UserID()
|
|
||||||
return &userDisplayDTO{
|
return &userDisplayDTO{
|
||||||
ID: userID,
|
ID: userID,
|
||||||
UID: uid,
|
UID: requester.GetRawIdentifier(),
|
||||||
Login: requester.GetLogin(),
|
Login: requester.GetLogin(),
|
||||||
Name: requester.GetDisplayName(),
|
Name: requester.GetDisplayName(),
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func getCurrentUser(ctx context.Context) (string, error) {
|
|||||||
return "", fmt.Errorf("%w: %w", ErrUserNotFoundInContext, err)
|
return "", fmt.Errorf("%w: %w", ErrUserNotFoundInContext, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.GetUID().String(), nil
|
return user.GetUID(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ptrOr returns the first non-nil pointer in the list or a new non-nil pointer.
|
// ptrOr returns the first non-nil pointer in the list or a new non-nil pointer.
|
||||||
|
@ -121,7 +121,7 @@ func TestIntegrationEntityServer(t *testing.T) {
|
|||||||
testCtx := createTestContext(t)
|
testCtx := createTestContext(t)
|
||||||
ctx := identity.WithRequester(testCtx.ctx, testCtx.user)
|
ctx := identity.WithRequester(testCtx.ctx, testCtx.user)
|
||||||
|
|
||||||
fakeUser := testCtx.user.GetUID().String()
|
fakeUser := testCtx.user.GetUID()
|
||||||
firstVersion := int64(0)
|
firstVersion := int64(0)
|
||||||
group := "test.grafana.app"
|
group := "test.grafana.app"
|
||||||
resource := "jsonobjs"
|
resource := "jsonobjs"
|
||||||
|
@ -43,7 +43,74 @@ type SignedInUser struct {
|
|||||||
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
|
// 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.
|
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
||||||
IDToken string `json:"-" xorm:"-"`
|
IDToken string `json:"-" xorm:"-"`
|
||||||
NamespacedID identity.TypedID
|
|
||||||
|
// When other settings are not deterministic, this value is used
|
||||||
|
FallbackType identity.IdentityType
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawIdentifier implements Requester.
|
||||||
|
func (u *SignedInUser) GetRawIdentifier() string {
|
||||||
|
if u.UserUID == "" {
|
||||||
|
// nolint:staticcheck
|
||||||
|
id, _ := u.GetInternalID()
|
||||||
|
return strconv.FormatInt(id, 10)
|
||||||
|
}
|
||||||
|
return u.UserUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use GetUID
|
||||||
|
func (u *SignedInUser) GetInternalID() (int64, error) {
|
||||||
|
switch {
|
||||||
|
case u.ApiKeyID != 0:
|
||||||
|
return u.ApiKeyID, nil
|
||||||
|
case u.IsAnonymous:
|
||||||
|
return 0, nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return u.UserID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdentityType implements Requester.
|
||||||
|
func (u *SignedInUser) GetIdentityType() identity.IdentityType {
|
||||||
|
switch {
|
||||||
|
case u.ApiKeyID != 0:
|
||||||
|
return identity.TypeAPIKey
|
||||||
|
case u.IsServiceAccount:
|
||||||
|
return identity.TypeServiceAccount
|
||||||
|
case u.UserID > 0:
|
||||||
|
return identity.TypeUser
|
||||||
|
case u.IsAnonymous:
|
||||||
|
return identity.TypeAnonymous
|
||||||
|
case u.AuthenticatedBy == "render" && u.UserID == 0:
|
||||||
|
return identity.TypeRenderService
|
||||||
|
}
|
||||||
|
return u.FallbackType
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName implements identity.Requester.
|
||||||
|
func (u *SignedInUser) GetName() string {
|
||||||
|
return u.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtra implements Requester.
|
||||||
|
func (u *SignedInUser) GetExtra() map[string][]string {
|
||||||
|
extra := map[string][]string{}
|
||||||
|
if u.IDToken != "" {
|
||||||
|
extra["id-token"] = []string{u.IDToken}
|
||||||
|
}
|
||||||
|
if u.OrgRole.IsValid() {
|
||||||
|
extra["user-instance-role"] = []string{string(u.GetOrgRole())}
|
||||||
|
}
|
||||||
|
return extra
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroups implements Requester.
|
||||||
|
func (u *SignedInUser) GetGroups() []string {
|
||||||
|
groups := []string{}
|
||||||
|
for _, t := range u.Teams {
|
||||||
|
groups = append(groups, strconv.FormatInt(t, 10))
|
||||||
|
}
|
||||||
|
return groups
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *SignedInUser) ShouldUpdateLastSeenAt() bool {
|
func (u *SignedInUser) ShouldUpdateLastSeenAt() bool {
|
||||||
@ -194,25 +261,11 @@ func (u *SignedInUser) GetTypedID() (identity.IdentityType, string) {
|
|||||||
return identity.TypeRenderService, "0"
|
return identity.TypeRenderService, "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.NamespacedID.Type(), u.NamespacedID.ID()
|
return u.FallbackType, strconv.FormatInt(u.UserID, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUID returns namespaced uid for the entity
|
func (u *SignedInUser) GetUID() string {
|
||||||
func (u *SignedInUser) GetUID() identity.TypedID {
|
return fmt.Sprintf("%s:%s", u.GetIdentityType(), u.GetRawIdentifier())
|
||||||
switch {
|
|
||||||
case u.ApiKeyID != 0:
|
|
||||||
return identity.NewTypedIDString(identity.TypeAPIKey, strconv.FormatInt(u.ApiKeyID, 10))
|
|
||||||
case u.IsServiceAccount:
|
|
||||||
return identity.NewTypedIDString(identity.TypeServiceAccount, u.UserUID)
|
|
||||||
case u.UserID > 0:
|
|
||||||
return identity.NewTypedIDString(identity.TypeUser, u.UserUID)
|
|
||||||
case u.IsAnonymous:
|
|
||||||
return identity.NewTypedIDString(identity.TypeAnonymous, "0")
|
|
||||||
case u.AuthenticatedBy == "render" && u.UserID == 0:
|
|
||||||
return identity.NewTypedIDString(identity.TypeRenderService, "0")
|
|
||||||
}
|
|
||||||
|
|
||||||
return identity.NewTypedIDString(identity.TypeEmpty, "0")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *SignedInUser) GetAuthID() string {
|
func (u *SignedInUser) GetAuthID() string {
|
||||||
|
@ -42,7 +42,7 @@ func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime
|
|||||||
obj.SetOriginInfo(origin)
|
obj.SetOriginInfo(origin)
|
||||||
obj.SetUpdatedBy("")
|
obj.SetUpdatedBy("")
|
||||||
obj.SetUpdatedTimestamp(nil)
|
obj.SetUpdatedTimestamp(nil)
|
||||||
obj.SetCreatedBy(user.GetUID().String())
|
obj.SetCreatedBy(user.GetUID())
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = s.codec.Encode(newObject, &buf)
|
err = s.codec.Encode(newObject, &buf)
|
||||||
@ -81,7 +81,7 @@ func (s *Storage) prepareObjectForUpdate(ctx context.Context, updateObject runti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
obj.SetOriginInfo(origin)
|
obj.SetOriginInfo(origin)
|
||||||
obj.SetUpdatedBy(user.GetUID().String())
|
obj.SetUpdatedBy(user.GetUID())
|
||||||
obj.SetUpdatedTimestampMillis(time.Now().UnixMilli())
|
obj.SetUpdatedTimestampMillis(time.Now().UnixMilli())
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
@ -150,7 +150,7 @@ func encodeIdentityInMetadata(user identity.Requester) metadata.MD {
|
|||||||
|
|
||||||
// Or we can create it directly
|
// Or we can create it directly
|
||||||
mdUserID, user.GetID().String(),
|
mdUserID, user.GetID().String(),
|
||||||
mdUserUID, user.GetUID().String(),
|
mdUserUID, user.GetUID(),
|
||||||
mdOrgName, user.GetOrgName(),
|
mdOrgName, user.GetOrgName(),
|
||||||
mdOrgID, strconv.FormatInt(user.GetOrgID(), 10),
|
mdOrgID, strconv.FormatInt(user.GetOrgID(), 10),
|
||||||
mdOrgRole, string(user.GetOrgRole()),
|
mdOrgRole, string(user.GetOrgRole()),
|
||||||
@ -158,6 +158,6 @@ func encodeIdentityInMetadata(user identity.Requester) metadata.MD {
|
|||||||
|
|
||||||
// TODO, Remove after this is deployed to unified storage
|
// TODO, Remove after this is deployed to unified storage
|
||||||
"grafana-userid", user.GetID().ID(),
|
"grafana-userid", user.GetID().ID(),
|
||||||
"grafana-useruid", user.GetUID().ID(),
|
"grafana-useruid", user.GetRawIdentifier(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ func TestBasicEncodeDecode(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, before.GetID(), after.GetID())
|
require.Equal(t, before.GetID(), after.GetID())
|
||||||
require.Equal(t, before.GetUID(), after.GetUID())
|
require.Equal(t, before.GetUID(), after.GetUID())
|
||||||
|
require.Equal(t, before.GetIdentityType(), after.GetIdentityType())
|
||||||
require.Equal(t, before.GetLogin(), after.GetLogin())
|
require.Equal(t, before.GetLogin(), after.GetLogin())
|
||||||
require.Equal(t, before.GetOrgID(), after.GetOrgID())
|
require.Equal(t, before.GetOrgID(), after.GetOrgID())
|
||||||
require.Equal(t, before.GetOrgName(), after.GetOrgName())
|
require.Equal(t, before.GetOrgName(), after.GetOrgName())
|
||||||
|
@ -447,7 +447,7 @@ func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRespons
|
|||||||
obj.SetUpdatedTimestamp(&now.Time)
|
obj.SetUpdatedTimestamp(&now.Time)
|
||||||
obj.SetManagedFields(nil)
|
obj.SetManagedFields(nil)
|
||||||
obj.SetFinalizers(nil)
|
obj.SetFinalizers(nil)
|
||||||
obj.SetUpdatedBy(requester.GetUID().String())
|
obj.SetUpdatedBy(requester.GetUID())
|
||||||
marker.TypeMeta = metav1.TypeMeta{
|
marker.TypeMeta = metav1.TypeMeta{
|
||||||
Kind: "DeletedMarker",
|
Kind: "DeletedMarker",
|
||||||
APIVersion: "common.grafana.app/v0alpha1", // ?? or can we stick this in common?
|
APIVersion: "common.grafana.app/v0alpha1", // ?? or can we stick this in common?
|
||||||
|
@ -116,7 +116,7 @@ func TestSimpleServer(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
obj.SetAnnotation("test", "hello")
|
obj.SetAnnotation("test", "hello")
|
||||||
obj.SetUpdatedTimestampMillis(now)
|
obj.SetUpdatedTimestampMillis(now)
|
||||||
obj.SetUpdatedBy(testUserA.GetUID().String())
|
obj.SetUpdatedBy(testUserA.GetUID())
|
||||||
raw, err = json.Marshal(tmp)
|
raw, err = json.Marshal(tmp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -466,7 +466,8 @@ func (c *K8sTestHelper) CreateUser(name string, orgName string, basicRole org.Ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *K8sTestHelper) SetPermissions(user User, permissions []resourcepermissions.SetResourcePermissionCommand) {
|
func (c *K8sTestHelper) SetPermissions(user User, permissions []resourcepermissions.SetResourcePermissionCommand) {
|
||||||
id, err := user.Identity.GetID().UserID()
|
// nolint:staticcheck
|
||||||
|
id, err := user.Identity.GetInternalID()
|
||||||
require.NoError(c.t, err)
|
require.NoError(c.t, err)
|
||||||
|
|
||||||
permissionsStore := resourcepermissions.NewStore(c.env.Cfg, c.env.SQLStore, featuremgmt.WithFeatures())
|
permissionsStore := resourcepermissions.NewStore(c.env.Cfg, c.env.SQLStore, featuremgmt.WithFeatures())
|
||||||
|
@ -175,7 +175,7 @@ func TestApplyUserHeader(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("X-Grafana-User", "admin")
|
req.Header.Set("X-Grafana-User", "admin")
|
||||||
|
|
||||||
ApplyUserHeader(false, req, &user.SignedInUser{Login: "admin", NamespacedID: identity.MustParseTypedID("user:1")})
|
ApplyUserHeader(false, req, &user.SignedInUser{Login: "admin", UserID: 1, FallbackType: identity.TypeUser})
|
||||||
require.NotContains(t, req.Header, "X-Grafana-User")
|
require.NotContains(t, req.Header, "X-Grafana-User")
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ func TestApplyUserHeader(t *testing.T) {
|
|||||||
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ApplyUserHeader(true, req, &user.SignedInUser{IsAnonymous: true, NamespacedID: identity.MustParseTypedID("anonymous:0")})
|
ApplyUserHeader(true, req, &user.SignedInUser{IsAnonymous: true, FallbackType: identity.TypeAnonymous})
|
||||||
require.NotContains(t, req.Header, "X-Grafana-User")
|
require.NotContains(t, req.Header, "X-Grafana-User")
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ func TestApplyUserHeader(t *testing.T) {
|
|||||||
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ApplyUserHeader(true, req, &user.SignedInUser{Login: "admin", NamespacedID: identity.MustParseTypedID("user:1")})
|
ApplyUserHeader(true, req, &user.SignedInUser{Login: "admin", UserID: 1, FallbackType: identity.TypeUser})
|
||||||
require.Equal(t, "admin", req.Header.Get("X-Grafana-User"))
|
require.Equal(t, "admin", req.Header.Get("X-Grafana-User"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user