diff --git a/src/Admin/Controllers/HomeController.cs b/src/Admin/Controllers/HomeController.cs index f2c899064..a1b74283e 100644 --- a/src/Admin/Controllers/HomeController.cs +++ b/src/Admin/Controllers/HomeController.cs @@ -1,11 +1,12 @@ using System.Diagnostics; using System.Net.Http; +using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Bit.Admin.Models; using Bit.Core.Settings; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json.Linq; namespace Bit.Admin.Controllers { @@ -37,20 +38,21 @@ namespace Bit.Admin.Controllers }); } - public async Task GetLatestDockerHubVersion(string repository) + public async Task GetLatestDockerHubVersion(string repository, CancellationToken cancellationToken) { try { var response = await _httpClient.GetAsync( - $"https://hub.docker.com/v2/repositories/bitwarden/{repository}/tags/"); + $"https://hub.docker.com/v2/repositories/bitwarden/{repository}/tags/", cancellationToken); if (response.IsSuccessStatusCode) { - var json = await response.Content.ReadAsStringAsync(); - var data = JObject.Parse(json); - var results = data["results"] as JArray; - foreach (var result in results) + using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync(cancellationToken), cancellationToken: cancellationToken); + var root = jsonDocument.RootElement; + + var results = root.GetProperty("results"); + foreach (var result in results.EnumerateArray()) { - var name = result["name"].ToString(); + var name = result.GetProperty("name").GetString(); if (!string.IsNullOrWhiteSpace(name) && name.Length > 0 && char.IsNumber(name[0])) { return new JsonResult(name); @@ -63,17 +65,17 @@ namespace Bit.Admin.Controllers return new JsonResult("-"); } - public async Task GetInstalledWebVersion() + public async Task GetInstalledWebVersion(CancellationToken cancellationToken) { try { var response = await _httpClient.GetAsync( - $"{_globalSettings.BaseServiceUri.InternalVault}/version.json"); + $"{_globalSettings.BaseServiceUri.InternalVault}/version.json", cancellationToken); if (response.IsSuccessStatusCode) { - var json = await response.Content.ReadAsStringAsync(); - var data = JObject.Parse(json); - return new JsonResult(data["version"].ToString()); + using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync(cancellationToken), cancellationToken: cancellationToken); + var root = jsonDocument.RootElement; + return new JsonResult(root.GetProperty("version").GetString()); } } catch (HttpRequestException) { } diff --git a/src/Admin/Controllers/ToolsController.cs b/src/Admin/Controllers/ToolsController.cs index 7b56746b1..6d37a03f5 100644 --- a/src/Admin/Controllers/ToolsController.cs +++ b/src/Admin/Controllers/ToolsController.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Bit.Admin.Models; -using Bit.Core; using Bit.Core.Entities; using Bit.Core.Repositories; using Bit.Core.Services; @@ -14,7 +14,6 @@ using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; namespace Bit.Admin.Controllers { @@ -264,14 +263,16 @@ namespace Bit.Admin.Controllers { var license = await _organizationService.GenerateLicenseAsync(organization, model.InstallationId.Value, model.Version); - return File(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(license, Formatting.Indented)), - "text/plain", "bitwarden_organization_license.json"); + var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented); + return File(ms, "text/plain", "bitwarden_organization_license.json"); } else if (user != null) { var license = await _userService.GenerateLicenseAsync(user, null, model.Version); - return File(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(license, Formatting.Indented)), - "text/plain", "bitwarden_premium_license.json"); + var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented); + return File(ms, "text/plain", "bitwarden_premium_license.json"); } else { diff --git a/src/Admin/HostedServices/AzureQueueMailHostedService.cs b/src/Admin/HostedServices/AzureQueueMailHostedService.cs index fbf3c4641..670e16615 100644 --- a/src/Admin/HostedServices/AzureQueueMailHostedService.cs +++ b/src/Admin/HostedServices/AzureQueueMailHostedService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Azure.Storage.Queues; @@ -11,8 +12,6 @@ using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Bit.Admin.HostedServices { @@ -70,17 +69,19 @@ namespace Bit.Admin.HostedServices { try { - var token = JToken.Parse(message.DecodeMessageText()); - if (token is JArray) + using var document = JsonDocument.Parse(message.DecodeMessageText()); + var root = document.RootElement; + + if (root.ValueKind == JsonValueKind.Array) { - foreach (var mailQueueMessage in token.ToObject>()) + foreach (var mailQueueMessage in root.ToObject>()) { await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage); } } - else if (token is JObject) + else if (root.ValueKind == JsonValueKind.Object) { - var mailQueueMessage = token.ToObject(); + var mailQueueMessage = root.ToObject(); await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage); } } diff --git a/src/Admin/HostedServices/BlockIpHostedService.cs b/src/Admin/HostedServices/BlockIpHostedService.cs index 09dcac352..672414430 100644 --- a/src/Admin/HostedServices/BlockIpHostedService.cs +++ b/src/Admin/HostedServices/BlockIpHostedService.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Text; +using System.Net.Http.Json; using System.Threading; using System.Threading.Tasks; using Bit.Core.Settings; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; namespace Bit.Admin.HostedServices { @@ -65,7 +64,7 @@ namespace Bit.Admin.HostedServices request.RequestUri = new Uri("https://api.cloudflare.com/" + $"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules"); - var bodyContent = JsonConvert.SerializeObject(new + request.Content = JsonContent.Create(new { mode = "block", configuration = new @@ -75,7 +74,6 @@ namespace Bit.Admin.HostedServices }, notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}." }); - request.Content = new StringContent(bodyContent, Encoding.UTF8, "application/json"); var response = await _httpClient.SendAsync(request, cancellationToken); if (!response.IsSuccessStatusCode) @@ -83,8 +81,7 @@ namespace Bit.Admin.HostedServices return; } - var responseString = await response.Content.ReadAsStringAsync(); - var accessRuleResponse = JsonConvert.DeserializeObject(responseString); + var accessRuleResponse = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); if (!accessRuleResponse.Success) { return; @@ -118,8 +115,7 @@ namespace Bit.Admin.HostedServices return; } - var responseString = await response.Content.ReadAsStringAsync(); - var listResponse = JsonConvert.DeserializeObject(responseString); + var listResponse = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); if (!listResponse.Success) { return; diff --git a/src/Admin/Models/LogModel.cs b/src/Admin/Models/LogModel.cs index 16f90b5df..6675c81ee 100644 --- a/src/Admin/Models/LogModel.cs +++ b/src/Admin/Models/LogModel.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using Bit.Core.Utilities; using Microsoft.Azure.Documents; -using Newtonsoft.Json.Linq; namespace Bit.Admin.Models { @@ -17,40 +19,48 @@ namespace Bit.Admin.Models public class LogDetailsModel : LogModel { - public JObject Exception { get; set; } + public JsonDocument Exception { get; set; } - public string ExceptionToString(JObject e) + public string ExceptionToString(JsonDocument e) { if (e == null) { return null; } - var val = string.Empty; - if (e["Message"] != null && e["Message"].ToObject() != null) + var root = e.RootElement; + + var sb = new StringBuilder(); + if (root.TryGetProperty("Message", out var messageProp) && messageProp.GetString() != null) { - val += "Message:\n"; - val += e["Message"] + "\n"; + sb.AppendLine("Message:"); + sb.AppendLine(messageProp.GetString()); } - if (e["StackTrace"] != null && e["StackTrace"].ToObject() != null) + if (root.TryGetProperty("StackTrace", out var stackTraceProp) && stackTraceProp.GetString() != null) { - val += "\nStack Trace:\n"; - val += e["StackTrace"]; + sb.AppendLine(); + sb.AppendLine("Stack Trace:"); + sb.Append(stackTraceProp.GetString()); } - else if (e["StackTraceString"] != null && e["StackTraceString"].ToObject() != null) + else if (root.TryGetProperty("StackTraceString", out var stackTraceStringProp) && stackTraceStringProp.GetString() != null) { - val += "\nStack Trace String:\n"; - val += e["StackTraceString"]; + sb.AppendLine(); + sb.AppendLine("Stack Trace String:"); + sb.Append(stackTraceStringProp.GetString()); } - if (e["InnerException"] != null && e["InnerException"].ToObject() != null) + if (root.TryGetProperty("InnerException", out var innerExceptionProp) && innerExceptionProp.ValueKind == JsonValueKind.Object) { - val += "\n\n=== Inner Exception ===\n\n"; - val += ExceptionToString(e["InnerException"].ToObject()); + sb.AppendLine(); + sb.AppendLine(); + sb.AppendLine("=== Inner Exception ==="); + sb.AppendLine(); + sb.AppendLine(ExceptionToString(innerExceptionProp.ToObject())); + sb.AppendLine(); } - return val; + return sb.ToString(); } } } diff --git a/src/Api/Controllers/CiphersController.cs b/src/Api/Controllers/CiphersController.cs index 88ca1994e..b301ad8ff 100644 --- a/src/Api/Controllers/CiphersController.cs +++ b/src/Api/Controllers/CiphersController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Azure.Messaging.EventGrid; using Bit.Api.Models.Request; @@ -21,7 +22,6 @@ using Core.Models.Data; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace Bit.Api.Controllers { @@ -802,7 +802,7 @@ namespace Bit.Api.Controllers } catch (Exception e) { - _logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonConvert.SerializeObject(eventGridEvent)}"); + _logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}"); return; } } diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index 7d28946ad..c06eaad61 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Api.Models.Request; using Bit.Api.Models.Request.Accounts; @@ -17,7 +18,6 @@ using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; namespace Bit.Api.Controllers { @@ -185,7 +185,7 @@ namespace Bit.Api.Controllers return new OrganizationAutoEnrollStatusResponseModel(organization.Id, false); } - var data = JsonConvert.DeserializeObject(resetPasswordPolicy.Data); + var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data); return new OrganizationAutoEnrollStatusResponseModel(organization.Id, data?.AutoEnrollEnabled ?? false); } diff --git a/src/Api/Controllers/SendsController.cs b/src/Api/Controllers/SendsController.cs index 9536d8353..f21885b1c 100644 --- a/src/Api/Controllers/SendsController.cs +++ b/src/Api/Controllers/SendsController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Azure.Messaging.EventGrid; using Bit.Api.Models.Request; @@ -19,7 +20,6 @@ using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace Bit.Api.Controllers { @@ -227,7 +227,7 @@ namespace Bit.Api.Controllers var userId = _userService.GetProperUserId(User).Value; var sendId = new Guid(id); var send = await _sendRepository.GetByIdAsync(sendId); - var fileData = JsonConvert.DeserializeObject(send?.Data); + var fileData = JsonSerializer.Deserialize(send?.Data); if (send == null || send.Type != SendType.File || (send.UserId.HasValue && send.UserId.Value != userId) || !send.UserId.HasValue || fileData.Id != fileId || fileData.Validated) @@ -289,7 +289,7 @@ namespace Bit.Api.Controllers } catch (Exception e) { - _logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonConvert.SerializeObject(eventGridEvent)}"); + _logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}"); return; } } diff --git a/src/Api/Models/Public/Request/PolicyUpdateRequestModel.cs b/src/Api/Models/Public/Request/PolicyUpdateRequestModel.cs index 239f89afa..faede3da0 100644 --- a/src/Api/Models/Public/Request/PolicyUpdateRequestModel.cs +++ b/src/Api/Models/Public/Request/PolicyUpdateRequestModel.cs @@ -1,6 +1,6 @@ using System; +using System.Text.Json; using Bit.Core.Entities; -using Newtonsoft.Json; namespace Bit.Api.Models.Public.Request { @@ -17,7 +17,7 @@ namespace Bit.Api.Models.Public.Request public virtual Policy ToPolicy(Policy existingPolicy) { existingPolicy.Enabled = Enabled.GetValueOrDefault(); - existingPolicy.Data = Data != null ? JsonConvert.SerializeObject(Data) : null; + existingPolicy.Data = Data != null ? JsonSerializer.Serialize(Data) : null; return existingPolicy; } } diff --git a/src/Api/Models/Public/Response/PolicyResponseModel.cs b/src/Api/Models/Public/Response/PolicyResponseModel.cs index a4d0c2fd6..fbbcf6a0e 100644 --- a/src/Api/Models/Public/Response/PolicyResponseModel.cs +++ b/src/Api/Models/Public/Response/PolicyResponseModel.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; -using Newtonsoft.Json; +using Bit.Core.Utilities; namespace Bit.Api.Models.Public.Response { @@ -24,7 +25,7 @@ namespace Bit.Api.Models.Public.Response Enabled = policy.Enabled; if (!string.IsNullOrWhiteSpace(policy.Data)) { - Data = JsonConvert.DeserializeObject>(policy.Data); + Data = JsonSerializer.Deserialize>(policy.Data); } } diff --git a/src/Api/Models/Request/CipherRequestModel.cs b/src/Api/Models/Request/CipherRequestModel.cs index 51462ed66..417fb4497 100644 --- a/src/Api/Models/Request/CipherRequestModel.cs +++ b/src/Api/Models/Request/CipherRequestModel.cs @@ -2,13 +2,14 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Utilities; using Core.Models.Data; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using NS = Newtonsoft.Json; +using NSL = Newtonsoft.Json.Linq; namespace Bit.Api.Models.Request { @@ -69,22 +70,20 @@ namespace Bit.Api.Models.Request switch (existingCipher.Type) { case CipherType.Login: - var loginObj = JObject.FromObject(ToCipherLoginData(), - new JsonSerializer { NullValueHandling = NullValueHandling.Ignore }); + var loginObj = NSL.JObject.FromObject(ToCipherLoginData(), + new NS.JsonSerializer { NullValueHandling = NS.NullValueHandling.Ignore }); + // TODO: Switch to JsonNode in .NET 6 https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-use-dom-utf8jsonreader-utf8jsonwriter?pivots=dotnet-6-0 loginObj[nameof(CipherLoginData.Uri)]?.Parent?.Remove(); - existingCipher.Data = loginObj.ToString(Formatting.None); + existingCipher.Data = loginObj.ToString(NS.Formatting.None); break; case CipherType.Card: - existingCipher.Data = JsonConvert.SerializeObject(ToCipherCardData(), - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingCipher.Data = JsonSerializer.Serialize(ToCipherCardData(), JsonHelpers.IgnoreWritingNull); break; case CipherType.Identity: - existingCipher.Data = JsonConvert.SerializeObject(ToCipherIdentityData(), - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingCipher.Data = JsonSerializer.Serialize(ToCipherIdentityData(), JsonHelpers.IgnoreWritingNull); break; case CipherType.SecureNote: - existingCipher.Data = JsonConvert.SerializeObject(ToCipherSecureNoteData(), - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingCipher.Data = JsonSerializer.Serialize(ToCipherSecureNoteData(), JsonHelpers.IgnoreWritingNull); break; default: throw new ArgumentException("Unsupported type: " + nameof(Type) + "."); diff --git a/src/Api/Models/Request/SendRequestModel.cs b/src/Api/Models/Request/SendRequestModel.cs index 924c4b097..106f4b5c6 100644 --- a/src/Api/Models/Request/SendRequestModel.cs +++ b/src/Api/Models/Request/SendRequestModel.cs @@ -1,12 +1,12 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Services; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Api.Models.Request { @@ -65,15 +65,13 @@ namespace Bit.Api.Models.Request switch (existingSend.Type) { case SendType.File: - var fileData = JsonConvert.DeserializeObject(existingSend.Data); + var fileData = JsonSerializer.Deserialize(existingSend.Data); fileData.Name = Name; fileData.Notes = Notes; - existingSend.Data = JsonConvert.SerializeObject(fileData, - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingSend.Data = JsonSerializer.Serialize(fileData, JsonHelpers.IgnoreWritingNull); break; case SendType.Text: - existingSend.Data = JsonConvert.SerializeObject(ToSendData(), - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingSend.Data = JsonSerializer.Serialize(ToSendData(), JsonHelpers.IgnoreWritingNull); break; default: throw new ArgumentException("Unsupported type: " + nameof(Type) + "."); diff --git a/src/Api/Models/Request/UpdateDomainsRequestModel.cs b/src/Api/Models/Request/UpdateDomainsRequestModel.cs index 9ba4334fa..385a3f6a5 100644 --- a/src/Api/Models/Request/UpdateDomainsRequestModel.cs +++ b/src/Api/Models/Request/UpdateDomainsRequestModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; -using Newtonsoft.Json; namespace Bit.Api.Models.Request { @@ -12,9 +12,9 @@ namespace Bit.Api.Models.Request public User ToUser(User existingUser) { - existingUser.EquivalentDomains = EquivalentDomains != null ? JsonConvert.SerializeObject(EquivalentDomains) : null; + existingUser.EquivalentDomains = EquivalentDomains != null ? JsonSerializer.Serialize(EquivalentDomains) : null; existingUser.ExcludedGlobalEquivalentDomains = ExcludedGlobalEquivalentDomains != null ? - JsonConvert.SerializeObject(ExcludedGlobalEquivalentDomains) : null; + JsonSerializer.Serialize(ExcludedGlobalEquivalentDomains) : null; return existingUser; } } diff --git a/src/Api/Models/Response/AttachmentResponseModel.cs b/src/Api/Models/Response/AttachmentResponseModel.cs index dcb5e041d..4edd8eae3 100644 --- a/src/Api/Models/Response/AttachmentResponseModel.cs +++ b/src/Api/Models/Response/AttachmentResponseModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using Bit.Core.Entities; using Bit.Core.Models.Api; using Bit.Core.Models.Data; @@ -18,7 +19,7 @@ namespace Bit.Api.Models.Response Url = $"{globalSettings.Attachment.BaseUrl}/{cipher.Id}/{id}"; FileName = data.FileName; Key = data.Key; - Size = data.SizeString; + Size = data.Size; SizeName = CoreHelpers.ReadableBytesSize(data.Size); } @@ -26,7 +27,8 @@ namespace Bit.Api.Models.Response public string Url { get; set; } public string FileName { get; set; } public string Key { get; set; } - public string Size { get; set; } + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] + public long Size { get; set; } public string SizeName { get; set; } public static IEnumerable FromCipher(Cipher cipher, IGlobalSettings globalSettings) diff --git a/src/Api/Models/Response/CipherResponseModel.cs b/src/Api/Models/Response/CipherResponseModel.cs index 0ef56da66..6ebf671e6 100644 --- a/src/Api/Models/Response/CipherResponseModel.cs +++ b/src/Api/Models/Response/CipherResponseModel.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Data; using Bit.Core.Settings; using Core.Models.Data; -using Newtonsoft.Json; namespace Bit.Api.Models.Response { @@ -28,25 +28,25 @@ namespace Bit.Api.Models.Response switch (cipher.Type) { case CipherType.Login: - var loginData = JsonConvert.DeserializeObject(cipher.Data); + var loginData = JsonSerializer.Deserialize(cipher.Data); cipherData = loginData; Data = loginData; Login = new CipherLoginModel(loginData); break; case CipherType.SecureNote: - var secureNoteData = JsonConvert.DeserializeObject(cipher.Data); + var secureNoteData = JsonSerializer.Deserialize(cipher.Data); Data = secureNoteData; cipherData = secureNoteData; SecureNote = new CipherSecureNoteModel(secureNoteData); break; case CipherType.Card: - var cardData = JsonConvert.DeserializeObject(cipher.Data); + var cardData = JsonSerializer.Deserialize(cipher.Data); Data = cardData; cipherData = cardData; Card = new CipherCardModel(cardData); break; case CipherType.Identity: - var identityData = JsonConvert.DeserializeObject(cipher.Data); + var identityData = JsonSerializer.Deserialize(cipher.Data); Data = identityData; cipherData = identityData; Identity = new CipherIdentityModel(identityData); diff --git a/src/Api/Models/Response/DomainsResponseModel.cs b/src/Api/Models/Response/DomainsResponseModel.cs index 73f7c18e3..4c7126100 100644 --- a/src/Api/Models/Response/DomainsResponseModel.cs +++ b/src/Api/Models/Response/DomainsResponseModel.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; -using Newtonsoft.Json; namespace Bit.Api.Models.Response { @@ -19,10 +19,10 @@ namespace Bit.Api.Models.Response } EquivalentDomains = user.EquivalentDomains != null ? - JsonConvert.DeserializeObject>>(user.EquivalentDomains) : null; + JsonSerializer.Deserialize>>(user.EquivalentDomains) : null; var excludedGlobalEquivalentDomains = user.ExcludedGlobalEquivalentDomains != null ? - JsonConvert.DeserializeObject>(user.ExcludedGlobalEquivalentDomains) : + JsonSerializer.Deserialize>(user.ExcludedGlobalEquivalentDomains) : new List(); var globalDomains = new List(); var domainsToInclude = excluded ? Core.Utilities.StaticStore.GlobalDomains : diff --git a/src/Api/Models/Response/PolicyResponseModel.cs b/src/Api/Models/Response/PolicyResponseModel.cs index f28bef85d..b7d97af92 100644 --- a/src/Api/Models/Response/PolicyResponseModel.cs +++ b/src/Api/Models/Response/PolicyResponseModel.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; -using Newtonsoft.Json; namespace Bit.Api.Models.Response { @@ -23,7 +23,7 @@ namespace Bit.Api.Models.Response Enabled = policy.Enabled; if (!string.IsNullOrWhiteSpace(policy.Data)) { - Data = JsonConvert.DeserializeObject>(policy.Data); + Data = JsonSerializer.Deserialize>(policy.Data); } } diff --git a/src/Api/Models/Response/SendAccessResponseModel.cs b/src/Api/Models/Response/SendAccessResponseModel.cs index 569029800..2a32b86c9 100644 --- a/src/Api/Models/Response/SendAccessResponseModel.cs +++ b/src/Api/Models/Response/SendAccessResponseModel.cs @@ -1,11 +1,11 @@ using System; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Data; using Bit.Core.Settings; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Api.Models.Response { @@ -26,12 +26,12 @@ namespace Bit.Api.Models.Response switch (send.Type) { case SendType.File: - var fileData = JsonConvert.DeserializeObject(send.Data); + var fileData = JsonSerializer.Deserialize(send.Data); sendData = fileData; File = new SendFileModel(fileData); break; case SendType.Text: - var textData = JsonConvert.DeserializeObject(send.Data); + var textData = JsonSerializer.Deserialize(send.Data); sendData = textData; Text = new SendTextModel(textData); break; diff --git a/src/Api/Models/Response/SendResponseModel.cs b/src/Api/Models/Response/SendResponseModel.cs index f11e3dd34..3dde73b65 100644 --- a/src/Api/Models/Response/SendResponseModel.cs +++ b/src/Api/Models/Response/SendResponseModel.cs @@ -1,11 +1,11 @@ using System; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Data; using Bit.Core.Settings; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Api.Models.Response { @@ -36,12 +36,12 @@ namespace Bit.Api.Models.Response switch (send.Type) { case SendType.File: - var fileData = JsonConvert.DeserializeObject(send.Data); + var fileData = JsonSerializer.Deserialize(send.Data); sendData = fileData; File = new SendFileModel(fileData); break; case SendType.Text: - var textData = JsonConvert.DeserializeObject(send.Data); + var textData = JsonSerializer.Deserialize(send.Data); sendData = textData; Text = new SendTextModel(textData); break; diff --git a/src/Api/Models/SendFileModel.cs b/src/Api/Models/SendFileModel.cs index 006fada45..b39aa33ea 100644 --- a/src/Api/Models/SendFileModel.cs +++ b/src/Api/Models/SendFileModel.cs @@ -1,4 +1,5 @@ -using Bit.Core.Models.Data; +using System.Text.Json.Serialization; +using Bit.Core.Models.Data; using Bit.Core.Utilities; namespace Bit.Api.Models @@ -11,7 +12,7 @@ namespace Bit.Api.Models { Id = data.Id; FileName = data.FileName; - Size = data.SizeString; + Size = data.Size; SizeName = CoreHelpers.ReadableBytesSize(data.Size); } @@ -19,7 +20,8 @@ namespace Bit.Api.Models [EncryptedString] [EncryptedStringLength(1000)] public string FileName { get; set; } - public string Size { get; set; } + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] + public long Size { get; set; } public string SizeName { get; set; } } } diff --git a/src/Api/Utilities/ApiHelpers.cs b/src/Api/Utilities/ApiHelpers.cs index 5b86ef33e..e9ce5b093 100644 --- a/src/Api/Utilities/ApiHelpers.cs +++ b/src/Api/Utilities/ApiHelpers.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; using System.Threading.Tasks; using Azure.Messaging.EventGrid; using Azure.Messaging.EventGrid.SystemEvents; using Bit.Core.Utilities; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; namespace Bit.Api.Utilities { @@ -27,7 +27,7 @@ namespace Bit.Api.Utilities var s = await reader.ReadToEndAsync(); if (!string.IsNullOrWhiteSpace(s)) { - obj = JsonConvert.DeserializeObject(s); + obj = JsonSerializer.Deserialize(s); } } } diff --git a/src/Api/Utilities/MultipartFormDataHelper.cs b/src/Api/Utilities/MultipartFormDataHelper.cs index 20f875ba2..0c1fcd1d1 100644 --- a/src/Api/Utilities/MultipartFormDataHelper.cs +++ b/src/Api/Utilities/MultipartFormDataHelper.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text.Json; using System.Threading.Tasks; using Bit.Api.Models.Request; using Microsoft.AspNetCore.Http; @@ -7,7 +8,6 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; namespace Bit.Api.Utilities { @@ -78,13 +78,6 @@ namespace Bit.Api.Utilities { if (ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out _)) { - // Request model json, then data - string requestModelJson = null; - using (var sr = new StreamReader(firstSection.Body)) - { - requestModelJson = await sr.ReadToEndAsync(); - } - var secondSection = await reader.ReadNextSectionAsync(); if (secondSection != null) { @@ -94,7 +87,7 @@ namespace Bit.Api.Utilities var fileName = HeaderUtilities.RemoveQuotes(secondContent.FileName).ToString(); using (secondSection.Body) { - var model = JsonConvert.DeserializeObject(requestModelJson); + var model = await JsonSerializer.DeserializeAsync(firstSection.Body); await callback(secondSection.Body, fileName, model); } } diff --git a/src/Billing/Controllers/AppleController.cs b/src/Billing/Controllers/AppleController.cs index 944a2db49..c3b9bf5fa 100644 --- a/src/Billing/Controllers/AppleController.cs +++ b/src/Billing/Controllers/AppleController.cs @@ -1,13 +1,13 @@ using System; using System.IO; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core; using Bit.Core.Utilities; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; namespace Bit.Billing.Controllers { @@ -53,7 +53,7 @@ namespace Bit.Billing.Controllers try { - var json = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(body), Formatting.Indented); + var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(body), JsonHelpers.Indented); _logger.LogInformation(Constants.BypassFiltersEventId, "Apple IAP Notification:\n\n{0}", json); return new OkResult(); } diff --git a/src/Billing/Controllers/FreshdeskController.cs b/src/Billing/Controllers/FreshdeskController.cs index 6012733ef..331f8eae4 100644 --- a/src/Billing/Controllers/FreshdeskController.cs +++ b/src/Billing/Controllers/FreshdeskController.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Http.Json; using System.Reflection; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Repositories; using Bit.Core.Settings; @@ -13,7 +14,6 @@ using Bit.Core.Utilities; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; namespace Bit.Billing.Controllers { @@ -62,23 +62,18 @@ namespace Bit.Billing.Controllers return new BadRequestResult(); } - string body = null; - using (var reader = new StreamReader(HttpContext.Request.Body, Encoding.UTF8)) - { - body = await reader.ReadToEndAsync(); - } - - if (string.IsNullOrWhiteSpace(body)) + using var body = await JsonSerializer.DeserializeAsync(HttpContext.Request.Body); + var root = body.RootElement; + if (root.ValueKind != JsonValueKind.Object) { return new BadRequestResult(); } try { - dynamic data = JsonConvert.DeserializeObject(body); - string ticketId = data.ticket_id; - string ticketContactEmail = data.ticket_contact_email; - string ticketTags = data.ticket_tags; + var ticketId = root.GetProperty("ticket_id").GetString(); + var ticketContactEmail = root.GetProperty("ticket_contact_email").GetString(); + var ticketTags = root.GetProperty("ticket_tags").GetString(); if (string.IsNullOrWhiteSpace(ticketId) || string.IsNullOrWhiteSpace(ticketContactEmail)) { return new BadRequestResult(); @@ -120,9 +115,11 @@ namespace Bit.Billing.Controllers updateBody.Add("tags", tagsToUpdate); } var updateRequest = new HttpRequestMessage(HttpMethod.Put, - string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}", ticketId)); - updateRequest.Content = new StringContent(JsonConvert.SerializeObject(updateBody), - Encoding.UTF8, "application/json"); + string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}", ticketId)) + { + Content = JsonContent.Create(updateBody), + }; + await CallFreshdeskApiAsync(updateRequest); @@ -132,9 +129,10 @@ namespace Bit.Billing.Controllers { "private", true } }; var noteRequest = new HttpRequestMessage(HttpMethod.Post, - string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}/notes", ticketId)); - noteRequest.Content = new StringContent(JsonConvert.SerializeObject(noteBody), - Encoding.UTF8, "application/json"); + string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}/notes", ticketId)) + { + Content = JsonContent.Create(noteBody), + }; await CallFreshdeskApiAsync(noteRequest); } diff --git a/src/Billing/Models/AppleReceiptNotification.cs b/src/Billing/Models/AppleReceiptNotification.cs deleted file mode 100644 index c71fa0eb1..000000000 --- a/src/Billing/Models/AppleReceiptNotification.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using Bit.Core.Utilities; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace Bit.Billing.Models -{ - public class AppleReceiptNotification - { - [JsonProperty("notification_type")] - public string NotificationType { get; set; } - [JsonProperty("environment")] - public string Environment { get; set; } - [JsonProperty("auto_renew_status")] - public string AutoRenewStatus { get; set; } - [JsonProperty("auto_renew_product_id")] - public string AutoRenewProductId { get; set; } - [JsonProperty("auto_renew_status_change_date_ms")] - [JsonConverter(typeof(MsEpochConverter))] - public DateTime? AutoRenewStatusChangeDate { get; set; } - [JsonProperty("latest_receipt")] - public string LatestReceipt { get; set; } - [JsonProperty("latest_receipt_info")] - public AppleReceiptNotificationInfo LatestReceiptInfo { get; set; } - [JsonProperty("latest_expired_receipt")] - public string LatestExpiredReceipt { get; set; } - [JsonProperty("latest_expired_receipt_info")] - public AppleReceiptNotificationInfo LatestExpiredReceiptInfo { get; set; } - - public string GetOriginalTransactionId() - { - if (LatestReceiptInfo != null) - { - return LatestReceiptInfo.OriginalTransactionId; - } - return LatestExpiredReceiptInfo?.OriginalTransactionId; - } - - public string GetTransactionId() - { - if (LatestReceiptInfo != null) - { - return LatestReceiptInfo.TransactionId; - } - return LatestExpiredReceiptInfo?.TransactionId; - } - - public DateTime? GetExpiresDate() - { - if (LatestReceiptInfo != null) - { - return LatestReceiptInfo.ExpiresDate; - } - return LatestExpiredReceiptInfo?.ExpiresDate; - } - - public string GetReceiptData() - { - return string.IsNullOrWhiteSpace(LatestReceipt) ? LatestExpiredReceipt : LatestReceipt; - } - - public class AppleReceiptNotificationInfo - { - [JsonProperty("bid")] - public string Bid { get; set; } - public string ProductId { get; set; } - [JsonProperty("original_purchase_date_ms")] - [JsonConverter(typeof(MsEpochConverter))] - public DateTime? OriginalPurchaseDate { get; set; } - [JsonProperty("expires_date")] - [JsonConverter(typeof(MsEpochConverter))] - public DateTime? ExpiresDate { get; set; } - [JsonProperty("purchase_date_ms")] - [JsonConverter(typeof(MsEpochConverter))] - public DateTime? PurchaseDate { get; set; } - [JsonProperty("subscription_group_identifier")] - public string SubscriptionGroupIdentifier { get; set; } - [JsonProperty("unique_identifier")] - public string UniqueIdentifier { get; set; } - [JsonProperty("original_transaction_id")] - public string OriginalTransactionId { get; set; } - [JsonProperty("transaction_id")] - public string TransactionId { get; set; } - [JsonProperty("quantity")] - public string Quantity { get; set; } - [JsonProperty("web_order_line_item_id")] - public string WebOrderLineItemId { get; set; } - [JsonProperty("item_id")] - public string ItemId { get; set; } - } - - public class MsEpochConverter : DateTimeConverterBase - { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteRawValue(CoreHelpers.ToEpocMilliseconds((DateTime)value).ToString()); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return CoreHelpers.FromEpocMilliseconds(long.Parse(reader.Value.ToString())); - } - } - } -} diff --git a/src/Core/Entities/Cipher.cs b/src/Core/Entities/Cipher.cs index ac0d96e89..fa19b47d9 100644 --- a/src/Core/Entities/Cipher.cs +++ b/src/Core/Entities/Cipher.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; +using System.Text.Json; using Bit.Core.Models.Data; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Core.Entities { @@ -42,7 +42,7 @@ namespace Bit.Core.Entities try { - _attachmentData = JsonConvert.DeserializeObject>(Attachments); + _attachmentData = JsonSerializer.Deserialize>(Attachments); foreach (var kvp in _attachmentData) { kvp.Value.AttachmentId = kvp.Key; @@ -65,7 +65,7 @@ namespace Bit.Core.Entities } _attachmentData = data; - Attachments = JsonConvert.SerializeObject(_attachmentData); + Attachments = JsonSerializer.Serialize(_attachmentData); } public void AddAttachment(string id, CipherAttachment.MetaData data) diff --git a/src/Core/Entities/Organization.cs b/src/Core/Entities/Organization.cs index 7366fb91c..5050ea120 100644 --- a/src/Core/Entities/Organization.cs +++ b/src/Core/Entities/Organization.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Text.Json; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Core.Entities { @@ -142,13 +142,13 @@ namespace Bit.Core.Entities if (_twoFactorProviders == null) { _twoFactorProviders = - JsonConvert.DeserializeObject>( + JsonSerializer.Deserialize>( TwoFactorProviders); } return _twoFactorProviders; } - catch (JsonSerializationException) + catch (JsonException) { return null; } @@ -163,10 +163,7 @@ namespace Bit.Core.Entities return; } - TwoFactorProviders = JsonConvert.SerializeObject(providers, new JsonSerializerSettings - { - ContractResolver = new EnumKeyResolver() - }); + TwoFactorProviders = JsonSerializer.Serialize(providers); _twoFactorProviders = providers; } diff --git a/src/Core/Entities/User.cs b/src/Core/Entities/User.cs index f80948909..50e634981 100644 --- a/src/Core/Entities/User.cs +++ b/src/Core/Entities/User.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Text.Json; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Utilities; using Microsoft.AspNetCore.Identity; -using Newtonsoft.Json; namespace Bit.Core.Entities { @@ -108,13 +108,13 @@ namespace Bit.Core.Entities if (_twoFactorProviders == null) { _twoFactorProviders = - JsonConvert.DeserializeObject>( + JsonHelpers.LegacyDeserialize>( TwoFactorProviders); } return _twoFactorProviders; } - catch (JsonSerializationException) + catch (JsonException) { return null; } @@ -132,10 +132,7 @@ namespace Bit.Core.Entities public void SetTwoFactorProviders(Dictionary providers) { - TwoFactorProviders = JsonConvert.SerializeObject(providers, new JsonSerializerSettings - { - ContractResolver = new EnumKeyResolver() - }); + TwoFactorProviders = JsonHelpers.LegacySerialize(providers); _twoFactorProviders = providers; } diff --git a/src/Core/Identity/U2fTokenProvider.cs b/src/Core/Identity/U2fTokenProvider.cs index 613ab7a43..fa3b5d3fc 100644 --- a/src/Core/Identity/U2fTokenProvider.cs +++ b/src/Core/Identity/U2fTokenProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Enums; @@ -10,7 +11,6 @@ using Bit.Core.Services; using Bit.Core.Settings; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using U2F.Core.Exceptions; using U2F.Core.Models; using U2F.Core.Utils; @@ -107,8 +107,8 @@ namespace Bit.Core.Identity }); } - var oldToken = JsonConvert.SerializeObject(oldChallenges); - var token = JsonConvert.SerializeObject(new + var oldToken = JsonSerializer.Serialize(oldChallenges); + var token = JsonSerializer.Serialize(new { appId = appId, challenge = challengeBytes.ByteArrayToBase64String(), diff --git a/src/Core/Identity/WebAuthnTokenProvider.cs b/src/Core/Identity/WebAuthnTokenProvider.cs index 15b463a5c..d180ccaad 100644 --- a/src/Core/Identity/WebAuthnTokenProvider.cs +++ b/src/Core/Identity/WebAuthnTokenProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Enums; @@ -12,7 +13,6 @@ using Fido2NetLib; using Fido2NetLib.Objects; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; namespace Bit.Core.Identity { @@ -98,7 +98,7 @@ namespace Bit.Core.Identity return false; } - var clientResponse = JsonConvert.DeserializeObject(token); + var clientResponse = JsonSerializer.Deserialize(token); var jsonOptions = provider.MetaData["login"].ToString(); var options = AssertionOptions.FromJson(jsonOptions); diff --git a/src/Core/MailTemplates/Handlebars/OrganizationUserInvited.html.hbs b/src/Core/MailTemplates/Handlebars/OrganizationUserInvited.html.hbs index 9233e7f66..14ba3cc1b 100644 --- a/src/Core/MailTemplates/Handlebars/OrganizationUserInvited.html.hbs +++ b/src/Core/MailTemplates/Handlebars/OrganizationUserInvited.html.hbs @@ -15,7 +15,7 @@ If you do not wish to join this organization, you can safely ignore this email. - {{#if OrganizationCanSponsor}} + {{#jsonIf OrganizationCanSponsor}}

@@ -24,7 +24,7 @@ Members of {{OrganizationName}} receive a complimentary Families subscription. Learn more at the following link: https://bitwarden.com/help/article/families-for-enterprise/

- {{/if}} + {{/jsonIf}} diff --git a/src/Core/MailTemplates/Handlebars/OrganizationUserInvited.text.hbs b/src/Core/MailTemplates/Handlebars/OrganizationUserInvited.text.hbs index 4c61adbe5..9fbfff10a 100644 --- a/src/Core/MailTemplates/Handlebars/OrganizationUserInvited.text.hbs +++ b/src/Core/MailTemplates/Handlebars/OrganizationUserInvited.text.hbs @@ -6,7 +6,7 @@ You have been invited to join the {{OrganizationName}} organization. To accept t This link expires on {{ExpirationDate}}. If you do not wish to join this organization, you can safely ignore this email. -{{#if OrganizationCanSponsor}} +{{#jsonIf OrganizationCanSponsor}} Did you know? Members of {{OrganizationName}} receive a complimentary Families subscription. Learn more here: https://bitwarden.com/help/article/families-for-enterprise/ -{{/if}} +{{/jsonIf}} {{/BasicTextLayout}} diff --git a/src/Core/Models/Api/Request/Accounts/RegisterRequestModel.cs b/src/Core/Models/Api/Request/Accounts/RegisterRequestModel.cs index ad5e9e747..407acbe75 100644 --- a/src/Core/Models/Api/Request/Accounts/RegisterRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/RegisterRequestModel.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Core.Models.Api.Request.Accounts { @@ -43,7 +43,7 @@ namespace Bit.Core.Models.Api.Request.Accounts if (ReferenceData != null) { - user.ReferenceData = JsonConvert.SerializeObject(ReferenceData); + user.ReferenceData = JsonSerializer.Serialize(ReferenceData); } if (Key != null) diff --git a/src/Core/Models/Business/AppleReceiptStatus.cs b/src/Core/Models/Business/AppleReceiptStatus.cs index df8be4a9b..fc7fce533 100644 --- a/src/Core/Models/Business/AppleReceiptStatus.cs +++ b/src/Core/Models/Business/AppleReceiptStatus.cs @@ -1,27 +1,28 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Utilities; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; namespace Bit.Billing.Models { public class AppleReceiptStatus { - [JsonProperty("status")] + [JsonPropertyName("status")] public int? Status { get; set; } - [JsonProperty("environment")] + [JsonPropertyName("environment")] public string Environment { get; set; } - [JsonProperty("latest_receipt")] + [JsonPropertyName("latest_receipt")] public string LatestReceipt { get; set; } - [JsonProperty("receipt")] + [JsonPropertyName("receipt")] public AppleReceipt Receipt { get; set; } - [JsonProperty("latest_receipt_info")] + [JsonPropertyName("latest_receipt_info")] public List LatestReceiptInfo { get; set; } - [JsonProperty("pending_renewal_info")] + [JsonPropertyName("pending_renewal_info")] public List PendingRenewalInfo { get; set; } public string GetOriginalTransactionId() @@ -81,72 +82,59 @@ namespace Bit.Billing.Models public class AppleReceipt { - [JsonProperty("receipt_type")] + [JsonPropertyName("receipt_type")] public string ReceiptType { get; set; } - [JsonProperty("bundle_id")] + [JsonPropertyName("bundle_id")] public string BundleId { get; set; } - [JsonProperty("receipt_creation_date_ms")] + [JsonPropertyName("receipt_creation_date_ms")] [JsonConverter(typeof(MsEpochConverter))] public DateTime ReceiptCreationDate { get; set; } - [JsonProperty("in_app")] + [JsonPropertyName("in_app")] public List InApp { get; set; } } public class AppleRenewalInfo { - [JsonProperty("expiration_intent")] + [JsonPropertyName("expiration_intent")] public string ExpirationIntent { get; set; } - [JsonProperty("auto_renew_product_id")] + [JsonPropertyName("auto_renew_product_id")] public string AutoRenewProductId { get; set; } - [JsonProperty("original_transaction_id")] + [JsonPropertyName("original_transaction_id")] public string OriginalTransactionId { get; set; } - [JsonProperty("is_in_billing_retry_period")] + [JsonPropertyName("is_in_billing_retry_period")] public string IsInBillingRetryPeriod { get; set; } - [JsonProperty("product_id")] + [JsonPropertyName("product_id")] public string ProductId { get; set; } - [JsonProperty("auto_renew_status")] + [JsonPropertyName("auto_renew_status")] public string AutoRenewStatus { get; set; } } public class AppleTransaction { - [JsonProperty("quantity")] + [JsonPropertyName("quantity")] public string Quantity { get; set; } - [JsonProperty("product_id")] + [JsonPropertyName("product_id")] public string ProductId { get; set; } - [JsonProperty("transaction_id")] + [JsonPropertyName("transaction_id")] public string TransactionId { get; set; } - [JsonProperty("original_transaction_id")] + [JsonPropertyName("original_transaction_id")] public string OriginalTransactionId { get; set; } - [JsonProperty("purchase_date_ms")] + [JsonPropertyName("purchase_date_ms")] [JsonConverter(typeof(MsEpochConverter))] public DateTime PurchaseDate { get; set; } - [JsonProperty("original_purchase_date_ms")] + [JsonPropertyName("original_purchase_date_ms")] [JsonConverter(typeof(MsEpochConverter))] public DateTime OriginalPurchaseDate { get; set; } - [JsonProperty("expires_date_ms")] + [JsonPropertyName("expires_date_ms")] [JsonConverter(typeof(MsEpochConverter))] public DateTime ExpiresDate { get; set; } - [JsonProperty("cancellation_date_ms")] + [JsonPropertyName("cancellation_date_ms")] [JsonConverter(typeof(MsEpochConverter))] public DateTime? CancellationDate { get; set; } - [JsonProperty("web_order_line_item_id")] + [JsonPropertyName("web_order_line_item_id")] public string WebOrderLineItemId { get; set; } - [JsonProperty("cancellation_reason")] + [JsonPropertyName("cancellation_reason")] public string CancellationReason { get; set; } } - - public class MsEpochConverter : DateTimeConverterBase - { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteRawValue(CoreHelpers.ToEpocMilliseconds((DateTime)value).ToString()); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return CoreHelpers.FromEpocMilliseconds(long.Parse(reader.Value.ToString())); - } - } } } diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index 30449d119..fcd0d9fb5 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -4,11 +4,11 @@ using System.Reflection; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json.Serialization; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Services; using Bit.Core.Settings; -using Newtonsoft.Json; namespace Bit.Core.Models.Business { diff --git a/src/Core/Models/Business/ReferenceEvent.cs b/src/Core/Models/Business/ReferenceEvent.cs index f0fe81e7f..ea6368ae3 100644 --- a/src/Core/Models/Business/ReferenceEvent.cs +++ b/src/Core/Models/Business/ReferenceEvent.cs @@ -1,13 +1,10 @@ using System; +using System.Text.Json.Serialization; using Bit.Core.Entities; using Bit.Core.Enums; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; namespace Bit.Core.Models.Business { - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] public class ReferenceEvent { public ReferenceEvent() { } @@ -23,10 +20,10 @@ namespace Bit.Core.Models.Business } } - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public ReferenceEventType Type { get; set; } - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public ReferenceEventSource Source { get; set; } public Guid Id { get; set; } @@ -52,7 +49,7 @@ namespace Bit.Core.Models.Business public short? Storage { get; set; } - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public SendType? SendType { get; set; } public int? MaxAccessCount { get; set; } diff --git a/src/Core/Models/Business/ReferenceEventData.cs b/src/Core/Models/Business/ReferenceEventData.cs deleted file mode 100644 index 5a8d5a92b..000000000 --- a/src/Core/Models/Business/ReferenceEventData.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace Bit.Core.Models.Business -{ - [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] - public class ReferenceEventData - { - public string Id { get; set; } - - public string Layout { get; set; } - - public string Flow { get; set; } - } -} diff --git a/src/Core/Models/Business/UserLicense.cs b/src/Core/Models/Business/UserLicense.cs index 86c685960..95d3870d4 100644 --- a/src/Core/Models/Business/UserLicense.cs +++ b/src/Core/Models/Business/UserLicense.cs @@ -4,9 +4,9 @@ using System.Reflection; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json.Serialization; using Bit.Core.Entities; using Bit.Core.Services; -using Newtonsoft.Json; namespace Bit.Core.Models.Business { diff --git a/src/Core/Models/Data/CipherAttachment.cs b/src/Core/Models/Data/CipherAttachment.cs index 911180438..b9be06b44 100644 --- a/src/Core/Models/Data/CipherAttachment.cs +++ b/src/Core/Models/Data/CipherAttachment.cs @@ -1,5 +1,5 @@ using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Bit.Core.Models.Data { @@ -15,21 +15,14 @@ namespace Bit.Core.Models.Data { private long _size; - [JsonIgnore] + // We serialize Size as a string since JSON (or Javascript) doesn't support full precision for long numbers + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] public long Size { get { return _size; } set { _size = value; } } - // We serialize Size as a string since JSON (or Javascript) doesn't support full precision for long numbers - [JsonProperty("Size")] - public string SizeString - { - get { return _size.ToString(); } - set { _size = Convert.ToInt64(value); } - } - public string FileName { get; set; } public string Key { get; set; } diff --git a/src/Core/Models/Data/EmergencyAccessNotify.cs b/src/Core/Models/Data/EmergencyAccessNotify.cs index b71e7102a..3f1e68602 100644 --- a/src/Core/Models/Data/EmergencyAccessNotify.cs +++ b/src/Core/Models/Data/EmergencyAccessNotify.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using Bit.Core.Entities; using Bit.Core.Enums; -using Newtonsoft.Json; namespace Bit.Core.Models.Data { diff --git a/src/Core/Models/Data/OrganizationUserUserDetails.cs b/src/Core/Models/Data/OrganizationUserUserDetails.cs index 55a4f22d8..49dbe36cb 100644 --- a/src/Core/Models/Data/OrganizationUserUserDetails.cs +++ b/src/Core/Models/Data/OrganizationUserUserDetails.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; +using System.Text.Json; using Bit.Core.Enums; -using Newtonsoft.Json; +using Bit.Core.Utilities; namespace Bit.Core.Models.Data { @@ -37,13 +38,13 @@ namespace Bit.Core.Models.Data if (_twoFactorProviders == null) { _twoFactorProviders = - JsonConvert.DeserializeObject>( + JsonHelpers.LegacyDeserialize>( TwoFactorProviders); } return _twoFactorProviders; } - catch (JsonSerializationException) + catch (Newtonsoft.Json.JsonException) { return null; } diff --git a/src/Core/Models/Data/Permissions.cs b/src/Core/Models/Data/Permissions.cs index 507c22035..ac6011af6 100644 --- a/src/Core/Models/Data/Permissions.cs +++ b/src/Core/Models/Data/Permissions.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Bit.Core.Models.Data { @@ -25,7 +25,6 @@ namespace Bit.Core.Models.Data public bool ManageResetPassword { get; set; } [JsonIgnore] - [System.Text.Json.Serialization.JsonIgnore] public List<(bool Permission, string ClaimName)> ClaimsMap => new() { (AccessEventLogs, "accesseventlogs"), diff --git a/src/Core/Models/Data/SendFileData.cs b/src/Core/Models/Data/SendFileData.cs index 200cb68a7..bfda83bf1 100644 --- a/src/Core/Models/Data/SendFileData.cs +++ b/src/Core/Models/Data/SendFileData.cs @@ -1,12 +1,10 @@ using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Bit.Core.Models.Data { public class SendFileData : SendData { - private long _size; - public SendFileData() { } public SendFileData(string name, string notes, string fileName) @@ -15,20 +13,9 @@ namespace Bit.Core.Models.Data FileName = fileName; } - [JsonIgnore] - public long Size - { - get { return _size; } - set { _size = value; } - } - // We serialize Size as a string since JSON (or Javascript) doesn't support full precision for long numbers - [JsonProperty("Size")] - public string SizeString - { - get { return _size.ToString(); } - set { _size = Convert.ToInt64(value); } - } + [JsonNumberHandling(JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString)] + public long Size { get; set; } public string Id { get; set; } public string FileName { get; set; } diff --git a/src/Core/Models/Mail/IMailQueueMessage.cs b/src/Core/Models/Mail/IMailQueueMessage.cs index 32ec67db4..bd305d13e 100644 --- a/src/Core/Models/Mail/IMailQueueMessage.cs +++ b/src/Core/Models/Mail/IMailQueueMessage.cs @@ -9,6 +9,6 @@ namespace Bit.Core.Models.Mail IEnumerable BccEmails { get; set; } string Category { get; set; } string TemplateName { get; set; } - dynamic Model { get; set; } + object Model { get; set; } } } diff --git a/src/Core/Models/Mail/MailQueueMessage.cs b/src/Core/Models/Mail/MailQueueMessage.cs index 5dd9616b9..5d1a93f2b 100644 --- a/src/Core/Models/Mail/MailQueueMessage.cs +++ b/src/Core/Models/Mail/MailQueueMessage.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; +using System.Text.Json.Serialization; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Core.Models.Mail { @@ -11,12 +11,13 @@ namespace Bit.Core.Models.Mail public IEnumerable BccEmails { get; set; } public string Category { get; set; } public string TemplateName { get; set; } - [JsonConverter(typeof(ExpandoObjectJsonConverter))] - public dynamic Model { get; set; } + + [JsonConverter(typeof(HandlebarsObjectJsonConverter))] + public object Model { get; set; } public MailQueueMessage() { } - public MailQueueMessage(MailMessage message, string templateName, dynamic model) + public MailQueueMessage(MailMessage message, string templateName, object model) { Subject = message.Subject; ToEmails = message.ToEmails; diff --git a/src/Core/Models/TwoFactorProvider.cs b/src/Core/Models/TwoFactorProvider.cs index 2468c6699..64ccd7bde 100644 --- a/src/Core/Models/TwoFactorProvider.cs +++ b/src/Core/Models/TwoFactorProvider.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Security.Cryptography; +using System.Text.Json.Serialization; using Bit.Core.Enums; +using Bit.Core.Utilities; using Fido2NetLib.Objects; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; using PeterO.Cbor; using U2F.Core.Utils; @@ -102,7 +102,7 @@ namespace Bit.Core.Models catch { // Handle newtonsoft parsing - Descriptor = JsonConvert.DeserializeObject(o.Descriptor.ToString()); + Descriptor = JsonHelpers.LegacyDeserialize(o.Descriptor.ToString()); } PublicKey = o.PublicKey; UserHandle = o.UserHandle; diff --git a/src/Core/Services/Implementations/AppleIapService.cs b/src/Core/Services/Implementations/AppleIapService.cs index 0d0e35631..69fe601cf 100644 --- a/src/Core/Services/Implementations/AppleIapService.cs +++ b/src/Core/Services/Implementations/AppleIapService.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using Bit.Billing.Models; using Bit.Core.Repositories; @@ -9,8 +11,6 @@ using Bit.Core.Settings; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Bit.Core.Services { @@ -97,14 +97,16 @@ namespace Bit.Core.Services } var url = string.Format("https://{0}.itunes.apple.com/verifyReceipt", prod ? "buy" : "sandbox"); - var json = new JObject(new JProperty("receipt-data", receiptData), - new JProperty("password", _globalSettings.AppleIap.Password)).ToString(); - var response = await _httpClient.PostAsync(url, new StringContent(json)); + var response = await _httpClient.PostAsJsonAsync(url, new AppleVerifyReceiptRequestModel + { + ReceiptData = receiptData, + Password = _globalSettings.AppleIap.Password + }); + if (response.IsSuccessStatusCode) { - var responseJson = await response.Content.ReadAsStringAsync(); - var receiptStatus = JsonConvert.DeserializeObject(responseJson); + var receiptStatus = await response.Content.ReadFromJsonAsync(); if (receiptStatus.Status == 21007) { return await GetReceiptStatusAsync(receiptData, false, attempt + 1, receiptStatus); @@ -124,4 +126,12 @@ namespace Bit.Core.Services return null; } } + + public class AppleVerifyReceiptRequestModel + { + [JsonPropertyName("receipt-data")] + public string ReceiptData { get; set; } + [JsonPropertyName("password")] + public string Password { get; set; } + } } diff --git a/src/Core/Services/Implementations/AzureQueueEventWriteService.cs b/src/Core/Services/Implementations/AzureQueueEventWriteService.cs index 47dfaa1e7..88fc7edad 100644 --- a/src/Core/Services/Implementations/AzureQueueEventWriteService.cs +++ b/src/Core/Services/Implementations/AzureQueueEventWriteService.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Azure.Storage.Queues; using Bit.Core.Models.Data; using Bit.Core.Settings; -using Newtonsoft.Json; +using Bit.Core.Utilities; namespace Bit.Core.Services { @@ -13,7 +13,9 @@ namespace Bit.Core.Services { public AzureQueueEventWriteService(GlobalSettings globalSettings) : base( new QueueClient(globalSettings.Events.ConnectionString, "event"), - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }) + JsonHelpers.IgnoreWritingNull) { } + + public Task CreateAsync(IEvent e) => CreateManyAsync(new[] { e }); } } diff --git a/src/Core/Services/Implementations/AzureQueueMailService.cs b/src/Core/Services/Implementations/AzureQueueMailService.cs index 05d91d323..f87ca1467 100644 --- a/src/Core/Services/Implementations/AzureQueueMailService.cs +++ b/src/Core/Services/Implementations/AzureQueueMailService.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Azure.Storage.Queues; using Bit.Core.Models.Mail; using Bit.Core.Settings; -using Newtonsoft.Json; +using Bit.Core.Utilities; namespace Bit.Core.Services { @@ -13,11 +13,11 @@ namespace Bit.Core.Services { public AzureQueueMailService(GlobalSettings globalSettings) : base( new QueueClient(globalSettings.Mail.ConnectionString, "mail"), - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }) + JsonHelpers.IgnoreWritingNull) { } public Task EnqueueAsync(IMailQueueMessage message, Func fallback) => - CreateAsync(message); + CreateManyAsync(new[] { message }); public Task EnqueueManyAsync(IEnumerable messages, Func fallback) => CreateManyAsync(messages); diff --git a/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs b/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs index 619ebe03b..1d837d1f5 100644 --- a/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs +++ b/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; using Azure.Storage.Queues; using Bit.Core.Context; @@ -7,8 +8,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Settings; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -18,11 +19,6 @@ namespace Bit.Core.Services private readonly GlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; - private JsonSerializerSettings _jsonSettings = new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore - }; - public AzureQueuePushNotificationService( GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor) @@ -170,8 +166,8 @@ namespace Bit.Core.Services private async Task SendMessageAsync(PushType type, T payload, bool excludeCurrentContext) { var contextId = GetContextIdentifier(excludeCurrentContext); - var message = JsonConvert.SerializeObject(new PushNotificationData(type, payload, contextId), - _jsonSettings); + var message = JsonSerializer.Serialize(new PushNotificationData(type, payload, contextId), + JsonHelpers.IgnoreWritingNull); await _queueClient.SendMessageAsync(message); } diff --git a/src/Core/Services/Implementations/AzureQueueReferenceEventService.cs b/src/Core/Services/Implementations/AzureQueueReferenceEventService.cs index a8caad81d..b9d3177bf 100644 --- a/src/Core/Services/Implementations/AzureQueueReferenceEventService.cs +++ b/src/Core/Services/Implementations/AzureQueueReferenceEventService.cs @@ -1,10 +1,11 @@ using System; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Azure.Storage.Queues; using Bit.Core.Models.Business; using Bit.Core.Settings; -using Newtonsoft.Json; +using Bit.Core.Utilities; namespace Bit.Core.Services { @@ -14,10 +15,6 @@ namespace Bit.Core.Services private readonly QueueClient _queueClient; private readonly GlobalSettings _globalSettings; - private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore, - }; public AzureQueueReferenceEventService( GlobalSettings globalSettings) @@ -40,7 +37,7 @@ namespace Bit.Core.Services } try { - var message = JsonConvert.SerializeObject(referenceEvent, _jsonSerializerSettings); + var message = JsonSerializer.Serialize(referenceEvent, JsonHelpers.IgnoreWritingNullAndCamelCase); // Messages need to be base64 encoded var encodedMessage = Convert.ToBase64String(Encoding.UTF8.GetBytes(message)); await _queueClient.SendMessageAsync(encodedMessage); diff --git a/src/Core/Services/Implementations/AzureQueueService.cs b/src/Core/Services/Implementations/AzureQueueService.cs index 2c3b1234d..98be59777 100644 --- a/src/Core/Services/Implementations/AzureQueueService.cs +++ b/src/Core/Services/Implementations/AzureQueueService.cs @@ -1,29 +1,22 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Azure.Storage.Queues; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Core.Services { public abstract class AzureQueueService { protected QueueClient _queueClient; - protected JsonSerializerSettings _jsonSettings; + protected JsonSerializerOptions _jsonOptions; - protected AzureQueueService(QueueClient queueClient, JsonSerializerSettings jsonSettings) + protected AzureQueueService(QueueClient queueClient, JsonSerializerOptions jsonOptions) { _queueClient = queueClient; - _jsonSettings = jsonSettings; - } - - public async Task CreateAsync(T message) - { - var json = JsonConvert.SerializeObject(message, _jsonSettings); - var base64 = CoreHelpers.Base64EncodeString(json); - await _queueClient.SendMessageAsync(base64); + _jsonOptions = jsonOptions; } public async Task CreateManyAsync(IEnumerable messages) @@ -33,19 +26,13 @@ namespace Bit.Core.Services return; } - if (!messages.Skip(1).Any()) - { - await CreateAsync(messages.First()); - return; - } - - foreach (var json in SerializeMany(messages, _jsonSettings)) + foreach (var json in SerializeMany(messages, _jsonOptions)) { await _queueClient.SendMessageAsync(json); } } - protected IEnumerable SerializeMany(IEnumerable messages, JsonSerializerSettings jsonSettings) + protected IEnumerable SerializeMany(IEnumerable messages, JsonSerializerOptions jsonOptions) { // Calculate Base-64 encoded text with padding int getBase64Size(int byteCount) => ((4 * byteCount / 3) + 3) & ~3; @@ -69,7 +56,7 @@ namespace Bit.Core.Services } var serializedMessages = messages.Select(message => - JsonConvert.SerializeObject(message, jsonSettings)); + JsonSerializer.Serialize(message, jsonOptions)); foreach (var message in serializedMessages) { diff --git a/src/Core/Services/Implementations/BaseIdentityClientService.cs b/src/Core/Services/Implementations/BaseIdentityClientService.cs index 26abf21fc..34527f2f3 100644 --- a/src/Core/Services/Implementations/BaseIdentityClientService.cs +++ b/src/Core/Services/Implementations/BaseIdentityClientService.cs @@ -3,23 +3,23 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Net.Http.Json; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Utilities; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Bit.Core.Services { - public abstract class BaseIdentityClientService + public abstract class BaseIdentityClientService : IDisposable { private readonly string _identityScope; private readonly string _identityClientId; private readonly string _identityClientSecret; private readonly ILogger _logger; - private dynamic _decodedToken; + private JsonDocument _decodedToken; private DateTime? _nextAuthAttempt = null; public BaseIdentityClientService( @@ -127,9 +127,9 @@ namespace Bit.Core.Services return false; } - var responseContent = await response.Content.ReadAsStringAsync(); - dynamic tokenResponse = JsonConvert.DeserializeObject(responseContent); - AccessToken = (string)tokenResponse.access_token; + using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); + + AccessToken = jsonDocument.RootElement.GetProperty("access_token").GetString(); return true; } @@ -145,8 +145,7 @@ namespace Bit.Core.Services { if (requestObject != null) { - var stringContent = JsonConvert.SerializeObject(requestObject); - Content = new StringContent(stringContent, Encoding.UTF8, "application/json"); + Content = JsonContent.Create(requestObject); } } } @@ -154,17 +153,16 @@ namespace Bit.Core.Services protected bool TokenNeedsRefresh(int minutes = 5) { var decoded = DecodeToken(); - var exp = decoded?["exp"]; - if (exp == null) + if (!decoded.RootElement.TryGetProperty("exp", out var expProp)) { throw new InvalidOperationException("No exp in token."); } - var expiration = CoreHelpers.FromEpocSeconds(exp.Value()); + var expiration = CoreHelpers.FromEpocSeconds(expProp.GetInt64()); return DateTime.UtcNow.AddMinutes(-1 * minutes) > expiration; } - protected JObject DecodeToken() + protected JsonDocument DecodeToken() { if (_decodedToken != null) { @@ -188,8 +186,13 @@ namespace Bit.Core.Services throw new InvalidOperationException($"{nameof(AccessToken)} must have 3 parts"); } - _decodedToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes, 0, decodedBytes.Length)); + _decodedToken = JsonDocument.Parse(decodedBytes); return _decodedToken; } + + public void Dispose() + { + _decodedToken.Dispose(); + } } } diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs index 9482a1585..4f2a78559 100644 --- a/src/Core/Services/Implementations/CipherService.cs +++ b/src/Core/Services/Implementations/CipherService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Enums; @@ -12,7 +13,6 @@ using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Utilities; using Core.Models.Data; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -208,7 +208,7 @@ namespace Bit.Core.Services UserId = cipher.UserId, OrganizationId = cipher.OrganizationId, AttachmentId = attachmentId, - AttachmentData = JsonConvert.SerializeObject(data) + AttachmentData = JsonSerializer.Serialize(data) }); cipher.AddAttachment(attachmentId, data); await _pushService.PushSyncCipherUpdateAsync(cipher, null); @@ -241,7 +241,7 @@ namespace Bit.Core.Services UserId = cipher.UserId, OrganizationId = cipher.OrganizationId, AttachmentId = attachmentId, - AttachmentData = JsonConvert.SerializeObject(data) + AttachmentData = JsonSerializer.Serialize(data) }; await _cipherRepository.UpdateAttachmentAsync(attachment); @@ -312,7 +312,7 @@ namespace Bit.Core.Services UserId = cipher.UserId, OrganizationId = cipher.OrganizationId, AttachmentId = attachmentId, - AttachmentData = JsonConvert.SerializeObject(attachments[attachmentId]) + AttachmentData = JsonSerializer.Serialize(attachments[attachmentId]) }; await _cipherRepository.UpdateAttachmentAsync(updatedAttachment); @@ -347,7 +347,7 @@ namespace Bit.Core.Services UserId = cipher.UserId, OrganizationId = cipher.OrganizationId, AttachmentId = attachmentData.AttachmentId, - AttachmentData = JsonConvert.SerializeObject(attachmentData) + AttachmentData = JsonSerializer.Serialize(attachmentData) }; diff --git a/src/Core/Services/Implementations/HCaptchaValidationService.cs b/src/Core/Services/Implementations/HCaptchaValidationService.cs index b93238cfd..30d06e7e6 100644 --- a/src/Core/Services/Implementations/HCaptchaValidationService.cs +++ b/src/Core/Services/Implementations/HCaptchaValidationService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Context; using Bit.Core.Entities; @@ -8,7 +10,6 @@ using Bit.Core.Models.Business.Tokenables; using Bit.Core.Settings; using Bit.Core.Tokens; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -77,9 +78,9 @@ namespace Bit.Core.Services return false; } - var responseContent = await responseMessage.Content.ReadAsStringAsync(); - dynamic jsonResponse = JsonConvert.DeserializeObject(responseContent); - return (bool)jsonResponse.success; + using var jsonDocument = await responseMessage.Content.ReadFromJsonAsync(); + var root = jsonDocument.RootElement; + return root.GetProperty("success").GetBoolean(); } public bool RequireCaptchaValidation(ICurrentContext currentContext) => diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index f953d7070..20294af9e 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Reflection; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Entities.Provider; @@ -565,6 +566,35 @@ namespace Bit.Core.Services var clickTrackingText = (clickTrackingOff ? "clicktracking=off" : string.Empty); writer.WriteSafeString($"{text}"); }); + + Handlebars.RegisterHelper("jsonIf", (output, options, context, arguments) => + { + // Special case for JsonElement + if (arguments[0] is JsonElement jsonElement + && (jsonElement.ValueKind == JsonValueKind.True || jsonElement.ValueKind == JsonValueKind.False)) + { + if (jsonElement.GetBoolean()) + { + options.Template(output, context); + } + else + { + options.Inverse(output, context); + } + + return; + } + + // Fallback to normal + if (HandlebarsUtils.IsTruthy(arguments[0])) + { + options.Template(output, context); + } + else + { + options.Inverse(output, context); + } + }); } public async Task SendEmergencyAccessInviteEmailAsync(EmergencyAccess emergencyAccess, string name, string token) diff --git a/src/Core/Services/Implementations/LicensingService.cs b/src/Core/Services/Implementations/LicensingService.cs index 1b2c70415..4190c5373 100644 --- a/src/Core/Services/Implementations/LicensingService.cs +++ b/src/Core/Services/Implementations/LicensingService.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Models.Business; @@ -13,7 +14,6 @@ using Bit.Core.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -246,7 +246,7 @@ namespace Bit.Core.Services } var data = File.ReadAllText(filePath, Encoding.UTF8); - return JsonConvert.DeserializeObject(data); + return JsonSerializer.Deserialize(data); } private OrganizationLicense ReadOrganizationLicense(Organization organization) @@ -258,7 +258,7 @@ namespace Bit.Core.Services } var data = File.ReadAllText(filePath, Encoding.UTF8); - return JsonConvert.DeserializeObject(data); + return JsonSerializer.Deserialize(data); } } } diff --git a/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs b/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs index 95c8673d6..dbe3d3f18 100644 --- a/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs +++ b/src/Core/Services/Implementations/NotificationHubPushNotificationService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using Bit.Core.Context; @@ -11,7 +12,6 @@ using Bit.Core.Repositories; using Bit.Core.Settings; using Microsoft.AspNetCore.Http; using Microsoft.Azure.NotificationHubs; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -229,7 +229,7 @@ namespace Bit.Core.Services new Dictionary { { "type", ((byte)type).ToString() }, - { "payload", JsonConvert.SerializeObject(payload) } + { "payload", JsonSerializer.Serialize(payload) } }, tag); } diff --git a/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs b/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs index 7cf41cbad..518404247 100644 --- a/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs +++ b/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs @@ -9,7 +9,6 @@ using Bit.Core.Models; using Bit.Core.Settings; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -18,11 +17,6 @@ namespace Bit.Core.Services private readonly GlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; - private JsonSerializerSettings _jsonSettings = new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore - }; - public NotificationsApiPushNotificationService( GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 28a026e75..e00247e81 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -15,7 +15,6 @@ using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Stripe; namespace Bit.Core.Services @@ -727,8 +726,8 @@ namespace Bit.Core.Services var dir = $"{_globalSettings.LicenseDirectory}/organization"; Directory.CreateDirectory(dir); - System.IO.File.WriteAllText($"{dir}/{organization.Id}.json", - JsonConvert.SerializeObject(license, Formatting.Indented)); + using var fs = System.IO.File.OpenWrite(Path.Combine(dir, $"{organization.Id}.json")); + await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented); return result; } @@ -900,8 +899,8 @@ namespace Bit.Core.Services var dir = $"{_globalSettings.LicenseDirectory}/organization"; Directory.CreateDirectory(dir); - System.IO.File.WriteAllText($"{dir}/{organization.Id}.json", - JsonConvert.SerializeObject(license, Formatting.Indented)); + using var fs = System.IO.File.OpenWrite(Path.Combine(dir, $"{organization.Id}.json")); + await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented); organization.Name = license.Name; organization.BusinessName = license.BusinessName; @@ -1146,10 +1145,7 @@ namespace Bit.Core.Services if (invite.Permissions != null) { - orgUser.Permissions = System.Text.Json.JsonSerializer.Serialize(invite.Permissions, new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }); + orgUser.Permissions = JsonSerializer.Serialize(invite.Permissions, JsonHelpers.CamelCase); } if (!orgUser.AccessAll && invite.Collections.Any()) @@ -1765,7 +1761,7 @@ namespace Bit.Core.Services // Block the user from withdrawal if auto enrollment is enabled if (resetPasswordKey == null && resetPasswordPolicy.Data != null) { - var data = JsonConvert.DeserializeObject(resetPasswordPolicy.Data); + var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data); if (data?.AutoEnrollEnabled ?? false) { diff --git a/src/Core/Services/Implementations/PostalMailDeliveryService.cs b/src/Core/Services/Implementations/PostalMailDeliveryService.cs index e6e278348..7f4b80998 100644 --- a/src/Core/Services/Implementations/PostalMailDeliveryService.cs +++ b/src/Core/Services/Implementations/PostalMailDeliveryService.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Text; +using System.Net.Http.Json; using System.Threading.Tasks; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -76,15 +75,13 @@ namespace Bit.Core.Services request.tag = string.Concat(request.tag, "-Cat_", message.Category); } - var reqJson = JsonConvert.SerializeObject(request); - var responseMessage = await httpClient.PostAsync( + var responseMessage = await httpClient.PostAsJsonAsync( $"https://{_globalSettings.Mail.PostalDomain}/api/v1/send/message", - new StringContent(reqJson, Encoding.UTF8, "application/json")); + request); if (responseMessage.IsSuccessStatusCode) { - var json = await responseMessage.Content.ReadAsStringAsync(); - var response = JsonConvert.DeserializeObject(json); + var response = await responseMessage.Content.ReadFromJsonAsync(); if (response.status != "success") { _logger.LogError("Postal send status was not successful: {0}, {1}", diff --git a/src/Core/Services/Implementations/SendService.cs b/src/Core/Services/Implementations/SendService.cs index 36cf82ad6..ac7a60363 100644 --- a/src/Core/Services/Implementations/SendService.cs +++ b/src/Core/Services/Implementations/SendService.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Context; using Bit.Core.Entities; @@ -12,7 +13,6 @@ using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Identity; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -104,8 +104,8 @@ namespace Bit.Core.Services data.Id = fileId; data.Size = fileLength; data.Validated = false; - send.Data = JsonConvert.SerializeObject(data, - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + send.Data = JsonSerializer.Serialize(data, + JsonHelpers.IgnoreWritingNull); await SaveSendAsync(send); return await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId); } @@ -129,7 +129,7 @@ namespace Bit.Core.Services throw new BadRequestException("Not a File Type Send."); } - var data = JsonConvert.DeserializeObject(send.Data); + var data = JsonSerializer.Deserialize(send.Data); if (data.Validated) { @@ -146,7 +146,7 @@ namespace Bit.Core.Services public async Task ValidateSendFile(Send send) { - var fileData = JsonConvert.DeserializeObject(send.Data); + var fileData = JsonSerializer.Deserialize(send.Data); var (valid, realSize) = await _sendFileStorageService.ValidateFileAsync(send, fileData.Id, fileData.Size, _fileSizeLeeway); @@ -163,8 +163,8 @@ namespace Bit.Core.Services fileData.Size = realSize.Value; } fileData.Validated = true; - send.Data = JsonConvert.SerializeObject(fileData, - new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + send.Data = JsonSerializer.Serialize(fileData, + JsonHelpers.IgnoreWritingNull); await SaveSendAsync(send); return valid; @@ -175,7 +175,7 @@ namespace Bit.Core.Services await _sendRepository.DeleteAsync(send); if (send.Type == Enums.SendType.File) { - var data = JsonConvert.DeserializeObject(send.Data); + var data = JsonSerializer.Deserialize(send.Data); await _sendFileStorageService.DeleteFileAsync(send, data.Id); } await _pushService.PushSyncSendDeleteAsync(send); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index ffeb436e9..575a3763e 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Claims; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Context; using Bit.Core.Entities; @@ -19,7 +20,6 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; using File = System.IO.File; using U2fLib = U2F.Core.Crypto.U2F; @@ -983,7 +983,8 @@ namespace Bit.Core.Services var dir = $"{_globalSettings.LicenseDirectory}/user"; Directory.CreateDirectory(dir); - File.WriteAllText($"{dir}/{user.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented)); + using var fs = File.OpenWrite(Path.Combine(dir, $"{user.Id}.json")); + await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented); } else { @@ -1068,7 +1069,8 @@ namespace Bit.Core.Services var dir = $"{_globalSettings.LicenseDirectory}/user"; Directory.CreateDirectory(dir); - File.WriteAllText($"{dir}/{user.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented)); + using var fs = File.OpenWrite(Path.Combine(dir, $"{user.Id}.json")); + await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented); user.Premium = license.Premium; user.RevisionDate = DateTime.UtcNow; diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index 315fecf11..e6ef1ad46 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -23,7 +23,6 @@ using Bit.Core.Settings; using IdentityModel; using Microsoft.AspNetCore.DataProtection; using MimeKit; -using Newtonsoft.Json; namespace Bit.Core.Utilities { @@ -332,12 +331,12 @@ namespace Bit.Core.Utilities /// /// Creates a clone of the given object through serializing to json and deserializing. - /// This method is subject to the limitations of Newstonsoft. For example, properties with + /// This method is subject to the limitations of System.Text.Json. For example, properties with /// inaccessible setters will not be set. /// public static T CloneObject(T obj) { - return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + return JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); } public static bool SettingHasValue(string setting) diff --git a/src/Core/Utilities/CustomIpRateLimitMiddleware.cs b/src/Core/Utilities/CustomIpRateLimitMiddleware.cs index 3101fd84b..9a82af308 100644 --- a/src/Core/Utilities/CustomIpRateLimitMiddleware.cs +++ b/src/Core/Utilities/CustomIpRateLimitMiddleware.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; namespace Bit.Core.Utilities { @@ -41,10 +40,8 @@ namespace Bit.Core.Utilities $"Slow down! Too many requests. Try again in {rule.Period}." : _options.QuotaExceededMessage; httpContext.Response.Headers["Retry-After"] = retryAfter; httpContext.Response.StatusCode = _options.HttpStatusCode; - - httpContext.Response.ContentType = "application/json"; var errorModel = new ErrorResponseModel { Message = message }; - return httpContext.Response.WriteAsync(JsonConvert.SerializeObject(errorModel)); + return httpContext.Response.WriteAsJsonAsync(errorModel, cancellationToken: httpContext.RequestAborted); } public override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, diff --git a/src/Core/Utilities/DuoApi.cs b/src/Core/Utilities/DuoApi.cs index 472df506d..aff938e14 100644 --- a/src/Core/Utilities/DuoApi.cs +++ b/src/Core/Utilities/DuoApi.cs @@ -15,9 +15,9 @@ using System.IO; using System.Net; using System.Security.Cryptography; using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using System.Web; -using Newtonsoft.Json; namespace Bit.Core.Utilities.Duo { @@ -175,7 +175,7 @@ namespace Bit.Core.Utilities.Duo var res = ApiCall(method, path, parameters, timeout, out var statusCode); try { - var dict = JsonConvert.DeserializeObject>(res); + var dict = JsonSerializer.Deserialize>(res); if (dict["stat"] as string == "OK") { return dict["response"] as T; diff --git a/src/Core/Utilities/EnumKeyResolver.cs b/src/Core/Utilities/EnumKeyResolver.cs deleted file mode 100644 index 92029008f..000000000 --- a/src/Core/Utilities/EnumKeyResolver.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Newtonsoft.Json.Serialization; - -namespace Bit.Core.Utilities -{ - public class EnumKeyResolver : DefaultContractResolver where T : struct - { - protected override JsonDictionaryContract CreateDictionaryContract(Type objectType) - { - var contract = base.CreateDictionaryContract(objectType); - var keyType = contract.DictionaryKeyType; - - if (keyType.BaseType == typeof(Enum)) - { - contract.DictionaryKeyResolver = propName => ((T)Enum.Parse(keyType, propName)).ToString(); - } - - return contract; - } - } -} diff --git a/src/Core/Utilities/ExpandoObjectJsonConverter.cs b/src/Core/Utilities/ExpandoObjectJsonConverter.cs deleted file mode 100644 index 9cade71f9..000000000 --- a/src/Core/Utilities/ExpandoObjectJsonConverter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Dynamic; -using Newtonsoft.Json; - -namespace Bit.Core.Utilities -{ - public class ExpandoObjectJsonConverter : JsonConverter - { - public override bool CanWrite => false; - public override bool CanConvert(Type objectType) => true; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return serializer.Deserialize(reader); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException(); - } -} diff --git a/src/Core/Utilities/HandlebarsObjectJsonConverter.cs b/src/Core/Utilities/HandlebarsObjectJsonConverter.cs new file mode 100644 index 000000000..f7de06cf9 --- /dev/null +++ b/src/Core/Utilities/HandlebarsObjectJsonConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Bit.Core.Utilities +{ + public class HandlebarsObjectJsonConverter : JsonConverter + { + public override bool CanConvert(Type typeToConvert) => true; + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize>(ref reader, options); + } + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); + } + } +} diff --git a/src/Core/Utilities/JsonHelpers.cs b/src/Core/Utilities/JsonHelpers.cs new file mode 100644 index 000000000..10c165256 --- /dev/null +++ b/src/Core/Utilities/JsonHelpers.cs @@ -0,0 +1,106 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NS = Newtonsoft.Json; + +namespace Bit.Core.Utilities +{ + public static class JsonHelpers + { + public static JsonSerializerOptions Default { get; } + public static JsonSerializerOptions Indented { get; } + public static JsonSerializerOptions IgnoreWritingNull { get; } + public static JsonSerializerOptions CamelCase { get; } + public static JsonSerializerOptions IgnoreWritingNullAndCamelCase { get; } + + static JsonHelpers() + { + Default = new JsonSerializerOptions(); + + Indented = new JsonSerializerOptions + { + WriteIndented = true, + }; + + IgnoreWritingNull = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + CamelCase = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + IgnoreWritingNullAndCamelCase = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + } + + // NOTE: This is built into .NET 6, it SHOULD be removed when we upgrade + public static T ToObject(this JsonElement element, JsonSerializerOptions options = null) + { + return JsonSerializer.Deserialize(element.GetRawText(), options ?? Default); + } + + public static T DeserializeOrNew(string json, JsonSerializerOptions options = null) + where T : new() + { + if (string.IsNullOrWhiteSpace(json)) + { + return new T(); + } + + return JsonSerializer.Deserialize(json, options); + } + + #region Legacy Newtonsoft.Json usage + private const string LegacyMessage = "Usage of Newtonsoft.Json should be kept to a minimum and will further be removed when we move to .NET 6"; + + [Obsolete(LegacyMessage)] + public static NS.JsonSerializerSettings LegacyDefault { get; } = new NS.JsonSerializerSettings(); + + [Obsolete(LegacyMessage)] + public static string LegacySerialize(object value, NS.JsonSerializerSettings settings = null) + { + return NS.JsonConvert.SerializeObject(value, settings ?? LegacyDefault); + } + + [Obsolete(LegacyMessage)] + public static T LegacyDeserialize(string value, NS.JsonSerializerSettings settings = null) + { + return NS.JsonConvert.DeserializeObject(value, settings ?? LegacyDefault); + } + #endregion + } + + public class MsEpochConverter : JsonConverter + { + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (!long.TryParse(reader.GetString(), out var milliseconds)) + { + return null; + } + + return CoreHelpers.FromEpocMilliseconds(milliseconds); + } + + public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) + { + if (!value.HasValue) + { + writer.WriteNullValue(); + } + + writer.WriteStringValue(CoreHelpers.ToEpocMilliseconds(value.Value).ToString()); + } + } +} diff --git a/src/EventsProcessor/AzureQueueHostedService.cs b/src/EventsProcessor/AzureQueueHostedService.cs index b72ae224f..c82278b90 100644 --- a/src/EventsProcessor/AzureQueueHostedService.cs +++ b/src/EventsProcessor/AzureQueueHostedService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Azure.Storage.Queues; @@ -11,8 +12,6 @@ using Bit.Core.Utilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Bit.EventsProcessor { @@ -109,30 +108,27 @@ namespace Bit.EventsProcessor _logger.LogInformation("Processing message."); var events = new List(); - var token = JToken.Parse(message); - if (token is JArray) + using var jsonDocument = JsonDocument.Parse(message); + var root = jsonDocument.RootElement; + if (root.ValueKind == JsonValueKind.Array) { - var indexedEntities = token.ToObject>() + var indexedEntities = root.ToObject>() .SelectMany(e => EventTableEntity.IndexEvent(e)); events.AddRange(indexedEntities); } - else if (token is JObject) + else if (root.ValueKind == JsonValueKind.Object) { - var eventMessage = token.ToObject(); + var eventMessage = root.ToObject(); events.AddRange(EventTableEntity.IndexEvent(eventMessage)); } await _eventWriteService.CreateManyAsync(events); _logger.LogInformation("Processed message."); } - catch (JsonReaderException) + catch (JsonException) { _logger.LogError("JsonReaderException: Unable to parse message."); } - catch (JsonSerializationException) - { - _logger.LogError("JsonSerializationException: Unable to serialize token."); - } } } } diff --git a/src/Infrastructure.Dapper/Repositories/CipherRepository.cs b/src/Infrastructure.Dapper/Repositories/CipherRepository.cs index b3915b878..deb8158aa 100644 --- a/src/Infrastructure.Dapper/Repositories/CipherRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CipherRepository.cs @@ -3,15 +3,14 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; -using Bit.Core.Utilities; using Core.Models.Data; using Dapper; -using Newtonsoft.Json; namespace Bit.Infrastructure.Dapper.Repositories { @@ -106,8 +105,8 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task CreateAsync(Cipher cipher, IEnumerable collectionIds) { cipher.SetNewId(); - var objWithCollections = JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(cipher)); + var objWithCollections = JsonSerializer.Deserialize( + JsonSerializer.Serialize(cipher)); objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) { @@ -133,8 +132,8 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task CreateAsync(CipherDetails cipher, IEnumerable collectionIds) { cipher.SetNewId(); - var objWithCollections = JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(cipher)); + var objWithCollections = JsonSerializer.Deserialize( + JsonSerializer.Serialize(cipher)); objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) { @@ -170,8 +169,8 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task ReplaceAsync(Cipher obj, IEnumerable collectionIds) { - var objWithCollections = JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(obj)); + var objWithCollections = JsonSerializer.Deserialize( + JsonSerializer.Serialize(obj)); objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 2f8e87a5b..d21c5672d 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -3,14 +3,13 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; -using Bit.Core.Utilities; using Dapper; -using Newtonsoft.Json; namespace Bit.Infrastructure.Dapper.Repositories { @@ -112,7 +111,7 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task CreateAsync(Collection obj, IEnumerable groups) { obj.SetNewId(); - var objWithGroups = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + var objWithGroups = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); objWithGroups.Groups = groups.ToArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) @@ -126,7 +125,7 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task ReplaceAsync(Collection obj, IEnumerable groups) { - var objWithGroups = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + var objWithGroups = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); objWithGroups.Groups = groups.ToArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/Repositories/GroupRepository.cs b/src/Infrastructure.Dapper/Repositories/GroupRepository.cs index d7ea1fec0..678eb2bf6 100644 --- a/src/Infrastructure.Dapper/Repositories/GroupRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/GroupRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Models.Data; @@ -10,7 +11,6 @@ using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Utilities; using Dapper; -using Newtonsoft.Json; namespace Bit.Infrastructure.Dapper.Repositories { @@ -95,7 +95,7 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task CreateAsync(Group obj, IEnumerable collections) { obj.SetNewId(); - var objWithCollections = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + var objWithCollections = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); objWithCollections.Collections = collections.ToArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) @@ -109,7 +109,7 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task ReplaceAsync(Group obj, IEnumerable collections) { - var objWithCollections = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + var objWithCollections = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); objWithCollections.Collections = collections.ToArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs index a129d4b75..267634f3f 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Bit.Core.Entities; using Bit.Core.Enums; @@ -11,7 +12,6 @@ using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Utilities; using Dapper; -using Newtonsoft.Json; namespace Bit.Infrastructure.Dapper.Repositories { @@ -244,8 +244,8 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task CreateAsync(OrganizationUser obj, IEnumerable collections) { obj.SetNewId(); - var objWithCollections = JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(obj)); + var objWithCollections = JsonSerializer.Deserialize( + JsonSerializer.Serialize(obj)); objWithCollections.Collections = collections.ToArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) @@ -261,8 +261,8 @@ namespace Bit.Infrastructure.Dapper.Repositories public async Task ReplaceAsync(OrganizationUser obj, IEnumerable collections) { - var objWithCollections = JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(obj)); + var objWithCollections = JsonSerializer.Deserialize( + JsonSerializer.Serialize(obj)); objWithCollections.Collections = collections.ToArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Notifications/HubHelpers.cs b/src/Notifications/HubHelpers.cs index d71912ceb..853dd7d4c 100644 --- a/src/Notifications/HubHelpers.cs +++ b/src/Notifications/HubHelpers.cs @@ -1,9 +1,9 @@ -using System.Threading; +using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Bit.Core.Enums; using Bit.Core.Models; using Microsoft.AspNetCore.SignalR; -using Newtonsoft.Json; namespace Bit.Notifications { @@ -12,7 +12,7 @@ namespace Bit.Notifications public static async Task SendNotificationToHubAsync(string notificationJson, IHubContext hubContext, CancellationToken cancellationToken = default(CancellationToken)) { - var notification = JsonConvert.DeserializeObject>(notificationJson); + var notification = JsonSerializer.Deserialize>(notificationJson); switch (notification.Type) { case PushType.SyncCipherUpdate: @@ -20,7 +20,7 @@ namespace Bit.Notifications case PushType.SyncCipherDelete: case PushType.SyncLoginDelete: var cipherNotification = - JsonConvert.DeserializeObject>( + JsonSerializer.Deserialize>( notificationJson); if (cipherNotification.Payload.UserId.HasValue) { @@ -38,7 +38,7 @@ namespace Bit.Notifications case PushType.SyncFolderCreate: case PushType.SyncFolderDelete: var folderNotification = - JsonConvert.DeserializeObject>( + JsonSerializer.Deserialize>( notificationJson); await hubContext.Clients.User(folderNotification.Payload.UserId.ToString()) .SendAsync("ReceiveMessage", folderNotification, cancellationToken); @@ -49,7 +49,7 @@ namespace Bit.Notifications case PushType.SyncSettings: case PushType.LogOut: var userNotification = - JsonConvert.DeserializeObject>( + JsonSerializer.Deserialize>( notificationJson); await hubContext.Clients.User(userNotification.Payload.UserId.ToString()) .SendAsync("ReceiveMessage", userNotification, cancellationToken); @@ -58,7 +58,7 @@ namespace Bit.Notifications case PushType.SyncSendUpdate: case PushType.SyncSendDelete: var sendNotification = - JsonConvert.DeserializeObject>( + JsonSerializer.Deserialize>( notificationJson); await hubContext.Clients.User(sendNotification.Payload.UserId.ToString()) .SendAsync("ReceiveMessage", sendNotification, cancellationToken); diff --git a/test/Api.Test/Controllers/SendsControllerTests.cs b/test/Api.Test/Controllers/SendsControllerTests.cs index 49fa09cf1..47122edb9 100644 --- a/test/Api.Test/Controllers/SendsControllerTests.cs +++ b/test/Api.Test/Controllers/SendsControllerTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; using AutoFixture.Xunit2; using Bit.Api.Controllers; @@ -14,7 +15,6 @@ using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using NSubstitute; using Xunit; @@ -66,7 +66,7 @@ namespace Bit.Api.Test.Controllers send.Id = default; send.Type = SendType.Text; - send.Data = JsonConvert.SerializeObject(new Dictionary()); + send.Data = JsonSerializer.Serialize(new Dictionary()); send.HideEmail = true; _sendService.AccessAsync(id, null).Returns((send, false, false)); @@ -81,4 +81,3 @@ namespace Bit.Api.Test.Controllers } } } - diff --git a/test/Common/Helpers/AssertHelper.cs b/test/Common/Helpers/AssertHelper.cs index a2bfbfcb5..3c42ba4f1 100644 --- a/test/Common/Helpers/AssertHelper.cs +++ b/test/Common/Helpers/AssertHelper.cs @@ -1,9 +1,9 @@ using System; -using System.IO; using System.Linq; -using System.Reflection; -using Newtonsoft.Json; +using System.Text.Json; +using Bit.Core.Utilities; using Xunit; +using Xunit.Sdk; namespace Bit.Test.Common.Helpers { @@ -29,10 +29,9 @@ namespace Bit.Test.Common.Helpers if (actualPropInfo == null) { - var settings = new JsonSerializerSettings { Formatting = Formatting.Indented }; throw new Exception(string.Concat($"Expected actual object to contain a property named {expectedPropInfo.Name}, but it does not\n", - $"Expected:\n{JsonConvert.SerializeObject(expected, settings)}\n", - $"Actual:\n{JsonConvert.SerializeObject(actual, new JsonSerializerSettings { Formatting = Formatting.Indented })}")); + $"Expected:\n{JsonSerializer.Serialize(expected, JsonHelpers.Indented)}\n", + $"Actual:\n{JsonSerializer.Serialize(actual, JsonHelpers.Indented)}")); } if (expectedPropInfo.PropertyType == typeof(string) || expectedPropInfo.PropertyType.IsValueType) @@ -54,5 +53,16 @@ namespace Bit.Test.Common.Helpers Assert.Equal(expected, actual); return true; }; + + public static JsonElement AssertJsonProperty(JsonElement element, string propertyName, JsonValueKind jsonValueKind) + { + if (!element.TryGetProperty(propertyName, out var subElement)) + { + throw new XunitException($"Could not find property by name '{propertyName}'"); + } + + Assert.Equal(jsonValueKind, subElement.ValueKind); + return subElement; + } } } diff --git a/test/Core.Test/AutoFixture/CipherAttachmentMetaDataFixtures.cs b/test/Core.Test/AutoFixture/CipherAttachmentMetaDataFixtures.cs index 972614f56..7b41f76be 100644 --- a/test/Core.Test/AutoFixture/CipherAttachmentMetaDataFixtures.cs +++ b/test/Core.Test/AutoFixture/CipherAttachmentMetaDataFixtures.cs @@ -9,7 +9,7 @@ namespace Bit.Core.Test.AutoFixture.CipherAttachmentMetaData protected virtual IPostprocessComposer ComposerAction(IFixture fixture, ICustomizationComposer composer) { - return composer.With(d => d.Size, fixture.Create()).Without(d => d.SizeString); + return composer.With(d => d.Size, fixture.Create()); } public void Customize(IFixture fixture) { diff --git a/test/Core.Test/Identity/AuthenticationTokenProviderTests.cs b/test/Core.Test/Identity/AuthenticationTokenProviderTests.cs new file mode 100644 index 000000000..cf8d060d0 --- /dev/null +++ b/test/Core.Test/Identity/AuthenticationTokenProviderTests.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Identity; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.Identity +{ + public class AuthenticationTokenProviderTests : BaseTokenProviderTests + { + public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Authenticator; + + public static IEnumerable CanGenerateTwoFactorTokenAsyncData + => SetupCanGenerateData( + ( + new Dictionary + { + ["Key"] = "stuff", + }, + true + ), + ( + new Dictionary + { + ["Key"] = "" + }, + false + ) + ); + + [Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))] + public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary metaData, bool expectedResponse, + User user, SutProvider sutProvider) + { + await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider); + } + } +} diff --git a/test/Core.Test/Identity/BaseTokenProviderTests.cs b/test/Core.Test/Identity/BaseTokenProviderTests.cs new file mode 100644 index 000000000..7efcceb32 --- /dev/null +++ b/test/Core.Test/Identity/BaseTokenProviderTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Core.Services; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Identity +{ + [SutProviderCustomize] + public abstract class BaseTokenProviderTests + where T : IUserTwoFactorTokenProvider + { + public abstract TwoFactorProviderType TwoFactorProviderType { get; } + + #region Helpers + protected static IEnumerable SetupCanGenerateData(params (Dictionary MetaData, bool ExpectedResponse)[] data) + { + return data.Select(d => + new object[] + { + d.MetaData, + d.ExpectedResponse, + }); + } + + protected virtual IUserService AdditionalSetup(SutProvider sutProvider, User user) + { + var userService = Substitute.For(); + + sutProvider.GetDependency() + .GetService(typeof(IUserService)) + .Returns(userService); + + SetupUserService(userService, user); + + return userService; + } + + protected virtual void SetupUserService(IUserService userService, User user) + { + userService + .TwoFactorProviderIsEnabledAsync(TwoFactorProviderType, user) + .Returns(true); + } + + protected static UserManager SubstituteUserManager() + { + return new UserManager(Substitute.For>(), + Substitute.For>(), + Substitute.For>(), + Enumerable.Empty>(), + Enumerable.Empty>(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For>>()); + } + + protected void MockDatabase(User user, Dictionary metaData) + { + var providers = new Dictionary + { + [TwoFactorProviderType] = new TwoFactorProvider + { + Enabled = true, + MetaData = metaData, + }, + }; + + user.TwoFactorProviders = JsonHelpers.LegacySerialize(providers); + } + #endregion + + public virtual async Task RunCanGenerateTwoFactorTokenAsync(Dictionary metaData, bool expectedResponse, + User user, SutProvider sutProvider) + { + var userManager = SubstituteUserManager(); + MockDatabase(user, metaData); + + AdditionalSetup(sutProvider, user); + + var response = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(userManager, user); + Assert.Equal(expectedResponse, response); + } + } +} diff --git a/test/Core.Test/Identity/EmailTokenProviderTests.cs b/test/Core.Test/Identity/EmailTokenProviderTests.cs new file mode 100644 index 000000000..355efe099 --- /dev/null +++ b/test/Core.Test/Identity/EmailTokenProviderTests.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Identity; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.Identity +{ + public class EmailTokenProviderTests : BaseTokenProviderTests + { + public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Email; + + public static IEnumerable CanGenerateTwoFactorTokenAsyncData + => SetupCanGenerateData( + ( + new Dictionary + { + ["Email"] = "test@email.com", + }, + true + ), + ( + new Dictionary + { + ["NotEmail"] = "value", + }, + false + ), + ( + new Dictionary + { + ["Email"] = "", + }, + false + ) + ); + + [Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))] + public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary metaData, bool expectedResponse, + User user, SutProvider sutProvider) + { + await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider); + } + } +} diff --git a/test/Core.Test/Identity/U2fTokenProviderTests.cs b/test/Core.Test/Identity/U2fTokenProviderTests.cs new file mode 100644 index 000000000..7ce603bb1 --- /dev/null +++ b/test/Core.Test/Identity/U2fTokenProviderTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Identity; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Identity +{ + public class U2fTokenProviderTests : BaseTokenProviderTests + { + public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.U2f; + + public static IEnumerable CanGenerateTwoFactorTokenAsyncData() + { + return new[] + { + new object[] + { + new Dictionary + { + ["Something"] = "Hello" + }, + true, // canAccessPremium + true, // expectedResponse + }, + new object[] + { + new Dictionary(), + true, // canAccessPremium + false, // expectedResponse + }, + new object[] + { + new Dictionary + { + ["Key"] = "Value" + }, + false, // canAccessPremium + false, // expectedResponse + }, + }; + } + + [Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))] + public async Task CanGenerateTwoFactorTokenAsync_Success(Dictionary metaData, bool canAccessPremium, + bool expectedResponse, User user, SutProvider sutProvider) + { + var userManager = SubstituteUserManager(); + MockDatabase(user, metaData); + AdditionalSetup(sutProvider, user) + .CanAccessPremium(user) + .Returns(canAccessPremium); + + var response = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(userManager, user); + Assert.Equal(expectedResponse, response); + } + } +} diff --git a/test/Core.Test/Models/CipherTests.cs b/test/Core.Test/Models/CipherTests.cs index 4a339b8e1..3993f4caf 100644 --- a/test/Core.Test/Models/CipherTests.cs +++ b/test/Core.Test/Models/CipherTests.cs @@ -1,6 +1,6 @@ -using Bit.Core.Entities; +using System.Text.Json; +using Bit.Core.Entities; using Bit.Core.Test.AutoFixture.CipherFixtures; -using Newtonsoft.Json; using Xunit; namespace Bit.Core.Test.Models @@ -12,7 +12,7 @@ namespace Bit.Core.Test.Models [InlineOrganizationCipherAutoData] public void Clone_CreatesExactCopy(Cipher cipher) { - Assert.Equal(JsonConvert.SerializeObject(cipher), JsonConvert.SerializeObject(cipher.Clone())); + Assert.Equal(JsonSerializer.Serialize(cipher), JsonSerializer.Serialize(cipher.Clone())); } } } diff --git a/test/Core.Test/Models/Data/SendFileDataTests.cs b/test/Core.Test/Models/Data/SendFileDataTests.cs new file mode 100644 index 000000000..7a7dc9bc5 --- /dev/null +++ b/test/Core.Test/Models/Data/SendFileDataTests.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using Bit.Core.Models.Data; +using Bit.Test.Common.Helpers; +using Xunit; + +namespace Bit.Core.Test.Models.Data +{ + public class SendFileDataTests + { + [Fact] + public void Serialize_Success() + { + var sut = new SendFileData + { + Id = "test", + Size = 100, + FileName = "thing.pdf", + Validated = true, + }; + + var json = JsonSerializer.Serialize(sut); + var document = JsonDocument.Parse(json); + var root = document.RootElement; + AssertHelper.AssertJsonProperty(root, "Size", JsonValueKind.String); + Assert.False(root.TryGetProperty("SizeString", out _)); + } + } +} diff --git a/test/Core.Test/Models/PermissionsTests.cs b/test/Core.Test/Models/PermissionsTests.cs index 5b15b7d4b..a45a23972 100644 --- a/test/Core.Test/Models/PermissionsTests.cs +++ b/test/Core.Test/Models/PermissionsTests.cs @@ -1,10 +1,6 @@ -using System; -using System.Text.Json; -using AutoFixture.Xunit2; +using System.Text.Json; using Bit.Core.Models.Data; using Bit.Core.Utilities; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using Xunit; namespace Bit.Core.Test.Models @@ -33,21 +29,30 @@ namespace Bit.Core.Test.Models [Fact] public void Serialization_Success() { - // minify expected json - var expected = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(_exampleSerializedPermissions)); - - DefaultContractResolver contractResolver = new DefaultContractResolver + var permissions = new Permissions { - NamingStrategy = new CamelCaseNamingStrategy() + AccessEventLogs = false, + AccessImportExport = false, + AccessReports = false, + CreateNewCollections = true, + EditAnyCollection = true, + DeleteAnyCollection = true, + EditAssignedCollections = false, + DeleteAssignedCollections = false, + ManageGroups = false, + ManagePolicies = false, + ManageSso = false, + ManageUsers = false, + ManageResetPassword = false, }; - var actual = JsonConvert.SerializeObject( - CoreHelpers.LoadClassFromJsonData(_exampleSerializedPermissions), new JsonSerializerSettings - { - ContractResolver = contractResolver, - }); + // minify expected json + var expected = JsonSerializer.Serialize(permissions, JsonHelpers.CamelCase); + + var actual = JsonSerializer.Serialize( + JsonHelpers.DeserializeOrNew(_exampleSerializedPermissions, JsonHelpers.CamelCase), + JsonHelpers.CamelCase); - Console.WriteLine(actual); Assert.Equal(expected, actual); } } diff --git a/test/Core.Test/Models/Tables/UserTests.cs b/test/Core.Test/Models/Tables/UserTests.cs index 3db8d3055..8d2059484 100644 --- a/test/Core.Test/Models/Tables/UserTests.cs +++ b/test/Core.Test/Models/Tables/UserTests.cs @@ -1,4 +1,9 @@ -using Bit.Core.Entities; +using System.Collections.Generic; +using System.Text.Json; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Test.Common.Helpers; using Xunit; namespace Bit.Core.Test.Models.Tables @@ -39,5 +44,43 @@ namespace Bit.Core.Test.Models.Tables Assert.Equal(expectedRemainingBytes, bytesRemaining); } + + [Fact] + public void SetTwoFactorProviders() + { + var user = new User(); + user.SetTwoFactorProviders(new Dictionary + { + [TwoFactorProviderType.WebAuthn] = new TwoFactorProvider + { + Enabled = true, + MetaData = new Dictionary + { + ["Item"] = "thing", + }, + }, + [TwoFactorProviderType.Email] = new TwoFactorProvider + { + Enabled = false, + MetaData = new Dictionary + { + ["Email"] = "test@email.com", + }, + }, + }); + + using var jsonDocument = JsonDocument.Parse(user.TwoFactorProviders); + var root = jsonDocument.RootElement; + + var webAuthn = AssertHelper.AssertJsonProperty(root, "WebAuthn", JsonValueKind.Object); + AssertHelper.AssertJsonProperty(webAuthn, "Enabled", JsonValueKind.True); + var webMetaData = AssertHelper.AssertJsonProperty(webAuthn, "MetaData", JsonValueKind.Object); + AssertHelper.AssertJsonProperty(webMetaData, "Item", JsonValueKind.String); + + var email = AssertHelper.AssertJsonProperty(root, "Email", JsonValueKind.Object); + AssertHelper.AssertJsonProperty(email, "Enabled", JsonValueKind.False); + var emailMetaData = AssertHelper.AssertJsonProperty(email, "MetaData", JsonValueKind.Object); + AssertHelper.AssertJsonProperty(emailMetaData, "Email", JsonValueKind.String); + } } } diff --git a/test/Core.Test/Services/UserServiceTests.cs b/test/Core.Test/Services/UserServiceTests.cs index ee43a5c13..1a45fde14 100644 --- a/test/Core.Test/Services/UserServiceTests.cs +++ b/test/Core.Test/Services/UserServiceTests.cs @@ -1,10 +1,17 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; using Bit.Core.Context; using Bit.Core.Entities; +using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; using Fido2NetLib; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; @@ -17,103 +24,41 @@ namespace Bit.Core.Test.Services { public class UserServiceTests { - private readonly UserService _sut; - - private readonly IUserRepository _userRepository; - private readonly ICipherRepository _cipherRepository; - private readonly IOrganizationUserRepository _organizationUserRepository; - private readonly IOrganizationRepository _organizationRepository; - private readonly IMailService _mailService; - private readonly IPushNotificationService _pushService; - private readonly IUserStore _userStore; - private readonly IOptions _optionsAccessor; - private readonly IPasswordHasher _passwordHasher; - private readonly IEnumerable> _userValidators; - private readonly IEnumerable> _passwordValidators; - private readonly ILookupNormalizer _keyNormalizer; - private readonly IdentityErrorDescriber _errors; - private readonly IServiceProvider _services; - private readonly ILogger> _logger; - private readonly ILicensingService _licenseService; - private readonly IEventService _eventService; - private readonly IApplicationCacheService _applicationCacheService; - private readonly IDataProtectionProvider _dataProtectionProvider; - private readonly IPaymentService _paymentService; - private readonly IPolicyRepository _policyRepository; - private readonly IReferenceEventService _referenceEventService; - private readonly IFido2 _fido2; - private readonly CurrentContext _currentContext; - private readonly GlobalSettings _globalSettings; - private readonly IOrganizationService _organizationService; - private readonly IProviderUserRepository _providerUserRepository; - - public UserServiceTests() + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task UpdateLicenseAsync_Success(SutProvider sutProvider, + User user, UserLicense userLicense) { - _userRepository = Substitute.For(); - _cipherRepository = Substitute.For(); - _organizationUserRepository = Substitute.For(); - _organizationRepository = Substitute.For(); - _mailService = Substitute.For(); - _pushService = Substitute.For(); - _userStore = Substitute.For>(); - _optionsAccessor = Substitute.For>(); - _passwordHasher = Substitute.For>(); - _userValidators = new List>(); - _passwordValidators = new List>(); - _keyNormalizer = Substitute.For(); - _errors = new IdentityErrorDescriber(); - _services = Substitute.For(); - _logger = Substitute.For>>(); - _licenseService = Substitute.For(); - _eventService = Substitute.For(); - _applicationCacheService = Substitute.For(); - _dataProtectionProvider = Substitute.For(); - _paymentService = Substitute.For(); - _policyRepository = Substitute.For(); - _referenceEventService = Substitute.For(); - _fido2 = Substitute.For(); - _currentContext = new CurrentContext(null); - _globalSettings = new GlobalSettings(); - _organizationService = Substitute.For(); - _providerUserRepository = Substitute.For(); + using var tempDir = new TempDirectory(); - _sut = new UserService( - _userRepository, - _cipherRepository, - _organizationUserRepository, - _organizationRepository, - _mailService, - _pushService, - _userStore, - _optionsAccessor, - _passwordHasher, - _userValidators, - _passwordValidators, - _keyNormalizer, - _errors, - _services, - _logger, - _licenseService, - _eventService, - _applicationCacheService, - _dataProtectionProvider, - _paymentService, - _policyRepository, - _referenceEventService, - _fido2, - _currentContext, - _globalSettings, - _organizationService, - _providerUserRepository - ); - } + var now = DateTime.UtcNow; + userLicense.Issued = now.AddDays(-10); + userLicense.Expires = now.AddDays(10); + userLicense.Version = 1; + userLicense.Premium = true; - // Remove this test when we add actual tests. It only proves that - // we've properly constructed the system under test. - [Fact] - public void ServiceExists() - { - Assert.NotNull(_sut); + user.EmailVerified = true; + user.Email = userLicense.Email; + + sutProvider.GetDependency().SelfHosted = true; + sutProvider.GetDependency().LicenseDirectory = tempDir.Directory; + sutProvider.GetDependency() + .VerifyLicense(userLicense) + .Returns(true); + + await sutProvider.Sut.UpdateLicenseAsync(user, userLicense); + + var filePath = Path.Combine(tempDir.Directory, "user", $"{user.Id}.json"); + Assert.True(File.Exists(filePath)); + var document = JsonDocument.Parse(File.OpenRead(filePath)); + var root = document.RootElement; + Assert.Equal(JsonValueKind.Object, root.ValueKind); + // Sort of a lazy way to test that it is indented but not sure of a better way + Assert.Contains('\n', root.GetRawText()); + AssertHelper.AssertJsonProperty(root, "LicenseKey", JsonValueKind.String); + AssertHelper.AssertJsonProperty(root, "Id", JsonValueKind.String); + AssertHelper.AssertJsonProperty(root, "Premium", JsonValueKind.True); + var versionProp = AssertHelper.AssertJsonProperty(root, "Version", JsonValueKind.Number); + Assert.Equal(1, versionProp.GetInt32()); } } } diff --git a/test/Core.Test/Utilities/JsonHelpersTests.cs b/test/Core.Test/Utilities/JsonHelpersTests.cs new file mode 100644 index 000000000..8a9a26614 --- /dev/null +++ b/test/Core.Test/Utilities/JsonHelpersTests.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using Bit.Core.Utilities; +using Xunit; + +namespace Bit.Core.Test.Helpers +{ + public class JsonHelpersTests + { + private static void CompareJson(T value, JsonSerializerOptions options, Newtonsoft.Json.JsonSerializerSettings settings) + { + var stgJson = JsonSerializer.Serialize(value, options); + var nsJson = Newtonsoft.Json.JsonConvert.SerializeObject(value, settings); + + Assert.Equal(stgJson, nsJson); + } + + + [Fact] + public void DefaultJsonOptions() + { + var testObject = new SimpleTestObject + { + Id = 0, + Name = "Test", + }; + + CompareJson(testObject, JsonHelpers.Default, new Newtonsoft.Json.JsonSerializerSettings()); + } + + [Fact] + public void IndentedJsonOptions() + { + var testObject = new SimpleTestObject + { + Id = 10, + Name = "Test Name" + }; + + CompareJson(testObject, JsonHelpers.Indented, new Newtonsoft.Json.JsonSerializerSettings + { + Formatting = Newtonsoft.Json.Formatting.Indented, + }); + } + + [Fact] + public void NullValueHandlingJsonOptions() + { + var testObject = new SimpleTestObject + { + Id = 14, + Name = null, + }; + + CompareJson(testObject, JsonHelpers.IgnoreWritingNull, new Newtonsoft.Json.JsonSerializerSettings + { + NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, + }); + } + } + + public class SimpleTestObject + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/util/Setup/Program.cs b/util/Setup/Program.cs index b9d87e3c4..353035225 100644 --- a/util/Setup/Program.cs +++ b/util/Setup/Program.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Data.SqlClient; using System.Globalization; using System.Net.Http; +using System.Text.Json; using Bit.Migrator; -using Newtonsoft.Json; namespace Bit.Setup { @@ -273,8 +273,9 @@ namespace Bit.Setup } var resultString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - var result = JsonConvert.DeserializeObject(resultString); - if (!(bool)result.Enabled) + using var jsonDocument = JsonSerializer.Deserialize(resultString); + var root = jsonDocument.RootElement; + if (!root.GetProperty("Enabled").GetBoolean()) { Console.WriteLine("Installation id has been disabled."); return false; diff --git a/util/Setup/Setup.csproj b/util/Setup/Setup.csproj index 744242e60..bc57dac25 100644 --- a/util/Setup/Setup.csproj +++ b/util/Setup/Setup.csproj @@ -12,7 +12,6 @@ -