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 8 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 @@ -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
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></returns>
martinkyjac marked this conversation as resolved.
Show resolved Hide resolved
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
40 changes: 37 additions & 3 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 == parameters.ShoppingCartItemID);
martinkyjac marked this conversation as resolved.
Show resolved Hide resolved
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,45 @@ 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 == variantGraphQLId);
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 givent variant graphQL ID
martinkyjac marked this conversation as resolved.
Show resolved Hide resolved
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 == cartItemId);
if (shopifyCartLine == null)
{
return new CartOperationResult(cart, true);
Expand Down
14 changes: 7 additions & 7 deletions src/Kentico.Xperience.Shopify/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,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 @@ -80,11 +80,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
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ namespace Kentico.Xperience.Shopify.Tests.Mocks
{
internal class GraphQLHttpClientMock : IGraphQLClient
{
private ShoppingCartRepository CartRepository => new();
private readonly IShoppingCartRepository cartRepository;

private DiscountCodesRepository DiscountCodesRepository => new();

public GraphQLHttpClientMock()
public GraphQLHttpClientMock(IShoppingCartRepository cartRepository)
{

this.cartRepository = cartRepository;
}

public IObservable<GraphQLResponse<TResponse>> CreateSubscriptionStream<TResponse>(GraphQLRequest request) => throw new NotImplementedException();
Expand Down Expand Up @@ -60,7 +61,7 @@ private UpdateDiscountCodesResponse HandleUpdateDiscountCodes(GraphQLRequest req
string[]? discountCodes = request.GetProperty<string[]>("discountCodes");

var availableDiscountCodes = DiscountCodesRepository.DiscountCodes;
var cart = CartRepository.Carts.FirstOrDefault(x => x.Id == cartId);
var cart = cartRepository.Carts.FirstOrDefault(x => x.Id == cartId);

if (cart != null && discountCodes != null)
{
Expand All @@ -83,7 +84,7 @@ private UpdateDiscountCodesResponse HandleUpdateDiscountCodes(GraphQLRequest req

private CreateCartResponse HandleCreateCart(GraphQLRequest request)
{
var cart = CartRepository.Carts.First();
var cart = cartRepository.Carts.First();
var createCartParams = request.GetProperty<CreateCartParameters>("CartInput");

if (cart.Lines?.Edges is not null && createCartParams?.Lines is not null)
Expand Down Expand Up @@ -131,7 +132,7 @@ public Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(GraphQLRequest
CartLinesUpdate = new CartResponseBase()
{
UserErrors = [],
Cart = CartRepository.Carts.First()
Cart = cartRepository.Carts.First()
}
};
var cartLines = cartResponse.CartLinesUpdate.Cart.Lines?.Edges;
Expand Down Expand Up @@ -159,7 +160,7 @@ public Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(GraphQLRequest
{
string? cartId = request.GetProperty("cartId")?.ToString();

var cart = CartRepository.Carts.FirstOrDefault(x => x.Id == cartId);
var cart = cartRepository.Carts.FirstOrDefault(x => x.Id == cartId);
if (cart?.Id is null || cartId is null || cart.Id != cartId)
{
return null;
Expand All @@ -176,7 +177,7 @@ private RemoveCartItemResponse HandleCartItemRemove(GraphQLRequest request)
string? cartId = request.GetProperty("CartId")?.ToString();
string[]? cartLinesToRemove = (string[]?)Convert.ChangeType(request.GetProperty("LineIds"), typeof(string[]));

var cart = CartRepository.Carts.FirstOrDefault(cart => cart.Id == cartId);
var cart = cartRepository.Carts.FirstOrDefault(cart => cart.Id == cartId);

var updatedCartLines = cart?.Lines?.Edges.Where(x => !cartLinesToRemove?.Contains(x.Node.Id) ?? false) ?? [];
if (cart?.Lines is not null)
Expand Down Expand Up @@ -231,7 +232,7 @@ private AddToCartResponse HandleAddToCart(GraphQLRequest request)
}
};

var cart = CartRepository.Carts.FirstOrDefault(cart => cart.Id == cartId);
var cart = cartRepository.Carts.FirstOrDefault(cart => cart.Id == cartId);
var updatedCartLines = cart?.Lines?.Edges.Append(newLineItem) ?? [];
if (cart?.Lines is not null)
{
Expand Down
Loading