diff --git a/docker-compose.yml b/docker-compose.yml index 679c7e03a..0e490c9b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,7 +66,10 @@ services: - ordering.data ordering.data: - image: eshop/ordering.data.sqlserver.linux + image: microsoft/mssql-server-linux + environment: + - SA_PASSWORD=Pass@word + - ACCEPT_EULA=Y ports: - "5432:1433" diff --git a/src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs b/src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs index 8c96f16fd..21e9a7295 100644 --- a/src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs +++ b/src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs @@ -61,6 +61,55 @@ namespace eShopOnContainers.Identity.Configuration "orders", "basket" } + }, + new Client + { + ClientId = "xamarin", + ClientName = "eShop Xamarin OpenId Client", + AllowedGrantTypes = GrantTypes.Implicit, + AllowAccessTokensViaBrowser = true, + + RedirectUris = { "http://localhost:5003/callback.html" }, + PostLogoutRedirectUris = { "http://localhost:5003/index.html" }, + AllowedCorsOrigins = { "http://localhost:5003" }, + + AllowedScopes = + { + StandardScopes.OpenId.Name, + StandardScopes.Profile.Name, + "orders", + "basket" + } + }, + new Client + { + ClientId = "mvc", + ClientName = "MVC Client", + ClientSecrets = new List + { + new Secret("secret".Sha256()) + }, + ClientUri = "http://localhost:2114", + + AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, + + RedirectUris = new List + { + "http://localhost:2114/signin-oidc" + }, + PostLogoutRedirectUris = new List + { + "http://localhost:2114/" + }, + LogoutUri = "http://localhost:2114/signout-oidc", + AllowedScopes = new List + { + StandardScopes.OpenId.Name, + StandardScopes.Profile.Name, + StandardScopes.OfflineAccess.Name, + "orders", + "basket", + }, } }; } diff --git a/src/Services/Identity/eShopOnContainers.Identity/Extensions/PrincipalExtensions.cs b/src/Services/Identity/eShopOnContainers.Identity/Extensions/PrincipalExtensions.cs new file mode 100644 index 000000000..a1247cb92 --- /dev/null +++ b/src/Services/Identity/eShopOnContainers.Identity/Extensions/PrincipalExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace eShopOnContainers.Identity.Extensions +{ + //public static class PrincipalExtensions + //{ + // public static string GetSubjectId(this IPrincipal principal) + // { + // return principal.Identity.GetSubjectId(); + // } + //} +} diff --git a/src/Services/Identity/eShopOnContainers.Identity/Services/ProfileService.cs b/src/Services/Identity/eShopOnContainers.Identity/Services/ProfileService.cs new file mode 100644 index 000000000..e30a6ca7e --- /dev/null +++ b/src/Services/Identity/eShopOnContainers.Identity/Services/ProfileService.cs @@ -0,0 +1,127 @@ +using IdentityServer4.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Identity; +using eShopOnContainers.Identity.Models; +using System.Security.Claims; +using IdentityModel; + +namespace eShopOnContainers.Identity.Services +{ + public class ProfileService : IProfileService + { + private readonly UserManager _userManager; + + public ProfileService(UserManager userManager) + { + _userManager = userManager; + } + + async public Task GetProfileDataAsync(ProfileDataRequestContext context) + { + var subject = context.Subject; + if (subject == null) throw new ArgumentNullException(nameof(context.Subject)); + + var subjectId = subject.Claims.Where(x => x.Type == "sub").FirstOrDefault().Value; + + var user = await _userManager.FindByIdAsync(subjectId); + if (user == null) + throw new ArgumentException("Invalid subject identifier"); + + var claims = GetClaimsFromUser(user); + context.IssuedClaims = claims.ToList(); + } + + async public Task IsActiveAsync(IsActiveContext context) + { + var subject = context.Subject; + if (subject == null) throw new ArgumentNullException(nameof(context.Subject)); + + var subjectId = subject.Claims.Where(x => x.Type == "sub").FirstOrDefault().Value; + var user = await _userManager.FindByIdAsync(subjectId); + + context.IsActive = false; + + if (user != null) + { + if (_userManager.SupportsUserSecurityStamp) + { + var security_stamp = subject.Claims.Where(c => c.Type == "security_stamp").Select(c => c.Value).SingleOrDefault(); + if (security_stamp != null) + { + var db_security_stamp = await _userManager.GetSecurityStampAsync(user); + if (db_security_stamp != security_stamp) + return; + } + } + + context.IsActive = + !user.LockoutEnabled || + !user.LockoutEnd.HasValue || + user.LockoutEnd <= DateTime.Now; + } + } + + private IEnumerable GetClaimsFromUser(ApplicationUser user) + { + var claims = new List + { + new Claim(JwtClaimTypes.Subject, user.Id), + new Claim(JwtClaimTypes.PreferredUserName, user.UserName) + }; + + if (!string.IsNullOrWhiteSpace(user.Name)) + claims.Add(new Claim("name", user.Name)); + + if (!string.IsNullOrWhiteSpace(user.Name)) + claims.Add(new Claim("last_name", user.LastName)); + + if (!string.IsNullOrWhiteSpace(user.CardNumber)) + claims.Add(new Claim("card_number", user.CardNumber)); + + if (!string.IsNullOrWhiteSpace(user.CardHolderName)) + claims.Add(new Claim("card_holder", user.CardHolderName)); + + if (!string.IsNullOrWhiteSpace(user.SecurityNumber)) + claims.Add(new Claim("card_security_number", user.SecurityNumber)); + + if (!string.IsNullOrWhiteSpace(user.City)) + claims.Add(new Claim("address_city", user.City)); + + if (!string.IsNullOrWhiteSpace(user.Country)) + claims.Add(new Claim("address_country", user.Country)); + + if (!string.IsNullOrWhiteSpace(user.State)) + claims.Add(new Claim("address_state", user.State)); + + if (!string.IsNullOrWhiteSpace(user.Street)) + claims.Add(new Claim("address_street", user.Street)); + + if (!string.IsNullOrWhiteSpace(user.ZipCode)) + claims.Add(new Claim("address_zip_code", user.ZipCode)); + + if (_userManager.SupportsUserEmail) + { + claims.AddRange(new[] + { + new Claim(JwtClaimTypes.Email, user.Email), + new Claim(JwtClaimTypes.EmailVerified, user.EmailConfirmed ? "true" : "false", ClaimValueTypes.Boolean) + }); + } + + if (_userManager.SupportsUserPhoneNumber && !string.IsNullOrWhiteSpace(user.PhoneNumber)) + { + claims.AddRange(new[] + { + new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber), + new Claim(JwtClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed ? "true" : "false", ClaimValueTypes.Boolean) + }); + } + + return claims; + } + } +} diff --git a/src/Services/Identity/eShopOnContainers.Identity/Startup.cs b/src/Services/Identity/eShopOnContainers.Identity/Startup.cs index ac442763b..50fe05010 100644 --- a/src/Services/Identity/eShopOnContainers.Identity/Startup.cs +++ b/src/Services/Identity/eShopOnContainers.Identity/Startup.cs @@ -13,6 +13,7 @@ using eShopOnContainers.Identity.Data; using eShopOnContainers.Identity.Models; using eShopOnContainers.Identity.Services; using eShopOnContainers.Identity.Configuration; +using IdentityServer4.Services; namespace eShopOnContainers.Identity { @@ -47,6 +48,7 @@ namespace eShopOnContainers.Identity services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); + services.AddMvc(); @@ -59,7 +61,8 @@ namespace eShopOnContainers.Identity .AddTemporarySigningCredential() .AddInMemoryScopes(Config.GetScopes()) .AddInMemoryClients(Config.GetClients()) - .AddAspNetIdentity(); + .AddAspNetIdentity() + .Services.AddTransient(); //Configuration Settings: services.AddOptions(); @@ -89,6 +92,7 @@ namespace eShopOnContainers.Identity // Adds IdentityServer app.UseIdentityServer(); + app.UseMvc(routes => { routes.MapRoute( diff --git a/src/Services/Identity/eShopOnContainers.Identity/appsettings.json b/src/Services/Identity/eShopOnContainers.Identity/appsettings.json index 7f5069bcc..4b630798d 100644 --- a/src/Services/Identity/eShopOnContainers.Identity/appsettings.json +++ b/src/Services/Identity/eShopOnContainers.Identity/appsettings.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=identity.data;Database=aspnet-Microsoft.eShopOnContainers.WebMVC;User Id=sa;Password=Pass@word" - //"DefaultConnection": "Server=127.0.0.1,5433;Database=aspnet-Microsoft.eShopOnContainers.WebMVC;User Id=sa;Password=Pass@word" + //"DefaultConnection": "Server=identity.data;Database=aspnet-Microsoft.eShopOnContainers.WebMVC;User Id=sa;Password=Pass@word" + "DefaultConnection": "Server=127.0.0.1,5433;Database=aspnet-Microsoft.eShopOnContainers.WebMVC;User Id=sa;Password=Pass@word" }, "ClientsCallBackUrls": { "Spa": "http://localhost:5003" diff --git a/src/Services/Ordering/Ordering.API/settings.json b/src/Services/Ordering/Ordering.API/settings.json index a95b8262f..5788e4939 100644 --- a/src/Services/Ordering/Ordering.API/settings.json +++ b/src/Services/Ordering/Ordering.API/settings.json @@ -1,3 +1,4 @@ { - "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;" + //"ConnectionString": "Server=tcp:ordering.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;" + "ConnectionString": "Server=tcp:127.0.0.1,5432;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;" } diff --git a/src/Web/WebMVC/Controllers/AccountController.cs b/src/Web/WebMVC/Controllers/AccountController.cs index a62a477eb..e835c9e88 100644 --- a/src/Web/WebMVC/Controllers/AccountController.cs +++ b/src/Web/WebMVC/Controllers/AccountController.cs @@ -4,438 +4,497 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.Extensions.Logging; using Microsoft.eShopOnContainers.WebMVC.Models; using Microsoft.eShopOnContainers.WebMVC.Models.AccountViewModels; using Microsoft.eShopOnContainers.WebMVC.Services; +using System.Net.Http; +using Newtonsoft.Json.Linq; +using Microsoft.eShopOnContainers.WebMVC.Extensions; namespace Microsoft.eShopOnContainers.WebMVC.Controllers { [Authorize] public class AccountController : Controller { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly ILogger _logger; - - public AccountController( - UserManager userManager, - SignInManager signInManager, - ILoggerFactory loggerFactory) + private readonly IIdentityParser _identityParser; + public AccountController(IIdentityParser identityParser) { - _userManager = userManager; - _signInManager = signInManager; - _logger = loggerFactory.CreateLogger(); + _identityParser = identityParser; } - // - // GET: /Account/Login - [HttpGet] - [AllowAnonymous] - public IActionResult Login(string returnUrl = null) + public ActionResult Index() { - ViewData["ReturnUrl"] = returnUrl; return View(); } - // - // POST: /Account/Login - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Login(LoginViewModel model, string returnUrl = null) + [Authorize] + public IActionResult SignIn(string returnUrl) { - ViewData["ReturnUrl"] = returnUrl; - if (ModelState.IsValid) + var user = User as ClaimsPrincipal; + + //TODO - Not retrieving AccessToken yet + var token = user.FindFirst("access_token"); + if (token != null) { - // This doesn't count login failures towards account lockout - // To enable password failures to trigger account lockout, set lockoutOnFailure: true - var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); - if (result.Succeeded) - { - _logger.LogInformation(1, "User logged in."); - return RedirectToLocal(returnUrl); - } - if (result.RequiresTwoFactor) - { - return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); - } - if (result.IsLockedOut) - { - _logger.LogWarning(2, "User account locked out."); - return View("Lockout"); - } - else - { - ModelState.AddModelError(string.Empty, "Invalid login attempt."); - return View(model); - } + ViewData["access_token"] = token.Value; } - // If we got this far, something failed, redisplay form - return View(model); + return Redirect("/"); } - // - // GET: /Account/Register - [HttpGet] - [AllowAnonymous] - public IActionResult Register(string returnUrl = null) + [Authorize] + public async Task CallApi() { - ViewData["ReturnUrl"] = returnUrl; - return View(); - } - - // - // POST: /Account/Register - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Register(RegisterViewModel model, string returnUrl = null) - { - ViewData["ReturnUrl"] = returnUrl; - if (ModelState.IsValid) - { - var user = new ApplicationUser - { - UserName = model.Email, - Email = model.Email, - CardHolderName = model.User.CardHolderName, - CardNumber = model.User.CardNumber, - CardType = model.User.CardType, - City = model.User.City, - Country = model.User.Country, - Expiration = model.User.Expiration, - LastName = model.User.LastName, - Name = model.User.Name, - Street = model.User.Street, - State = model.User.State, - ZipCode = model.User.ZipCode, - PhoneNumber = model.User.PhoneNumber, - SecurityNumber = model.User.SecurityNumber - }; - var result = await _userManager.CreateAsync(user, model.Password); - if (result.Succeeded) - { - // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 - // Send an email with this link - //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); - //var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); - //await _emailSender.SendEmailAsync(model.Email, "Confirm your account", - // $"Please confirm your account by clicking this link: link"); - await _signInManager.SignInAsync(user, isPersistent: false); - _logger.LogInformation(3, "User created a new account with password."); - return RedirectToLocal(returnUrl); - } - AddErrors(result); - } - - // If we got this far, something failed, redisplay form - return View(model); - } - - // - // POST: /Account/LogOff - [HttpPost] - [ValidateAntiForgeryToken] - public async Task LogOff() - { - await _signInManager.SignOutAsync(); - _logger.LogInformation(4, "User logged out."); - return RedirectToAction(nameof(CatalogController.Index), "Catalog"); - } - - // - // POST: /Account/ExternalLogin - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public IActionResult ExternalLogin(string provider, string returnUrl = null) - { - // Request a redirect to the external login provider. - var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); - var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); - return Challenge(properties, provider); - } - - // - // GET: /Account/ExternalLoginCallback - [HttpGet] - [AllowAnonymous] - public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null) - { - if (remoteError != null) - { - ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}"); - return View(nameof(Login)); - } - var info = await _signInManager.GetExternalLoginInfoAsync(); - if (info == null) - { - return RedirectToAction(nameof(Login)); - } - - // Sign in the user with this external login provider if the user already has a login. - var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); - if (result.Succeeded) - { - _logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider); - return RedirectToLocal(returnUrl); - } - if (result.RequiresTwoFactor) - { - return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl }); - } - if (result.IsLockedOut) - { - return View("Lockout"); - } - else - { - // If the user does not have an account, then ask the user to create an account. - ViewData["ReturnUrl"] = returnUrl; - ViewData["LoginProvider"] = info.LoginProvider; - var email = info.Principal.FindFirstValue(ClaimTypes.Email); - return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); - } - } - - // - // POST: /Account/ExternalLoginConfirmation - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null) - { - if (ModelState.IsValid) - { - // Get the information about the user from the external login provider - var info = await _signInManager.GetExternalLoginInfoAsync(); - if (info == null) - { - return View("ExternalLoginFailure"); - } - var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; - var result = await _userManager.CreateAsync(user); - if (result.Succeeded) - { - result = await _userManager.AddLoginAsync(user, info); - if (result.Succeeded) - { - await _signInManager.SignInAsync(user, isPersistent: false); - _logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider); - return RedirectToLocal(returnUrl); - } - } - AddErrors(result); - } - - ViewData["ReturnUrl"] = returnUrl; - return View(model); - } - - // GET: /Account/ConfirmEmail - [HttpGet] - [AllowAnonymous] - public async Task ConfirmEmail(string userId, string code) - { - if (userId == null || code == null) - { - return View("Error"); - } - var user = await _userManager.FindByIdAsync(userId); - if (user == null) - { - return View("Error"); - } - var result = await _userManager.ConfirmEmailAsync(user, code); - return View(result.Succeeded ? "ConfirmEmail" : "Error"); - } - - // - // GET: /Account/ForgotPassword - [HttpGet] - [AllowAnonymous] - public IActionResult ForgotPassword() - { - return View(); - } - - // - // POST: /Account/ForgotPassword - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ForgotPassword(ForgotPasswordViewModel model) - { - if (ModelState.IsValid) - { - var user = await _userManager.FindByNameAsync(model.Email); - if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) - { - // Don't reveal that the user does not exist or is not confirmed - return View("ForgotPasswordConfirmation"); - } - - // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 - // Send an email with this link - //var code = await _userManager.GeneratePasswordResetTokenAsync(user); - //var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); - //await _emailSender.SendEmailAsync(model.Email, "Reset Password", - // $"Please reset your password by clicking here: link"); - //return View("ForgotPasswordConfirmation"); - } + var token = (User as ClaimsPrincipal).FindFirst("access_token").Value; - // If we got this far, something failed, redisplay form - return View(model); - } + var client = new HttpClient(); + client.SetBearerToken(token); - // - // GET: /Account/ForgotPasswordConfirmation - [HttpGet] - [AllowAnonymous] - public IActionResult ForgotPasswordConfirmation() - { - return View(); - } + var result = await client.GetStringAsync("apiuri"); + ViewBag.Json = JArray.Parse(result.ToString()); - // - // GET: /Account/ResetPassword - [HttpGet] - [AllowAnonymous] - public IActionResult ResetPassword(string code = null) - { - return code == null ? View("Error") : View(); - } - - // - // POST: /Account/ResetPassword - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task ResetPassword(ResetPasswordViewModel model) - { - if (!ModelState.IsValid) - { - return View(model); - } - var user = await _userManager.FindByNameAsync(model.Email); - if (user == null) - { - // Don't reveal that the user does not exist - return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); - } - var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); - if (result.Succeeded) - { - return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); - } - AddErrors(result); return View(); } - // - // GET: /Account/ResetPasswordConfirmation - [HttpGet] - [AllowAnonymous] - public IActionResult ResetPasswordConfirmation() - { - return View(); - } - // - // GET: /Account/SendCode - [HttpGet] - [AllowAnonymous] - public async Task SendCode(string returnUrl = null, bool rememberMe = false) + public async Task Signout() { - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); - if (user == null) - { - return View("Error"); - } - var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user); - var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); - return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); + await Request.HttpContext.Authentication.SignOutAsync("oidc"); + return Redirect("/"); } - // - // GET: /Account/VerifyCode - [HttpGet] - [AllowAnonymous] - public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + public async Task SignoutCleanup(string sid) { - // Require that the user has already logged in via username/password or external login - var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); - if (user == null) - { - return View("Error"); - } - return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); - } - - // - // POST: /Account/VerifyCode - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task VerifyCode(VerifyCodeViewModel model) - { - if (!ModelState.IsValid) - { - return View(model); - } - - // The following code protects for brute force attacks against the two factor codes. - // If a user enters incorrect codes for a specified amount of time then the user account - // will be locked out for a specified amount of time. - var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); - if (result.Succeeded) - { - return RedirectToLocal(model.ReturnUrl); - } - if (result.IsLockedOut) - { - _logger.LogWarning(7, "User account locked out."); - return View("Lockout"); - } - else - { - ModelState.AddModelError(string.Empty, "Invalid code."); - return View(model); - } - } - - #region Helpers - - private void AddErrors(IdentityResult result) - { - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } - } - - private Task GetCurrentUserAsync() - { - return _userManager.GetUserAsync(HttpContext.User); - } - - private IActionResult RedirectToLocal(string returnUrl) - { - if (Url.IsLocalUrl(returnUrl)) - { - return Redirect(returnUrl); - } - else + var cp = (ClaimsPrincipal)User; + var sidClaim = cp.FindFirst("sid"); + if (sidClaim != null && sidClaim.Value == sid) { - return RedirectToAction(nameof(CatalogController.Index), "Catalog"); + await Request.HttpContext.Authentication.SignOutAsync("Cookies"); } } - #endregion + //private readonly UserManager _userManager; + //private readonly SignInManager _signInManager; + //private readonly ILogger _logger; + + //public AccountController( + // UserManager userManager, + // SignInManager signInManager, + // ILoggerFactory loggerFactory) + //{ + // _userManager = userManager; + // _signInManager = signInManager; + // _logger = loggerFactory.CreateLogger(); + //} + + //// + //// GET: /Account/Login + //[HttpGet] + //[AllowAnonymous] + //public IActionResult Login(string returnUrl = null) + //{ + // ViewData["ReturnUrl"] = returnUrl; + // return View(); + //} + + //// + //// POST: /Account/Login + //[HttpPost] + //[AllowAnonymous] + //[ValidateAntiForgeryToken] + //public async Task Login(LoginViewModel model, string returnUrl = null) + //{ + // ViewData["ReturnUrl"] = returnUrl; + // if (ModelState.IsValid) + // { + // // This doesn't count login failures towards account lockout + // // To enable password failures to trigger account lockout, set lockoutOnFailure: true + // var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); + // if (result.Succeeded) + // { + // _logger.LogInformation(1, "User logged in."); + // return RedirectToLocal(returnUrl); + // } + // if (result.RequiresTwoFactor) + // { + // return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); + // } + // if (result.IsLockedOut) + // { + // _logger.LogWarning(2, "User account locked out."); + // return View("Lockout"); + // } + // else + // { + // ModelState.AddModelError(string.Empty, "Invalid login attempt."); + // return View(model); + // } + // } + + // // If we got this far, something failed, redisplay form + // return View(model); + //} + + //// + //// GET: /Account/Register + //[HttpGet] + //[AllowAnonymous] + //public IActionResult Register(string returnUrl = null) + //{ + // ViewData["ReturnUrl"] = returnUrl; + // return View(); + //} + + //// + //// POST: /Account/Register + //[HttpPost] + //[AllowAnonymous] + //[ValidateAntiForgeryToken] + //public async Task Register(RegisterViewModel model, string returnUrl = null) + //{ + // ViewData["ReturnUrl"] = returnUrl; + // if (ModelState.IsValid) + // { + // var user = new ApplicationUser + // { + // UserName = model.Email, + // Email = model.Email, + // CardHolderName = model.User.CardHolderName, + // CardNumber = model.User.CardNumber, + // CardType = model.User.CardType, + // City = model.User.City, + // Country = model.User.Country, + // Expiration = model.User.Expiration, + // LastName = model.User.LastName, + // Name = model.User.Name, + // Street = model.User.Street, + // State = model.User.State, + // ZipCode = model.User.ZipCode, + // PhoneNumber = model.User.PhoneNumber, + // SecurityNumber = model.User.SecurityNumber + // }; + // var result = await _userManager.CreateAsync(user, model.Password); + // if (result.Succeeded) + // { + // // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 + // // Send an email with this link + // //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + // //var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); + // //await _emailSender.SendEmailAsync(model.Email, "Confirm your account", + // // $"Please confirm your account by clicking this link: link"); + // await _signInManager.SignInAsync(user, isPersistent: false); + // _logger.LogInformation(3, "User created a new account with password."); + // return RedirectToLocal(returnUrl); + // } + // AddErrors(result); + // } + + // // If we got this far, something failed, redisplay form + // return View(model); + //} + + //// + //// POST: /Account/LogOff + //[HttpPost] + //[ValidateAntiForgeryToken] + //public async Task LogOff() + //{ + // await _signInManager.SignOutAsync(); + // _logger.LogInformation(4, "User logged out."); + // return RedirectToAction(nameof(CatalogController.Index), "Catalog"); + //} + + //// + //// POST: /Account/ExternalLogin + //[HttpPost] + //[AllowAnonymous] + //[ValidateAntiForgeryToken] + //public IActionResult ExternalLogin(string provider, string returnUrl = null) + //{ + // // Request a redirect to the external login provider. + // var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); + // var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + // return Challenge(properties, provider); + //} + + //// + //// GET: /Account/ExternalLoginCallback + //[HttpGet] + //[AllowAnonymous] + //public async Task ExternalLoginCallback(string returnUrl = null, string remoteError = null) + //{ + // if (remoteError != null) + // { + // ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}"); + // return View(nameof(Login)); + // } + // var info = await _signInManager.GetExternalLoginInfoAsync(); + // if (info == null) + // { + // return RedirectToAction(nameof(Login)); + // } + + // // Sign in the user with this external login provider if the user already has a login. + // var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); + // if (result.Succeeded) + // { + // _logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider); + // return RedirectToLocal(returnUrl); + // } + // if (result.RequiresTwoFactor) + // { + // return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl }); + // } + // if (result.IsLockedOut) + // { + // return View("Lockout"); + // } + // else + // { + // // If the user does not have an account, then ask the user to create an account. + // ViewData["ReturnUrl"] = returnUrl; + // ViewData["LoginProvider"] = info.LoginProvider; + // var email = info.Principal.FindFirstValue(ClaimTypes.Email); + // return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); + // } + //} + + //// + //// POST: /Account/ExternalLoginConfirmation + //[HttpPost] + //[AllowAnonymous] + //[ValidateAntiForgeryToken] + //public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null) + //{ + // if (ModelState.IsValid) + // { + // // Get the information about the user from the external login provider + // var info = await _signInManager.GetExternalLoginInfoAsync(); + // if (info == null) + // { + // return View("ExternalLoginFailure"); + // } + // var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + // var result = await _userManager.CreateAsync(user); + // if (result.Succeeded) + // { + // result = await _userManager.AddLoginAsync(user, info); + // if (result.Succeeded) + // { + // await _signInManager.SignInAsync(user, isPersistent: false); + // _logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider); + // return RedirectToLocal(returnUrl); + // } + // } + // AddErrors(result); + // } + + // ViewData["ReturnUrl"] = returnUrl; + // return View(model); + //} + + //// GET: /Account/ConfirmEmail + //[HttpGet] + //[AllowAnonymous] + //public async Task ConfirmEmail(string userId, string code) + //{ + // if (userId == null || code == null) + // { + // return View("Error"); + // } + // var user = await _userManager.FindByIdAsync(userId); + // if (user == null) + // { + // return View("Error"); + // } + // var result = await _userManager.ConfirmEmailAsync(user, code); + // return View(result.Succeeded ? "ConfirmEmail" : "Error"); + //} + + //// + //// GET: /Account/ForgotPassword + //[HttpGet] + //[AllowAnonymous] + //public IActionResult ForgotPassword() + //{ + // return View(); + //} + + //// + //// POST: /Account/ForgotPassword + //[HttpPost] + //[AllowAnonymous] + //[ValidateAntiForgeryToken] + //public async Task ForgotPassword(ForgotPasswordViewModel model) + //{ + // if (ModelState.IsValid) + // { + // var user = await _userManager.FindByNameAsync(model.Email); + // if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) + // { + // // Don't reveal that the user does not exist or is not confirmed + // return View("ForgotPasswordConfirmation"); + // } + + // // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713 + // // Send an email with this link + // //var code = await _userManager.GeneratePasswordResetTokenAsync(user); + // //var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); + // //await _emailSender.SendEmailAsync(model.Email, "Reset Password", + // // $"Please reset your password by clicking here: link"); + // //return View("ForgotPasswordConfirmation"); + // } + + // // If we got this far, something failed, redisplay form + // return View(model); + //} + + //// + //// GET: /Account/ForgotPasswordConfirmation + //[HttpGet] + //[AllowAnonymous] + //public IActionResult ForgotPasswordConfirmation() + //{ + // return View(); + //} + + //// + //// GET: /Account/ResetPassword + //[HttpGet] + //[AllowAnonymous] + //public IActionResult ResetPassword(string code = null) + //{ + // return code == null ? View("Error") : View(); + //} + + //// + //// POST: /Account/ResetPassword + //[HttpPost] + //[AllowAnonymous] + //[ValidateAntiForgeryToken] + //public async Task ResetPassword(ResetPasswordViewModel model) + //{ + // if (!ModelState.IsValid) + // { + // return View(model); + // } + // var user = await _userManager.FindByNameAsync(model.Email); + // if (user == null) + // { + // // Don't reveal that the user does not exist + // return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); + // } + // var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password); + // if (result.Succeeded) + // { + // return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account"); + // } + // AddErrors(result); + // return View(); + //} + + //// + //// GET: /Account/ResetPasswordConfirmation + //[HttpGet] + //[AllowAnonymous] + //public IActionResult ResetPasswordConfirmation() + //{ + // return View(); + //} + + //// + //// GET: /Account/SendCode + //[HttpGet] + //[AllowAnonymous] + //public async Task SendCode(string returnUrl = null, bool rememberMe = false) + //{ + // var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + // if (user == null) + // { + // return View("Error"); + // } + // var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user); + // var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); + // return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); + //} + + //// + //// GET: /Account/VerifyCode + //[HttpGet] + //[AllowAnonymous] + //public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + //{ + // // Require that the user has already logged in via username/password or external login + // var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + // if (user == null) + // { + // return View("Error"); + // } + // return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + //} + + //// + //// POST: /Account/VerifyCode + //[HttpPost] + //[AllowAnonymous] + //[ValidateAntiForgeryToken] + //public async Task VerifyCode(VerifyCodeViewModel model) + //{ + // if (!ModelState.IsValid) + // { + // return View(model); + // } + + // // The following code protects for brute force attacks against the two factor codes. + // // If a user enters incorrect codes for a specified amount of time then the user account + // // will be locked out for a specified amount of time. + // var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); + // if (result.Succeeded) + // { + // return RedirectToLocal(model.ReturnUrl); + // } + // if (result.IsLockedOut) + // { + // _logger.LogWarning(7, "User account locked out."); + // return View("Lockout"); + // } + // else + // { + // ModelState.AddModelError(string.Empty, "Invalid code."); + // return View(model); + // } + //} + + //#region Helpers + + //private void AddErrors(IdentityResult result) + //{ + // foreach (var error in result.Errors) + // { + // ModelState.AddModelError(string.Empty, error.Description); + // } + //} + + //private Task GetCurrentUserAsync() + //{ + // return _userManager.GetUserAsync(HttpContext.User); + //} + + //private IActionResult RedirectToLocal(string returnUrl) + //{ + // if (Url.IsLocalUrl(returnUrl)) + // { + // return Redirect(returnUrl); + // } + // else + // { + // return RedirectToAction(nameof(CatalogController.Index), "Catalog"); + // } + //} + + //#endregion } } diff --git a/src/Web/WebMVC/Controllers/CartController.cs b/src/Web/WebMVC/Controllers/CartController.cs index 1ed24b398..e3166e46f 100644 --- a/src/Web/WebMVC/Controllers/CartController.cs +++ b/src/Web/WebMVC/Controllers/CartController.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Identity; using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.Models; using Microsoft.AspNetCore.Authorization; @@ -13,21 +12,22 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers [Authorize] public class CartController : Controller { - private readonly UserManager _userManager; private readonly IBasketService _basketSvc; private readonly ICatalogService _catalogSvc; + private readonly IIdentityParser _appUserParser; - public CartController(IBasketService basketSvc, ICatalogService catalogSvc, UserManager userManager) + public CartController(IBasketService basketSvc, ICatalogService catalogSvc, IIdentityParser appUserParser) { - _userManager = userManager; _basketSvc = basketSvc; _catalogSvc = catalogSvc; + _appUserParser = appUserParser; } public async Task Index() { - var user = await _userManager.GetUserAsync(HttpContext.User); + var user = _appUserParser.Parse(HttpContext.User); var vm = await _basketSvc.GetBasket(user); + return View(vm); } @@ -36,7 +36,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers [HttpPost] public async Task Index(Dictionary quantities, string action) { - var user = await _userManager.GetUserAsync(HttpContext.User); + var user = _appUserParser.Parse(HttpContext.User); var basket = await _basketSvc.SetQuantities(user, quantities); var vm = await _basketSvc.UpdateBasket(basket); @@ -51,8 +51,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers public async Task AddToCart(CatalogItem productDetails) { - var user = await _userManager.GetUserAsync(HttpContext.User); - //var productDetails = _catalogSvc.GetCatalogItem(productId); + var user = _appUserParser.Parse(HttpContext.User); var product = new BasketItem() { Id = Guid.NewGuid().ToString(), @@ -66,4 +65,4 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers return RedirectToAction("Index", "Catalog"); } } -} \ No newline at end of file +} diff --git a/src/Web/WebMVC/Controllers/OrderController.cs b/src/Web/WebMVC/Controllers/OrderController.cs index ca1bc5781..f87d97bed 100644 --- a/src/Web/WebMVC/Controllers/OrderController.cs +++ b/src/Web/WebMVC/Controllers/OrderController.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.Models; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using Microsoft.eShopOnContainers.WebMVC.Models.OrderViewModels; namespace Microsoft.eShopOnContainers.WebMVC.Controllers @@ -16,10 +15,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers { private IOrderingService _orderSvc; private IBasketService _basketSvc; - private readonly UserManager _userManager; - public OrderController(IOrderingService orderSvc, IBasketService basketSvc, UserManager userManager) + private readonly IIdentityParser _appUserParser; + public OrderController(IOrderingService orderSvc, IBasketService basketSvc, IIdentityParser appUserParser) { - _userManager = userManager; + _appUserParser = appUserParser; _orderSvc = orderSvc; _basketSvc = basketSvc; } @@ -27,7 +26,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers public async Task Create() { var vm = new CreateOrderViewModel(); - var user = await _userManager.GetUserAsync(HttpContext.User); + var user = _appUserParser.Parse(HttpContext.User); var basket = await _basketSvc.GetBasket(user); var order = _basketSvc.MapBasketToOrder(basket); vm.Order = _orderSvc.MapUserInfoIntoOrder(user, order); @@ -38,7 +37,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers [HttpPost] public async Task Create(CreateOrderViewModel model, Dictionary quantities, string action) { - var user = await _userManager.GetUserAsync(HttpContext.User); + var user = _appUserParser.Parse(HttpContext.User); var basket = await _basketSvc.SetQuantities(user, quantities); basket = await _basketSvc.UpdateBasket(basket); var order = _basketSvc.MapBasketToOrder(basket); @@ -48,10 +47,18 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers if (action == "[ Place Order ]") { - _orderSvc.CreateOrder(user, order); + try + { + await _orderSvc.CreateOrder(user, order); - //Empty basket for current user. - await _basketSvc.CleanBasket(user); + //Empty basket for current user. + await _basketSvc.CleanBasket(user); + + } + catch (Exception) { + //redirect to some error page if the operation fails. + return Redirect("http://www.google.com"); + } //Redirect to historic list. return RedirectToAction("Index"); @@ -60,17 +67,17 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers return View(model); } - public async Task Detail(string orderId) + public IActionResult Detail(string orderId) { - var user = await _userManager.GetUserAsync(HttpContext.User); + var user = _appUserParser.Parse(HttpContext.User); var order = _orderSvc.GetOrder(user, orderId); return View(order); } - public async Task Index(Order item) + public IActionResult Index(Order item) { - var user = await _userManager.GetUserAsync(HttpContext.User); + var user = _appUserParser.Parse(HttpContext.User); return View(_orderSvc.GetMyOrders(user)); } } diff --git a/src/Web/WebMVC/Data/ApplicationDbContext.cs b/src/Web/WebMVC/Data/ApplicationDbContext.cs deleted file mode 100644 index efdd11b46..000000000 --- a/src/Web/WebMVC/Data/ApplicationDbContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopOnContainers.WebMVC.Models; - -namespace Microsoft.eShopOnContainers.WebMVC.Data -{ - public class ApplicationDbContext : IdentityDbContext - { - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - // Customize the ASP.NET Identity model and override the defaults if needed. - // For example, you can rename the ASP.NET Identity table names and more. - // Add your customizations after calling base.OnModelCreating(builder); - } - } -} diff --git a/src/Web/WebMVC/Extensions/HttpClientExtensions.cs b/src/Web/WebMVC/Extensions/HttpClientExtensions.cs new file mode 100644 index 000000000..d1b83dc00 --- /dev/null +++ b/src/Web/WebMVC/Extensions/HttpClientExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.WebMVC.Extensions +{ + public static class HttpClientExtensions + { + public static void SetBasicAuthentication(this HttpClient client, string userName, string password) + { + client.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(userName, password); + } + + public static void SetToken(this HttpClient client, string scheme, string token) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, token); + } + + public static void SetBearerToken(this HttpClient client, string token) + { + client.SetToken(JwtConstants.TokenType, token); + } + } + + public class BasicAuthenticationHeaderValue : AuthenticationHeaderValue + { + public BasicAuthenticationHeaderValue(string userName, string password) + : base("Basic", EncodeCredential(userName, password)) + { } + + private static string EncodeCredential(string userName, string password) + { + Encoding encoding = Encoding.GetEncoding("iso-8859-1"); + string credential = String.Format("{0}:{1}", userName, password); + + return Convert.ToBase64String(encoding.GetBytes(credential)); + } + } +} diff --git a/src/Web/WebMVC/Models/ApplicationUser.cs b/src/Web/WebMVC/Models/ApplicationUser.cs index 7a27e7084..722b1f072 100644 --- a/src/Web/WebMVC/Models/ApplicationUser.cs +++ b/src/Web/WebMVC/Models/ApplicationUser.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; namespace Microsoft.eShopOnContainers.WebMVC.Models { diff --git a/src/Web/WebMVC/Models/OrderRequest.cs b/src/Web/WebMVC/Models/OrderRequest.cs new file mode 100644 index 000000000..1bfc6374d --- /dev/null +++ b/src/Web/WebMVC/Models/OrderRequest.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.WebMVC.Models +{ + public class OrderRequest + { + public OrderRequest() { + + } + + public string City { get; set; } + + public string Street { get; set; } + + public string State { get; set; } + + public string Country { get; set; } + + public string ZipCode { get; set; } + + public string CardNumber { get; set; } + + public string CardHolderName { get; set; } + + public DateTime CardExpiration { get; set; } + + public string CardSecurityNumber { get; set; } + + public int CardTypeId { get; set; } + + public string Buyer { get; set; } + } +} diff --git a/src/Web/WebMVC/Services/IIdentityParser.cs b/src/Web/WebMVC/Services/IIdentityParser.cs new file mode 100644 index 000000000..a4f98c61c --- /dev/null +++ b/src/Web/WebMVC/Services/IIdentityParser.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.WebMVC.Services +{ + public interface IIdentityParser + { + T Parse(IPrincipal principal); + } +} diff --git a/src/Web/WebMVC/Services/IOrderingService.cs b/src/Web/WebMVC/Services/IOrderingService.cs index 605f8f22a..3cf52808b 100644 --- a/src/Web/WebMVC/Services/IOrderingService.cs +++ b/src/Web/WebMVC/Services/IOrderingService.cs @@ -8,9 +8,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services { public interface IOrderingService { - List GetMyOrders(ApplicationUser user); - Order GetOrder(ApplicationUser user, string orderId); - void CreateOrder(ApplicationUser user, Order order); + Task> GetMyOrders(ApplicationUser user); + Task GetOrder(ApplicationUser user, string orderId); + Task CreateOrder(ApplicationUser user, Order order); Order MapUserInfoIntoOrder(ApplicationUser user, Order order); void OverrideUserInfoIntoOrder(Order original, Order destination); } diff --git a/src/Web/WebMVC/Services/IdentityParser.cs b/src/Web/WebMVC/Services/IdentityParser.cs new file mode 100644 index 000000000..5cef8b4cd --- /dev/null +++ b/src/Web/WebMVC/Services/IdentityParser.cs @@ -0,0 +1,38 @@ +using Microsoft.eShopOnContainers.WebMVC.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.WebMVC.Services +{ + public class IdentityParser:IIdentityParser + { + public ApplicationUser Parse(IPrincipal principal) + { + var user = new ApplicationUser(); + var claims = (ClaimsPrincipal)principal; + + user.CardHolderName = (claims.Claims.Where(x => x.Type == "card_holder").Count() > 0) ? claims.Claims.First(x => x.Type == "card_holder").Value : ""; + user.CardNumber = (claims.Claims.Where(x => x.Type == "card_number").Count() > 0) ? claims.Claims.First(x => x.Type == "card_number").Value : ""; + user.CardType = (claims.Claims.Where(x => x.Type == "missing").Count() > 0) ? int.Parse(claims.Claims.First(x => x.Type == "missing").Value) : 0; + user.City = (claims.Claims.Where(x => x.Type == "address_city").Count() > 0) ? claims.Claims.First(x => x.Type == "address_city").Value : ""; + user.Country = (claims.Claims.Where(x => x.Type == "address_country").Count() > 0) ? claims.Claims.First(x => x.Type == "address_country").Value : ""; + user.Email = (claims.Claims.Where(x => x.Type == "email").Count() > 0) ? claims.Claims.First(x => x.Type == "email").Value : ""; + user.Id = (claims.Claims.Where(x => x.Type == "sub").Count() > 0) ? claims.Claims.First(x => x.Type == "sub").Value : ""; + user.LastName = (claims.Claims.Where(x => x.Type == "last_name").Count() > 0) ? claims.Claims.First(x => x.Type == "last_name").Value : ""; + user.Name = (claims.Claims.Where(x => x.Type == "name").Count() > 0) ? claims.Claims.First(x => x.Type == "name").Value : ""; + user.PhoneNumber = (claims.Claims.Where(x => x.Type == "phone_number").Count() > 0) ? claims.Claims.First(x => x.Type == "phone_number").Value : ""; + user.SecurityNumber = (claims.Claims.Where(x => x.Type == "card_security_number").Count() > 0) ? claims.Claims.First(x => x.Type == "card_security_number").Value : ""; + user.State = (claims.Claims.Where(x => x.Type == "address_state").Count() > 0) ? claims.Claims.First(x => x.Type == "address_state").Value : ""; + user.Street = (claims.Claims.Where(x => x.Type == "address_street").Count() > 0) ? claims.Claims.First(x => x.Type == "address_street").Value : ""; + user.ZipCode = (claims.Claims.Where(x => x.Type == "address_zip_code").Count() > 0) ? claims.Claims.First(x => x.Type == "address_zip_code").Value : ""; + + return user; + } + } +} + + diff --git a/src/Web/WebMVC/Services/OrderingService.cs b/src/Web/WebMVC/Services/OrderingService.cs index 4940ca97e..efe45db52 100644 --- a/src/Web/WebMVC/Services/OrderingService.cs +++ b/src/Web/WebMVC/Services/OrderingService.cs @@ -4,65 +4,80 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.eShopOnContainers.WebMVC.Models; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using System.Net.Http; +using Newtonsoft.Json; namespace Microsoft.eShopOnContainers.WebMVC.Services { public class OrderingService : IOrderingService { - private List _orders; - - //var ordersUrl = _settings.OrderingUrl + "/api/ordering/orders"; - //var dataString = await _http.GetStringAsync(ordersUrl); - //var items = JsonConvert.DeserializeObject>(dataString); + private HttpClient _apiClient; + private readonly string _remoteServiceBaseUrl; + private readonly IOptions _settings; - public OrderingService(IHttpContextAccessor httpContextAccessor) + public OrderingService(IOptions settings) { - _orders = new List() - { - new Order() - { - Id = Guid.NewGuid().ToString(), - BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(), - OrderDate = DateTime.Now, - State = OrderState.InProcess, - OrderItems = new List() - { - new OrderItem() { UnitPrice = 12.40m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" } - } - }, - new Order() - { - Id = Guid.NewGuid().ToString(), - BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(), - OrderDate = DateTime.Now, - State = OrderState.InProcess, - OrderItems = new List() - { - new OrderItem() { UnitPrice = 12.00m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" } - } - }, - new Order() - { - Id = Guid.NewGuid().ToString(), - BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(), - OrderDate = DateTime.Now, - State = OrderState.Delivered, - OrderItems = new List() - { - new OrderItem() { UnitPrice = 12.05m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" } - } - } - }; + _remoteServiceBaseUrl = $"{settings.Value.OrderingUrl}/api/v1/orders"; + _settings = settings; + #region fake items + //_orders = new List() + //{ + // new Order() + // { + // Id = Guid.NewGuid().ToString(), + // BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(), + // OrderDate = DateTime.Now, + // State = OrderState.InProcess, + // OrderItems = new List() + // { + // new OrderItem() { UnitPrice = 12.40m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" } + // } + // }, + // new Order() + // { + // Id = Guid.NewGuid().ToString(), + // BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(), + // OrderDate = DateTime.Now, + // State = OrderState.InProcess, + // OrderItems = new List() + // { + // new OrderItem() { UnitPrice = 12.00m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" } + // } + // }, + // new Order() + // { + // Id = Guid.NewGuid().ToString(), + // BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(), + // OrderDate = DateTime.Now, + // State = OrderState.Delivered, + // OrderItems = new List() + // { + // new OrderItem() { UnitPrice = 12.05m, PictureUrl = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt", Quantity = 1, ProductName="Roslyn Red T-Shirt" } + // } + // } + //}; + #endregion } - public Order GetOrder(ApplicationUser user, string Id) + async public Task GetOrder(ApplicationUser user, string Id) { - return _orders.Where(x => x.BuyerId.Equals(user.Id) && x.Id.Equals(Id)).FirstOrDefault(); + _apiClient = new HttpClient(); + var ordersUrl = $"{_remoteServiceBaseUrl}/{Id}"; + var dataString = await _apiClient.GetStringAsync(ordersUrl); + var response = JsonConvert.DeserializeObject(dataString); + + return response; } - public List GetMyOrders(ApplicationUser user) + async public Task> GetMyOrders(ApplicationUser user) { - return _orders.Where(x => x.BuyerId.Equals(user.Id)).ToList(); + _apiClient = new HttpClient(); + var ordersUrl = _remoteServiceBaseUrl; + var dataString = await _apiClient.GetStringAsync(ordersUrl); + var response = JsonConvert.DeserializeObject>(dataString); + + return response; } public Order MapUserInfoIntoOrder(ApplicationUser user, Order order) @@ -80,15 +95,34 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services return order; } - public void CreateOrder(ApplicationUser user, Order order) + public OrderRequest MapOrderIntoOrderRequest(Order order) + { + return new OrderRequest() + { + CardHolderName = order.PaymentInfo.CardHolderName, + CardNumber = order.PaymentInfo.CardNumber, + CardSecurityNumber = order.PaymentInfo.SecurityNumber, + CardTypeId = (int)order.PaymentInfo.CardType, + City = order.ShippingAddress.City, + Country = order.ShippingAddress.Country, + State = order.ShippingAddress.State, + Street = order.ShippingAddress.Street, + ZipCode = order.ShippingAddress.ZipCode + }; + } + + async public Task CreateOrder(ApplicationUser user, Order order) { - order.OrderDate = DateTime.Now; - order.Id = Guid.NewGuid().ToString(); - order.BuyerId = user.Id; - order.SequenceNumber = new Random(100).Next(); - order.State = OrderState.InProcess; + _apiClient = new HttpClient(); + var ordersUrl = $"{_remoteServiceBaseUrl}/new"; + order.PaymentInfo.CardType = CardType.AMEX; + OrderRequest request = MapOrderIntoOrderRequest(order); + StringContent content = new StringContent(JsonConvert.SerializeObject(request), System.Text.Encoding.UTF8, "application/json"); + + var response = await _apiClient.PostAsync(ordersUrl, content); - _orders.Add(order); + if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) + throw new Exception("Error creating order, try later"); } public void OverrideUserInfoIntoOrder(Order original, Order destination) diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index d0e5af369..a18c21764 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -4,14 +4,13 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.eShopOnContainers.WebMVC.Data; using Microsoft.eShopOnContainers.WebMVC.Models; using Microsoft.eShopOnContainers.WebMVC.Services; +using System.IdentityModel.Tokens.Jwt; +using Microsoft.IdentityModel.Tokens; namespace Microsoft.eShopOnContainers.WebMVC { @@ -40,12 +39,12 @@ namespace Microsoft.eShopOnContainers.WebMVC public void ConfigureServices(IServiceCollection services) { // Add framework services. - services.AddDbContext(options => - options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); + //services.AddDbContext(options => + // options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); - services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); + //services.AddIdentity() + // .AddEntityFrameworkStores() + // .AddDefaultTokenProviders(); services.AddMvc(); @@ -53,6 +52,7 @@ namespace Microsoft.eShopOnContainers.WebMVC services.AddTransient(); services.AddSingleton(); //CCE: Once services are integrated, a singleton is not needed we can left transient. services.AddTransient(); + services.AddTransient, IdentityParser>(); services.Configure(Configuration); } @@ -60,13 +60,14 @@ namespace Microsoft.eShopOnContainers.WebMVC // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseDatabaseErrorPage(); app.UseBrowserLink(); } else @@ -76,7 +77,46 @@ namespace Microsoft.eShopOnContainers.WebMVC app.UseStaticFiles(); - app.UseIdentity(); + app.UseCookieAuthentication(new CookieAuthenticationOptions + { + AuthenticationScheme = "cookies", + AutomaticAuthenticate = true, + }); + + //app.UseIdentity(); + + var oidcOptions = new OpenIdConnectOptions + { + AuthenticationScheme = "oidc", + SignInScheme = "cookies", + + Authority = "http://localhost:5000", + ClientId = "mvc", + ClientSecret = "secret", + ResponseType = "code id_token", + SaveTokens = true, + GetClaimsFromUserInfoEndpoint = true, + RequireHttpsMetadata = false, + + //TokenValidationParameters = new TokenValidationParameters + //{ + // NameClaimType = "name", + // RoleClaimType = "role" + //} + }; + + oidcOptions.Scope.Clear(); + oidcOptions.Scope.Add("openid"); + oidcOptions.Scope.Add("profile"); + oidcOptions.Scope.Add("orders"); + + app.UseOpenIdConnectAuthentication(oidcOptions); + app.UseJwtBearerAuthentication(new JwtBearerOptions + { + Authority = "http://localhost:5000/", // base address of your OIDC server. + Audience = "http://localhost:5000/", // base address of your API. + RequireHttpsMetadata = false + }); app.UseMvc(routes => { diff --git a/src/Web/WebMVC/Views/Cart/Index.cshtml b/src/Web/WebMVC/Views/Cart/Index.cshtml index cb8f1026f..7db8211e8 100644 --- a/src/Web/WebMVC/Views/Cart/Index.cshtml +++ b/src/Web/WebMVC/Views/Cart/Index.cshtml @@ -1,5 +1,7 @@ +@using Microsoft.eShopOnContainers.WebMVC.Services + @model Microsoft.eShopOnContainers.WebMVC.Models.Basket -@inject UserManager UserManager +@inject IIdentityParser UserManager @{ ViewData["Title"] = "My Cart"; @@ -14,7 +16,7 @@
- @await Component.InvokeAsync("CartList", new { user = await UserManager.GetUserAsync(User) }) + @await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) })
UserManager +@inject IIdentityParser UserManager @{ ViewData["Title"] = "New Order"; @@ -65,7 +66,7 @@

- @await Component.InvokeAsync("CartList", new { user = await UserManager.GetUserAsync(User) }) + @await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) })
diff --git a/src/Web/WebMVC/Views/Shared/_Layout.cshtml b/src/Web/WebMVC/Views/Shared/_Layout.cshtml index 45d2f2c73..d08b381a4 100644 --- a/src/Web/WebMVC/Views/Shared/_Layout.cshtml +++ b/src/Web/WebMVC/Views/Shared/_Layout.cshtml @@ -27,6 +27,9 @@
diff --git a/src/Web/WebMVC/Views/Shared/_LoginPartial.cshtml b/src/Web/WebMVC/Views/Shared/_LoginPartial.cshtml index 5177d84ea..13e3e2120 100644 --- a/src/Web/WebMVC/Views/Shared/_LoginPartial.cshtml +++ b/src/Web/WebMVC/Views/Shared/_LoginPartial.cshtml @@ -1,16 +1,16 @@ @using Microsoft.AspNetCore.Identity @using Microsoft.eShopOnContainers.WebMVC.Models +@using Microsoft.eShopOnContainers.WebMVC.Services -@inject SignInManager SignInManager -@inject UserManager UserManager +@inject IIdentityParser UserManager -@if (SignInManager.IsSignedIn(User)) +@if(Context.User.Identity.IsAuthenticated) {