ServiceAccount: Update service account api resource and add service account token (#92972)

* Create own legacy store function to list service accounts and update api model

* Add service account tokens as a sub resource for service accounts
This commit is contained in:
Karl Persson
2024-09-05 13:43:54 +02:00
committed by GitHub
parent 47cd8288cd
commit 2bfa607ad0
32 changed files with 688 additions and 184 deletions

View File

@@ -3,7 +3,7 @@ package common
import (
"strconv"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/services/team"
)
@@ -16,10 +16,10 @@ func OptionalFormatInt(num int64) string {
return ""
}
func MapTeamPermission(p team.PermissionType) identityv0.TeamPermission {
func MapTeamPermission(p team.PermissionType) iamv0.TeamPermission {
if p == team.PermissionTypeAdmin {
return identityv0.TeamPermissionAdmin
return iamv0.TeamPermissionAdmin
} else {
return identityv0.TeamPermissionMember
return iamv0.TeamPermissionMember
}
}

View File

@@ -0,0 +1,205 @@
package legacy
import (
"context"
"fmt"
"time"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
type ListServiceAccountsQuery struct {
UID string
OrgID int64
Pagination common.Pagination
}
type ListServiceAccountResult struct {
Items []ServiceAccount
Continue int64
RV int64
}
type ServiceAccount struct {
ID int64
UID string
Name string
Disabled bool
Created time.Time
Updated time.Time
}
var sqlQueryServiceAccountsTemplate = mustTemplate("service_accounts_query.sql")
func newListServiceAccounts(sql *legacysql.LegacyDatabaseHelper, q *ListServiceAccountsQuery) listServiceAccountsQuery {
return listServiceAccountsQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
OrgUserTable: sql.Table("org_user"),
Query: q,
}
}
type listServiceAccountsQuery struct {
sqltemplate.SQLTemplate
Query *ListServiceAccountsQuery
UserTable string
OrgUserTable string
}
func (r listServiceAccountsQuery) Validate() error {
return nil // TODO
}
func (s *legacySQLStore) ListServiceAccounts(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountsQuery) (*ListServiceAccountResult, error) {
// for continue
query.Pagination.Limit += 1
query.OrgID = ns.OrgID
if ns.OrgID == 0 {
return nil, fmt.Errorf("expected non zero orgID")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
req := newListServiceAccounts(sql, &query)
q, err := sqltemplate.Execute(sqlQueryServiceAccountsTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryServiceAccountsTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
res := &ListServiceAccountResult{}
if err != nil {
return nil, err
}
var lastID int64
for rows.Next() {
var s ServiceAccount
err := rows.Scan(&s.ID, &s.UID, &s.Name, &s.Disabled, &s.Created, &s.Updated)
if err != nil {
return res, err
}
lastID = s.ID
res.Items = append(res.Items, s)
if len(res.Items) > int(query.Pagination.Limit)-1 {
res.Items = res.Items[0 : len(res.Items)-1]
res.Continue = lastID
break
}
}
if query.UID == "" {
// FIXME: we need to filer for service accounts here..
res.RV, err = sql.GetResourceVersion(ctx, "user", "updated")
}
return res, err
}
type ListServiceAccountTokenQuery struct {
// UID is the service account uid.
UID string
OrgID int64
Pagination common.Pagination
}
type ListServiceAccountTokenResult struct {
Items []ServiceAccountToken
Continue int64
RV int64
}
type ServiceAccountToken struct {
ID int64
Name string
Revoked bool
Expires *int64
LastUsed *time.Time
Created time.Time
Updated time.Time
}
var sqlQueryServiceAccountTokensTemplate = mustTemplate("service_account_tokens_query.sql")
func newListServiceAccountTokens(sql *legacysql.LegacyDatabaseHelper, q *ListServiceAccountTokenQuery) listServiceAccountTokensQuery {
return listServiceAccountTokensQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
UserTable: sql.Table("user"),
OrgUserTable: sql.Table("org_user"),
TokenTable: sql.Table("api_key"),
Query: q,
}
}
type listServiceAccountTokensQuery struct {
sqltemplate.SQLTemplate
Query *ListServiceAccountTokenQuery
UserTable string
TokenTable string
OrgUserTable string
}
func (s *legacySQLStore) ListServiceAccountTokens(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountTokenQuery) (*ListServiceAccountTokenResult, error) {
// for continue
query.Pagination.Limit += 1
query.OrgID = ns.OrgID
if ns.OrgID == 0 {
return nil, fmt.Errorf("expected non zero orgID")
}
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
req := newListServiceAccountTokens(sql, &query)
q, err := sqltemplate.Execute(sqlQueryServiceAccountTokensTemplate, req)
if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlQueryServiceAccountTokensTemplate.Name(), err)
}
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
defer func() {
if rows != nil {
_ = rows.Close()
}
}()
res := &ListServiceAccountTokenResult{}
if err != nil {
return nil, err
}
var lastID int64
for rows.Next() {
var t ServiceAccountToken
err := rows.Scan(&t.ID, &t.Name, &t.Revoked, &t.LastUsed, &t.Expires, &t.Created, &t.Updated)
if err != nil {
return res, err
}
lastID = t.ID
res.Items = append(res.Items, t)
if len(res.Items) > int(query.Pagination.Limit)-1 {
res.Items = res.Items[0 : len(res.Items)-1]
res.Continue = lastID
break
}
}
return res, err
}

View File

@@ -0,0 +1,19 @@
SELECT
t.id,
t.name,
t.is_revoked,
t.last_used_at,
t.expires,
t.created,
t.updated
FROM {{ .Ident .TokenTable }} as t
INNER JOIN {{ .Ident .UserTable }} as u ON t.service_account_id = u.id
INNER JOIN {{ .Ident .OrgUserTable }} as o ON u.id = o.user_id
WHERE o.org_id = {{ .Arg .Query.OrgID }}
AND u.is_service_account
AND u.uid = {{ .Arg .Query.UID }}
{{ if .Query.Pagination.Continue }}
AND t.id >= {{ .Arg .Query.Pagination.Continue }}
{{ end }}
ORDER BY t.id asc
LIMIT {{ .Arg .Query.Pagination.Limit }}

View File

@@ -0,0 +1,18 @@
SELECT
u.id,
u.uid,
u.name,
u.is_disabled,
u.created,
u.updated
FROM {{ .Ident .UserTable }} as u JOIN {{ .Ident .OrgUserTable }} as o ON u.id = o.user_id
WHERE o.org_id = {{ .Arg .Query.OrgID }}
AND u.is_service_account
{{ if .Query.UID }}
AND u.uid = {{ .Arg .Query.UID }}
{{ end }}
{{ if .Query.Pagination.Continue }}
AND u.id >= {{ .Arg .Query.Pagination.Continue }}
{{ end }}
ORDER BY u.id asc
LIMIT {{ .Arg .Query.Pagination.Limit }}

View File

@@ -17,6 +17,9 @@ type LegacyIdentityStore interface {
ListUsers(ctx context.Context, ns claims.NamespaceInfo, query ListUserQuery) (*ListUserResult, error)
ListUserTeams(ctx context.Context, ns claims.NamespaceInfo, query ListUserTeamsQuery) (*ListUserTeamsResult, error)
ListServiceAccounts(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountsQuery) (*ListServiceAccountResult, error)
ListServiceAccountTokens(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountTokenQuery) (*ListServiceAccountTokenResult, error)
ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error)
ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error)
ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error)

View File

@@ -2,6 +2,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
ORDER BY u.id asc
LIMIT 5

View File

@@ -2,7 +2,7 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
AND u.id >= 2
ORDER BY u.id asc
LIMIT 1

View File

@@ -2,7 +2,7 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `grafana`.`user` as u JOIN `grafana`.`org_user` as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
AND u.uid = 'abc'
ORDER BY u.id asc
LIMIT 1

View File

@@ -2,6 +2,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
ORDER BY u.id asc
LIMIT 5

View File

@@ -2,7 +2,7 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
AND u.id >= 2
ORDER BY u.id asc
LIMIT 1

View File

@@ -2,7 +2,7 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
AND u.uid = 'abc'
ORDER BY u.id asc
LIMIT 1

View File

@@ -2,6 +2,6 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
ORDER BY u.id asc
LIMIT 5

View File

@@ -2,7 +2,7 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
AND u.id >= 2
ORDER BY u.id asc
LIMIT 1

View File

@@ -2,7 +2,7 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "grafana"."user" as u JOIN "grafana"."org_user" as o ON u.id = o.user_id
WHERE o.org_id = 0
AND u.is_service_account = FALSE
AND NOT u.is_service_account
AND u.uid = 'abc'
ORDER BY u.id asc
LIMIT 1

View File

@@ -14,9 +14,8 @@ import (
)
type ListUserQuery struct {
OrgID int64
UID string
IsServiceAccount bool
OrgID int64
UID string
Pagination common.Pagination
}

View File

@@ -2,7 +2,7 @@ SELECT o.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM {{ .Ident .UserTable }} as u JOIN {{ .Ident .OrgUserTable }} as o ON u.id = o.user_id
WHERE o.org_id = {{ .Arg .Query.OrgID }}
AND u.is_service_account = {{ .Arg .Query.IsServiceAccount }}
AND NOT u.is_service_account
{{ if .Query.UID }}
AND u.uid = {{ .Arg .Query.UID }}
{{ end }}

View File

@@ -4,7 +4,7 @@ import (
"context"
"github.com/grafana/grafana/pkg/apimachinery/identity"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
@@ -55,19 +55,19 @@ func RegisterAPIService(
}
func (b *IdentityAccessManagementAPIBuilder) GetGroupVersion() schema.GroupVersion {
return identityv0.SchemeGroupVersion
return iamv0.SchemeGroupVersion
}
func (b *IdentityAccessManagementAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
identityv0.AddKnownTypes(scheme, identityv0.VERSION)
iamv0.AddKnownTypes(scheme, iamv0.VERSION)
// Link this version to the internal representation.
// This is used for server-side-apply (PATCH), and avoids the error:
// "no kind is registered for the type"
identityv0.AddKnownTypes(scheme, runtime.APIVersionInternal)
iamv0.AddKnownTypes(scheme, runtime.APIVersionInternal)
metav1.AddToGroupVersion(scheme, identityv0.SchemeGroupVersion)
return scheme.SetVersionPriority(identityv0.SchemeGroupVersion)
metav1.AddToGroupVersion(scheme, iamv0.SchemeGroupVersion)
return scheme.SetVersionPriority(iamv0.SchemeGroupVersion)
}
func (b *IdentityAccessManagementAPIBuilder) GetAPIGroupInfo(
@@ -76,37 +76,38 @@ func (b *IdentityAccessManagementAPIBuilder) GetAPIGroupInfo(
optsGetter generic.RESTOptionsGetter,
dualWriteBuilder grafanarest.DualWriteBuilder,
) (*genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(identityv0.GROUP, scheme, metav1.ParameterCodec, codecs)
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(iamv0.GROUP, scheme, metav1.ParameterCodec, codecs)
storage := map[string]rest.Storage{}
teamResource := identityv0.TeamResourceInfo
teamResource := iamv0.TeamResourceInfo
storage[teamResource.StoragePath()] = team.NewLegacyStore(b.Store)
storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.Store)
teamBindingResource := identityv0.TeamBindingResourceInfo
teamBindingResource := iamv0.TeamBindingResourceInfo
storage[teamBindingResource.StoragePath()] = team.NewLegacyBindingStore(b.Store)
userResource := identityv0.UserResourceInfo
userResource := iamv0.UserResourceInfo
storage[userResource.StoragePath()] = user.NewLegacyStore(b.Store)
storage[userResource.StoragePath("teams")] = user.NewLegacyTeamMemberREST(b.Store)
serviceaccountResource := identityv0.ServiceAccountResourceInfo
serviceaccountResource := iamv0.ServiceAccountResourceInfo
storage[serviceaccountResource.StoragePath()] = serviceaccount.NewLegacyStore(b.Store)
storage[serviceaccountResource.StoragePath("tokens")] = serviceaccount.NewLegacyTokenREST(b.Store)
if b.SSOService != nil {
ssoResource := identityv0.SSOSettingResourceInfo
ssoResource := iamv0.SSOSettingResourceInfo
storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.SSOService)
}
// The display endpoint -- NOTE, this uses a rewrite hack to allow requests without a name parameter
storage["display"] = user.NewLegacyDisplayREST(b.Store)
apiGroupInfo.VersionedResourcesStorageMap[identityv0.VERSION] = storage
apiGroupInfo.VersionedResourcesStorageMap[iamv0.VERSION] = storage
return &apiGroupInfo, nil
}
func (b *IdentityAccessManagementAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
return identityv0.GetOpenAPIDefinitions
return iamv0.GetOpenAPIDefinitions
}
func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes() *builder.APIRoutes {

View File

@@ -0,0 +1,115 @@
package serviceaccount
import (
"context"
"net/http"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
var (
_ rest.Storage = (*LegacyTokenRest)(nil)
_ rest.Scoper = (*LegacyTokenRest)(nil)
_ rest.StorageMetadata = (*LegacyTokenRest)(nil)
_ rest.Connecter = (*LegacyTokenRest)(nil)
)
func NewLegacyTokenREST(store legacy.LegacyIdentityStore) *LegacyTokenRest {
return &LegacyTokenRest{store}
}
type LegacyTokenRest struct {
store legacy.LegacyIdentityStore
}
// New implements rest.Storage.
func (s *LegacyTokenRest) New() runtime.Object {
return &iamv0.UserTeamList{}
}
// Destroy implements rest.Storage.
func (s *LegacyTokenRest) Destroy() {}
// NamespaceScoped implements rest.Scoper.
func (s *LegacyTokenRest) NamespaceScoped() bool {
return true
}
// ProducesMIMETypes implements rest.StorageMetadata.
func (s *LegacyTokenRest) ProducesMIMETypes(verb string) []string {
return []string{"application/json"}
}
// ProducesObject implements rest.StorageMetadata.
func (s *LegacyTokenRest) ProducesObject(verb string) interface{} {
return s.New()
}
// Connect implements rest.Connecter.
func (s *LegacyTokenRest) Connect(ctx context.Context, name string, options runtime.Object, responder rest.Responder) (http.Handler, error) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
res, err := s.store.ListServiceAccountTokens(ctx, ns, legacy.ListServiceAccountTokenQuery{
UID: name,
Pagination: common.PaginationFromListQuery(r.URL.Query()),
})
if err != nil {
responder.Error(err)
return
}
list := &iamv0.ServiceAccountTokenList{Items: make([]iamv0.ServiceAccountToken, 0, len(res.Items))}
for _, t := range res.Items {
list.Items = append(list.Items, mapToToken(t))
}
list.ListMeta.Continue = common.OptionalFormatInt(res.Continue)
responder.Object(http.StatusOK, list)
}), nil
}
// NewConnectOptions implements rest.Connecter.
func (s *LegacyTokenRest) NewConnectOptions() (runtime.Object, bool, string) {
return nil, false, ""
}
// ConnectMethods implements rest.Connecter.
func (s *LegacyTokenRest) ConnectMethods() []string {
return []string{http.MethodGet}
}
func mapToToken(t legacy.ServiceAccountToken) iamv0.ServiceAccountToken {
var expires, lastUsed *metav1.Time
if t.Expires != nil {
ts := metav1.NewTime(time.Unix(*t.Expires, 0))
expires = &ts
}
if t.LastUsed != nil {
ts := metav1.NewTime(*t.LastUsed)
lastUsed = &ts
}
return iamv0.ServiceAccountToken{
Name: t.Name,
Expires: expires,
LastUsed: lastUsed,
Revoked: t.Revoked,
Created: metav1.NewTime(t.Created),
}
}

View File

@@ -11,11 +11,10 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/utils"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/user"
)
var (
@@ -26,7 +25,7 @@ var (
_ rest.Storage = (*LegacyStore)(nil)
)
var resource = identityv0.ServiceAccountResourceInfo
var resource = iamv0.ServiceAccountResourceInfo
func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore {
return &LegacyStore{store}
@@ -63,20 +62,18 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt
if err != nil {
return nil, err
}
query := legacy.ListUserQuery{
OrgID: ns.OrgID,
IsServiceAccount: true,
Pagination: common.PaginationFromListOptions(options),
}
found, err := s.store.ListUsers(ctx, ns, query)
found, err := s.store.ListServiceAccounts(ctx, ns, legacy.ListServiceAccountsQuery{
OrgID: ns.OrgID,
Pagination: common.PaginationFromListOptions(options),
})
if err != nil {
return nil, err
}
list := &identityv0.ServiceAccountList{}
for _, item := range found.Users {
list.Items = append(list.Items, *toSAItem(&item, ns.Value))
list := &iamv0.ServiceAccountList{}
for _, item := range found.Items {
list.Items = append(list.Items, toSAItem(item, ns.Value))
}
list.ListMeta.Continue = common.OptionalFormatInt(found.Continue)
@@ -85,26 +82,24 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt
return list, err
}
func toSAItem(u *user.User, ns string) *identityv0.ServiceAccount {
item := &identityv0.ServiceAccount{
func toSAItem(sa legacy.ServiceAccount, ns string) iamv0.ServiceAccount {
item := iamv0.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: u.UID,
Name: sa.UID,
Namespace: ns,
ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()),
CreationTimestamp: metav1.NewTime(u.Created),
ResourceVersion: fmt.Sprintf("%d", sa.Updated.UnixMilli()),
CreationTimestamp: metav1.NewTime(sa.Created),
},
Spec: identityv0.ServiceAccountSpec{
Name: u.Name,
Email: u.Email,
EmailVerified: u.EmailVerified,
Disabled: u.IsDisabled,
Spec: iamv0.ServiceAccountSpec{
Title: sa.Name,
Disabled: sa.Disabled,
},
}
obj, _ := utils.MetaAccessor(item)
obj.SetUpdatedTimestamp(&u.Updated)
obj, _ := utils.MetaAccessor(&item)
obj.SetUpdatedTimestamp(&sa.Updated)
obj.SetOriginInfo(&utils.ResourceOriginInfo{
Name: "SQL",
Path: strconv.FormatInt(u.ID, 10),
Path: strconv.FormatInt(sa.ID, 10),
})
return item
}
@@ -114,18 +109,19 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO
if err != nil {
return nil, err
}
query := legacy.ListUserQuery{
OrgID: ns.OrgID,
IsServiceAccount: true,
Pagination: common.Pagination{Limit: 1},
}
found, err := s.store.ListUsers(ctx, ns, query)
found, err := s.store.ListServiceAccounts(ctx, ns, legacy.ListServiceAccountsQuery{
UID: name,
OrgID: ns.OrgID,
Pagination: common.Pagination{Limit: 1},
})
if found == nil || err != nil {
return nil, resource.NewNotFound(name)
}
if len(found.Users) < 1 {
if len(found.Items) < 1 {
return nil, resource.NewNotFound(name)
}
return toSAItem(&found.Users[0], ns.Value), nil
res := toSAItem(found.Items[0], ns.Value)
return &res, nil
}

View File

@@ -14,7 +14,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/registry/rest"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/ssosettings"
ssomodels "github.com/grafana/grafana/pkg/services/ssosettings/models"
@@ -30,7 +30,7 @@ var (
_ rest.GracefulDeleter = (*LegacyStore)(nil)
)
var resource = identityv0.SSOSettingResourceInfo
var resource = iamv0.SSOSettingResourceInfo
func NewLegacyStore(service ssosettings.Service) *LegacyStore {
return &LegacyStore{service}
@@ -78,7 +78,7 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt
return nil, fmt.Errorf("failed to list sso settings: %w", err)
}
list := &identityv0.SSOSettingList{}
list := &iamv0.SSOSettingList{}
for _, s := range settings {
list.Items = append(list.Items, mapToObject(ns.Value, s))
}
@@ -128,7 +128,7 @@ func (s *LegacyStore) Update(
return old, created, err
}
setting, ok := obj.(*identityv0.SSOSetting)
setting, ok := obj.(*iamv0.SSOSetting)
if !ok {
return old, created, errors.New("expected ssosetting after update")
}
@@ -153,7 +153,7 @@ func (s *LegacyStore) Delete(
return obj, false, err
}
old, ok := obj.(*identityv0.SSOSetting)
old, ok := obj.(*iamv0.SSOSetting)
if !ok {
return obj, false, errors.New("expected ssosetting")
}
@@ -182,10 +182,10 @@ func (s *LegacyStore) Delete(
return afterDelete, false, err
}
func mapToObject(ns string, s *ssomodels.SSOSettings) identityv0.SSOSetting {
source := identityv0.SourceDB
func mapToObject(ns string, s *ssomodels.SSOSettings) iamv0.SSOSetting {
source := iamv0.SourceDB
if s.Source == ssomodels.System {
source = identityv0.SourceSystem
source = iamv0.SourceSystem
}
version := "0"
@@ -193,7 +193,7 @@ func mapToObject(ns string, s *ssomodels.SSOSettings) identityv0.SSOSetting {
version = fmt.Sprintf("%d", s.Updated.UnixMilli())
}
object := identityv0.SSOSetting{
object := iamv0.SSOSetting{
ObjectMeta: metav1.ObjectMeta{
Name: s.Provider,
Namespace: ns,
@@ -201,7 +201,7 @@ func mapToObject(ns string, s *ssomodels.SSOSettings) identityv0.SSOSetting {
ResourceVersion: version,
CreationTimestamp: metav1.NewTime(s.Updated),
},
Spec: identityv0.SSOSettingSpec{
Spec: iamv0.SSOSettingSpec{
Source: source,
Settings: commonv1.Unstructured{Object: s.Settings},
},
@@ -210,7 +210,7 @@ func mapToObject(ns string, s *ssomodels.SSOSettings) identityv0.SSOSetting {
return object
}
func mapToModel(obj *identityv0.SSOSetting) *ssomodels.SSOSettings {
func mapToModel(obj *iamv0.SSOSetting) *ssomodels.SSOSettings {
return &ssomodels.SSOSettings{
Provider: obj.Name,
Settings: obj.Spec.Settings.Object,

View File

@@ -9,7 +9,7 @@ import (
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/api/dtos"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
@@ -33,7 +33,7 @@ type LegacyTeamMemberREST struct {
// New implements rest.Storage.
func (s *LegacyTeamMemberREST) New() runtime.Object {
return &identityv0.TeamMemberList{}
return &iamv0.TeamMemberList{}
}
// Destroy implements rest.Storage.
@@ -71,7 +71,7 @@ func (s *LegacyTeamMemberREST) Connect(ctx context.Context, name string, options
return
}
list := &identityv0.TeamMemberList{Items: make([]identityv0.TeamMember, 0, len(res.Members))}
list := &iamv0.TeamMemberList{Items: make([]iamv0.TeamMember, 0, len(res.Members))}
for _, m := range res.Members {
list.Items = append(list.Items, mapToTeamMember(m))
@@ -95,9 +95,9 @@ func (s *LegacyTeamMemberREST) ConnectMethods() []string {
var cfg = &setting.Cfg{}
func mapToTeamMember(m legacy.TeamMember) identityv0.TeamMember {
return identityv0.TeamMember{
IdentityDisplay: identityv0.IdentityDisplay{
func mapToTeamMember(m legacy.TeamMember) iamv0.TeamMember {
return iamv0.TeamMember{
IdentityDisplay: iamv0.IdentityDisplay{
IdentityType: claims.TypeUser,
UID: m.UserUID,
Display: m.Name,

View File

@@ -11,7 +11,7 @@ import (
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/apimachinery/utils"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
@@ -25,7 +25,7 @@ var (
_ rest.Storage = (*LegacyStore)(nil)
)
var resource = identityv0.TeamResourceInfo
var resource = iamv0.TeamResourceInfo
func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore {
return &LegacyStore{store}
@@ -58,25 +58,25 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object,
return resource.TableConverter().ConvertToTable(ctx, object, tableOptions)
}
func (s *LegacyStore) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*identityv0.TeamList, error) {
func (s *LegacyStore) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*iamv0.TeamList, error) {
rsp, err := s.store.ListTeams(ctx, ns, query)
if err != nil {
return nil, err
}
list := &identityv0.TeamList{
list := &iamv0.TeamList{
ListMeta: metav1.ListMeta{
ResourceVersion: strconv.FormatInt(rsp.RV, 10),
},
}
for _, team := range rsp.Teams {
item := identityv0.Team{
item := iamv0.Team{
ObjectMeta: metav1.ObjectMeta{
Name: team.UID,
Namespace: ns.Value,
CreationTimestamp: metav1.NewTime(team.Created),
ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10),
},
Spec: identityv0.TeamSpec{
Spec: iamv0.TeamSpec{
Title: team.Name,
Email: team.Email,
},

View File

@@ -11,14 +11,14 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/authlib/claims"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/team"
)
var bindingResource = identityv0.TeamBindingResourceInfo
var bindingResource = iamv0.TeamBindingResourceInfo
var (
_ rest.Storage = (*LegacyBindingStore)(nil)
@@ -102,8 +102,8 @@ func (l *LegacyBindingStore) List(ctx context.Context, options *internalversion.
return nil, err
}
list := identityv0.TeamBindingList{
Items: make([]identityv0.TeamBinding, 0, len(res.Bindings)),
list := iamv0.TeamBindingList{
Items: make([]iamv0.TeamBinding, 0, len(res.Bindings)),
}
for _, b := range res.Bindings {
@@ -116,7 +116,7 @@ func (l *LegacyBindingStore) List(ctx context.Context, options *internalversion.
return &list, nil
}
func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) identityv0.TeamBinding {
func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) iamv0.TeamBinding {
rv := time.Time{}
ct := time.Now()
@@ -129,15 +129,15 @@ func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) identityv
}
}
return identityv0.TeamBinding{
return iamv0.TeamBinding{
ObjectMeta: metav1.ObjectMeta{
Name: b.TeamUID,
Namespace: ns.Value,
ResourceVersion: strconv.FormatInt(rv.UnixMilli(), 10),
CreationTimestamp: metav1.NewTime(ct),
},
Spec: identityv0.TeamBindingSpec{
TeamRef: identityv0.TeamRef{
Spec: iamv0.TeamBindingSpec{
TeamRef: iamv0.TeamRef{
Name: b.TeamUID,
},
Subjects: mapToSubjects(b.Members),
@@ -145,10 +145,10 @@ func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) identityv
}
}
func mapToSubjects(members []legacy.TeamMember) []identityv0.TeamSubject {
out := make([]identityv0.TeamSubject, 0, len(members))
func mapToSubjects(members []legacy.TeamMember) []iamv0.TeamSubject {
out := make([]iamv0.TeamSubject, 0, len(members))
for _, m := range members {
out = append(out, identityv0.TeamSubject{
out = append(out, iamv0.TeamSubject{
Name: m.MemberID(),
Permission: common.MapTeamPermission(m.Permission),
})
@@ -156,10 +156,10 @@ func mapToSubjects(members []legacy.TeamMember) []identityv0.TeamSubject {
return out
}
func mapPermisson(p team.PermissionType) identityv0.TeamPermission {
func mapPermisson(p team.PermissionType) iamv0.TeamPermission {
if p == team.PermissionTypeAdmin {
return identityv0.TeamPermissionAdmin
return iamv0.TeamPermissionAdmin
} else {
return identityv0.TeamPermissionMember
return iamv0.TeamPermissionMember
}
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/api/dtos"
identity "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/setting"
@@ -35,7 +35,7 @@ func NewLegacyDisplayREST(store legacy.LegacyIdentityStore) *LegacyDisplayREST {
}
func (r *LegacyDisplayREST) New() runtime.Object {
return &identity.IdentityDisplayResults{}
return &iamv0.IdentityDisplayResults{}
}
func (r *LegacyDisplayREST) Destroy() {}
@@ -54,7 +54,7 @@ func (r *LegacyDisplayREST) ProducesMIMETypes(verb string) []string {
}
func (r *LegacyDisplayREST) ProducesObject(verb string) any {
return &identity.IdentityDisplayResults{}
return &iamv0.IdentityDisplayResults{}
}
func (r *LegacyDisplayREST) ConnectMethods() []string {
@@ -91,13 +91,13 @@ func (r *LegacyDisplayREST) Connect(ctx context.Context, name string, _ runtime.
return
}
rsp := &identity.IdentityDisplayResults{
rsp := &iamv0.IdentityDisplayResults{
Keys: keys.keys,
InvalidKeys: keys.invalid,
Display: make([]identity.IdentityDisplay, 0, len(users.Users)+len(keys.disp)+1),
Display: make([]iamv0.IdentityDisplay, 0, len(users.Users)+len(keys.disp)+1),
}
for _, user := range users.Users {
disp := identity.IdentityDisplay{
disp := iamv0.IdentityDisplay{
IdentityType: claims.TypeUser,
Display: user.NameOrFallback(),
UID: user.UID,
@@ -124,7 +124,7 @@ type dispKeys struct {
invalid []string
// For terminal keys, this is a constant
disp []identity.IdentityDisplay
disp []iamv0.IdentityDisplay
}
func parseKeys(req []string) dispKeys {
@@ -145,14 +145,14 @@ func parseKeys(req []string) dispKeys {
switch t {
case claims.TypeAnonymous:
keys.disp = append(keys.disp, identity.IdentityDisplay{
keys.disp = append(keys.disp, iamv0.IdentityDisplay{
IdentityType: t,
Display: "Anonymous",
AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)),
})
continue
case claims.TypeAPIKey:
keys.disp = append(keys.disp, identity.IdentityDisplay{
keys.disp = append(keys.disp, iamv0.IdentityDisplay{
IdentityType: t,
UID: key,
Display: "API Key",
@@ -160,7 +160,7 @@ func parseKeys(req []string) dispKeys {
})
continue
case claims.TypeProvisioning:
keys.disp = append(keys.disp, identity.IdentityDisplay{
keys.disp = append(keys.disp, iamv0.IdentityDisplay{
IdentityType: t,
UID: "Provisioning",
Display: "Provisioning",
@@ -176,7 +176,7 @@ func parseKeys(req []string) dispKeys {
id, err := strconv.ParseInt(key, 10, 64)
if err == nil {
if id == 0 {
keys.disp = append(keys.disp, identity.IdentityDisplay{
keys.disp = append(keys.disp, iamv0.IdentityDisplay{
IdentityType: claims.TypeUser,
UID: key,
Display: "System admin",

View File

@@ -7,7 +7,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
@@ -30,7 +30,7 @@ type LegacyUserTeamREST struct {
// New implements rest.Storage.
func (s *LegacyUserTeamREST) New() runtime.Object {
return &identityv0.UserTeamList{}
return &iamv0.UserTeamList{}
}
// Destroy implements rest.Storage.
@@ -68,7 +68,7 @@ func (s *LegacyUserTeamREST) Connect(ctx context.Context, name string, options r
return
}
list := &identityv0.UserTeamList{Items: make([]identityv0.UserTeam, 0, len(res.Items))}
list := &iamv0.UserTeamList{Items: make([]iamv0.UserTeam, 0, len(res.Items))}
for _, m := range res.Items {
list.Items = append(list.Items, mapToUserTeam(m))
@@ -90,10 +90,10 @@ func (s *LegacyUserTeamREST) ConnectMethods() []string {
return []string{http.MethodGet}
}
func mapToUserTeam(t legacy.UserTeam) identityv0.UserTeam {
return identityv0.UserTeam{
func mapToUserTeam(t legacy.UserTeam) iamv0.UserTeam {
return iamv0.UserTeam{
Title: t.Name,
TeamRef: identityv0.TeamRef{
TeamRef: iamv0.TeamRef{
Name: t.UID,
},
Permission: common.MapTeamPermission(t.Permission),

View File

@@ -11,7 +11,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/utils"
identityv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
@@ -26,7 +26,7 @@ var (
_ rest.Storage = (*LegacyStore)(nil)
)
var resource = identityv0.UserResourceInfo
var resource = iamv0.UserResourceInfo
func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore {
return &LegacyStore{store}
@@ -65,15 +65,14 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt
}
found, err := s.store.ListUsers(ctx, ns, legacy.ListUserQuery{
OrgID: ns.OrgID,
IsServiceAccount: false,
Pagination: common.PaginationFromListOptions(options),
OrgID: ns.OrgID,
Pagination: common.PaginationFromListOptions(options),
})
if err != nil {
return nil, err
}
list := &identityv0.UserList{}
list := &iamv0.UserList{}
for _, item := range found.Users {
list.Items = append(list.Items, *toUserItem(&item, ns.Value))
}
@@ -90,9 +89,8 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO
return nil, err
}
query := legacy.ListUserQuery{
OrgID: ns.OrgID,
IsServiceAccount: false,
Pagination: common.Pagination{Limit: 1},
OrgID: ns.OrgID,
Pagination: common.Pagination{Limit: 1},
}
found, err := s.store.ListUsers(ctx, ns, query)
@@ -105,15 +103,15 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO
return toUserItem(&found.Users[0], ns.Value), nil
}
func toUserItem(u *user.User, ns string) *identityv0.User {
item := &identityv0.User{
func toUserItem(u *user.User, ns string) *iamv0.User {
item := &iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: u.UID,
Namespace: ns,
ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()),
CreationTimestamp: metav1.NewTime(u.Created),
},
Spec: identityv0.UserSpec{
Spec: iamv0.UserSpec{
Name: u.Name,
Login: u.Login,
Email: u.Email,