diff --git a/pkg/apis/iam/v0alpha1/types_display.go b/pkg/apis/iam/v0alpha1/types_display.go index 02c06662d0924..9d3f27b4a4141 100644 --- a/pkg/apis/iam/v0alpha1/types_display.go +++ b/pkg/apis/iam/v0alpha1/types_display.go @@ -33,16 +33,17 @@ type Display struct { // AvatarURL is the url where we can get the avatar for identity AvatarURL string `json:"avatarURL,omitempty"` - // InternalID is the legacy numreric id for identity, this is deprecated and should be phased out + // InternalID is the legacy numeric id for identity, + // Deprecated: use the identityRef where possible 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 + // For a full list see https://github.com/grafana/authlib/blob/d6737a7dc8f55e9d42834adb83b5da607ceed293/types/type.go#L15 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 is the unique identifier for identity, guaranteed to be a unique value for the type within a namespace. Name string `json:"name"` } diff --git a/pkg/apis/iam/v0alpha1/zz_generated.openapi.go b/pkg/apis/iam/v0alpha1/zz_generated.openapi.go index 51c53868df86e..391aa5579d130 100644 --- a/pkg/apis/iam/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/iam/v0alpha1/zz_generated.openapi.go @@ -72,7 +72,7 @@ func schema_pkg_apis_iam_v0alpha1_Display(ref common.ReferenceCallback) common.O }, "internalId": { SchemaProps: spec.SchemaProps{ - Description: "InternalID is the legacy numreric id for identity, this is deprecated and should be phased out", + Description: "InternalID is the legacy numeric id for identity, Deprecated: use the identityRef where possible", Type: []string{"integer"}, Format: "int64", }, @@ -188,7 +188,7 @@ func schema_pkg_apis_iam_v0alpha1_IdentityRef(ref common.ReferenceCallback) comm 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", + Description: "Type of identity e.g. \"user\". For a full list see https://github.com/grafana/authlib/blob/d6737a7dc8f55e9d42834adb83b5da607ceed293/types/type.go#L15", Default: "", Type: []string{"string"}, Format: "", @@ -196,7 +196,7 @@ func schema_pkg_apis_iam_v0alpha1_IdentityRef(ref common.ReferenceCallback) comm }, "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is the unique identifier for identity, guaranteed jo be a unique value for the type within a namespace.", + Description: "Name is the unique identifier for identity, guaranteed to be a unique value for the type within a namespace.", Default: "", Type: []string{"string"}, Format: "", @@ -763,7 +763,7 @@ func schema_pkg_apis_iam_v0alpha1_TeamMember(ref common.ReferenceCallback) commo }, "internalId": { SchemaProps: spec.SchemaProps{ - Description: "InternalID is the legacy numreric id for identity, this is deprecated and should be phased out", + Description: "InternalID is the legacy numeric id for identity, Deprecated: use the identityRef where possible", Type: []string{"integer"}, Format: "int64", }, diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 42f69649a6121..2789d85934734 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -10,6 +10,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" common "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/validation/spec" "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -34,6 +35,9 @@ type IdentityAccessManagementAPIBuilder struct { authorizer authorizer.Authorizer accessClient types.AccessClient + // non-k8s api route + display *user.LegacyDisplayREST + // Not set for multi-tenant deployment for now sso ssosettings.Service } @@ -52,6 +56,7 @@ func RegisterAPIService( sso: ssoService, authorizer: authorizer, accessClient: client, + display: user.NewLegacyDisplayREST(store), } apiregistration.RegisterAPI(builder) @@ -60,7 +65,8 @@ func RegisterAPIService( func NewAPIService(store legacy.LegacyIdentityStore) *IdentityAccessManagementAPIBuilder { return &IdentityAccessManagementAPIBuilder{ - store: store, + store: store, + display: user.NewLegacyDisplayREST(store), authorizer: authorizer.AuthorizerFunc( func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { user, err := identity.GetRequester(ctx) @@ -114,9 +120,6 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.sso) } - // The display endpoint -- NOTE, this uses a rewrite hack to allow requests without a name parameter - storage["display"] = user.NewLegacyDisplayREST(b.store) - apiGroupInfo.VersionedResourcesStorageMap[iamv0.VERSION] = storage return nil } @@ -125,6 +128,11 @@ func (b *IdentityAccessManagementAPIBuilder) GetOpenAPIDefinitions() common.GetO return iamv0.GetOpenAPIDefinitions } +func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes() *builder.APIRoutes { + defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} }) + return b.display.GetAPIRoutes(defs) +} + func (b *IdentityAccessManagementAPIBuilder) GetAuthorizer() authorizer.Authorizer { return b.authorizer } diff --git a/pkg/registry/apis/iam/user/rest_display.go b/pkg/registry/apis/iam/user/rest_display.go index 112db40233c18..c82567878c7e1 100644 --- a/pkg/registry/apis/iam/user/rest_display.go +++ b/pkg/registry/apis/iam/user/rest_display.go @@ -1,123 +1,154 @@ package user import ( - "context" + "encoding/json" "net/http" "strconv" "strings" - errorsK8s "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/registry/rest" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/spec3" + "k8s.io/kube-openapi/pkg/validation/spec" - claims "github.com/grafana/authlib/types" + authlib "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/api/dtos" - iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" + iam "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/services/apiserver/builder" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util/errhttp" ) type LegacyDisplayREST struct { store legacy.LegacyIdentityStore } -var ( - _ rest.Storage = (*LegacyDisplayREST)(nil) - _ rest.SingularNameProvider = (*LegacyDisplayREST)(nil) - _ rest.Connecter = (*LegacyDisplayREST)(nil) - _ rest.Scoper = (*LegacyDisplayREST)(nil) - _ rest.StorageMetadata = (*LegacyDisplayREST)(nil) -) - func NewLegacyDisplayREST(store legacy.LegacyIdentityStore) *LegacyDisplayREST { return &LegacyDisplayREST{store} } -func (r *LegacyDisplayREST) New() runtime.Object { - return &iamv0.DisplayList{} -} - -func (r *LegacyDisplayREST) Destroy() {} - -func (r *LegacyDisplayREST) NamespaceScoped() bool { - return true -} - -func (r *LegacyDisplayREST) GetSingularName() string { - return "display" -} - -func (r *LegacyDisplayREST) ProducesMIMETypes(verb string) []string { - return []string{"application/json"} -} - -func (r *LegacyDisplayREST) ProducesObject(verb string) any { - return &iamv0.DisplayList{} -} - -func (r *LegacyDisplayREST) ConnectMethods() []string { - return []string{http.MethodGet} -} - -func (r *LegacyDisplayREST) NewConnectOptions() (runtime.Object, bool, string) { - return nil, false, "" // true means you can use the trailing path as a variable +func (r *LegacyDisplayREST) GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes { + listSchema := defs["github.com/grafana/grafana/pkg/apis/iam/v0alpha1.DisplayList"].Schema + displaySchema := defs["github.com/grafana/grafana/pkg/apis/iam/v0alpha1.Display"].Schema + identitySchema := defs["github.com/grafana/grafana/pkg/apis/iam/v0alpha1.IdentityRef"].Schema + listSchema.Properties["display"].Items.Schema = &displaySchema // not sure why this is lost + displaySchema.Properties["identity"] = identitySchema // not sure why this is lost + return &builder.APIRoutes{ + Namespace: []builder.APIRouteHandler{ + { + Path: "display", + Spec: &spec3.PathProps{ + Get: &spec3.Operation{ + OperationProps: spec3.OperationProps{ + OperationId: "getDisplayMapping", // This is used by RTK client generator + Tags: []string{"Display"}, + Description: "Show user display information", + Parameters: []*spec3.Parameter{ + { + ParameterProps: spec3.ParameterProps{ + Name: "namespace", + In: "path", + Required: true, + Example: "default", + Description: "workspace", + Schema: spec.StringProperty(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "key", + In: "query", + Description: "Display keys", + Required: true, + Example: "user:u000000001", + Schema: spec.ArrayProperty(spec.StringProperty()), + // Style: "form", + Explode: true, + }, + }, + }, + Responses: &spec3.Responses{ + ResponsesProps: spec3.ResponsesProps{ + StatusCodeResponses: map[int]*spec3.Response{ + 200: { + ResponseProps: spec3.ResponseProps{ + Content: map[string]*spec3.MediaType{ + "application/json": { + MediaTypeProps: spec3.MediaTypeProps{ + Schema: &listSchema, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Handler: r.handleDisplay, + }, + }, + } } // This will always have an empty app url var fakeCfgForGravatar = &setting.Cfg{} -func (r *LegacyDisplayREST) 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" { - return nil, errorsK8s.NewNotFound(schema.GroupResource{}, name) +func (r *LegacyDisplayREST) handleDisplay(w http.ResponseWriter, req *http.Request) { + ctx := req.Context() + user, ok := authlib.AuthInfoFrom(ctx) + if !ok { + errhttp.Write(ctx, apierrors.NewUnauthorized("missing auth info"), w) + return + } + + ns, err := authlib.ParseNamespace(user.GetNamespace()) + if err != nil { + errhttp.Write(ctx, err, w) + return } - ns, err := request.NamespaceInfoFrom(ctx, true) + keys := parseKeys(req.URL.Query()["key"]) + users, err := r.store.ListDisplay(ctx, ns, legacy.ListDisplayQuery{ + OrgID: ns.OrgID, + UIDs: keys.uids, + IDs: keys.ids, + }) if err != nil { - return nil, err + errhttp.Write(ctx, err, w) + return } - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - keys := parseKeys(req.URL.Query()["key"]) - users, err := r.store.ListDisplay(ctx, ns, legacy.ListDisplayQuery{ - OrgID: ns.OrgID, - UIDs: keys.uids, - IDs: keys.ids, - }) - if err != nil { - responder.Error(err) - return + rsp := &iam.DisplayList{ + Keys: keys.keys, + InvalidKeys: keys.invalid, + Items: make([]iam.Display, 0, len(users.Users)+len(keys.disp)+1), + } + for _, user := range users.Users { + disp := iam.Display{ + Identity: iam.IdentityRef{ + Type: authlib.TypeUser, + Name: user.UID, + }, + DisplayName: user.NameOrFallback(), + InternalID: user.ID, // nolint:staticcheck } - - rsp := &iamv0.DisplayList{ - Keys: keys.keys, - InvalidKeys: keys.invalid, - Items: make([]iamv0.Display, 0, len(users.Users)+len(keys.disp)+1), - } - for _, user := range users.Users { - disp := iamv0.Display{ - Identity: iamv0.IdentityRef{ - Type: claims.TypeUser, - Name: user.UID, - }, - DisplayName: user.NameOrFallback(), - InternalID: user.ID, - } - if user.IsServiceAccount { - disp.Identity.Type = claims.TypeServiceAccount - } - disp.AvatarURL = dtos.GetGravatarUrlWithDefault(fakeCfgForGravatar, user.Email, disp.DisplayName) - rsp.Items = append(rsp.Items, disp) + if user.IsServiceAccount { + disp.Identity.Type = authlib.TypeServiceAccount } + disp.AvatarURL = dtos.GetGravatarUrlWithDefault(fakeCfgForGravatar, user.Email, disp.DisplayName) + rsp.Items = append(rsp.Items, disp) + } - // Append the constants here - if len(keys.disp) > 0 { - rsp.Items = append(rsp.Items, keys.disp...) - } - responder.Object(200, rsp) - }), nil + // Append the constants here + if len(keys.disp) > 0 { + rsp.Items = append(rsp.Items, keys.disp...) + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(rsp) } type dispKeys struct { @@ -127,7 +158,7 @@ type dispKeys struct { invalid []string // For terminal keys, this is a constant - disp []iamv0.Display + disp []iam.Display } func parseKeys(req []string) dispKeys { @@ -139,7 +170,7 @@ func parseKeys(req []string) dispKeys { for _, key := range req { idx := strings.Index(key, ":") if idx > 0 { - t, err := claims.ParseType(key[0:idx]) + t, err := authlib.ParseType(key[0:idx]) if err != nil { keys.invalid = append(keys.invalid, key) continue @@ -147,18 +178,18 @@ func parseKeys(req []string) dispKeys { key = key[idx+1:] switch t { - case claims.TypeAnonymous: - keys.disp = append(keys.disp, iamv0.Display{ - Identity: iamv0.IdentityRef{ + case authlib.TypeAnonymous: + keys.disp = append(keys.disp, iam.Display{ + Identity: iam.IdentityRef{ Type: t, }, DisplayName: "Anonymous", AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)), }) continue - case claims.TypeAPIKey: - keys.disp = append(keys.disp, iamv0.Display{ - Identity: iamv0.IdentityRef{ + case authlib.TypeAPIKey: + keys.disp = append(keys.disp, iam.Display{ + Identity: iam.IdentityRef{ Type: t, Name: key, }, @@ -166,9 +197,9 @@ func parseKeys(req []string) dispKeys { AvatarURL: dtos.GetGravatarUrl(fakeCfgForGravatar, string(t)), }) continue - case claims.TypeProvisioning: - keys.disp = append(keys.disp, iamv0.Display{ - Identity: iamv0.IdentityRef{ + case authlib.TypeProvisioning: + keys.disp = append(keys.disp, iam.Display{ + Identity: iam.IdentityRef{ Type: t, }, DisplayName: "Provisioning", @@ -184,9 +215,9 @@ func parseKeys(req []string) dispKeys { id, err := strconv.ParseInt(key, 10, 64) if err == nil { if id == 0 { - keys.disp = append(keys.disp, iamv0.Display{ - Identity: iamv0.IdentityRef{ - Type: claims.TypeUser, + keys.disp = append(keys.disp, iam.Display{ + Identity: iam.IdentityRef{ + Type: authlib.TypeUser, Name: key, }, DisplayName: "System admin", diff --git a/pkg/registry/apis/iam/user/rest_user_team.go b/pkg/registry/apis/iam/user/rest_user_team.go index d9d0c22d2caf7..79f781fa7e034 100644 --- a/pkg/registry/apis/iam/user/rest_user_team.go +++ b/pkg/registry/apis/iam/user/rest_user_team.go @@ -15,7 +15,6 @@ import ( var ( _ rest.Storage = (*LegacyUserTeamREST)(nil) - _ rest.Scoper = (*LegacyUserTeamREST)(nil) _ rest.StorageMetadata = (*LegacyUserTeamREST)(nil) _ rest.Connecter = (*LegacyUserTeamREST)(nil) ) @@ -36,11 +35,6 @@ func (s *LegacyUserTeamREST) New() runtime.Object { // Destroy implements rest.Storage. func (s *LegacyUserTeamREST) Destroy() {} -// NamespaceScoped implements rest.Scoper. -func (s *LegacyUserTeamREST) NamespaceScoped() bool { - return true -} - // ProducesMIMETypes implements rest.StorageMetadata. func (s *LegacyUserTeamREST) ProducesMIMETypes(verb string) []string { return []string{"application/json"} diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index fd95a2832b495..c1f81ae6a3ba3 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -52,12 +52,6 @@ var PathRewriters = []filters.PathRewriter{ return matches[1] + "/name" // connector requires a name }, }, - { - Pattern: regexp.MustCompile(`(/apis/iam.grafana.app/v0alpha1/namespaces/.*/display$)`), - ReplaceFunc: func(matches []string) string { - return matches[1] + "/name" // connector requires a name - }, - }, { Pattern: regexp.MustCompile(`(/apis/.*/v0alpha1/namespaces/.*/queryconvert$)`), ReplaceFunc: func(matches []string) string { diff --git a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json index a737716430901..4ad7f837fdb16 100644 --- a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json @@ -35,54 +35,133 @@ } } }, - "/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/display/{name}": { + "/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/display": { "get": { "tags": [ - "DisplayList" + "Display" + ], + "description": "Show user display information", + "operationId": "getDisplayMapping", + "parameters": [ + { + "name": "namespace", + "in": "path", + "description": "workspace", + "required": true, + "schema": { + "type": "string" + }, + "example": "default" + }, + { + "name": "key", + "in": "query", + "description": "Display keys", + "required": true, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": "user:u000000001" + } ], - "description": "connect GET requests to DisplayList", - "operationId": "getDisplayList", "responses": { "200": { - "description": "OK", "content": { - "*/*": { + "application/json": { "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.DisplayList" + "type": "object", + "required": [ + "keys", + "display" + ], + "properties": { + "apiVersion": { + "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" + }, + "display": { + "description": "Matching items (the caller may need to remap from keys to results)", + "type": "array", + "items": { + "type": "object", + "required": [ + "identity", + "displayName" + ], + "properties": { + "avatarURL": { + "description": "AvatarURL is the url where we can get the avatar for identity", + "type": "string" + }, + "displayName": { + "description": "Display name for identity.", + "type": "string", + "default": "" + }, + "identity": { + "type": "object", + "required": [ + "type", + "name" + ], + "properties": { + "name": { + "description": "Name is the unique identifier for identity, guaranteed to be a unique value for the type within a namespace.", + "type": "string", + "default": "" + }, + "type": { + "description": "Type of identity e.g. \"user\". For a full list see https://github.com/grafana/authlib/blob/d6737a7dc8f55e9d42834adb83b5da607ceed293/types/type.go#L15", + "type": "string", + "default": "" + } + } + }, + "internalId": { + "description": "InternalID is the legacy numeric id for identity, Deprecated: use the identityRef where possible", + "type": "integer", + "format": "int64" + } + } + }, + "x-kubernetes-list-type": "atomic" + }, + "invalidKeys": { + "description": "Input keys that were not useable", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "set" + }, + "keys": { + "description": "Request keys used to lookup the display value", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "set" + }, + "kind": { + "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" + }, + "metadata": { + "default": {} + } + } } } } } - }, - "x-kubernetes-action": "connect", - "x-kubernetes-group-version-kind": { - "group": "iam.grafana.app", - "version": "v0alpha1", - "kind": "DisplayList" } - }, - "parameters": [ - { - "name": "name", - "in": "path", - "description": "name of the DisplayList", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "namespace", - "in": "path", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - } - ] + } }, "/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/serviceaccounts": { "get": { @@ -2432,105 +2511,6 @@ "additionalProperties": true, "x-kubernetes-preserve-unknown-fields": true }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.Display": { - "type": "object", - "required": [ - "identity", - "displayName" - ], - "properties": { - "avatarURL": { - "description": "AvatarURL is the url where we can get the avatar for identity", - "type": "string" - }, - "displayName": { - "description": "Display name for identity.", - "type": "string", - "default": "" - }, - "identity": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.IdentityRef" - } - ] - }, - "internalId": { - "description": "InternalID is the legacy numreric id for identity, this is deprecated and should be phased out", - "type": "integer", - "format": "int64" - } - } - }, - "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.DisplayList": { - "type": "object", - "required": [ - "keys", - "display" - ], - "properties": { - "apiVersion": { - "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" - }, - "display": { - "description": "Matching items (the caller may need to remap from keys to results)", - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.iam.v0alpha1.Display" - } - ] - }, - "x-kubernetes-list-type": "atomic" - }, - "invalidKeys": { - "description": "Input keys that were not useable", - "type": "array", - "items": { - "type": "string", - "default": "" - }, - "x-kubernetes-list-type": "set" - }, - "keys": { - "description": "Request keys used to lookup the display value", - "type": "array", - "items": { - "type": "string", - "default": "" - }, - "x-kubernetes-list-type": "set" - }, - "kind": { - "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" - }, - "metadata": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - } - ] - } - }, - "x-kubernetes-group-version-kind": [ - { - "group": "iam.grafana.app", - "kind": "DisplayList", - "version": "__internal" - }, - { - "group": "iam.grafana.app", - "kind": "DisplayList", - "version": "v0alpha1" - } - ] - }, "com.github.grafana.grafana.pkg.apis.iam.v0alpha1.IdentityRef": { "type": "object", "required": [ @@ -2539,12 +2519,12 @@ ], "properties": { "name": { - "description": "Name is the unique identifier for identity, guaranteed jo be a unique value for the type within a namespace.", + "description": "Name is the unique identifier for identity, guaranteed to be a unique value for the type within a namespace.", "type": "string", "default": "" }, "type": { - "description": "Type of identity e.g. \"user\". For a full list see https://github.com/grafana/authlib/blob/2f8d13a83ca3e82da08b53726de1697ee5b5b4cc/claims/type.go#L15-L24", + "description": "Type of identity e.g. \"user\". For a full list see https://github.com/grafana/authlib/blob/d6737a7dc8f55e9d42834adb83b5da607ceed293/types/type.go#L15", "type": "string", "default": "" } @@ -2977,7 +2957,7 @@ ] }, "internalId": { - "description": "InternalID is the legacy numreric id for identity, this is deprecated and should be phased out", + "description": "InternalID is the legacy numeric id for identity, Deprecated: use the identityRef where possible", "type": "integer", "format": "int64" },