K8s/IAM: Use raw handler for display (not rest.Connector) (#99898)

pull/99963/head
Ryan McKinley 6 months ago committed by GitHub
parent 2aa78139c4
commit b636b81b16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      pkg/apis/iam/v0alpha1/types_display.go
  2. 8
      pkg/apis/iam/v0alpha1/zz_generated.openapi.go
  3. 16
      pkg/registry/apis/iam/register.go
  4. 233
      pkg/registry/apis/iam/user/rest_display.go
  5. 6
      pkg/registry/apis/iam/user/rest_user_team.go
  6. 6
      pkg/services/apiserver/builder/helper.go
  7. 256
      pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json

@ -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"`
}

@ -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",
},

@ -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
}

@ -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",

@ -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"}

@ -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 {

@ -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"
},

Loading…
Cancel
Save