From b6540e2a18ea5a551e1e8447aa44781bb724a796 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 21 Aug 2024 09:16:47 +0200 Subject: [PATCH] SSOSettings: Add api:s (#92018) * apis: add sso setting resource * Implement Storage for sso * Rename packages * Merge identity and sso package * Update table format and expose GetNestedBool * Restructure identity api package --- .../apis/common/v0alpha1/unstructured.go | 8 + .../apis/identity/v0alpha1/types.go | 101 -------- .../apis/identity/v0alpha1/doc.go | 0 .../apis/identity/v0alpha1/register.go | 41 +++- pkg/apis/identity/v0alpha1/types_identity.go | 33 +++ .../v0alpha1/types_servier_account.go | 26 +++ pkg/apis/identity/v0alpha1/types_sso.go | 43 ++++ pkg/apis/identity/v0alpha1/types_team.go | 23 ++ pkg/apis/identity/v0alpha1/types_user.go | 27 +++ .../v0alpha1/zz_generated.deepcopy.go | 77 +++++++ .../v0alpha1/zz_generated.defaults.go | 0 .../identity/v0alpha1/zz_generated.openapi.go | 197 +++++++++++++--- ...enerated.openapi_violation_exceptions.list | 6 +- .../apis/identity/legacy_user_teams.go | 87 ------- pkg/registry/apis/identity/register.go | 85 +++---- .../{legacy_sa.go => serviceaccount/store.go} | 76 +++--- pkg/registry/apis/identity/sso/store.go | 218 ++++++++++++++++++ .../{legacy_teams.go => team/store.go} | 85 +++---- .../apis/identity/team/user_team_store.go | 88 +++++++ .../{display.go => user/display_store.go} | 41 ++-- .../{legacy_users.go => user/store.go} | 110 ++++----- pkg/registry/apis/wireset.go | 1 + 22 files changed, 938 insertions(+), 435 deletions(-) delete mode 100644 pkg/apimachinery/apis/identity/v0alpha1/types.go rename pkg/{apimachinery => }/apis/identity/v0alpha1/doc.go (100%) rename pkg/{apimachinery => }/apis/identity/v0alpha1/register.go (78%) create mode 100644 pkg/apis/identity/v0alpha1/types_identity.go create mode 100644 pkg/apis/identity/v0alpha1/types_servier_account.go create mode 100644 pkg/apis/identity/v0alpha1/types_sso.go create mode 100644 pkg/apis/identity/v0alpha1/types_team.go create mode 100644 pkg/apis/identity/v0alpha1/types_user.go rename pkg/{apimachinery => }/apis/identity/v0alpha1/zz_generated.deepcopy.go (78%) rename pkg/{apimachinery => }/apis/identity/v0alpha1/zz_generated.defaults.go (100%) rename pkg/{apimachinery => }/apis/identity/v0alpha1/zz_generated.openapi.go (62%) rename pkg/{apimachinery => }/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list (52%) delete mode 100644 pkg/registry/apis/identity/legacy_user_teams.go rename pkg/registry/apis/identity/{legacy_sa.go => serviceaccount/store.go} (52%) create mode 100644 pkg/registry/apis/identity/sso/store.go rename pkg/registry/apis/identity/{legacy_teams.go => team/store.go} (57%) create mode 100644 pkg/registry/apis/identity/team/user_team_store.go rename pkg/registry/apis/identity/{display.go => user/display_store.go} (76%) rename pkg/registry/apis/identity/{legacy_users.go => user/store.go} (54%) diff --git a/pkg/apimachinery/apis/common/v0alpha1/unstructured.go b/pkg/apimachinery/apis/common/v0alpha1/unstructured.go index 3a735623a68..af26531c59a 100644 --- a/pkg/apimachinery/apis/common/v0alpha1/unstructured.go +++ b/pkg/apimachinery/apis/common/v0alpha1/unstructured.go @@ -101,6 +101,14 @@ func (u *Unstructured) GetNestedString(fields ...string) string { return val } +func (u *Unstructured) GetNestedBool(fields ...string) bool { + val, found, err := unstructured.NestedBool(u.Object, fields...) + if !found || err != nil { + return false + } + return val +} + func (u *Unstructured) GetNestedStringSlice(fields ...string) []string { val, found, err := unstructured.NestedStringSlice(u.Object, fields...) if !found || err != nil { diff --git a/pkg/apimachinery/apis/identity/v0alpha1/types.go b/pkg/apimachinery/apis/identity/v0alpha1/types.go deleted file mode 100644 index 05c286d79ac..00000000000 --- a/pkg/apimachinery/apis/identity/v0alpha1/types.go +++ /dev/null @@ -1,101 +0,0 @@ -package v0alpha1 - -import ( - "github.com/grafana/authlib/claims" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type User struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec UserSpec `json:"spec,omitempty"` -} - -type UserSpec struct { - Name string `json:"name,omitempty"` - Login string `json:"login,omitempty"` - Email string `json:"email,omitempty"` - EmailVerified bool `json:"emailVerified,omitempty"` - Disabled bool `json:"disabled,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type UserList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []User `json:"items,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type Team struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec TeamSpec `json:"spec,omitempty"` -} - -type TeamSpec struct { - Title string `json:"name,omitempty"` - Email string `json:"email,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type TeamList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []Team `json:"items,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ServiceAccount struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec ServiceAccountSpec `json:"spec,omitempty"` -} - -type ServiceAccountSpec struct { - Name string `json:"name,omitempty"` - Email string `json:"email,omitempty"` - EmailVerified bool `json:"emailVerified,omitempty"` - Disabled bool `json:"disabled,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ServiceAccountList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []ServiceAccount `json:"items,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type IdentityDisplayResults struct { - metav1.TypeMeta `json:",inline"` - - // Request keys used to lookup the display value - // +listType=set - Keys []string `json:"keys"` - - // Matching items (the caller may need to remap from keys to results) - // +listType=atomic - Display []IdentityDisplay `json:"display"` - - // Input keys that were not useable - // +listType=set - InvalidKeys []string `json:"invalidKeys,omitempty"` -} - -type IdentityDisplay struct { - IdentityType claims.IdentityType `json:"type"` // The namespaced UID, eg `user|api-key|...` - UID string `json:"uid"` // The namespaced UID, eg `xyz` - Display string `json:"display"` - AvatarURL string `json:"avatarURL,omitempty"` - - // Legacy internal ID -- usage of this value should be phased out - InternalID int64 `json:"internalId,omitempty"` -} diff --git a/pkg/apimachinery/apis/identity/v0alpha1/doc.go b/pkg/apis/identity/v0alpha1/doc.go similarity index 100% rename from pkg/apimachinery/apis/identity/v0alpha1/doc.go rename to pkg/apis/identity/v0alpha1/doc.go diff --git a/pkg/apimachinery/apis/identity/v0alpha1/register.go b/pkg/apis/identity/v0alpha1/register.go similarity index 78% rename from pkg/apimachinery/apis/identity/v0alpha1/register.go rename to pkg/apis/identity/v0alpha1/register.go index 3c1ddaeaa1f..48da15e9797 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/register.go +++ b/pkg/apis/identity/v0alpha1/register.go @@ -95,6 +95,32 @@ var ServiceAccountResourceInfo = common.NewResourceInfo(GROUP, VERSION, }, ) +var SSOSettingResourceInfo = common.NewResourceInfo( + GROUP, VERSION, "ssosettings", "ssosetting", "SSOSetting", + func() runtime.Object { return &SSOSetting{} }, + func() runtime.Object { return &SSOSettingList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Source", Type: "string"}, + {Name: "Enabled", Type: "boolean"}, + {Name: "Created At", Type: "string", Format: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*SSOSetting) + if !ok { + return nil, fmt.Errorf("expected sso setting") + } + return []interface{}{ + m.Name, + m.Spec.Source, + m.Spec.Settings.GetNestedBool("enabled"), + m.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + }, + }, +) + var ( // SchemeGroupVersion is group version used to register these objects SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION} @@ -105,15 +131,10 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -func init() { - localSchemeBuilder.Register(func(s *runtime.Scheme) error { - return AddKnownTypes(s, VERSION) - }) -} - // Adds the list of known types to the given scheme. -func AddKnownTypes(scheme *runtime.Scheme, version string) error { - scheme.AddKnownTypes(schema.GroupVersion{Group: GROUP, Version: version}, +func AddKnownTypes(scheme *runtime.Scheme, version string) { + scheme.AddKnownTypes( + schema.GroupVersion{Group: GROUP, Version: version}, &User{}, &UserList{}, &ServiceAccount{}, @@ -121,9 +142,9 @@ func AddKnownTypes(scheme *runtime.Scheme, version string) error { &Team{}, &TeamList{}, &IdentityDisplayResults{}, + &SSOSetting{}, + &SSOSettingList{}, ) - // metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil } // Resource takes an unqualified resource and returns a Group qualified GroupResource diff --git a/pkg/apis/identity/v0alpha1/types_identity.go b/pkg/apis/identity/v0alpha1/types_identity.go new file mode 100644 index 00000000000..3ad562f2315 --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_identity.go @@ -0,0 +1,33 @@ +package v0alpha1 + +import ( + "github.com/grafana/authlib/claims" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type IdentityDisplayResults struct { + metav1.TypeMeta `json:",inline"` + + // Request keys used to lookup the display value + // +listType=set + Keys []string `json:"keys"` + + // Matching items (the caller may need to remap from keys to results) + // +listType=atomic + Display []IdentityDisplay `json:"display"` + + // Input keys that were not useable + // +listType=set + InvalidKeys []string `json:"invalidKeys,omitempty"` +} + +type IdentityDisplay struct { + IdentityType claims.IdentityType `json:"type"` // The namespaced UID, eg `user|api-key|...` + UID string `json:"uid"` // The namespaced UID, eg `xyz` + Display string `json:"display"` + AvatarURL string `json:"avatarURL,omitempty"` + + // Legacy internal ID -- usage of this value should be phased out + InternalID int64 `json:"internalId,omitempty"` +} diff --git a/pkg/apis/identity/v0alpha1/types_servier_account.go b/pkg/apis/identity/v0alpha1/types_servier_account.go new file mode 100644 index 00000000000..a00d8bb7663 --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_servier_account.go @@ -0,0 +1,26 @@ +package v0alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ServiceAccount struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ServiceAccountSpec `json:"spec,omitempty"` +} + +type ServiceAccountSpec struct { + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + EmailVerified bool `json:"emailVerified,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ServiceAccountList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []ServiceAccount `json:"items,omitempty"` +} diff --git a/pkg/apis/identity/v0alpha1/types_sso.go b/pkg/apis/identity/v0alpha1/types_sso.go new file mode 100644 index 00000000000..64eaed7d4b6 --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_sso.go @@ -0,0 +1,43 @@ +package v0alpha1 + +import ( + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type SSOSetting struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SSOSettingSpec `json:"spec,omitempty"` +} + +// SSOSettingSpec defines model for SSOSettingSpec. +type SSOSettingSpec struct { + Source Source `json:"source"` + Settings common.Unstructured `json:"settings"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type SSOSettingList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + Items []SSOSetting `json:"items,omitempty"` +} + +// Source for settings. +// +enum +type Source string + +// Defines values for ItemType. +const ( + SourceDB Source = "db" + // system is from config file, env or argument + SourceSystem Source = "system" +) diff --git a/pkg/apis/identity/v0alpha1/types_team.go b/pkg/apis/identity/v0alpha1/types_team.go new file mode 100644 index 00000000000..7b4ecba072c --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_team.go @@ -0,0 +1,23 @@ +package v0alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type Team struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TeamSpec `json:"spec,omitempty"` +} +type TeamSpec struct { + Title string `json:"name,omitempty"` + Email string `json:"email,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TeamList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []Team `json:"items,omitempty"` +} diff --git a/pkg/apis/identity/v0alpha1/types_user.go b/pkg/apis/identity/v0alpha1/types_user.go new file mode 100644 index 00000000000..4fc1f3448b7 --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_user.go @@ -0,0 +1,27 @@ +package v0alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type User struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UserSpec `json:"spec,omitempty"` +} + +type UserSpec struct { + Name string `json:"name,omitempty"` + Login string `json:"login,omitempty"` + Email string `json:"email,omitempty"` + EmailVerified bool `json:"emailVerified,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type UserList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []User `json:"items,omitempty"` +} diff --git a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/identity/v0alpha1/zz_generated.deepcopy.go similarity index 78% rename from pkg/apimachinery/apis/identity/v0alpha1/zz_generated.deepcopy.go rename to pkg/apis/identity/v0alpha1/zz_generated.deepcopy.go index fba461df2e9..652138bcec2 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/identity/v0alpha1/zz_generated.deepcopy.go @@ -67,6 +67,83 @@ func (in *IdentityDisplayResults) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SSOSetting) DeepCopyInto(out *SSOSetting) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSOSetting. +func (in *SSOSetting) DeepCopy() *SSOSetting { + if in == nil { + return nil + } + out := new(SSOSetting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SSOSetting) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SSOSettingList) DeepCopyInto(out *SSOSettingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SSOSetting, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSOSettingList. +func (in *SSOSettingList) DeepCopy() *SSOSettingList { + if in == nil { + return nil + } + out := new(SSOSettingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SSOSettingList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SSOSettingSpec) DeepCopyInto(out *SSOSettingSpec) { + *out = *in + in.Settings.DeepCopyInto(&out.Settings) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSOSettingSpec. +func (in *SSOSettingSpec) DeepCopy() *SSOSettingSpec { + if in == nil { + return nil + } + out := new(SSOSettingSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) { *out = *in diff --git a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.defaults.go b/pkg/apis/identity/v0alpha1/zz_generated.defaults.go similarity index 100% rename from pkg/apimachinery/apis/identity/v0alpha1/zz_generated.defaults.go rename to pkg/apis/identity/v0alpha1/zz_generated.defaults.go diff --git a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi.go b/pkg/apis/identity/v0alpha1/zz_generated.openapi.go similarity index 62% rename from pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi.go rename to pkg/apis/identity/v0alpha1/zz_generated.openapi.go index a5e4db9ca2b..9ba88920d25 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/identity/v0alpha1/zz_generated.openapi.go @@ -14,21 +14,24 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay": schema_apimachinery_apis_identity_v0alpha1_IdentityDisplay(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplayResults": schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayResults(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount": schema_apimachinery_apis_identity_v0alpha1_ServiceAccount(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountList": schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec": schema_apimachinery_apis_identity_v0alpha1_ServiceAccountSpec(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team": schema_apimachinery_apis_identity_v0alpha1_Team(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamList": schema_apimachinery_apis_identity_v0alpha1_TeamList(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec": schema_apimachinery_apis_identity_v0alpha1_TeamSpec(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User": schema_apimachinery_apis_identity_v0alpha1_User(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserList": schema_apimachinery_apis_identity_v0alpha1_UserList(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec": schema_apimachinery_apis_identity_v0alpha1_UserSpec(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.IdentityDisplay": schema_pkg_apis_identity_v0alpha1_IdentityDisplay(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.IdentityDisplayResults": schema_pkg_apis_identity_v0alpha1_IdentityDisplayResults(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSetting": schema_pkg_apis_identity_v0alpha1_SSOSetting(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSettingList": schema_pkg_apis_identity_v0alpha1_SSOSettingList(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSettingSpec": schema_pkg_apis_identity_v0alpha1_SSOSettingSpec(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccount": schema_pkg_apis_identity_v0alpha1_ServiceAccount(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccountList": schema_pkg_apis_identity_v0alpha1_ServiceAccountList(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccountSpec": schema_pkg_apis_identity_v0alpha1_ServiceAccountSpec(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.Team": schema_pkg_apis_identity_v0alpha1_Team(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.TeamList": schema_pkg_apis_identity_v0alpha1_TeamList(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.TeamSpec": schema_pkg_apis_identity_v0alpha1_TeamSpec(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.User": schema_pkg_apis_identity_v0alpha1_User(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.UserList": schema_pkg_apis_identity_v0alpha1_UserList(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.UserSpec": schema_pkg_apis_identity_v0alpha1_UserSpec(ref), } } -func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplay(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_IdentityDisplay(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -77,7 +80,7 @@ func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplay(ref common.Refer } } -func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayResults(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_IdentityDisplayResults(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -130,7 +133,7 @@ func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayResults(ref commo Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.IdentityDisplay"), }, }, }, @@ -161,11 +164,11 @@ func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayResults(ref commo }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.IdentityDisplay"}, } } -func schema_apimachinery_apis_identity_v0alpha1_ServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_SSOSetting(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -187,25 +190,26 @@ func schema_apimachinery_apis_identity_v0alpha1_ServiceAccount(ref common.Refere }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Description: "Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSettingSpec"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSettingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_SSOSettingList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -238,7 +242,7 @@ func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref common.Re Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSetting"), }, }, }, @@ -248,11 +252,128 @@ func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref common.Re }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSetting", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_SSOSettingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SSOSettingSpec defines model for SSOSettingSpec.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "source": { + SchemaProps: spec.SchemaProps{ + Description: "Possible enum values:\n - `\"db\"`\n - `\"system\"` system is from config file, env or argument", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"db", "system"}, + }, + }, + "settings": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), + }, + }, + }, + Required: []string{"source", "settings"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"}, + } +} + +func schema_pkg_apis_identity_v0alpha1_ServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccountSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccountSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_identity_v0alpha1_ServiceAccountList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccount"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_identity_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -288,7 +409,7 @@ func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountSpec(ref common.Re } } -func schema_apimachinery_apis_identity_v0alpha1_Team(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_Team(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -317,18 +438,18 @@ func schema_apimachinery_apis_identity_v0alpha1_Team(ref common.ReferenceCallbac "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.TeamSpec"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.TeamSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_TeamList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_TeamList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -361,7 +482,7 @@ func schema_apimachinery_apis_identity_v0alpha1_TeamList(ref common.ReferenceCal Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.Team"), }, }, }, @@ -371,11 +492,11 @@ func schema_apimachinery_apis_identity_v0alpha1_TeamList(ref common.ReferenceCal }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.Team", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_TeamSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_TeamSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -399,7 +520,7 @@ func schema_apimachinery_apis_identity_v0alpha1_TeamSpec(ref common.ReferenceCal } } -func schema_apimachinery_apis_identity_v0alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -428,18 +549,18 @@ func schema_apimachinery_apis_identity_v0alpha1_User(ref common.ReferenceCallbac "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.UserSpec"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.UserSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -472,7 +593,7 @@ func schema_apimachinery_apis_identity_v0alpha1_UserList(ref common.ReferenceCal Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.User"), }, }, }, @@ -482,11 +603,11 @@ func schema_apimachinery_apis_identity_v0alpha1_UserList(ref common.ReferenceCal }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ diff --git a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list similarity index 52% rename from pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list rename to pkg/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list index 93ccd0928ad..90ecfe61684 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,3 +1,3 @@ -API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,IdentityDisplay,IdentityType -API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,IdentityDisplay,InternalID -API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,TeamSpec,Title +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/identity/v0alpha1,IdentityDisplay,IdentityType +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/identity/v0alpha1,IdentityDisplay,InternalID +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/identity/v0alpha1,TeamSpec,Title diff --git a/pkg/registry/apis/identity/legacy_user_teams.go b/pkg/registry/apis/identity/legacy_user_teams.go deleted file mode 100644 index c164ba4dbce..00000000000 --- a/pkg/registry/apis/identity/legacy_user_teams.go +++ /dev/null @@ -1,87 +0,0 @@ -package identity - -import ( - "context" - "net/http" - - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/registry/rest" -) - -type userTeamsREST struct { - logger log.Logger - store legacy.LegacyIdentityStore -} - -var ( - _ rest.Storage = (*userTeamsREST)(nil) - _ rest.SingularNameProvider = (*userTeamsREST)(nil) - _ rest.Connecter = (*userTeamsREST)(nil) - _ rest.Scoper = (*userTeamsREST)(nil) - _ rest.StorageMetadata = (*userTeamsREST)(nil) -) - -func newUserTeamsREST(store legacy.LegacyIdentityStore) *userTeamsREST { - return &userTeamsREST{ - logger: log.New("user teams"), - store: store, - } -} - -func (r *userTeamsREST) New() runtime.Object { - return &identity.TeamList{} -} - -func (r *userTeamsREST) Destroy() {} - -func (r *userTeamsREST) NamespaceScoped() bool { - return true -} - -func (r *userTeamsREST) GetSingularName() string { - return "TeamList" // Used for the -} - -func (r *userTeamsREST) ProducesMIMETypes(verb string) []string { - return []string{"application/json"} // and parquet! -} - -func (r *userTeamsREST) ProducesObject(verb string) interface{} { - return &identity.TeamList{} -} - -func (r *userTeamsREST) ConnectMethods() []string { - return []string{"GET"} -} - -func (r *userTeamsREST) NewConnectOptions() (runtime.Object, bool, string) { - return nil, false, "" // true means you can use the trailing path as a variable -} - -func (r *userTeamsREST) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) { - ns, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - teams, err := r.store.GetUserTeams(ctx, ns, name) - if err != nil { - return nil, err - } - - return http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { - list := &identity.TeamList{} - for _, team := range teams { - t, err := asTeam(&team, ns.Value) - if err != nil { - responder.Error(err) - return - } - list.Items = append(list.Items, *t) - } - responder.Object(200, list) - }), nil -} diff --git a/pkg/registry/apis/identity/register.go b/pkg/registry/apis/identity/register.go index 778602f35a7..a39e59fae19 100644 --- a/pkg/registry/apis/identity/register.go +++ b/pkg/registry/apis/identity/register.go @@ -13,13 +13,18 @@ import ( genericapiserver "k8s.io/apiserver/pkg/server" common "k8s.io/kube-openapi/pkg/common" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - identityapi "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/identity" + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/registry/apis/identity/serviceaccount" + "github.com/grafana/grafana/pkg/registry/apis/identity/sso" + "github.com/grafana/grafana/pkg/registry/apis/identity/team" + "github.com/grafana/grafana/pkg/registry/apis/identity/user" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/ssosettings" "github.com/grafana/grafana/pkg/storage/legacysql" ) @@ -27,14 +32,14 @@ var _ builder.APIGroupBuilder = (*IdentityAPIBuilder)(nil) // This is used just so wire has something unique to return type IdentityAPIBuilder struct { - Store legacy.LegacyIdentityStore + Store legacy.LegacyIdentityStore + SSOService ssosettings.Service } func RegisterAPIService( features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, - // svcTeam team.Service, - // svcUser user.Service, + ssoService ssosettings.Service, sql db.DB, ) (*IdentityAPIBuilder, error) { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { @@ -42,34 +47,28 @@ func RegisterAPIService( } builder := &IdentityAPIBuilder{ - Store: legacy.NewLegacySQLStores(legacysql.NewDatabaseProvider(sql)), + Store: legacy.NewLegacySQLStores(legacysql.NewDatabaseProvider(sql)), + SSOService: ssoService, } apiregistration.RegisterAPI(builder) + return builder, nil } func (b *IdentityAPIBuilder) GetGroupVersion() schema.GroupVersion { - return identity.SchemeGroupVersion + return identityv0.SchemeGroupVersion } func (b *IdentityAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - if err := identity.AddKnownTypes(scheme, identity.VERSION); err != nil { - return err - } + identityv0.AddKnownTypes(scheme, identityv0.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" - if err := identity.AddKnownTypes(scheme, runtime.APIVersionInternal); err != nil { - return err - } + // "no kind is registered for the type" + identityv0.AddKnownTypes(scheme, runtime.APIVersionInternal) - // If multiple versions exist, then register conversions from zz_generated.conversion.go - // if err := playlist.RegisterConversions(scheme); err != nil { - // return err - // } - metav1.AddToGroupVersion(scheme, identity.SchemeGroupVersion) - return scheme.SetVersionPriority(identity.SchemeGroupVersion) + metav1.AddToGroupVersion(scheme, identityv0.SchemeGroupVersion) + return scheme.SetVersionPriority(identityv0.SchemeGroupVersion) } func (b *IdentityAPIBuilder) GetAPIGroupInfo( @@ -78,53 +77,45 @@ func (b *IdentityAPIBuilder) GetAPIGroupInfo( optsGetter generic.RESTOptionsGetter, dualWriteBuilder grafanarest.DualWriteBuilder, ) (*genericapiserver.APIGroupInfo, error) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(identity.GROUP, scheme, metav1.ParameterCodec, codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(identityv0.GROUP, scheme, metav1.ParameterCodec, codecs) storage := map[string]rest.Storage{} - team := identity.TeamResourceInfo - teamStore := &legacyTeamStorage{ - service: b.Store, - resourceInfo: team, - tableConverter: team.TableConverter(), - } - storage[team.StoragePath()] = teamStore + teamResource := identityv0.TeamResourceInfo + storage[teamResource.StoragePath()] = team.NewLegacyStore(b.Store) - user := identity.UserResourceInfo - userStore := &legacyUserStorage{ - service: b.Store, - resourceInfo: user, - tableConverter: user.TableConverter(), - } - storage[user.StoragePath()] = userStore - storage[user.StoragePath("teams")] = newUserTeamsREST(b.Store) + userResource := identityv0.UserResourceInfo + storage[userResource.StoragePath()] = user.NewLegacyStore(b.Store) + storage[userResource.StoragePath("teams")] = team.NewLegacyUserTeamsStore(b.Store) - sa := identity.ServiceAccountResourceInfo - saStore := &legacyServiceAccountStorage{ - service: b.Store, - resourceInfo: sa, - tableConverter: sa.TableConverter(), + serviceaccountResource := identityv0.ServiceAccountResourceInfo + storage[serviceaccountResource.StoragePath()] = serviceaccount.NewLegacyStore(b.Store) + + if b.SSOService != nil { + ssoResource := identityv0.SSOSettingResourceInfo + storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.SSOService) } - storage[sa.StoragePath()] = saStore // The display endpoint -- NOTE, this uses a rewrite hack to allow requests without a name parameter - storage["display"] = newDisplayREST(b.Store) + storage["display"] = user.NewLegacyDisplayStore(b.Store) - apiGroupInfo.VersionedResourcesStorageMap[identity.VERSION] = storage + apiGroupInfo.VersionedResourcesStorageMap[identityv0.VERSION] = storage return &apiGroupInfo, nil } func (b *IdentityAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return identity.GetOpenAPIDefinitions + return identityv0.GetOpenAPIDefinitions } func (b *IdentityAPIBuilder) GetAPIRoutes() *builder.APIRoutes { - return nil // no custom API routes + // no custom API routes + return nil } func (b *IdentityAPIBuilder) GetAuthorizer() authorizer.Authorizer { + // TODO: handle authorization based in entity. return authorizer.AuthorizerFunc( func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { - user, err := identityapi.GetRequester(ctx) + user, err := identity.GetRequester(ctx) if err != nil { return authorizer.DecisionDeny, "no identity found", err } diff --git a/pkg/registry/apis/identity/legacy_sa.go b/pkg/registry/apis/identity/serviceaccount/store.go similarity index 52% rename from pkg/registry/apis/identity/legacy_sa.go rename to pkg/registry/apis/identity/serviceaccount/store.go index 606b3a6b6a5..2d963c17248 100644 --- a/pkg/registry/apis/identity/legacy_sa.go +++ b/pkg/registry/apis/identity/serviceaccount/store.go @@ -1,59 +1,63 @@ -package identity +package serviceaccount import ( "context" "fmt" "strconv" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/utils" - "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/user" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/user" ) var ( - _ rest.Scoper = (*legacyServiceAccountStorage)(nil) - _ rest.SingularNameProvider = (*legacyServiceAccountStorage)(nil) - _ rest.Getter = (*legacyServiceAccountStorage)(nil) - _ rest.Lister = (*legacyServiceAccountStorage)(nil) - _ rest.Storage = (*legacyServiceAccountStorage)(nil) + _ rest.Scoper = (*LegacyStore)(nil) + _ rest.SingularNameProvider = (*LegacyStore)(nil) + _ rest.Getter = (*LegacyStore)(nil) + _ rest.Lister = (*LegacyStore)(nil) + _ rest.Storage = (*LegacyStore)(nil) ) -type legacyServiceAccountStorage struct { - service legacy.LegacyIdentityStore - tableConverter rest.TableConvertor - resourceInfo common.ResourceInfo +var resource = identityv0.ServiceAccountResourceInfo + +func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore { + return &LegacyStore{store} } -func (s *legacyServiceAccountStorage) New() runtime.Object { - return s.resourceInfo.NewFunc() +type LegacyStore struct { + store legacy.LegacyIdentityStore } -func (s *legacyServiceAccountStorage) Destroy() {} +func (s *LegacyStore) New() runtime.Object { + return resource.NewFunc() +} -func (s *legacyServiceAccountStorage) NamespaceScoped() bool { +func (s *LegacyStore) Destroy() {} + +func (s *LegacyStore) NamespaceScoped() bool { return true // namespace == org } -func (s *legacyServiceAccountStorage) GetSingularName() string { - return s.resourceInfo.GetSingularName() +func (s *LegacyStore) GetSingularName() string { + return resource.GetSingularName() } -func (s *legacyServiceAccountStorage) NewList() runtime.Object { - return s.resourceInfo.NewListFunc() +func (s *LegacyStore) NewList() runtime.Object { + return resource.NewListFunc() } -func (s *legacyServiceAccountStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) } -func (s *legacyServiceAccountStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -70,12 +74,12 @@ func (s *legacyServiceAccountStorage) List(ctx context.Context, options *interna } } - found, err := s.service.ListUsers(ctx, ns, query) + found, err := s.store.ListUsers(ctx, ns, query) if err != nil { return nil, err } - list := &identity.ServiceAccountList{} + list := &identityv0.ServiceAccountList{} for _, item := range found.Users { list.Items = append(list.Items, *toSAItem(&item, ns.Value)) } @@ -88,15 +92,15 @@ func (s *legacyServiceAccountStorage) List(ctx context.Context, options *interna return list, err } -func toSAItem(u *user.User, ns string) *identity.ServiceAccount { - item := &identity.ServiceAccount{ +func toSAItem(u *user.User, ns string) *identityv0.ServiceAccount { + item := &identityv0.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: u.UID, Namespace: ns, ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()), CreationTimestamp: metav1.NewTime(u.Created), }, - Spec: identity.ServiceAccountSpec{ + Spec: identityv0.ServiceAccountSpec{ Name: u.Name, Email: u.Email, EmailVerified: u.EmailVerified, @@ -112,7 +116,7 @@ func toSAItem(u *user.User, ns string) *identity.ServiceAccount { return item } -func (s *legacyServiceAccountStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { +func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -123,12 +127,12 @@ func (s *legacyServiceAccountStorage) Get(ctx context.Context, name string, opti IsServiceAccount: true, } - found, err := s.service.ListUsers(ctx, ns, query) + found, err := s.store.ListUsers(ctx, ns, query) if found == nil || err != nil { - return nil, s.resourceInfo.NewNotFound(name) + return nil, resource.NewNotFound(name) } if len(found.Users) < 1 { - return nil, s.resourceInfo.NewNotFound(name) + return nil, resource.NewNotFound(name) } return toSAItem(&found.Users[0], ns.Value), nil } diff --git a/pkg/registry/apis/identity/sso/store.go b/pkg/registry/apis/identity/sso/store.go new file mode 100644 index 00000000000..0e1fdf5f277 --- /dev/null +++ b/pkg/registry/apis/identity/sso/store.go @@ -0,0 +1,218 @@ +package sso + +import ( + "context" + "errors" + "fmt" + + commonv1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/registry/rest" + + identityv0 "github.com/grafana/grafana/pkg/apis/identity/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" +) + +var ( + _ rest.Storage = (*LegacyStore)(nil) + _ rest.Scoper = (*LegacyStore)(nil) + _ rest.Getter = (*LegacyStore)(nil) + _ rest.Lister = (*LegacyStore)(nil) + _ rest.Updater = (*LegacyStore)(nil) + _ rest.SingularNameProvider = (*LegacyStore)(nil) + _ rest.GracefulDeleter = (*LegacyStore)(nil) +) + +var resource = identityv0.SSOSettingResourceInfo + +func NewLegacyStore(service ssosettings.Service) *LegacyStore { + return &LegacyStore{service} +} + +type LegacyStore struct { + service ssosettings.Service +} + +// Destroy implements rest.Storage. +func (s *LegacyStore) Destroy() {} + +// NamespaceScoped implements rest.Scoper. +func (s *LegacyStore) NamespaceScoped() bool { + // this is maybe incorrect + return true +} + +// GetSingularName implements rest.SingularNameProvider. +func (s *LegacyStore) GetSingularName() string { + return resource.GetSingularName() +} + +// New implements rest.Storage. +func (s *LegacyStore) New() runtime.Object { + return resource.NewFunc() +} + +// ConvertToTable implements rest.Lister. +func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) +} + +// NewList implements rest.Lister. +func (s *LegacyStore) NewList() runtime.Object { + return resource.NewListFunc() +} + +// List implements rest.Lister. +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ns, _ := request.NamespaceInfoFrom(ctx, false) + + settings, err := s.service.List(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list sso settings: %w", err) + } + + list := &identityv0.SSOSettingList{} + for _, s := range settings { + list.Items = append(list.Items, mapToObject(ns.Value, s)) + } + + return list, nil +} + +// Get implements rest.Getter. +func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ns, _ := request.NamespaceInfoFrom(ctx, false) + + setting, err := s.service.GetForProviderWithRedactedSecrets(ctx, name) + if err != nil { + if errors.Is(err, ssosettings.ErrNotFound) { + return nil, resource.NewNotFound(name) + } + return nil, err + } + + object := mapToObject(ns.Value, setting) + return &object, nil +} + +// Update implements rest.Updater. +func (s *LegacyStore) Update( + ctx context.Context, + name string, + objInfo rest.UpdatedObjectInfo, + _ rest.ValidateObjectFunc, + _ rest.ValidateObjectUpdateFunc, + _ bool, + _ *metav1.UpdateOptions, +) (runtime.Object, bool, error) { + const created = false + ident, err := identity.GetRequester(ctx) + if err != nil { + return nil, created, err + } + + old, err := s.Get(ctx, name, nil) + if err != nil { + return old, created, err + } + + obj, err := objInfo.UpdatedObject(ctx, old) + if err != nil { + return old, created, err + } + + setting, ok := obj.(*identityv0.SSOSetting) + if !ok { + return old, created, errors.New("expected ssosetting after update") + } + + if err := s.service.Upsert(ctx, mapToModel(setting), ident); err != nil { + return old, created, err + } + + updated, err := s.Get(ctx, name, nil) + return updated, created, err +} + +// Delete implements rest.GracefulDeleter. +func (s *LegacyStore) Delete( + ctx context.Context, + name string, + _ rest.ValidateObjectFunc, + options *metav1.DeleteOptions, +) (runtime.Object, bool, error) { + obj, err := s.Get(ctx, name, nil) + if err != nil { + return obj, false, err + } + + old, ok := obj.(*identityv0.SSOSetting) + if !ok { + return obj, false, errors.New("expected ssosetting") + } + + // FIXME(kalleep): this should probably be validated in transaction + if options.Preconditions != nil && options.Preconditions.ResourceVersion != nil { + if *options.Preconditions.ResourceVersion != old.GetResourceVersion() { + return old, false, apierrors.NewConflict( + resource.GroupResource(), + name, + fmt.Errorf( + "the ResourceVersion in the precondition (%s) does not match the ResourceVersion in record (%s). The object might have been modified", + *options.Preconditions.ResourceVersion, + old.GetResourceVersion(), + ), + ) + } + } + + if err := s.service.Delete(ctx, name); err != nil { + return old, false, err + } + + // If settings for a provider is deleted from db they will fallback to settings from config file, env or arguments. + afterDelete, err := s.Get(ctx, name, nil) + return afterDelete, false, err +} + +func mapToObject(ns string, s *ssomodels.SSOSettings) identityv0.SSOSetting { + source := identityv0.SourceDB + if s.Source == ssomodels.System { + source = identityv0.SourceSystem + } + + version := "0" + if !s.Updated.IsZero() { + version = fmt.Sprintf("%d", s.Updated.UnixMilli()) + } + + object := identityv0.SSOSetting{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.Provider, + Namespace: ns, + UID: types.UID(s.Provider), + ResourceVersion: version, + CreationTimestamp: metav1.NewTime(s.Updated), + }, + Spec: identityv0.SSOSettingSpec{ + Source: source, + Settings: commonv1.Unstructured{Object: s.Settings}, + }, + } + + return object +} + +func mapToModel(obj *identityv0.SSOSetting) *ssomodels.SSOSettings { + return &ssomodels.SSOSettings{ + Provider: obj.Name, + Settings: obj.Spec.Settings.Object, + } +} diff --git a/pkg/registry/apis/identity/legacy_teams.go b/pkg/registry/apis/identity/team/store.go similarity index 57% rename from pkg/registry/apis/identity/legacy_teams.go rename to pkg/registry/apis/identity/team/store.go index bdcb84550fd..7bfde79ad35 100644 --- a/pkg/registry/apis/identity/legacy_teams.go +++ b/pkg/registry/apis/identity/team/store.go @@ -1,81 +1,86 @@ -package identity +package team import ( "context" "strconv" - "github.com/grafana/authlib/claims" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/utils" - "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/team" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + + "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/apimachinery/utils" + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/team" ) var ( - _ rest.Scoper = (*legacyTeamStorage)(nil) - _ rest.SingularNameProvider = (*legacyTeamStorage)(nil) - _ rest.Getter = (*legacyTeamStorage)(nil) - _ rest.Lister = (*legacyTeamStorage)(nil) - _ rest.Storage = (*legacyTeamStorage)(nil) + _ rest.Scoper = (*LegacyStore)(nil) + _ rest.SingularNameProvider = (*LegacyStore)(nil) + _ rest.Getter = (*LegacyStore)(nil) + _ rest.Lister = (*LegacyStore)(nil) + _ rest.Storage = (*LegacyStore)(nil) ) -type legacyTeamStorage struct { - service legacy.LegacyIdentityStore - tableConverter rest.TableConvertor - resourceInfo common.ResourceInfo +var resource = identityv0.TeamResourceInfo + +func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore { + return &LegacyStore{store} } -func (s *legacyTeamStorage) New() runtime.Object { - return s.resourceInfo.NewFunc() +type LegacyStore struct { + store legacy.LegacyIdentityStore } -func (s *legacyTeamStorage) Destroy() {} - -func (s *legacyTeamStorage) NamespaceScoped() bool { - return true // namespace == org +func (s *LegacyStore) New() runtime.Object { + return resource.NewFunc() } -func (s *legacyTeamStorage) GetSingularName() string { - return s.resourceInfo.GetSingularName() +func (s *LegacyStore) Destroy() {} + +func (s *LegacyStore) NamespaceScoped() bool { + // namespace == org + return true } -func (s *legacyTeamStorage) NewList() runtime.Object { - return s.resourceInfo.NewListFunc() +func (s *LegacyStore) GetSingularName() string { + return resource.GetSingularName() } -func (s *legacyTeamStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +func (s *LegacyStore) NewList() runtime.Object { + return resource.NewListFunc() } -func (s *legacyTeamStorage) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*identity.TeamList, error) { +func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) +} + +func (s *LegacyStore) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*identityv0.TeamList, error) { if query.Limit < 1 { query.Limit = 100 } - rsp, err := s.service.ListTeams(ctx, ns, query) + rsp, err := s.store.ListTeams(ctx, ns, query) if err != nil { return nil, err } - list := &identity.TeamList{ + list := &identityv0.TeamList{ ListMeta: metav1.ListMeta{ ResourceVersion: strconv.FormatInt(rsp.RV, 10), }, } for _, team := range rsp.Teams { - item := identity.Team{ + item := identityv0.Team{ ObjectMeta: metav1.ObjectMeta{ Name: team.UID, Namespace: ns.Value, CreationTimestamp: metav1.NewTime(team.Created), ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10), }, - Spec: identity.TeamSpec{ + Spec: identityv0.TeamSpec{ Title: team.Name, Email: team.Email, }, @@ -97,7 +102,7 @@ func (s *legacyTeamStorage) doList(ctx context.Context, ns claims.NamespaceInfo, return list, nil } -func (s *legacyTeamStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -115,7 +120,7 @@ func (s *legacyTeamStorage) List(ctx context.Context, options *internalversion.L return s.doList(ctx, ns, query) } -func (s *legacyTeamStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { +func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -131,18 +136,18 @@ func (s *legacyTeamStorage) Get(ctx context.Context, name string, options *metav if len(rsp.Items) > 0 { return &rsp.Items[0], nil } - return nil, s.resourceInfo.NewNotFound(name) + return nil, resource.NewNotFound(name) } -func asTeam(team *team.Team, ns string) (*identity.Team, error) { - item := &identity.Team{ +func asTeam(team *team.Team, ns string) (*identityv0.Team, error) { + item := &identityv0.Team{ ObjectMeta: metav1.ObjectMeta{ Name: team.UID, Namespace: ns, CreationTimestamp: metav1.NewTime(team.Created), ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10), }, - Spec: identity.TeamSpec{ + Spec: identityv0.TeamSpec{ Title: team.Name, Email: team.Email, }, diff --git a/pkg/registry/apis/identity/team/user_team_store.go b/pkg/registry/apis/identity/team/user_team_store.go new file mode 100644 index 00000000000..2d9128a5e33 --- /dev/null +++ b/pkg/registry/apis/identity/team/user_team_store.go @@ -0,0 +1,88 @@ +package team + +import ( + "context" + "net/http" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" + + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" +) + +var ( + _ rest.Storage = (*LegacyUserTeamsStore)(nil) + _ rest.SingularNameProvider = (*LegacyUserTeamsStore)(nil) + _ rest.Connecter = (*LegacyUserTeamsStore)(nil) + _ rest.Scoper = (*LegacyUserTeamsStore)(nil) + _ rest.StorageMetadata = (*LegacyUserTeamsStore)(nil) +) + +func NewLegacyUserTeamsStore(store legacy.LegacyIdentityStore) *LegacyUserTeamsStore { + return &LegacyUserTeamsStore{ + logger: log.New("user teams"), + store: store, + } +} + +type LegacyUserTeamsStore struct { + logger log.Logger + store legacy.LegacyIdentityStore +} + +func (r *LegacyUserTeamsStore) New() runtime.Object { + return &identityv0.TeamList{} +} + +func (r *LegacyUserTeamsStore) Destroy() {} + +func (r *LegacyUserTeamsStore) NamespaceScoped() bool { + return true +} + +func (r *LegacyUserTeamsStore) GetSingularName() string { + return "TeamList" +} + +func (r *LegacyUserTeamsStore) ProducesMIMETypes(verb string) []string { + return []string{"application/json"} +} + +func (r *LegacyUserTeamsStore) ProducesObject(verb string) interface{} { + return &identityv0.TeamList{} +} + +func (r *LegacyUserTeamsStore) ConnectMethods() []string { + return []string{"GET"} +} + +func (r *LegacyUserTeamsStore) NewConnectOptions() (runtime.Object, bool, string) { + return nil, false, "" // true means you can use the trailing path as a variable +} + +func (r *LegacyUserTeamsStore) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + teams, err := r.store.GetUserTeams(ctx, ns, name) + if err != nil { + return nil, err + } + + return http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + list := &identityv0.TeamList{} + for _, team := range teams { + t, err := asTeam(&team, ns.Value) + if err != nil { + responder.Error(err) + return + } + list.Items = append(list.Items, *t) + } + responder.Object(200, list) + }), nil +} diff --git a/pkg/registry/apis/identity/display.go b/pkg/registry/apis/identity/user/display_store.go similarity index 76% rename from pkg/registry/apis/identity/display.go rename to pkg/registry/apis/identity/user/display_store.go index 16527fbdaa2..6d399968520 100644 --- a/pkg/registry/apis/identity/display.go +++ b/pkg/registry/apis/identity/user/display_store.go @@ -1,4 +1,4 @@ -package identity +package user import ( "context" @@ -8,7 +8,7 @@ import ( "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" + identity "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/setting" @@ -18,56 +18,57 @@ import ( "k8s.io/apiserver/pkg/registry/rest" ) -type displayREST struct { +type LegacyDisplayStore struct { store legacy.LegacyIdentityStore } var ( - _ rest.Storage = (*displayREST)(nil) - _ rest.SingularNameProvider = (*displayREST)(nil) - _ rest.Connecter = (*displayREST)(nil) - _ rest.Scoper = (*displayREST)(nil) - _ rest.StorageMetadata = (*displayREST)(nil) + _ rest.Storage = (*LegacyDisplayStore)(nil) + _ rest.SingularNameProvider = (*LegacyDisplayStore)(nil) + _ rest.Connecter = (*LegacyDisplayStore)(nil) + _ rest.Scoper = (*LegacyDisplayStore)(nil) + _ rest.StorageMetadata = (*LegacyDisplayStore)(nil) ) -func newDisplayREST(store legacy.LegacyIdentityStore) *displayREST { - return &displayREST{store} +func NewLegacyDisplayStore(store legacy.LegacyIdentityStore) *LegacyDisplayStore { + return &LegacyDisplayStore{store} } -func (r *displayREST) New() runtime.Object { +func (r *LegacyDisplayStore) New() runtime.Object { return &identity.IdentityDisplayResults{} } -func (r *displayREST) Destroy() {} +func (r *LegacyDisplayStore) Destroy() {} -func (r *displayREST) NamespaceScoped() bool { +func (r *LegacyDisplayStore) NamespaceScoped() bool { return true } -func (r *displayREST) GetSingularName() string { - return "IdentityDisplay" // not actually used anywhere, but required by SingularNameProvider +func (r *LegacyDisplayStore) GetSingularName() string { + // not actually used anywhere, but required by SingularNameProvider + return "IdentityDisplay" } -func (r *displayREST) ProducesMIMETypes(verb string) []string { +func (r *LegacyDisplayStore) ProducesMIMETypes(verb string) []string { return []string{"application/json"} } -func (r *displayREST) ProducesObject(verb string) any { +func (r *LegacyDisplayStore) ProducesObject(verb string) any { return &identity.IdentityDisplayResults{} } -func (r *displayREST) ConnectMethods() []string { +func (r *LegacyDisplayStore) ConnectMethods() []string { return []string{"GET"} } -func (r *displayREST) NewConnectOptions() (runtime.Object, bool, string) { +func (r *LegacyDisplayStore) NewConnectOptions() (runtime.Object, bool, string) { return nil, false, "" // true means you can use the trailing path as a variable } // This will always have an empty app url var fakeCfgForGravatar = &setting.Cfg{} -func (r *displayREST) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) { +func (r *LegacyDisplayStore) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) { // See: /pkg/services/apiserver/builder/helper.go#L34 // The name is set with a rewriter hack if name != "name" { diff --git a/pkg/registry/apis/identity/legacy_users.go b/pkg/registry/apis/identity/user/store.go similarity index 54% rename from pkg/registry/apis/identity/legacy_users.go rename to pkg/registry/apis/identity/user/store.go index 321e755c8fa..80066c20efc 100644 --- a/pkg/registry/apis/identity/legacy_users.go +++ b/pkg/registry/apis/identity/user/store.go @@ -1,59 +1,63 @@ -package identity +package user import ( "context" "fmt" "strconv" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/utils" - "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/user" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/user" ) var ( - _ rest.Scoper = (*legacyUserStorage)(nil) - _ rest.SingularNameProvider = (*legacyUserStorage)(nil) - _ rest.Getter = (*legacyUserStorage)(nil) - _ rest.Lister = (*legacyUserStorage)(nil) - _ rest.Storage = (*legacyUserStorage)(nil) + _ rest.Scoper = (*LegacyStore)(nil) + _ rest.SingularNameProvider = (*LegacyStore)(nil) + _ rest.Getter = (*LegacyStore)(nil) + _ rest.Lister = (*LegacyStore)(nil) + _ rest.Storage = (*LegacyStore)(nil) ) -type legacyUserStorage struct { - service legacy.LegacyIdentityStore - tableConverter rest.TableConvertor - resourceInfo common.ResourceInfo +var resource = identityv0.UserResourceInfo + +func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore { + return &LegacyStore{store} } -func (s *legacyUserStorage) New() runtime.Object { - return s.resourceInfo.NewFunc() +type LegacyStore struct { + store legacy.LegacyIdentityStore } -func (s *legacyUserStorage) Destroy() {} +func (s *LegacyStore) New() runtime.Object { + return resource.NewFunc() +} -func (s *legacyUserStorage) NamespaceScoped() bool { +func (s *LegacyStore) Destroy() {} + +func (s *LegacyStore) NamespaceScoped() bool { return true // namespace == org } -func (s *legacyUserStorage) GetSingularName() string { - return s.resourceInfo.GetSingularName() +func (s *LegacyStore) GetSingularName() string { + return resource.GetSingularName() } -func (s *legacyUserStorage) NewList() runtime.Object { - return s.resourceInfo.NewListFunc() +func (s *LegacyStore) NewList() runtime.Object { + return resource.NewListFunc() } -func (s *legacyUserStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) } -func (s *legacyUserStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -70,12 +74,12 @@ func (s *legacyUserStorage) List(ctx context.Context, options *internalversion.L } } - found, err := s.service.ListUsers(ctx, ns, query) + found, err := s.store.ListUsers(ctx, ns, query) if err != nil { return nil, err } - list := &identity.UserList{} + list := &identityv0.UserList{} for _, item := range found.Users { list.Items = append(list.Items, *toUserItem(&item, ns.Value)) } @@ -88,15 +92,36 @@ func (s *legacyUserStorage) List(ctx context.Context, options *internalversion.L return list, err } -func toUserItem(u *user.User, ns string) *identity.User { - item := &identity.User{ +func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + query := legacy.ListUserQuery{ + OrgID: ns.OrgID, + Limit: 1, + IsServiceAccount: false, + } + + found, err := s.store.ListUsers(ctx, ns, query) + if found == nil || err != nil { + return nil, resource.NewNotFound(name) + } + if len(found.Users) < 1 { + return nil, resource.NewNotFound(name) + } + return toUserItem(&found.Users[0], ns.Value), nil +} + +func toUserItem(u *user.User, ns string) *identityv0.User { + item := &identityv0.User{ ObjectMeta: metav1.ObjectMeta{ Name: u.UID, Namespace: ns, ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()), CreationTimestamp: metav1.NewTime(u.Created), }, - Spec: identity.UserSpec{ + Spec: identityv0.UserSpec{ Name: u.Name, Login: u.Login, Email: u.Email, @@ -112,24 +137,3 @@ func toUserItem(u *user.User, ns string) *identity.User { }) return item } - -func (s *legacyUserStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - ns, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - query := legacy.ListUserQuery{ - OrgID: ns.OrgID, - Limit: 1, - IsServiceAccount: false, - } - - found, err := s.service.ListUsers(ctx, ns, query) - if found == nil || err != nil { - return nil, s.resourceInfo.NewNotFound(name) - } - if len(found.Users) < 1 { - return nil, s.resourceInfo.NewNotFound(name) - } - return toUserItem(&found.Users[0], ns.Value), nil -} diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index f7eb37f3064..f6ceafac35e 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -39,4 +39,5 @@ var WireSet = wire.NewSet( query.RegisterAPIService, scope.RegisterAPIService, notifications.RegisterAPIService, + //sso.RegisterAPIService, )