1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 13:08:17 -05:00

Fix OneLogin Import (#1899)

* Add PermissiveStringConverter

* Formatting

* Add value check

* Fix PR feedback

* Run formatter
This commit is contained in:
Justin Baur 2022-03-08 13:22:47 -05:00 committed by GitHub
parent a725802476
commit dd37745736
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 235 additions and 0 deletions

View File

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Bit.Core.Entities;
using Bit.Core.Models.Business;
using Bit.Core.Utilities;
namespace Bit.Api.Models.Public.Request
{
@ -41,10 +43,12 @@ namespace Bit.Api.Models.Public.Request
/// <example>external_id_123456</example>
[Required]
[StringLength(300)]
[JsonConverter(typeof(PermissiveStringConverter))]
public string ExternalId { get; set; }
/// <summary>
/// The associated external ids for members in this group.
/// </summary>
[JsonConverter(typeof(PermissiveStringEnumerableConverter))]
public IEnumerable<string> MemberExternalIds { get; set; }
public ImportedGroup ToImportedGroup(Guid organizationId)
@ -79,6 +83,7 @@ namespace Bit.Api.Models.Public.Request
/// <example>external_id_123456</example>
[Required]
[StringLength(300)]
[JsonConverter(typeof(PermissiveStringConverter))]
public string ExternalId { get; set; }
/// <summary>
/// Determines if this member should be removed from the organization during import.

View File

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using NS = Newtonsoft.Json;
@ -129,4 +132,72 @@ namespace Bit.Core.Utilities
writer.WriteStringValue(CoreHelpers.ToEpocMilliseconds(value.Value).ToString());
}
}
/// <summary>
/// Allows reading a string from a JSON number or string, should only be used on <see cref="string" /> properties
/// </summary>
public class PermissiveStringConverter : JsonConverter<string>
{
internal static PermissiveStringConverter Instance = new();
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType switch
{
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetDecimal().ToString(),
JsonTokenType.True => bool.TrueString,
JsonTokenType.False => bool.FalseString,
_ => throw new JsonException($"Unsupported TokenType: {reader.TokenType}"),
};
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}
/// <summary>
/// Allows reading a JSON array of number or string, should only be used on <see cref="IEnumerable{T}" /> whose generic type is <see cref="string" />
/// </summary>
public class PermissiveStringEnumerableConverter : JsonConverter<IEnumerable<string>>
{
public override IEnumerable<string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var stringList = new List<string>();
// Handle special cases or throw
if (reader.TokenType != JsonTokenType.StartArray)
{
// An array was expected but to be extra permissive allow reading from anything other than an object
if (reader.TokenType == JsonTokenType.StartObject)
{
throw new JsonException("Cannot read JSON Object to an IEnumerable<string>.");
}
stringList.Add(PermissiveStringConverter.Instance.Read(ref reader, typeof(string), options));
return stringList;
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
stringList.Add(PermissiveStringConverter.Instance.Read(ref reader, typeof(string), options));
}
return stringList;
}
public override void Write(Utf8JsonWriter writer, IEnumerable<string> value, JsonSerializerOptions options)
{
writer.WriteStartArray();
foreach (var str in value)
{
PermissiveStringConverter.Instance.Write(writer, str, options);
}
writer.WriteEndArray();
}
}
}

View File

@ -0,0 +1,159 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Bit.Core.Utilities;
using Bit.Test.Common.Helpers;
using Xunit;
namespace Bit.Core.Test.Utilities
{
public class PermissiveStringConverterTests
{
private const string numberJson = "{ \"StringProp\": 1, \"EnumerableStringProp\": [ 2, 3 ]}";
private const string stringJson = "{ \"StringProp\": \"1\", \"EnumerableStringProp\": [ \"2\", \"3\" ]}";
private const string nullAndEmptyJson = "{ \"StringProp\": null, \"EnumerableStringProp\": [] }";
private const string singleValueJson = "{ \"StringProp\": 1, \"EnumerableStringProp\": \"Hello!\" }";
private const string nullJson = "{ \"StringProp\": null, \"EnumerableStringProp\": null }";
private const string boolJson = "{ \"StringProp\": true, \"EnumerableStringProp\": [ false, 1.2]}";
private const string objectJsonOne = "{ \"StringProp\": { \"Message\": \"Hi\"}, \"EnumerableStringProp\": []}";
private const string objectJsonTwo = "{ \"StringProp\": \"Hi\", \"EnumerableStringProp\": {}}";
private readonly string bigNumbersJson =
"{ \"StringProp\":" + decimal.MinValue + ", \"EnumerableStringProp\": [" + ulong.MaxValue + ", " + long.MinValue + "]}";
[Theory]
[InlineData(numberJson)]
[InlineData(stringJson)]
public void Read_Success(string json)
{
var obj = JsonSerializer.Deserialize<TestObject>(json);
Assert.Equal("1", obj.StringProp);
Assert.Equal(2, obj.EnumerableStringProp.Count());
Assert.Equal("2", obj.EnumerableStringProp.ElementAt(0));
Assert.Equal("3", obj.EnumerableStringProp.ElementAt(1));
}
[Fact]
public void Read_Boolean_Success()
{
var obj = JsonSerializer.Deserialize<TestObject>(boolJson);
Assert.Equal("True", obj.StringProp);
Assert.Equal(2, obj.EnumerableStringProp.Count());
Assert.Equal("False", obj.EnumerableStringProp.ElementAt(0));
Assert.Equal("1.2", obj.EnumerableStringProp.ElementAt(1));
}
[Fact]
public void Read_BigNumbers_Success()
{
var obj = JsonSerializer.Deserialize<TestObject>(bigNumbersJson);
Assert.Equal(decimal.MinValue.ToString(), obj.StringProp);
Assert.Equal(2, obj.EnumerableStringProp.Count());
Assert.Equal(ulong.MaxValue.ToString(), obj.EnumerableStringProp.ElementAt(0));
Assert.Equal(long.MinValue.ToString(), obj.EnumerableStringProp.ElementAt(1));
}
[Fact]
public void Read_SingleValue_Success()
{
var obj = JsonSerializer.Deserialize<TestObject>(singleValueJson);
Assert.Equal("1", obj.StringProp);
Assert.Single(obj.EnumerableStringProp);
Assert.Equal("Hello!", obj.EnumerableStringProp.ElementAt(0));
}
[Fact]
public void Read_NullAndEmptyJson_Success()
{
var obj = JsonSerializer.Deserialize<TestObject>(nullAndEmptyJson);
Assert.Null(obj.StringProp);
Assert.Empty(obj.EnumerableStringProp);
}
[Fact]
public void Read_Null_Success()
{
var obj = JsonSerializer.Deserialize<TestObject>(nullJson);
Assert.Null(obj.StringProp);
Assert.Null(obj.EnumerableStringProp);
}
[Theory]
[InlineData(objectJsonOne)]
[InlineData(objectJsonTwo)]
public void Read_Object_Throws(string json)
{
var exception = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<TestObject>(json));
}
[Fact]
public void Write_Success()
{
var json = JsonSerializer.Serialize(new TestObject
{
StringProp = "1",
EnumerableStringProp = new List<string>
{
"2",
"3",
},
});
var jsonElement = JsonDocument.Parse(json).RootElement;
var stringProp = AssertHelper.AssertJsonProperty(jsonElement, "StringProp", JsonValueKind.String);
Assert.Equal("1", stringProp.GetString());
var list = AssertHelper.AssertJsonProperty(jsonElement, "EnumerableStringProp", JsonValueKind.Array);
Assert.Equal(2, list.GetArrayLength());
var firstElement = list[0];
Assert.Equal(JsonValueKind.String, firstElement.ValueKind);
Assert.Equal("2", firstElement.GetString());
var secondElement = list[1];
Assert.Equal(JsonValueKind.String, secondElement.ValueKind);
Assert.Equal("3", secondElement.GetString());
}
[Fact]
public void Write_Null()
{
// When the values are null the converters aren't actually ran and it automatically serializes null
var json = JsonSerializer.Serialize(new TestObject
{
StringProp = null,
EnumerableStringProp = null,
});
var jsonElement = JsonDocument.Parse(json).RootElement;
AssertHelper.AssertJsonProperty(jsonElement, "StringProp", JsonValueKind.Null);
AssertHelper.AssertJsonProperty(jsonElement, "EnumerableStringProp", JsonValueKind.Null);
}
[Fact]
public void Write_Empty()
{
// When the values are null the converters aren't actually ran and it automatically serializes null
var json = JsonSerializer.Serialize(new TestObject
{
StringProp = "",
EnumerableStringProp = Enumerable.Empty<string>(),
});
var jsonElement = JsonDocument.Parse(json).RootElement;
var stringVal = AssertHelper.AssertJsonProperty(jsonElement, "StringProp", JsonValueKind.String).GetString();
Assert.Equal("", stringVal);
var array = AssertHelper.AssertJsonProperty(jsonElement, "EnumerableStringProp", JsonValueKind.Array);
Assert.Equal(0, array.GetArrayLength());
}
}
public class TestObject
{
[JsonConverter(typeof(PermissiveStringConverter))]
public string StringProp { get; set; }
[JsonConverter(typeof(PermissiveStringEnumerableConverter))]
public IEnumerable<string> EnumerableStringProp { get; set; }
}
}