zitadel/internal/actions/object/metadata.go
Livio Spring 74624018c2
feat(actions): allow getting metadata of organizations from user grants (#7782)
* feat(actions): allow getting metadata of (other) organizations from user grants

* docs add action example
2024-04-22 11:34:23 +00:00

235 lines
5.6 KiB
Go

package object
import (
"context"
"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 OrgMetadataListFromQuery(c *actions.FieldConfig, orgMetadata *query.OrgMetadataList) goja.Value {
result := &metadataList{
Count: orgMetadata.Count,
Sequence: orgMetadata.Sequence,
Timestamp: orgMetadata.LastRun,
Metadata: make([]*metadata, len(orgMetadata.Metadata)),
}
for i, md := range orgMetadata.Metadata {
result.Metadata[i] = &metadata{
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 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 GetOrganizationMetadata(ctx context.Context, queries *query.Queries, c *actions.FieldConfig, organizationID string) goja.Value {
metadata, err := queries.SearchOrgMetadata(
ctx,
true,
organizationID,
&query.OrgMetadataSearchQueries{},
false,
)
if err != nil {
logging.WithError(err).Info("unable to get org metadata in action")
panic(err)
}
return OrgMetadataListFromQuery(c, metadata)
}
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 metadataList struct {
Count uint64
Sequence uint64
Timestamp time.Time
Metadata []*metadata
}
type metadata struct {
CreationDate time.Time
ChangeDate time.Time
ResourceOwner string
Sequence uint64
Key string
Value goja.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
}