Tim Möhlmann ba9b807854
perf(oidc): optimize the introspection endpoint (#6909)
* get key by id and cache them

* userinfo from events for v2 tokens

* improve keyset caching

* concurrent token and client checks

* client and project in single query

* logging and otel

* drop owner_removed column on apps and authN tables

* userinfo and project roles in go routines

* get  oidc user info from projections and add actions

* add avatar URL

* some cleanup

* pull oidc work branch

* remove storage from server

* add config flag for experimental introspection

* legacy introspection flag

* drop owner_removed column on user projections

* drop owner_removed column on useer_metadata

* query userinfo unit test

* query introspection client test

* add user_grants to the userinfo query

* handle PAT scopes

* bring triggers back

* test instance keys query

* add userinfo unit tests

* unit test keys

* go mod tidy

* solve some bugs

* fix missing preferred login name

* do not run triggers in go routines, they seem to deadlock

* initialize the trigger handlers late with a sync.OnceValue

* Revert "do not run triggers in go routines, they seem to deadlock"

This reverts commit 2a03da2127b7dc74552ec25d4772282a82cc1cba.

* add missing translations

* chore: update go version for linting

* pin oidc version

* parse a global time location for query test

* fix linter complains

* upgrade go lint

* fix more linting issues

---------

Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
2023-11-21 13:11:38 +01:00

181 lines
4.2 KiB
Go

package object
import (
"encoding/json"
"time"
"github.com/dop251/goja"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func UserMetadataListFromQuery(c *actions.FieldConfig, metadata *query.UserMetadataList) goja.Value {
result := &userMetadataList{
Count: metadata.Count,
Sequence: metadata.Sequence,
Timestamp: metadata.LastRun,
Metadata: make([]*userMetadata, len(metadata.Metadata)),
}
for i, md := range metadata.Metadata {
result.Metadata[i] = &userMetadata{
CreationDate: md.CreationDate,
ChangeDate: md.ChangeDate,
ResourceOwner: md.ResourceOwner,
Sequence: md.Sequence,
Key: md.Key,
Value: metadataByteArrayToValue(md.Value, c.Runtime),
}
}
return c.Runtime.ToValue(result)
}
func UserMetadataListFromSlice(c *actions.FieldConfig, metadata []query.UserMetadata) goja.Value {
result := &userMetadataList{
// Count was the only field ever queried from the DB in the old implementation,
// so Sequence and LastRun are omitted.
Count: uint64(len(metadata)),
Metadata: make([]*userMetadata, len(metadata)),
}
for i, md := range metadata {
result.Metadata[i] = &userMetadata{
CreationDate: md.CreationDate,
ChangeDate: md.ChangeDate,
ResourceOwner: md.ResourceOwner,
Sequence: md.Sequence,
Key: md.Key,
Value: metadataByteArrayToValue(md.Value, c.Runtime),
}
}
return c.Runtime.ToValue(result)
}
func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value {
var value interface{}
if !json.Valid(val) {
var err error
val, err = json.Marshal(string(val))
if err != nil {
logging.WithError(err).Debug("unable to marshal unknown value")
panic(err)
}
}
err := json.Unmarshal(val, &value)
if err != nil {
logging.WithError(err).Debug("unable to unmarshal into map")
panic(err)
}
return runtime.ToValue(value)
}
type userMetadataList struct {
Count uint64
Sequence uint64
Timestamp time.Time
Metadata []*userMetadata
}
type userMetadata struct {
CreationDate time.Time
ChangeDate time.Time
ResourceOwner string
Sequence uint64
Key string
Value goja.Value
}
type MetadataList struct {
metadata []*Metadata
}
type Metadata struct {
Key string
// Value is for exporting to javascript
Value goja.Value
// value is for mapping to [domain.Metadata]
value []byte
}
func (md *MetadataList) AppendMetadataFunc(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 2 {
panic("exactly 2 (key, value) arguments expected")
}
value, err := json.Marshal(call.Arguments[1].Export())
if err != nil {
logging.WithError(err).Debug("unable to marshal")
panic(err)
}
md.metadata = append(md.metadata,
&Metadata{
Key: call.Arguments[0].Export().(string),
Value: call.Arguments[1],
value: value,
})
return nil
}
func (md *MetadataList) MetadataListFromDomain(runtime *goja.Runtime) interface{} {
for i, metadata := range md.metadata {
md.metadata[i].Value = metadataByteArrayToValue(metadata.value, runtime)
}
return &md.metadata
}
func MetadataListFromDomain(metadata []*domain.Metadata) *MetadataList {
list := &MetadataList{metadata: make([]*Metadata, len(metadata))}
for i, md := range metadata {
list.metadata[i] = &Metadata{
Key: md.Key,
value: md.Value,
}
}
return list
}
func MetadataListToDomain(metadataList *MetadataList) []*domain.Metadata {
if metadataList == nil {
return nil
}
list := make([]*domain.Metadata, len(metadataList.metadata))
for i, metadata := range metadataList.metadata {
value := metadata.value
if len(value) == 0 {
value = mapBytesToByteArray(metadata.Value.Export())
}
list[i] = &domain.Metadata{
Key: metadata.Key,
Value: value,
}
}
return list
}
// mapBytesToByteArray is used for backwards compatibility of old metadata.push method
// converts the Javascript uint8 array which is exported as []interface{} to a []byte
func mapBytesToByteArray(i interface{}) []byte {
bytes, ok := i.([]interface{})
if !ok {
return nil
}
value := make([]byte, len(bytes))
for i, val := range bytes {
b, ok := val.(int64)
if !ok {
return nil
}
value[i] = byte(b)
}
return value
}