mirror of https://github.com/Squidex/squidex.git
22 changed files with 998 additions and 252 deletions
@ -0,0 +1,132 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentDataObject.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
using Jint.Native.Object; |
||||
|
using Jint.Runtime.Descriptors; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public sealed class ContentDataObject : ObjectInstance |
||||
|
{ |
||||
|
private readonly NamedContentData contentData; |
||||
|
private HashSet<string> fieldsToDelete; |
||||
|
private Dictionary<string, ContentDataProperty> fieldProperties; |
||||
|
private bool isChanged; |
||||
|
|
||||
|
public ContentDataObject(Engine engine, NamedContentData contentData) |
||||
|
: base(engine) |
||||
|
{ |
||||
|
Extensible = true; |
||||
|
|
||||
|
this.contentData = contentData; |
||||
|
} |
||||
|
|
||||
|
public void MarkChanged() |
||||
|
{ |
||||
|
isChanged = true; |
||||
|
} |
||||
|
|
||||
|
public bool TryUpdate(out NamedContentData result) |
||||
|
{ |
||||
|
result = contentData; |
||||
|
|
||||
|
if (isChanged) |
||||
|
{ |
||||
|
if (fieldsToDelete != null) |
||||
|
{ |
||||
|
foreach (var field in fieldsToDelete) |
||||
|
{ |
||||
|
contentData.Remove(field); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (fieldProperties != null) |
||||
|
{ |
||||
|
foreach (var kvp in fieldProperties) |
||||
|
{ |
||||
|
if (kvp.Value.ContentField.TryUpdate(out var fieldData)) |
||||
|
{ |
||||
|
contentData[kvp.Key] = fieldData; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return isChanged; |
||||
|
} |
||||
|
|
||||
|
public override void RemoveOwnProperty(string propertyName) |
||||
|
{ |
||||
|
if (fieldsToDelete == null) |
||||
|
{ |
||||
|
fieldsToDelete = new HashSet<string>(); |
||||
|
} |
||||
|
|
||||
|
fieldsToDelete.Add(propertyName); |
||||
|
fieldProperties?.Remove(propertyName); |
||||
|
|
||||
|
MarkChanged(); |
||||
|
} |
||||
|
|
||||
|
public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
if (!fieldProperties.ContainsKey(propertyName)) |
||||
|
{ |
||||
|
fieldProperties[propertyName] = new ContentDataProperty(this) { Value = desc.Value }; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public override void Put(string propertyName, JsValue value, bool throwOnError) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
fieldProperties.GetOrAdd(propertyName, x => new ContentDataProperty(this)).Value = value; |
||||
|
} |
||||
|
|
||||
|
public override PropertyDescriptor GetOwnProperty(string propertyName) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
return fieldProperties.GetOrDefault(propertyName) ?? new PropertyDescriptor(new ObjectInstance(Engine) { Extensible = true }, true, false, true); |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties() |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
foreach (var property in fieldProperties) |
||||
|
{ |
||||
|
yield return new KeyValuePair<string, PropertyDescriptor>(property.Key, property.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void EnsurePropertiesInitialized() |
||||
|
{ |
||||
|
if (fieldProperties == null) |
||||
|
{ |
||||
|
fieldProperties = new Dictionary<string, ContentDataProperty>(contentData.Count); |
||||
|
|
||||
|
foreach (var kvp in contentData) |
||||
|
{ |
||||
|
fieldProperties.Add(kvp.Key, new ContentDataProperty(this, new ContentFieldObject(this, kvp.Value, false))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentFieldProperty.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint.Native; |
||||
|
using Jint.Runtime; |
||||
|
using Jint.Runtime.Descriptors; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public sealed class ContentDataProperty : PropertyDescriptor |
||||
|
{ |
||||
|
private readonly ContentDataObject contentData; |
||||
|
private ContentFieldObject contentField; |
||||
|
private JsValue value; |
||||
|
|
||||
|
public override JsValue Value |
||||
|
{ |
||||
|
get { return value; } |
||||
|
set |
||||
|
{ |
||||
|
if (!Equals(this.value, value)) |
||||
|
{ |
||||
|
if (value == null || !value.IsObject()) |
||||
|
{ |
||||
|
throw new JavaScriptException("Can only assign object to content data."); |
||||
|
} |
||||
|
|
||||
|
var obj = value.AsObject(); |
||||
|
|
||||
|
contentField = new ContentFieldObject(contentData, new ContentFieldData(), true); |
||||
|
|
||||
|
foreach (var kvp in obj.GetOwnProperties()) |
||||
|
{ |
||||
|
contentField.Put(kvp.Key, kvp.Value.Value, true); |
||||
|
} |
||||
|
|
||||
|
this.value = new JsValue(contentField); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ContentFieldObject ContentField |
||||
|
{ |
||||
|
get { return contentField; } |
||||
|
} |
||||
|
|
||||
|
public ContentDataProperty(ContentDataObject contentData, ContentFieldObject contentField = null) |
||||
|
: base(null, true, true, true) |
||||
|
{ |
||||
|
this.contentData = contentData; |
||||
|
this.contentField = contentField; |
||||
|
|
||||
|
if (contentField != null) |
||||
|
{ |
||||
|
value = new JsValue(contentField); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,142 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentFieldObject.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Jint.Native.Object; |
||||
|
using Jint.Runtime.Descriptors; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public sealed class ContentFieldObject : ObjectInstance |
||||
|
{ |
||||
|
private readonly ContentDataObject contentData; |
||||
|
private readonly ContentFieldData fieldData; |
||||
|
private HashSet<string> valuesToDelete; |
||||
|
private Dictionary<string, ContentFieldProperty> valueProperties; |
||||
|
private bool isChanged; |
||||
|
|
||||
|
public bool IsChanged |
||||
|
{ |
||||
|
get { return isChanged; } |
||||
|
} |
||||
|
|
||||
|
public ContentFieldData FieldData |
||||
|
{ |
||||
|
get { return fieldData; } |
||||
|
} |
||||
|
|
||||
|
public ContentFieldObject(ContentDataObject contentData, ContentFieldData fieldData, bool isNew) |
||||
|
: base(contentData.Engine) |
||||
|
{ |
||||
|
Extensible = true; |
||||
|
|
||||
|
this.contentData = contentData; |
||||
|
this.fieldData = fieldData; |
||||
|
|
||||
|
if (isNew) |
||||
|
{ |
||||
|
MarkChanged(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void MarkChanged() |
||||
|
{ |
||||
|
isChanged = true; |
||||
|
|
||||
|
contentData.MarkChanged(); |
||||
|
} |
||||
|
|
||||
|
public bool TryUpdate(out ContentFieldData result) |
||||
|
{ |
||||
|
result = fieldData; |
||||
|
|
||||
|
if (isChanged) |
||||
|
{ |
||||
|
if (valuesToDelete != null) |
||||
|
{ |
||||
|
foreach (var field in valuesToDelete) |
||||
|
{ |
||||
|
fieldData.Remove(field); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (valueProperties != null) |
||||
|
{ |
||||
|
foreach (var kvp in valueProperties) |
||||
|
{ |
||||
|
if (kvp.Value.IsChanged) |
||||
|
{ |
||||
|
fieldData[kvp.Key] = kvp.Value.ContentValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return isChanged; |
||||
|
} |
||||
|
|
||||
|
public override void RemoveOwnProperty(string propertyName) |
||||
|
{ |
||||
|
if (valuesToDelete == null) |
||||
|
{ |
||||
|
valuesToDelete = new HashSet<string>(); |
||||
|
} |
||||
|
|
||||
|
valuesToDelete.Add(propertyName); |
||||
|
valueProperties?.Remove(propertyName); |
||||
|
|
||||
|
MarkChanged(); |
||||
|
} |
||||
|
|
||||
|
public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
if (!valueProperties.ContainsKey(propertyName)) |
||||
|
{ |
||||
|
valueProperties[propertyName] = new ContentFieldProperty(this) { Value = desc.Value }; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public override PropertyDescriptor GetOwnProperty(string propertyName) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
return valueProperties?.GetOrDefault(propertyName) ?? PropertyDescriptor.Undefined; |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties() |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
foreach (var property in valueProperties) |
||||
|
{ |
||||
|
yield return new KeyValuePair<string, PropertyDescriptor>(property.Key, property.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void EnsurePropertiesInitialized() |
||||
|
{ |
||||
|
if (valueProperties == null) |
||||
|
{ |
||||
|
valueProperties = new Dictionary<string, ContentFieldProperty>(FieldData.Count); |
||||
|
|
||||
|
foreach (var kvp in FieldData) |
||||
|
{ |
||||
|
valueProperties.Add(kvp.Key, new ContentFieldProperty(this, kvp.Value)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentFieldProperty.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint.Native; |
||||
|
using Jint.Runtime.Descriptors; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public sealed class ContentFieldProperty : PropertyDescriptor |
||||
|
{ |
||||
|
private readonly ContentFieldObject contentField; |
||||
|
private JToken contentValue; |
||||
|
private JsValue value; |
||||
|
private bool isChanged; |
||||
|
|
||||
|
public override JsValue Value |
||||
|
{ |
||||
|
get { return value ?? (value = JsonMapper.Map(contentValue, contentField.Engine)); } |
||||
|
set |
||||
|
{ |
||||
|
if (!Equals(this.value, value)) |
||||
|
{ |
||||
|
this.value = value; |
||||
|
|
||||
|
contentValue = null; |
||||
|
contentField.MarkChanged(); |
||||
|
|
||||
|
isChanged = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public JToken ContentValue |
||||
|
{ |
||||
|
get { return contentValue ?? (contentValue = JsonMapper.Map(value)); } |
||||
|
} |
||||
|
|
||||
|
public bool IsChanged |
||||
|
{ |
||||
|
get { return isChanged; } |
||||
|
} |
||||
|
|
||||
|
public ContentFieldProperty(ContentFieldObject contentField, JToken contentValue = null) |
||||
|
: base(null, true, true, true) |
||||
|
{ |
||||
|
this.contentField = contentField; |
||||
|
this.contentValue = contentValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JsonMapper.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
using Jint.Native.Object; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
// ReSharper disable SwitchStatementMissingSomeCases
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public static class JsonMapper |
||||
|
{ |
||||
|
public static JsValue Map(JToken value, Engine engine) |
||||
|
{ |
||||
|
if (value == null) |
||||
|
{ |
||||
|
return JsValue.Null; |
||||
|
} |
||||
|
|
||||
|
switch (value.Type) |
||||
|
{ |
||||
|
case JTokenType.Date: |
||||
|
case JTokenType.Guid: |
||||
|
case JTokenType.String: |
||||
|
case JTokenType.Uri: |
||||
|
case JTokenType.TimeSpan: |
||||
|
return new JsValue((string)value); |
||||
|
case JTokenType.Null: |
||||
|
return JsValue.Null; |
||||
|
case JTokenType.Undefined: |
||||
|
return JsValue.Undefined; |
||||
|
case JTokenType.Integer: |
||||
|
return new JsValue((long)value); |
||||
|
case JTokenType.Float: |
||||
|
return new JsValue((double)value); |
||||
|
case JTokenType.Boolean: |
||||
|
return new JsValue((bool)value); |
||||
|
case JTokenType.Object: |
||||
|
{ |
||||
|
var obj = (JObject)value; |
||||
|
|
||||
|
var target = new ObjectInstance(engine); |
||||
|
|
||||
|
foreach (var property in obj) |
||||
|
{ |
||||
|
target.FastAddProperty(property.Key, Map(property.Value, engine), false, true, true); |
||||
|
} |
||||
|
|
||||
|
return target; |
||||
|
} |
||||
|
case JTokenType.Array: |
||||
|
{ |
||||
|
var arr = (JArray)value; |
||||
|
|
||||
|
var target = new JsValue[arr.Count]; |
||||
|
|
||||
|
for (var i = 0; i < arr.Count; i++) |
||||
|
{ |
||||
|
target[i] = Map(arr[i], engine); |
||||
|
} |
||||
|
|
||||
|
return engine.Array.Construct(target); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new ArgumentException("Invalid json type", nameof(value)); |
||||
|
} |
||||
|
|
||||
|
public static JToken Map(JsValue value) |
||||
|
{ |
||||
|
if (value == null || value.IsNull()) |
||||
|
{ |
||||
|
return JValue.CreateNull(); |
||||
|
} |
||||
|
|
||||
|
if (value.IsUndefined()) |
||||
|
{ |
||||
|
return JValue.CreateUndefined(); |
||||
|
} |
||||
|
|
||||
|
if (value.IsString()) |
||||
|
{ |
||||
|
return new JValue(value.AsString()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsBoolean()) |
||||
|
{ |
||||
|
return new JValue(value.AsBoolean()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsNumber()) |
||||
|
{ |
||||
|
return new JValue(value.AsNumber()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsDate()) |
||||
|
{ |
||||
|
return new JValue(value.AsDate().ToDateTime()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsRegExp()) |
||||
|
{ |
||||
|
return JValue.CreateString(value.AsRegExp().Value?.ToString()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsArray()) |
||||
|
{ |
||||
|
var arr = value.AsArray(); |
||||
|
|
||||
|
var target = new JArray(); |
||||
|
|
||||
|
for (var i = 0; i < arr.GetLength(); i++) |
||||
|
{ |
||||
|
target.Add(Map(arr.Get(i.ToString()))); |
||||
|
} |
||||
|
|
||||
|
return target; |
||||
|
} |
||||
|
|
||||
|
if (value.IsObject()) |
||||
|
{ |
||||
|
var obj = value.AsObject(); |
||||
|
|
||||
|
var target = new JObject(); |
||||
|
|
||||
|
foreach (var kvp in obj.GetOwnProperties()) |
||||
|
{ |
||||
|
target[kvp.Key] = Map(kvp.Value.Value); |
||||
|
} |
||||
|
|
||||
|
return target; |
||||
|
} |
||||
|
|
||||
|
throw new ArgumentException("Invalid json type", nameof(value)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JintUser.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
using Jint.Native.Object; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public sealed class JintUser : ObjectInstance |
||||
|
{ |
||||
|
public JintUser(Engine engine, ClaimsPrincipal principal) |
||||
|
: base(engine) |
||||
|
{ |
||||
|
var subjectId = principal.OpenIdSubject(); |
||||
|
|
||||
|
var isClient = string.IsNullOrWhiteSpace(subjectId); |
||||
|
|
||||
|
if (!isClient) |
||||
|
{ |
||||
|
FastAddProperty("id", subjectId, false, true, false); |
||||
|
FastAddProperty("isClient", false, false, true, false); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
FastAddProperty("id", principal.OpenIdClientId(), false, true, false); |
||||
|
FastAddProperty("isClient", true, false, true, false); |
||||
|
} |
||||
|
|
||||
|
FastAddProperty("email", principal.OpenIdEmail(), false, true, false); |
||||
|
|
||||
|
var claimsInstance = new ObjectInstance(engine); |
||||
|
|
||||
|
foreach (var group in principal.Claims.GroupBy(x => x.Type)) |
||||
|
{ |
||||
|
claimsInstance.FastAddProperty(group.Key, engine.Array.Construct(group.Select(x => new JsValue(x.Value)).ToArray()), false, true, false); |
||||
|
} |
||||
|
|
||||
|
FastAddProperty("claims", claimsInstance, false, true, false); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,50 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// ScriptUser.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Security.Claims; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.Security; |
|
||||
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
|
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Scripting |
|
||||
{ |
|
||||
public sealed class ScriptUser |
|
||||
{ |
|
||||
public bool IsClient { get; set; } |
|
||||
|
|
||||
public string Id { get; set; } |
|
||||
|
|
||||
public string Email { get; set; } |
|
||||
|
|
||||
public Dictionary<string, string[]> Claims { get; set; } |
|
||||
|
|
||||
public static ScriptUser Create(ClaimsPrincipal principal) |
|
||||
{ |
|
||||
Guard.NotNull(principal, nameof(principal)); |
|
||||
|
|
||||
var subjectId = principal.OpenIdSubject(); |
|
||||
|
|
||||
var user = new ScriptUser { IsClient = string.IsNullOrWhiteSpace(subjectId), Email = principal.OpenIdEmail() }; |
|
||||
|
|
||||
if (!user.IsClient) |
|
||||
{ |
|
||||
user.Id = subjectId; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
user.Id = principal.OpenIdClientId(); |
|
||||
} |
|
||||
|
|
||||
user.Claims = principal.Claims.GroupBy(x => x.Type).ToDictionary(x => x.Key, x => x.Select(y => y.Value).ToArray()); |
|
||||
|
|
||||
return user; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,216 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentDataObjectTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint; |
||||
|
using Jint.Runtime; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public sealed class ContentDataObjectTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_update_content_when_setting_field() |
||||
|
{ |
||||
|
var original = new NamedContentData(); |
||||
|
|
||||
|
var expected = |
||||
|
new NamedContentData() |
||||
|
.AddField("number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 1.0)); |
||||
|
|
||||
|
var result = ExecuteScript(original, @"data.number = { iv: 1 }"); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_update_content_when_deleting_field() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 1.0)); |
||||
|
|
||||
|
var expected = new NamedContentData(); |
||||
|
|
||||
|
var result = ExecuteScript(original, @"delete data.number"); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_update_content_when_setting_field_value_with_string() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("string", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", "1")); |
||||
|
|
||||
|
var expected = |
||||
|
new NamedContentData() |
||||
|
.AddField("string", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", "1new")); |
||||
|
|
||||
|
var result = ExecuteScript(original, @"data.string.iv = data.string.iv + 'new'"); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_update_content_when_setting_field_value_with_number() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 1.0)); |
||||
|
|
||||
|
var expected = |
||||
|
new NamedContentData() |
||||
|
.AddField("number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 3.0)); |
||||
|
|
||||
|
var result = ExecuteScript(original, @"data.number.iv = data.number.iv + 2"); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_update_content_when_setting_field_value_with_array() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", new JArray(1.0, 2.0))); |
||||
|
|
||||
|
var expected = |
||||
|
new NamedContentData() |
||||
|
.AddField("number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", new JArray(1.0, 4.0, 5.0))); |
||||
|
|
||||
|
var result = ExecuteScript(original, @"data.number.iv = [data.number.iv[0], data.number.iv[1] + 2, 5]"); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_update_content_when_setting_field_value_with_object() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", new JObject(new JProperty("lat", 1.0)))); |
||||
|
|
||||
|
var expected = |
||||
|
new NamedContentData() |
||||
|
.AddField("number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", new JObject(new JProperty("lat", 1.0), new JProperty("lon", 4.0)))); |
||||
|
|
||||
|
var result = ExecuteScript(original, @"data.number.iv = { lat: data.number.iv.lat, lon: data.number.iv.lat + 3 }"); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_update_content_when_setting_field_value_with_boolean() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("boolean", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", false)); |
||||
|
|
||||
|
var expected = |
||||
|
new NamedContentData() |
||||
|
.AddField("boolean", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", true)); |
||||
|
|
||||
|
var result = ExecuteScript(original, @"data.boolean.iv = !data.boolean.iv"); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_update_content_when_deleting_field_value() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("string", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", "hello")); |
||||
|
|
||||
|
var expected = |
||||
|
new NamedContentData() |
||||
|
.AddField("string", |
||||
|
new ContentFieldData()); |
||||
|
|
||||
|
var result = ExecuteScript(original, @"delete data.string.iv"); |
||||
|
|
||||
|
Assert.Equal(expected, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_throw_exceptions_when_changing_objects() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("obj", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", new JObject(new JProperty("readonly", 1)))); |
||||
|
|
||||
|
Assert.Throws<JavaScriptException>(() => ExecuteScript(original, "data.obj.iv.invalid = 1")); |
||||
|
Assert.Throws<JavaScriptException>(() => ExecuteScript(original, "data.obj.iv.readonly = 2")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_not_throw_exceptions_when_changing_arrays() |
||||
|
{ |
||||
|
var original = |
||||
|
new NamedContentData() |
||||
|
.AddField("obj", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", new JArray())); |
||||
|
|
||||
|
ExecuteScript(original, "data.obj.iv[0] = 1"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_null_propagate_unknown_fields() |
||||
|
{ |
||||
|
ExecuteScript(new NamedContentData(), @"data.string.iv = 'hello'"); |
||||
|
} |
||||
|
|
||||
|
private static NamedContentData ExecuteScript(NamedContentData original, string script) |
||||
|
{ |
||||
|
var engine = new Engine(o => o.Strict()); |
||||
|
|
||||
|
var value = new ContentDataObject(engine, original); |
||||
|
|
||||
|
engine.SetValue("data", value); |
||||
|
engine.Execute(script); |
||||
|
|
||||
|
value.TryUpdate(out var result); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JintUserTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Security.Claims; |
||||
|
using Jint; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public class JintUserTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_set_user_id_from_client_id() |
||||
|
{ |
||||
|
var identity = new ClaimsIdentity(); |
||||
|
|
||||
|
identity.AddClaim(new Claim(OpenIdClaims.ClientId, "1")); |
||||
|
|
||||
|
Assert.Equal("1", GetValue(identity, "user.id")); |
||||
|
Assert.Equal(true, GetValue(identity, "user.isClient")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_set_user_id_from_subject_id() |
||||
|
{ |
||||
|
var identity = new ClaimsIdentity(); |
||||
|
|
||||
|
identity.AddClaim(new Claim(OpenIdClaims.Subject, "2")); |
||||
|
|
||||
|
Assert.Equal("2", GetValue(identity, "user.id")); |
||||
|
Assert.Equal(false, GetValue(identity, "user.isClient")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_set_email_from_claim() |
||||
|
{ |
||||
|
var identity = new ClaimsIdentity(); |
||||
|
|
||||
|
identity.AddClaim(new Claim(OpenIdClaims.Email, "hello@squidex.io")); |
||||
|
|
||||
|
Assert.Equal("hello@squidex.io", GetValue(identity, "user.email")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_set_claims() |
||||
|
{ |
||||
|
var identity = new ClaimsIdentity(); |
||||
|
|
||||
|
identity.AddClaim(new Claim("claim1", "1a")); |
||||
|
identity.AddClaim(new Claim("claim1", "1b")); |
||||
|
identity.AddClaim(new Claim("claim2", "2a")); |
||||
|
identity.AddClaim(new Claim("claim2", "2b")); |
||||
|
|
||||
|
Assert.Equal(new[] { "1a", "1b" }, GetValue(identity, "user.claims.claim1")); |
||||
|
Assert.Equal(new[] { "2a", "2b" }, GetValue(identity, "user.claims.claim2")); |
||||
|
} |
||||
|
|
||||
|
private static object GetValue(ClaimsIdentity identity, string script) |
||||
|
{ |
||||
|
var engine = new Engine(); |
||||
|
|
||||
|
engine.SetValue("user", new JintUser(engine, new ClaimsPrincipal(new[] { identity }))); |
||||
|
|
||||
|
return engine.Execute(script).GetCompletionValue().ToObject(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,80 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// ScriptUserTests.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using System.Security.Claims; |
|
||||
using FluentAssertions; |
|
||||
using Squidex.Infrastructure.Security; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Scripting |
|
||||
{ |
|
||||
public class ScriptUserTests |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void Should_create_script_user_from_user_principal() |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(); |
|
||||
|
|
||||
identity.AddClaim(new Claim(OpenIdClaims.Subject, "1")); |
|
||||
identity.AddClaim(new Claim(OpenIdClaims.Email, "hello@squidex.io")); |
|
||||
identity.AddClaim(new Claim("claim1", "1a")); |
|
||||
identity.AddClaim(new Claim("claim1", "1b")); |
|
||||
identity.AddClaim(new Claim("claim2", "2a")); |
|
||||
identity.AddClaim(new Claim("claim2", "2b")); |
|
||||
|
|
||||
var principal = new ClaimsPrincipal(new[] { identity }); |
|
||||
|
|
||||
var scriptUser = ScriptUser.Create(principal); |
|
||||
|
|
||||
scriptUser.ShouldBeEquivalentTo( |
|
||||
new ScriptUser |
|
||||
{ |
|
||||
Email = "hello@squidex.io", |
|
||||
Id = "1", |
|
||||
IsClient = false, |
|
||||
Claims = new Dictionary<string, string[]> |
|
||||
{ |
|
||||
{ "sub", new [] { "1" } }, |
|
||||
{ "claim1", new[] { "1a", "1b" } }, |
|
||||
{ "claim2", new[] { "2a", "2b" } }, |
|
||||
{ "email", new [] { "hello@squidex.io" } } |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_create_script_user_from_client_principal() |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(); |
|
||||
|
|
||||
identity.AddClaim(new Claim(OpenIdClaims.ClientId, "1")); |
|
||||
identity.AddClaim(new Claim("claim1", "1a")); |
|
||||
identity.AddClaim(new Claim("claim1", "1b")); |
|
||||
identity.AddClaim(new Claim("claim2", "2a")); |
|
||||
identity.AddClaim(new Claim("claim2", "2b")); |
|
||||
|
|
||||
var principal = new ClaimsPrincipal(new[] { identity }); |
|
||||
|
|
||||
var scriptUser = ScriptUser.Create(principal); |
|
||||
|
|
||||
scriptUser.ShouldBeEquivalentTo( |
|
||||
new ScriptUser |
|
||||
{ |
|
||||
Id = "1", |
|
||||
IsClient = true, |
|
||||
Claims = new Dictionary<string, string[]> |
|
||||
{ |
|
||||
{ "client_id", new [] { "1" } } , |
|
||||
{ "claim1", new[] { "1a", "1b" } }, |
|
||||
{ "claim2", new[] { "2a", "2b" } } |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue