From bc20592fd432367705032a63f73f4eafc3e2a13e Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Thu, 5 Sep 2024 16:10:23 +0200 Subject: [PATCH] IAM: Add and use identity ref (#92978) * Add and use identity ref --------- Co-authored-by: Ryan McKinley --- pkg/apis/iam/v0alpha1/register.go | 2 +- pkg/apis/iam/v0alpha1/types_identity.go | 23 +++-- pkg/apis/iam/v0alpha1/types_team.go | 6 +- .../iam/v0alpha1/zz_generated.deepcopy.go | 20 ++++- pkg/apis/iam/v0alpha1/zz_generated.openapi.go | 87 +++++++++++-------- ...enerated.openapi_violation_exceptions.list | 1 - pkg/registry/apis/iam/team/rest_members.go | 12 +-- pkg/registry/apis/iam/team/store_binding.go | 7 +- pkg/registry/apis/iam/user/rest_display.go | 48 ++++++---- 9 files changed, 132 insertions(+), 74 deletions(-) diff --git a/pkg/apis/iam/v0alpha1/register.go b/pkg/apis/iam/v0alpha1/register.go index 688befc53b2..d058a6f9d0f 100644 --- a/pkg/apis/iam/v0alpha1/register.go +++ b/pkg/apis/iam/v0alpha1/register.go @@ -137,7 +137,7 @@ var TeamBindingResourceInfo = utils.NewResourceInfo( } return []interface{}{ m.Name, - m.Spec.TeamRef.Name, + m.Spec.Team.Name, m.CreationTimestamp.UTC().Format(time.RFC3339), }, nil }, diff --git a/pkg/apis/iam/v0alpha1/types_identity.go b/pkg/apis/iam/v0alpha1/types_identity.go index f0735958851..5b5269641c9 100644 --- a/pkg/apis/iam/v0alpha1/types_identity.go +++ b/pkg/apis/iam/v0alpha1/types_identity.go @@ -2,6 +2,7 @@ package v0alpha1 import ( "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/apimachinery/identity" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -23,15 +24,10 @@ type IdentityDisplayResults struct { } type IdentityDisplay struct { - // Type of identity e.g. "user". - // For a full list see https://github.com/grafana/authlib/blob/2f8d13a83ca3e82da08b53726de1697ee5b5b4cc/claims/type.go#L15-L24 - IdentityType claims.IdentityType `json:"type"` - - // UID for identity, is a unique value for the type within a namespace. - UID string `json:"uid"` + Identity IdentityRef `json:"identity"` // Display name for identity. - Display string `json:"display"` + DisplayName string `json:"displayName"` // AvatarURL is the url where we can get the avatar for identity AvatarURL string `json:"avatarURL,omitempty"` @@ -39,3 +35,16 @@ type IdentityDisplay struct { // InternalID is the legacy numreric id for identity, this is deprecated and should be phased out InternalID int64 `json:"internalId,omitempty"` } + +type IdentityRef struct { + // Type of identity e.g. "user". + // For a full list see https://github.com/grafana/authlib/blob/2f8d13a83ca3e82da08b53726de1697ee5b5b4cc/claims/type.go#L15-L24 + Type claims.IdentityType `json:"type"` + + // Name is the unique identifier for identity, guaranteed jo be a unique value for the type within a namespace. + Name string `json:"name"` +} + +func (i *IdentityRef) String() string { + return identity.NewTypedIDString(i.Type, i.Name) +} diff --git a/pkg/apis/iam/v0alpha1/types_team.go b/pkg/apis/iam/v0alpha1/types_team.go index b4621401386..e9e0b027c3f 100644 --- a/pkg/apis/iam/v0alpha1/types_team.go +++ b/pkg/apis/iam/v0alpha1/types_team.go @@ -35,7 +35,7 @@ type TeamBinding struct { type TeamBindingSpec struct { Subjects []TeamSubject `json:"subjects,omitempty"` - TeamRef TeamRef `json:"teamRef,omitempty"` + Team TeamRef `json:"team,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -47,8 +47,8 @@ type TeamBindingList struct { } type TeamSubject struct { - // Name is the unique identifier for subject. - Name string `json:"name,omitempty"` + // Identity is a reference to the identity of this subject. + Identity IdentityRef `json:"identity"` // Permission subject has in team. Permission TeamPermission `json:"permission,omitempty"` diff --git a/pkg/apis/iam/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/iam/v0alpha1/zz_generated.deepcopy.go index 0ef8cff060b..c72c57ecdbd 100644 --- a/pkg/apis/iam/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/iam/v0alpha1/zz_generated.deepcopy.go @@ -14,6 +14,7 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IdentityDisplay) DeepCopyInto(out *IdentityDisplay) { *out = *in + out.Identity = in.Identity return } @@ -67,6 +68,22 @@ 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 *IdentityRef) DeepCopyInto(out *IdentityRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IdentityRef. +func (in *IdentityRef) DeepCopy() *IdentityRef { + if in == nil { + return nil + } + out := new(IdentityRef) + in.DeepCopyInto(out) + return out +} + // 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 @@ -373,7 +390,7 @@ func (in *TeamBindingSpec) DeepCopyInto(out *TeamBindingSpec) { *out = make([]TeamSubject, len(*in)) copy(*out, *in) } - out.TeamRef = in.TeamRef + out.Team = in.Team return } @@ -503,6 +520,7 @@ func (in *TeamSpec) DeepCopy() *TeamSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TeamSubject) DeepCopyInto(out *TeamSubject) { *out = *in + out.Identity = in.Identity return } diff --git a/pkg/apis/iam/v0alpha1/zz_generated.openapi.go b/pkg/apis/iam/v0alpha1/zz_generated.openapi.go index 8e1357781ab..49017279f61 100644 --- a/pkg/apis/iam/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/iam/v0alpha1/zz_generated.openapi.go @@ -16,6 +16,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA return map[string]common.OpenAPIDefinition{ "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityDisplay": schema_pkg_apis_iam_v0alpha1_IdentityDisplay(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityDisplayResults": schema_pkg_apis_iam_v0alpha1_IdentityDisplayResults(ref), + "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityRef": schema_pkg_apis_iam_v0alpha1_IdentityRef(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.SSOSetting": schema_pkg_apis_iam_v0alpha1_SSOSetting(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.SSOSettingList": schema_pkg_apis_iam_v0alpha1_SSOSettingList(ref), "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.SSOSettingSpec": schema_pkg_apis_iam_v0alpha1_SSOSettingSpec(ref), @@ -48,23 +49,13 @@ func schema_pkg_apis_iam_v0alpha1_IdentityDisplay(ref common.ReferenceCallback) SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "type": { + "identity": { SchemaProps: spec.SchemaProps{ - Description: "Type of identity e.g. \"user\". For a full list see https://github.com/grafana/authlib/blob/2f8d13a83ca3e82da08b53726de1697ee5b5b4cc/claims/type.go#L15-L24", - Default: "", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityRef"), }, }, - "uid": { - SchemaProps: spec.SchemaProps{ - Description: "UID for identity, is a unique value for the type within a namespace.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "display": { + "displayName": { SchemaProps: spec.SchemaProps{ Description: "Display name for identity.", Default: "", @@ -87,9 +78,11 @@ func schema_pkg_apis_iam_v0alpha1_IdentityDisplay(ref common.ReferenceCallback) }, }, }, - Required: []string{"type", "uid", "display"}, + Required: []string{"identity", "displayName"}, }, }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityRef"}, } } @@ -181,6 +174,35 @@ func schema_pkg_apis_iam_v0alpha1_IdentityDisplayResults(ref common.ReferenceCal } } +func schema_pkg_apis_iam_v0alpha1_IdentityRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of identity e.g. \"user\". For a full list see https://github.com/grafana/authlib/blob/2f8d13a83ca3e82da08b53726de1697ee5b5b4cc/claims/type.go#L15-L24", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the unique identifier for identity, guaranteed jo be a unique value for the type within a namespace.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "name"}, + }, + }, + } +} + func schema_pkg_apis_iam_v0alpha1_SSOSetting(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -645,7 +667,7 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback) }, }, }, - "teamRef": { + "team": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.TeamRef"), @@ -712,23 +734,13 @@ func schema_pkg_apis_iam_v0alpha1_TeamMember(ref common.ReferenceCallback) commo SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "type": { + "identity": { SchemaProps: spec.SchemaProps{ - Description: "Type of identity e.g. \"user\". For a full list see https://github.com/grafana/authlib/blob/2f8d13a83ca3e82da08b53726de1697ee5b5b4cc/claims/type.go#L15-L24", - Default: "", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityRef"), }, }, - "uid": { - SchemaProps: spec.SchemaProps{ - Description: "UID for identity, is a unique value for the type within a namespace.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "display": { + "displayName": { SchemaProps: spec.SchemaProps{ Description: "Display name for identity.", Default: "", @@ -766,9 +778,11 @@ func schema_pkg_apis_iam_v0alpha1_TeamMember(ref common.ReferenceCallback) commo }, }, }, - Required: []string{"type", "uid", "display"}, + Required: []string{"identity", "displayName"}, }, }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityRef"}, } } @@ -868,11 +882,11 @@ func schema_pkg_apis_iam_v0alpha1_TeamSubject(ref common.ReferenceCallback) comm SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { + "identity": { SchemaProps: spec.SchemaProps{ - Description: "Name is the unique identifier for subject.", - Type: []string{"string"}, - Format: "", + Description: "Identity is a reference to the identity of this subject.", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityRef"), }, }, "permission": { @@ -884,8 +898,11 @@ func schema_pkg_apis_iam_v0alpha1_TeamSubject(ref common.ReferenceCallback) comm }, }, }, + Required: []string{"identity"}, }, }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityRef"}, } } diff --git a/pkg/apis/iam/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/iam/v0alpha1/zz_generated.openapi_violation_exceptions.list index afb10a855dc..d3c707f202c 100644 --- a/pkg/apis/iam/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/iam/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,3 +1,2 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/iam/v0alpha1,TeamBindingSpec,Subjects -API rule violation: names_match,github.com/grafana/grafana/pkg/apis/iam/v0alpha1,IdentityDisplay,IdentityType API rule violation: names_match,github.com/grafana/grafana/pkg/apis/iam/v0alpha1,IdentityDisplay,InternalID diff --git a/pkg/registry/apis/iam/team/rest_members.go b/pkg/registry/apis/iam/team/rest_members.go index 1bab3a9f2a5..a3c11e76011 100644 --- a/pkg/registry/apis/iam/team/rest_members.go +++ b/pkg/registry/apis/iam/team/rest_members.go @@ -98,11 +98,13 @@ var cfg = &setting.Cfg{} func mapToTeamMember(m legacy.TeamMember) iamv0.TeamMember { return iamv0.TeamMember{ IdentityDisplay: iamv0.IdentityDisplay{ - IdentityType: claims.TypeUser, - UID: m.UserUID, - Display: m.Name, - AvatarURL: dtos.GetGravatarUrlWithDefault(cfg, m.Email, m.Name), - InternalID: m.UserID, + Identity: iamv0.IdentityRef{ + Type: claims.TypeUser, + Name: m.UserUID, + }, + DisplayName: m.Name, + AvatarURL: dtos.GetGravatarUrlWithDefault(cfg, m.Email, m.Name), + InternalID: m.UserID, }, External: m.External, Permission: mapPermisson(m.Permission), diff --git a/pkg/registry/apis/iam/team/store_binding.go b/pkg/registry/apis/iam/team/store_binding.go index 7251c70fd31..1dc1576f5d8 100644 --- a/pkg/registry/apis/iam/team/store_binding.go +++ b/pkg/registry/apis/iam/team/store_binding.go @@ -137,7 +137,7 @@ func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) iamv0.Tea CreationTimestamp: metav1.NewTime(ct), }, Spec: iamv0.TeamBindingSpec{ - TeamRef: iamv0.TeamRef{ + Team: iamv0.TeamRef{ Name: b.TeamUID, }, Subjects: mapToSubjects(b.Members), @@ -149,7 +149,10 @@ func mapToSubjects(members []legacy.TeamMember) []iamv0.TeamSubject { out := make([]iamv0.TeamSubject, 0, len(members)) for _, m := range members { out = append(out, iamv0.TeamSubject{ - Name: m.MemberID(), + Identity: iamv0.IdentityRef{ + Type: claims.TypeUser, + Name: m.UserUID, + }, Permission: common.MapTeamPermission(m.Permission), }) } diff --git a/pkg/registry/apis/iam/user/rest_display.go b/pkg/registry/apis/iam/user/rest_display.go index e0f316d1ec3..4a9940a1e95 100644 --- a/pkg/registry/apis/iam/user/rest_display.go +++ b/pkg/registry/apis/iam/user/rest_display.go @@ -98,14 +98,17 @@ func (r *LegacyDisplayREST) Connect(ctx context.Context, name string, _ runtime. } for _, user := range users.Users { disp := iamv0.IdentityDisplay{ - IdentityType: claims.TypeUser, - Display: user.NameOrFallback(), - UID: user.UID, + Identity: iamv0.IdentityRef{ + Type: claims.TypeUser, + Name: user.UID, + }, + DisplayName: user.NameOrFallback(), + InternalID: user.ID, } if user.IsServiceAccount { - disp.IdentityType = claims.TypeServiceAccount + disp.Identity.Type = claims.TypeServiceAccount } - disp.AvatarURL = dtos.GetGravatarUrlWithDefault(fakeCfgForGravatar, user.Email, disp.Display) + disp.AvatarURL = dtos.GetGravatarUrlWithDefault(fakeCfgForGravatar, user.Email, disp.DisplayName) rsp.Display = append(rsp.Display, disp) } @@ -146,25 +149,30 @@ func parseKeys(req []string) dispKeys { switch t { case claims.TypeAnonymous: keys.disp = append(keys.disp, iamv0.IdentityDisplay{ - IdentityType: t, - Display: "Anonymous", - AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)), + Identity: iamv0.IdentityRef{ + Type: t, + }, + DisplayName: "Anonymous", + AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)), }) continue case claims.TypeAPIKey: keys.disp = append(keys.disp, iamv0.IdentityDisplay{ - IdentityType: t, - UID: key, - Display: "API Key", - AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)), + Identity: iamv0.IdentityRef{ + Type: t, + Name: key, + }, + DisplayName: "API Key", + AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)), }) continue case claims.TypeProvisioning: keys.disp = append(keys.disp, iamv0.IdentityDisplay{ - IdentityType: t, - UID: "Provisioning", - Display: "Provisioning", - AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)), + Identity: iamv0.IdentityRef{ + Type: t, + }, + DisplayName: "Provisioning", + AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)), }) continue default: @@ -177,9 +185,11 @@ func parseKeys(req []string) dispKeys { if err == nil { if id == 0 { keys.disp = append(keys.disp, iamv0.IdentityDisplay{ - IdentityType: claims.TypeUser, - UID: key, - Display: "System admin", + Identity: iamv0.IdentityRef{ + Type: claims.TypeUser, + Name: key, + }, + DisplayName: "System admin", }) continue }