From cecdc40ac1e45298d0365feb5c5eb062ba8e4309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Fri, 15 Sep 2017 13:54:48 +0200 Subject: [PATCH] Fix Authentication issue in Locations.api Created input in user campaigns view to update the user's location --- docker-compose.override.yml | 2 + .../Location/Locations.API/Startup.cs | 19 ++++-- src/Web/WebMVC/AppSettings.cs | 1 + .../WebMVC/Controllers/CampaignsController.cs | 35 +++++++--- src/Web/WebMVC/Infrastructure/API.cs | 8 +++ src/Web/WebMVC/Models/LocationDTO.cs | 13 ++++ src/Web/WebMVC/Services/ILocationService.cs | 10 +++ src/Web/WebMVC/Services/LocationService.cs | 49 +++++++++++++ src/Web/WebMVC/Startup.cs | 2 + .../Annotations/LatitudeCoordinate.cs | 22 ++++++ .../Annotations/LongitudeCoordinate.cs | 22 ++++++ .../CampaignViewModel/CampaignViewModel.cs | 8 +++ src/Web/WebMVC/Views/Campaigns/Index.cshtml | 68 ++++++++++++++----- .../css/campaigns/campaigns.component.css | 11 +++ 14 files changed, 239 insertions(+), 31 deletions(-) create mode 100644 src/Web/WebMVC/Models/LocationDTO.cs create mode 100644 src/Web/WebMVC/Services/ILocationService.cs create mode 100644 src/Web/WebMVC/Services/LocationService.cs create mode 100644 src/Web/WebMVC/ViewModels/Annotations/LatitudeCoordinate.cs create mode 100644 src/Web/WebMVC/ViewModels/Annotations/LongitudeCoordinate.cs diff --git a/docker-compose.override.yml b/docker-compose.override.yml index f918eee3c..892db9b52 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -102,6 +102,7 @@ services: - IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103 - MarketingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110 + - LocationsUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5109 - CatalogUrlHC=http://catalog.api/hc - OrderingUrlHC=http://ordering.api/hc - IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. @@ -118,6 +119,7 @@ services: - CatalogUrl=http://catalog.api - OrderingUrl=http://ordering.api - BasketUrl=http://basket.api + - LocationsUrl=http://locations.api - IdentityUrl=http://10.0.75.1:5105 - MarketingUrl=http://marketing.api #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser. #Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser. diff --git a/src/Services/Location/Locations.API/Startup.cs b/src/Services/Location/Locations.API/Startup.cs index b2147cbd1..39e6a20bd 100644 --- a/src/Services/Location/Locations.API/Startup.cs +++ b/src/Services/Location/Locations.API/Startup.cs @@ -1,5 +1,6 @@ using Autofac; using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -169,13 +170,17 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API // prevent from mapping "sub" claim to nameidentifier. JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - services.AddAuthentication() - .AddJwtBearer(options => - { - options.Authority = Configuration.GetValue("IdentityUrl"); - options.Audience = "locations"; - options.RequireHttpsMetadata = false; - }); + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.Authority = Configuration.GetValue("IdentityUrl"); + options.Audience = "locations"; + options.RequireHttpsMetadata = false; + }); } protected virtual void ConfigureAuth(IApplicationBuilder app) diff --git a/src/Web/WebMVC/AppSettings.cs b/src/Web/WebMVC/AppSettings.cs index 42946262f..42100ab62 100644 --- a/src/Web/WebMVC/AppSettings.cs +++ b/src/Web/WebMVC/AppSettings.cs @@ -12,6 +12,7 @@ namespace Microsoft.eShopOnContainers.WebMVC public string OrderingUrl { get; set; } public string BasketUrl { get; set; } public string MarketingUrl { get; set; } + public string LocationsUrl { get; set; } public bool ActivateCampaignDetailFunction { get; set; } public Logging Logging { get; set; } public bool UseCustomizationData { get; set; } diff --git a/src/Web/WebMVC/Controllers/CampaignsController.cs b/src/Web/WebMVC/Controllers/CampaignsController.cs index cbb08e051..058012d26 100644 --- a/src/Web/WebMVC/Controllers/CampaignsController.cs +++ b/src/Web/WebMVC/Controllers/CampaignsController.cs @@ -1,28 +1,29 @@ -using Microsoft.EntityFrameworkCore.Query.Internal; -using WebMVC.ViewModels; - namespace Microsoft.eShopOnContainers.WebMVC.Controllers { using AspNetCore.Authorization; using AspNetCore.Mvc; + using global::WebMVC.Models; + using global::WebMVC.Services; + using global::WebMVC.ViewModels; + using Microsoft.Extensions.Options; using Services; - using ViewModels; - using System.Threading.Tasks; using System; + using System.Threading.Tasks; + using ViewModels; using ViewModels.Pagination; - using global::WebMVC.ViewModels; - using Microsoft.Extensions.Options; [Authorize] public class CampaignsController : Controller { private readonly ICampaignService _campaignService; + private readonly ILocationService _locationService; private readonly AppSettings _settings; - public CampaignsController(ICampaignService campaignService, IOptionsSnapshot settings) + public CampaignsController(ICampaignService campaignService, ILocationService locationService, IOptionsSnapshot settings) { _campaignService = campaignService; _settings = settings.Value; + _locationService = locationService; } public async Task Index(int page = 0, int pageSize = 10) @@ -76,5 +77,23 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers return View(campaign); } + + [HttpPost] + public async Task CreateNewUserLocation(CampaignViewModel model) + { + if (ModelState.IsValid) + { + var location = new LocationDTO() + { + Longitude = model.Lon, + Latitude = model.Lat + }; + await _locationService.CreateOrUpdateUserLocation(location); + + return RedirectToAction(nameof(Index)); + } + + return View(nameof(Index), model); + } } } \ No newline at end of file diff --git a/src/Web/WebMVC/Infrastructure/API.cs b/src/Web/WebMVC/Infrastructure/API.cs index b29afefa8..edb2c5ed5 100644 --- a/src/Web/WebMVC/Infrastructure/API.cs +++ b/src/Web/WebMVC/Infrastructure/API.cs @@ -94,5 +94,13 @@ namespace WebMVC.Infrastructure return $"{baseUri}{id}"; } } + + public static class Locations + { + public static string CreateOrUpdateUserLocation(string baseUri) + { + return baseUri; + } + } } } \ No newline at end of file diff --git a/src/Web/WebMVC/Models/LocationDTO.cs b/src/Web/WebMVC/Models/LocationDTO.cs new file mode 100644 index 000000000..88169c421 --- /dev/null +++ b/src/Web/WebMVC/Models/LocationDTO.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebMVC.Models +{ + public class LocationDTO + { + public double Longitude { get; set; } + public double Latitude { get; set; } + } +} diff --git a/src/Web/WebMVC/Services/ILocationService.cs b/src/Web/WebMVC/Services/ILocationService.cs new file mode 100644 index 000000000..ac2295e10 --- /dev/null +++ b/src/Web/WebMVC/Services/ILocationService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using WebMVC.Models; + +namespace WebMVC.Services +{ + public interface ILocationService + { + Task CreateOrUpdateUserLocation(LocationDTO location); + } +} diff --git a/src/Web/WebMVC/Services/LocationService.cs b/src/Web/WebMVC/Services/LocationService.cs new file mode 100644 index 000000000..652484f4b --- /dev/null +++ b/src/Web/WebMVC/Services/LocationService.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http; +using Microsoft.eShopOnContainers.WebMVC; +using Microsoft.eShopOnContainers.WebMVC.Services; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; +using WebMVC.Infrastructure; +using WebMVC.Models; + +namespace WebMVC.Services +{ + public class LocationService : ILocationService + { + private readonly IOptionsSnapshot _settings; + private readonly IHttpClient _apiClient; + private readonly ILogger _logger; + private readonly string _remoteServiceBaseUrl; + private readonly IHttpContextAccessor _httpContextAccesor; + + public LocationService(IOptionsSnapshot settings, IHttpClient httpClient, + ILogger logger, IHttpContextAccessor httpContextAccesor) + { + _settings = settings; + _apiClient = httpClient; + _logger = logger; + + _remoteServiceBaseUrl = $"{_settings.Value.LocationsUrl}/api/v1/locations/"; + _httpContextAccesor = httpContextAccesor ?? throw new ArgumentNullException(nameof(httpContextAccesor)); + } + + public async Task CreateOrUpdateUserLocation(LocationDTO location) + { + var createOrUpdateUserLocationUri = API.Locations.CreateOrUpdateUserLocation(_remoteServiceBaseUrl); + + var authorizationToken = await GetUserTokenAsync(); + var response = await _apiClient.PostAsync(createOrUpdateUserLocationUri, location, authorizationToken); + response.EnsureSuccessStatusCode(); + } + + private async Task GetUserTokenAsync() + { + var context = _httpContextAccesor.HttpContext; + return await context.GetTokenAsync("access_token"); + } + } +} diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index 58fae38f9..8ccdf997d 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Logging; using System; using System.IdentityModel.Tokens.Jwt; using WebMVC.Infrastructure; +using WebMVC.Services; namespace Microsoft.eShopOnContainers.WebMVC { @@ -64,6 +65,7 @@ namespace Microsoft.eShopOnContainers.WebMVC services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient, IdentityParser>(); if (Configuration.GetValue("UseResilientHttp") == bool.TrueString) diff --git a/src/Web/WebMVC/ViewModels/Annotations/LatitudeCoordinate.cs b/src/Web/WebMVC/ViewModels/Annotations/LatitudeCoordinate.cs new file mode 100644 index 000000000..5731b8895 --- /dev/null +++ b/src/Web/WebMVC/ViewModels/Annotations/LatitudeCoordinate.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace WebMVC.ViewModels.Annotations +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] + public class LatitudeCoordinate : ValidationAttribute + { + protected override ValidationResult + IsValid(object value, ValidationContext validationContext) + { + double coordinate; + if (!double.TryParse(value.ToString(), out coordinate) || (coordinate < -90 || coordinate > 90)) + { + return new ValidationResult + ("Latitude must be between -90 and 90 degrees inclusive."); + } + + return ValidationResult.Success; + } + } +} diff --git a/src/Web/WebMVC/ViewModels/Annotations/LongitudeCoordinate.cs b/src/Web/WebMVC/ViewModels/Annotations/LongitudeCoordinate.cs new file mode 100644 index 000000000..de2266711 --- /dev/null +++ b/src/Web/WebMVC/ViewModels/Annotations/LongitudeCoordinate.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace WebMVC.ViewModels.Annotations +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] + public class LongitudeCoordinate : ValidationAttribute + { + protected override ValidationResult + IsValid(object value, ValidationContext validationContext) + { + double coordinate; + if (!double.TryParse(value.ToString(), out coordinate) || (coordinate < -180 || coordinate > 180)) + { + return new ValidationResult + ("Longitude must be between -180 and 180 degrees inclusive."); + } + + return ValidationResult.Success; + } + } +} diff --git a/src/Web/WebMVC/ViewModels/CampaignViewModel/CampaignViewModel.cs b/src/Web/WebMVC/ViewModels/CampaignViewModel/CampaignViewModel.cs index 67fdf9cbb..80018202f 100644 --- a/src/Web/WebMVC/ViewModels/CampaignViewModel/CampaignViewModel.cs +++ b/src/Web/WebMVC/ViewModels/CampaignViewModel/CampaignViewModel.cs @@ -3,10 +3,18 @@ using System.Collections.Generic; using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination; + using WebMVC.ViewModels.Annotations; + using Newtonsoft.Json; + using System.ComponentModel.DataAnnotations; public class CampaignViewModel { public IEnumerable CampaignItems { get; set; } public PaginationInfo PaginationInfo { get; set; } + + [LongitudeCoordinate, Required] + public double Lon { get; set; } = -122.315752; + [LatitudeCoordinate, Required] + public double Lat { get; set; } = 47.604610; } } \ No newline at end of file diff --git a/src/Web/WebMVC/Views/Campaigns/Index.cshtml b/src/Web/WebMVC/Views/Campaigns/Index.cshtml index bca288ed6..96cbec7c9 100644 --- a/src/Web/WebMVC/Views/Campaigns/Index.cshtml +++ b/src/Web/WebMVC/Views/Campaigns/Index.cshtml @@ -13,23 +13,59 @@ new Header() { Controller = "Catalog", Text = "Back to catalog" } })
- @if(Model != null && Model.CampaignItems.Any()) - { -
- @foreach (var catalogItem in Model.CampaignItems) +
+
+ + @if (!ViewData.ModelState.IsValid) { -
- @Html.Partial("_campaign", catalogItem) +
+ @Html.ValidationSummary(false) +
+ } + +
+
+ UPDATE USER LOCATION +
+ +
+ +
+
Lon
+ +
+ +
+
Lat
+ +
+ +
+ +
+
+
+
+
+ @if (Model != null && Model.CampaignItems !=null && Model.CampaignItems.Any()) + { +
+ @foreach (var catalogItem in Model.CampaignItems) + { +
+ @Html.Partial("_campaign", catalogItem) +
+ } +
+ + @Html.Partial("_pagination", Model.PaginationInfo) + } + else + { +
+ THERE ARE NO CAMPAIGNS
} -
- - @Html.Partial("_pagination", Model.PaginationInfo) - } - else - { -
- THERE ARE NO CAMPAIGNS -
- }
+ + diff --git a/src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css b/src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css index f547f56e4..af3c6a899 100644 --- a/src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css +++ b/src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css @@ -72,6 +72,17 @@ transition: all 0.35s; width: 80%; } + +.esh-campaigns-form-button { + background-color: #83D01B; + border: none; + color: #FFFFFF; + cursor: pointer; + font-size: 1rem; + transition: all 0.35s; + width: 80%; +} + .esh-campaigns-button.is-disabled { opacity: .5; pointer-events: none;