Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/security review changes #8

Merged
merged 11 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<PackageVersion Include="kentico.xperience.imageprocessing" Version="29.0.2" />
<PackageVersion Include="Kentico.Xperience.Core" Version="29.0.2" />
<PackageVersion Include="Moq.AutoMock" Version="3.5.0" />
<PackageVersion Include="ShopifySharp" Version="6.13.0" />
<PackageVersion Include="ShopifySharp.Extensions.DependencyInjection" Version="1.4.0" />
<PackageVersion Include="ShopifySharp" Version="6.17.0" />
<PackageVersion Include="ShopifySharp.Extensions.DependencyInjection" Version="1.6.0" />
<PackageVersion Include="Microsoft.Extensions.ApiDescription.Client" Version="7.0.2" />
<PackageVersion Include="NSwag.ApiDescription.Client" Version="13.18.2" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="9.17.0.82934" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public async Task<IActionResult> Index(UpdateCartModel updateCartModel, CartOper
};

var result = cartOperation == CartOperation.Remove
? await shoppingService.RemoveCartItem(cartItemParams.MerchandiseID)
? await shoppingService.RemoveProductVariantFromCart(cartItemParams.MerchandiseID)
: await shoppingService.AddItemToCart(cartItemParams);

TempData[ERROR_MESSAGES_KEY] = result.ErrorMessages.ToArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,18 @@ public async Task<IActionResult> Index()
[HttpPost]
[Route("/cart/update")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update([FromForm] string variantGraphQLId, [FromForm] int quantity, [FromForm] string cartOperation)
public async Task<IActionResult> Update([FromForm] string cartItemId, [FromForm] int quantity, [FromForm] string cartOperation)
{
var country = ShopifySharp.GraphQL.CountryCode.CZ;
if (Enum.TryParse<CartOperation>(cartOperation, out var operationEnum))
{
var result = operationEnum == CartOperation.Remove
? await shoppingService.RemoveCartItem(variantGraphQLId)
? await shoppingService.RemoveCartItem(cartItemId)
: await shoppingService.UpdateCartItem(new ShoppingCartItemParameters()
{
Country = country,
Quantity = quantity,
MerchandiseID = variantGraphQLId
ShoppingCartItemID = cartItemId
});

AddErrorsToTempData(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public record ProductDetailViewModel
public static ProductDetailViewModel GetViewModel(ProductDetailPage page, string selectedVariantID, string country, string currency, string[] errorMessages)
{
var product = page.Product.First();
var selectedVariant = product.Variants.FirstOrDefault(x => x.ShopifyVariantID == selectedVariantID) ?? product.Variants.First();
var selectedVariant = product.Variants.FirstOrDefault(x => x.ShopifyVariantID.Equals(selectedVariantID, StringComparison.Ordinal)) ?? product.Variants.First();

var allImages = product.Images.Concat(product.Variants.Select(x => x.Image.FirstOrDefault()))
.Where(x => x != null)
Expand All @@ -48,7 +48,7 @@ public static ProductDetailViewModel GetViewModel(ProductDetailPage page, string
Images = allImages,
DescriptionHTML = product.Description,
ParametersSection = product.Parameters,
Variants = product.Variants.Select(x => new SelectListItem(x.Title, x.ShopifyVariantID, x.ShopifyVariantID == selectedVariant.ShopifyVariantID)).ToList(),
Variants = product.Variants.Select(x => new SelectListItem(x.Title, x.ShopifyVariantID, x.ShopifyVariantID.Equals(selectedVariant.ShopifyVariantID, StringComparison.Ordinal))).ToList(),
SelectedShopifyVariantId = selectedVariant.ShopifyVariantID,
ShopifyProductId = product.ShopifyProductID,
CountryCode = country,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@Html.Kentico().PageData()
<div class="cart-item-amount">
<span>@HtmlLocalizer["Qty"]</span>
<input type="hidden" name="@nameof(ShoppingCartItemViewModel.VariantGraphQLId)" value="@Model.VariantGraphQLId" />
<input type="hidden" name="@nameof(ShoppingCartItemViewModel.CartItemId)" value="@Model.CartItemId" />
<input type="number" class="add-to-cart-textbox" min="0" , name="@nameof(ShoppingCartItemViewModel.Quantity)" value="@Model.Quantity" />
<input type="submit" name="cartOperation" value="Update" class="btn btn-default" />
<input type="submit" name="cartOperation" value="Remove" class="btn btn-default" />
Expand Down
8 changes: 7 additions & 1 deletion examples/DancingGoat-Shopify/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@
}
},
"AllowedHosts": "*",
"CMSHashStringSalt": "9f38667d-c99f-43bb-9a92-043ce36ecb5d"
"CMSHashStringSalt": "9f38667d-c99f-43bb-9a92-043ce36ecb5d",
"CMSShopifyConfig": {
"ShopifyUrl": "https://your-shopify-store-url.com/",
"AdminApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"StorefrontApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"StorefrontApiVersion": "YYYY-MM"
}
}
20 changes: 10 additions & 10 deletions examples/DancingGoat-Shopify/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@
},
"ShopifySharp": {
"type": "Direct",
"requested": "[6.13.0, )",
"resolved": "6.13.0",
"contentHash": "GwM1K0FzTo9BrXSqG9hWxIOGGfhrbBfLXK+b6vMqaabmw0Uv4/n9gskQKSZ9CIB7vJksI4Grn+Tk9qW/dVqBJQ==",
"requested": "[6.17.0, )",
"resolved": "6.17.0",
"contentHash": "0yvFrbdvCaQyWHOXsd5Lp15mPpJDQ0G+PE8vJVRkMiH39nrEemI1i2UYrFTS8axgNndGJzB+E1XzctwELp7uug==",
"dependencies": {
"Microsoft.Extensions.Http": "2.1.0",
"System.Text.Json": "7.0.3",
Expand All @@ -66,11 +66,11 @@
},
"ShopifySharp.Extensions.DependencyInjection": {
"type": "Direct",
"requested": "[1.4.0, )",
"resolved": "1.4.0",
"contentHash": "jpaU4WpqjAXm/Tm7vQwC/gBbsdIHa4taO/aAN6BZxT8t91f9+OHkWZzFOsvChVUNn61cShcTR84tUfrv6X2cVg==",
"requested": "[1.6.0, )",
"resolved": "1.6.0",
"contentHash": "Hf0MexgLD6FbTcm81lFMPsw5kWK4XNEl7FPmYuoE9kPlWhWwR/wUWMYUpn6uSBTHCE+62yI2l+i7lsOb0zASnA==",
"dependencies": {
"ShopifySharp": "6.13.0",
"ShopifySharp": "6.16.0",
"microsoft.extensions.dependencyinjection": "8.0.0"
}
},
Expand Down Expand Up @@ -1015,16 +1015,16 @@
"Kentico.Xperience.Admin": "[29.0.2, )",
"Kentico.Xperience.Core": "[29.0.2, )",
"Kentico.Xperience.Ecommerce.Common": "[1.0.0-prerelease-1, )",
"ShopifySharp": "[6.13.0, )",
"ShopifySharp.Extensions.DependencyInjection": "[1.4.0, )",
"ShopifySharp": "[6.17.0, )",
"ShopifySharp.Extensions.DependencyInjection": "[1.6.0, )",
"System.Configuration.ConfigurationManager": "[8.0.0, )",
"System.Linq.Async": "[6.0.1, )"
}
},
"kentico.xperience.shopify.rcl": {
"type": "Project",
"dependencies": {
"Kentico.Xperience.Shopify": "[1.0.0-prerelease-1, )"
"Kentico.Xperience.Shopify": "[0.1.0-prerelease-1, )"
}
},
"GraphQL": {
Expand Down
18 changes: 9 additions & 9 deletions src/Kentico.Xperience.Shopify.Rcl/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -885,8 +885,8 @@
"Kentico.Xperience.Admin": "[29.0.2, )",
"Kentico.Xperience.Core": "[29.0.2, )",
"Kentico.Xperience.Ecommerce.Common": "[1.0.0-prerelease-1, )",
"ShopifySharp": "[6.13.0, )",
"ShopifySharp.Extensions.DependencyInjection": "[1.4.0, )",
"ShopifySharp": "[6.17.0, )",
"ShopifySharp.Extensions.DependencyInjection": "[1.6.0, )",
"System.Configuration.ConfigurationManager": "[8.0.0, )",
"System.Linq.Async": "[6.0.1, )"
}
Expand Down Expand Up @@ -974,9 +974,9 @@
},
"ShopifySharp": {
"type": "CentralTransitive",
"requested": "[6.13.0, )",
"resolved": "6.13.0",
"contentHash": "GwM1K0FzTo9BrXSqG9hWxIOGGfhrbBfLXK+b6vMqaabmw0Uv4/n9gskQKSZ9CIB7vJksI4Grn+Tk9qW/dVqBJQ==",
"requested": "[6.17.0, )",
"resolved": "6.17.0",
"contentHash": "0yvFrbdvCaQyWHOXsd5Lp15mPpJDQ0G+PE8vJVRkMiH39nrEemI1i2UYrFTS8axgNndGJzB+E1XzctwELp7uug==",
"dependencies": {
"Microsoft.Extensions.Http": "2.1.0",
"System.Text.Json": "7.0.3",
Expand All @@ -985,11 +985,11 @@
},
"ShopifySharp.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[1.4.0, )",
"resolved": "1.4.0",
"contentHash": "jpaU4WpqjAXm/Tm7vQwC/gBbsdIHa4taO/aAN6BZxT8t91f9+OHkWZzFOsvChVUNn61cShcTR84tUfrv6X2cVg==",
"requested": "[1.6.0, )",
"resolved": "1.6.0",
"contentHash": "Hf0MexgLD6FbTcm81lFMPsw5kWK4XNEl7FPmYuoE9kPlWhWwR/wUWMYUpn6uSBTHCE+62yI2l+i7lsOb0zASnA==",
"dependencies": {
"ShopifySharp": "6.13.0",
"ShopifySharp": "6.16.0",
"microsoft.extensions.dependencyinjection": "8.0.0"
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public ShopifyOrderService(

var result = await orderService.ListAsync(filter);

return result.Items.FirstOrDefault(x => x.SourceIdentifier == sourceId);
return result.Items.FirstOrDefault(x => x.SourceIdentifier.Equals(sourceId, StringComparison.Ordinal));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private async Task<IDictionary<string, ProductPriceModel>> GetProductsPriceInter
continue;
}

var prices = product.Variants.Select(x => x.PresentmentPrices.FirstOrDefault(x => x.Price.CurrencyCode == currency));
var prices = product.Variants.Select(x => x.PresentmentPrices.FirstOrDefault(x => x.Price.CurrencyCode.Equals(currency, StringComparison.Ordinal)));

dict.TryAdd(product.Id.Value.ToString(), new ProductPriceModel()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private async Task<Dictionary<string, ProductVariantListModel>> GetProductVarian
{
foreach (var variant in variants.Values)
{
var cartItem = cart.Items.FirstOrDefault(x => x.VariantGraphQLId == variant.MerchandiseID);
var cartItem = cart.Items.FirstOrDefault(x => x.VariantGraphQLId.Equals(variant.MerchandiseID, StringComparison.Ordinal));
variant.ItemsInCart = cartItem?.Quantity ?? 0;
}
}
Expand Down Expand Up @@ -163,7 +163,7 @@ private ListResultWrapper<ShopifyProductListModel> CreateResultModel(ListResult<
if (variants.Count() == 1)
{
var onlyVariant = variants.First();
var currencyPrice = onlyVariant.PresentmentPrices?.FirstOrDefault(x => x.Price.CurrencyCode == currency);
var currencyPrice = onlyVariant.PresentmentPrices?.FirstOrDefault(x => x.Price.CurrencyCode.Equals(currency, StringComparison.Ordinal));

return currencyPrice is { Price: not null } ?
(currencyPrice.Price.Amount, currencyPrice.CompareAtPrice?.Amount) : (null, null);
Expand All @@ -173,7 +173,7 @@ private ListResultWrapper<ShopifyProductListModel> CreateResultModel(ListResult<

foreach (var variant in variants)
{
var currencyPrice = variant.PresentmentPrices?.FirstOrDefault(x => x.Price.CurrencyCode == currency);
var currencyPrice = variant.PresentmentPrices?.FirstOrDefault(x => x.Price.CurrencyCode.Equals(currency, StringComparison.Ordinal));

if (currencyPrice?.Price.Amount != null)
{
Expand Down
6 changes: 6 additions & 0 deletions src/Kentico.Xperience.Shopify/ShopifyConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,11 @@ public static class ShopifyConstants
/// The default name for product variants.
/// </summary>
public const string DEFAULT_VARIANT_NAME = "default title";


/// <summary>
/// The name of the header where buyer IP address should be added.
/// </summary>
public const string STOREFRONT_API_BUYER_IP_NAME = "Shopify-Storefront-Buyer-IP";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@
using GraphQL.Client.Http;
using GraphQL.Client.Serializer.Newtonsoft;

using Microsoft.AspNetCore.Http;

namespace Kentico.Xperience.Shopify.ShoppingCart
{
internal class GraphQLHttpClientFactory : IGraphQLHttpClientFactory
{
private readonly HttpClient httpClient;

public GraphQLHttpClientFactory(IHttpClientFactory httpClientFactory)
public GraphQLHttpClientFactory(IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor)
{
httpClient = httpClientFactory.CreateClient(ShopifyConstants.STOREFRONT_API_CLIENT_NAME);
string? buyerIP = httpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString();

if (!string.IsNullOrEmpty(buyerIP))
{
httpClient.DefaultRequestHeaders.Add(ShopifyConstants.STOREFRONT_API_BUYER_IP_NAME, buyerIP);
}
}

/// <inheritdoc/>
Expand Down
12 changes: 10 additions & 2 deletions src/Kentico.Xperience.Shopify/ShoppingCart/IShoppingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,20 @@ public interface IShoppingService
Task<CartOperationResult> UpdateCartItem(ShoppingCartItemParameters parameters);


/// <summary>
/// Remove all shopping cart items of specific product variant.
/// </summary>
/// <param name="variantGraphQLId">Product variant graphQL ID.</param>
/// <returns><see cref="CartOperationResult"/> with updated shopping cart if operation was successful.</returns>
Task<CartOperationResult> RemoveProductVariantFromCart(string variantGraphQLId);


/// <summary>
/// Remove shopping cart item.
/// </summary>
/// <param name="merchandiseId">Shopify product variant ID</param>
/// <param name="cartItemId">Shopify shopping cart item ID</param>
/// <returns><see cref="CartOperationResult"/> with updated shopping cart if operation was successful.</returns>
Task<CartOperationResult> RemoveCartItem(string merchandiseId);
Task<CartOperationResult> RemoveCartItem(string cartItemId);


/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ public class ShoppingCartItemParameters
public string MerchandiseID { get; set; } = string.Empty;


/// <summary>
/// ID of the shopping cart item if item was already in shopping cart.
/// If the cart item is new, the value will be empty string.
/// </summary>
public string ShoppingCartItemID { get; set; } = string.Empty;


/// <summary>
/// Country code for the item.
/// </summary>
Expand Down
43 changes: 39 additions & 4 deletions src/Kentico.Xperience.Shopify/ShoppingCart/ShoppingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@ public async Task<CartOperationResult> UpdateCartItem(ShoppingCartItemParameters
{
var cart = await GetCurrentShoppingCart();

var cartItemToUpdate = cart?.Items.FirstOrDefault(x => x.ShopifyCartItemId == parameters.MerchandiseID);
var cartItemToUpdate = cart?.Items.FirstOrDefault(x => x.ShopifyCartItemId.Equals(parameters.ShoppingCartItemID, StringComparison.Ordinal));
if (cart == null || cartItemToUpdate == null)
{
return await AddItemToCart(parameters);
}
if (cartItemToUpdate.Quantity == parameters.Quantity)
{
return new CartOperationResult(cart, true);
}

int quantity = Math.Max(parameters.Quantity, 0);
var result = await UpdateCartItemInternal(cart.CartId, cartItemToUpdate, quantity);
Expand All @@ -62,15 +66,46 @@ public async Task<CartOperationResult> UpdateCartItem(ShoppingCartItemParameters
}


public async Task<CartOperationResult> RemoveCartItem(string merchandiseId)
public async Task<CartOperationResult> RemoveProductVariantFromCart(string variantGraphQLId)
{
var cart = await GetCurrentShoppingCart();
if (cart == null)
{
return new CartOperationResult(null, true);
}

var shopifyCartLines = cart.Items.Where(x => x.VariantGraphQLId.Equals(variantGraphQLId, StringComparison.Ordinal));
bool success = true;
CartOperationResult? result = null;
martinkyjac marked this conversation as resolved.
Show resolved Hide resolved

foreach (var shopifyCartLine in shopifyCartLines)
{
result = await RemoveCartItem(shopifyCartLine.ShopifyCartItemId);
if (!result.Success)
{
success = false;
}
}

// There was no cart item with given variant graphQL ID
if (result == null)
{
return new CartOperationResult(cart, success);
}

return new CartOperationResult(result.Cart, success);
}


public async Task<CartOperationResult> RemoveCartItem(string cartItemId)
{
var cart = await GetCurrentShoppingCart();
if (cart == null)
{
return new CartOperationResult(null, true);
}

var shopifyCartLine = cart.Items.FirstOrDefault(x => x.VariantGraphQLId == merchandiseId);
var shopifyCartLine = cart.Items.FirstOrDefault(x => x.ShopifyCartItemId.Equals(cartItemId, StringComparison.Ordinal));
if (shopifyCartLine == null)
{
return new CartOperationResult(cart, true);
Expand Down Expand Up @@ -166,7 +201,7 @@ public async Task<CartOperationResult> AddItemToCart(ShoppingCartItemParameters
StoreCartToCookiesAndSession(cart.CartId);
}

var addedItem = cart.Items.FirstOrDefault(x => x.VariantGraphQLId == parameters.MerchandiseID);
var addedItem = cart.Items.FirstOrDefault(x => x.VariantGraphQLId.Equals(parameters.MerchandiseID));
activityLogger.LogProductAddedToShoppingCartActivity(addedItem, parameters.Quantity);
}
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected IEnumerable<Guid> OrderItemsByShopify(IEnumerable<IContentItemBase> co
{
foreach (var shopifyObject in shopifyObjects.Where(x => x.Id.HasValue))
{
var contentItem = contentItems.FirstOrDefault(x => x.ShopifyObjectID == (shopifyObject.Id?.ToString() ?? string.Empty));
var contentItem = contentItems.FirstOrDefault(x => x.ShopifyObjectID.Equals(shopifyObject.Id?.ToString() ?? string.Empty, StringComparison.Ordinal));
yield return contentItem?.SystemFields.ContentItemGUID ?? Guid.Empty;
}
}
Expand Down
Loading