Browse Source

MVC App: Authentication against IdentityService and Order Api basic integration.

pull/49/merge
Carlos Cañizares Estévez 8 years ago
parent
commit
a939fe7a51
25 changed files with 981 additions and 540 deletions
  1. +4
    -1
      docker-compose.yml
  2. +49
    -0
      src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs
  3. +16
    -0
      src/Services/Identity/eShopOnContainers.Identity/Extensions/PrincipalExtensions.cs
  4. +127
    -0
      src/Services/Identity/eShopOnContainers.Identity/Services/ProfileService.cs
  5. +5
    -1
      src/Services/Identity/eShopOnContainers.Identity/Startup.cs
  6. +2
    -2
      src/Services/Identity/eShopOnContainers.Identity/appsettings.json
  7. +2
    -1
      src/Services/Ordering/Ordering.API/settings.json
  8. +451
    -392
      src/Web/WebMVC/Controllers/AccountController.cs
  9. +8
    -9
      src/Web/WebMVC/Controllers/CartController.cs
  10. +20
    -13
      src/Web/WebMVC/Controllers/OrderController.cs
  11. +0
    -26
      src/Web/WebMVC/Data/ApplicationDbContext.cs
  12. +44
    -0
      src/Web/WebMVC/Extensions/HttpClientExtensions.cs
  13. +1
    -1
      src/Web/WebMVC/Models/ApplicationUser.cs
  14. +36
    -0
      src/Web/WebMVC/Models/OrderRequest.cs
  15. +13
    -0
      src/Web/WebMVC/Services/IIdentityParser.cs
  16. +3
    -3
      src/Web/WebMVC/Services/IOrderingService.cs
  17. +38
    -0
      src/Web/WebMVC/Services/IdentityParser.cs
  18. +87
    -53
      src/Web/WebMVC/Services/OrderingService.cs
  19. +50
    -10
      src/Web/WebMVC/Startup.cs
  20. +4
    -2
      src/Web/WebMVC/Views/Cart/Index.cshtml
  21. +3
    -2
      src/Web/WebMVC/Views/Order/Create.cshtml
  22. +3
    -0
      src/Web/WebMVC/Views/Shared/_Layout.cshtml
  23. +6
    -6
      src/Web/WebMVC/Views/Shared/_LoginPartial.cshtml
  24. +4
    -4
      src/Web/WebMVC/appsettings.json
  25. +5
    -14
      src/Web/WebMVC/project.json

+ 4
- 1
docker-compose.yml View File

@ -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"


+ 49
- 0
src/Services/Identity/eShopOnContainers.Identity/Configuration/Config.cs View File

@ -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<Secret>
{
new Secret("secret".Sha256())
},
ClientUri = "http://localhost:2114",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RedirectUris = new List<string>
{
"http://localhost:2114/signin-oidc"
},
PostLogoutRedirectUris = new List<string>
{
"http://localhost:2114/"
},
LogoutUri = "http://localhost:2114/signout-oidc",
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
"orders",
"basket",
},
}
};
}


+ 16
- 0
src/Services/Identity/eShopOnContainers.Identity/Extensions/PrincipalExtensions.cs View File

@ -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();
// }
//}
}

+ 127
- 0
src/Services/Identity/eShopOnContainers.Identity/Services/ProfileService.cs View File

@ -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<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> 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<Claim> GetClaimsFromUser(ApplicationUser user)
{
var claims = new List<Claim>
{
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;
}
}
}

+ 5
- 1
src/Services/Identity/eShopOnContainers.Identity/Startup.cs View File

@ -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<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
@ -59,7 +61,8 @@ namespace eShopOnContainers.Identity
.AddTemporarySigningCredential()
.AddInMemoryScopes(Config.GetScopes())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
.AddAspNetIdentity<ApplicationUser>()
.Services.AddTransient<IProfileService, ProfileService>();
//Configuration Settings:
services.AddOptions();
@ -89,6 +92,7 @@ namespace eShopOnContainers.Identity
// Adds IdentityServer
app.UseIdentityServer();
app.UseMvc(routes =>
{
routes.MapRoute(


+ 2
- 2
src/Services/Identity/eShopOnContainers.Identity/appsettings.json View File

@ -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"


+ 2
- 1
src/Services/Ordering/Ordering.API/settings.json View File

@ -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;"
}

+ 451
- 392
src/Web/WebMVC/Controllers/AccountController.cs View File

@ -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<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger _logger;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILoggerFactory loggerFactory)
private readonly IIdentityParser<ApplicationUser> _identityParser;
public AccountController(IIdentityParser<ApplicationUser> identityParser)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = loggerFactory.CreateLogger<AccountController>();
_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<IActionResult> 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<ActionResult> CallApi()
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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: <a href='{callbackUrl}'>link</a>");
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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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: <a href='{callbackUrl}'>link</a>");
//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<IActionResult> 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<ActionResult> SendCode(string returnUrl = null, bool rememberMe = false)
public async Task<ActionResult> 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<IActionResult> 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<IActionResult> 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<ApplicationUser> 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<ApplicationUser> _userManager;
//private readonly SignInManager<ApplicationUser> _signInManager;
//private readonly ILogger _logger;
//public AccountController(
// UserManager<ApplicationUser> userManager,
// SignInManager<ApplicationUser> signInManager,
// ILoggerFactory loggerFactory)
//{
// _userManager = userManager;
// _signInManager = signInManager;
// _logger = loggerFactory.CreateLogger<AccountController>();
//}
////
//// 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<IActionResult> 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<IActionResult> 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: <a href='{callbackUrl}'>link</a>");
// 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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: <a href='{callbackUrl}'>link</a>");
// //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<IActionResult> 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<ActionResult> 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<IActionResult> 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<IActionResult> 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<ApplicationUser> 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
}
}

+ 8
- 9
src/Web/WebMVC/Controllers/CartController.cs View File

@ -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<ApplicationUser> _userManager;
private readonly IBasketService _basketSvc;
private readonly ICatalogService _catalogSvc;
private readonly IIdentityParser<ApplicationUser> _appUserParser;
public CartController(IBasketService basketSvc, ICatalogService catalogSvc, UserManager<ApplicationUser> userManager)
public CartController(IBasketService basketSvc, ICatalogService catalogSvc, IIdentityParser<ApplicationUser> appUserParser)
{
_userManager = userManager;
_basketSvc = basketSvc;
_catalogSvc = catalogSvc;
_appUserParser = appUserParser;
}
public async Task<IActionResult> 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<IActionResult> Index(Dictionary<string, int> 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<IActionResult> 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");
}
}
}
}

+ 20
- 13
src/Web/WebMVC/Controllers/OrderController.cs View File

@ -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<ApplicationUser> _userManager;
public OrderController(IOrderingService orderSvc, IBasketService basketSvc, UserManager<ApplicationUser> userManager)
private readonly IIdentityParser<ApplicationUser> _appUserParser;
public OrderController(IOrderingService orderSvc, IBasketService basketSvc, IIdentityParser<ApplicationUser> appUserParser)
{
_userManager = userManager;
_appUserParser = appUserParser;
_orderSvc = orderSvc;
_basketSvc = basketSvc;
}
@ -27,7 +26,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
public async Task<IActionResult> 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<IActionResult> Create(CreateOrderViewModel model, Dictionary<string, int> 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<IActionResult> 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<IActionResult> 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));
}
}

+ 0
- 26
src/Web/WebMVC/Data/ApplicationDbContext.cs View File

@ -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<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> 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);
}
}
}

+ 44
- 0
src/Web/WebMVC/Extensions/HttpClientExtensions.cs View File

@ -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));
}
}
}

+ 1
- 1
src/Web/WebMVC/Models/ApplicationUser.cs View File

@ -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
{


+ 36
- 0
src/Web/WebMVC/Models/OrderRequest.cs View File

@ -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; }
}
}

+ 13
- 0
src/Web/WebMVC/Services/IIdentityParser.cs View File

@ -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>
{
T Parse(IPrincipal principal);
}
}

+ 3
- 3
src/Web/WebMVC/Services/IOrderingService.cs View File

@ -8,9 +8,9 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public interface IOrderingService
{
List<Order> GetMyOrders(ApplicationUser user);
Order GetOrder(ApplicationUser user, string orderId);
void CreateOrder(ApplicationUser user, Order order);
Task<List<Order>> GetMyOrders(ApplicationUser user);
Task<Order> GetOrder(ApplicationUser user, string orderId);
Task CreateOrder(ApplicationUser user, Order order);
Order MapUserInfoIntoOrder(ApplicationUser user, Order order);
void OverrideUserInfoIntoOrder(Order original, Order destination);
}


+ 38
- 0
src/Web/WebMVC/Services/IdentityParser.cs View File

@ -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<ApplicationUser>
{
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;
}
}
}

+ 87
- 53
src/Web/WebMVC/Services/OrderingService.cs View File

@ -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<Order> _orders;
//var ordersUrl = _settings.OrderingUrl + "/api/ordering/orders";
//var dataString = await _http.GetStringAsync(ordersUrl);
//var items = JsonConvert.DeserializeObject<List<Order>>(dataString);
private HttpClient _apiClient;
private readonly string _remoteServiceBaseUrl;
private readonly IOptions<AppSettings> _settings;
public OrderingService(IHttpContextAccessor httpContextAccessor)
public OrderingService(IOptions<AppSettings> settings)
{
_orders = new List<Order>()
{
new Order()
{
Id = Guid.NewGuid().ToString(),
BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(),
OrderDate = DateTime.Now,
State = OrderState.InProcess,
OrderItems = new List<OrderItem>()
{
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<OrderItem>()
{
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<OrderItem>()
{
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<Order>()
//{
// new Order()
// {
// Id = Guid.NewGuid().ToString(),
// BuyerId = new Guid("ebcbcb4c-b032-4baa-834b-7fd66d37bc95").ToString(),
// OrderDate = DateTime.Now,
// State = OrderState.InProcess,
// OrderItems = new List<OrderItem>()
// {
// 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<OrderItem>()
// {
// 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<OrderItem>()
// {
// 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<Order> 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<Order>(dataString);
return response;
}
public List<Order> GetMyOrders(ApplicationUser user)
async public Task<List<Order>> 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<List<Order>>(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)


+ 50
- 10
src/Web/WebMVC/Startup.cs View File

@ -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<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
//services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//services.AddIdentity<ApplicationUser, IdentityRole>()
// .AddEntityFrameworkStores<ApplicationDbContext>()
// .AddDefaultTokenProviders();
services.AddMvc();
@ -53,6 +52,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
services.AddTransient<ICatalogService, CatalogService>();
services.AddSingleton<IOrderingService, OrderingService>(); //CCE: Once services are integrated, a singleton is not needed we can left transient.
services.AddTransient<IBasketService, BasketService>();
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
services.Configure<AppSettings>(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 =>
{


+ 4
- 2
src/Web/WebMVC/Views/Cart/Index.cshtml View File

@ -1,5 +1,7 @@
@using Microsoft.eShopOnContainers.WebMVC.Services
@model Microsoft.eShopOnContainers.WebMVC.Models.Basket
@inject UserManager<ApplicationUser> UserManager
@inject IIdentityParser<ApplicationUser> UserManager
@{
ViewData["Title"] = "My Cart";
@ -14,7 +16,7 @@
<form method="post" id="cartForm">
<div class="container cart-index-container">
<div class="row">
@await Component.InvokeAsync("CartList", new { user = await UserManager.GetUserAsync(User) })
@await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) })
<div class="col-md-offset-8 col-md-4">
<input type="submit"
class="btn btn-default btn-brand btn-cart"


+ 3
- 2
src/Web/WebMVC/Views/Order/Create.cshtml View File

@ -1,5 +1,6 @@
@using Microsoft.eShopOnContainers.WebMVC.Services
@model Microsoft.eShopOnContainers.WebMVC.Models.OrderViewModels.CreateOrderViewModel
@inject UserManager<ApplicationUser> UserManager
@inject IIdentityParser<ApplicationUser> UserManager
@{
ViewData["Title"] = "New Order";
@ -65,7 +66,7 @@
<br /><br />
<div class="col-md-12 order-create-section-items">
<section>
@await Component.InvokeAsync("CartList", new { user = await UserManager.GetUserAsync(User) })
@await Component.InvokeAsync("CartList", new { user = UserManager.Parse(User) })
</section>
</div>
<div class="form-group">


+ 3
- 0
src/Web/WebMVC/Views/Shared/_Layout.cshtml View File

@ -27,6 +27,9 @@
</a>
</div>
<div class="navbar-header col-sm-6 col-xs-4 text-center">
@*@Html.ActionLink("Claims", "Claims", "Account")
@Html.ActionLink("Call Api", "Claims", "Account")
@Html.ActionLink("Log Out", "Signout", "Account")*@
@await Html.PartialAsync("_LoginPartial")
</div>
</div>


+ 6
- 6
src/Web/WebMVC/Views/Shared/_LoginPartial.cshtml View File

@ -1,16 +1,16 @@
@using Microsoft.AspNetCore.Identity
@using Microsoft.eShopOnContainers.WebMVC.Models
@using Microsoft.eShopOnContainers.WebMVC.Services
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@inject IIdentityParser<ApplicationUser> UserManager
@if (SignInManager.IsSignedIn(User))
@if(Context.User.Identity.IsAuthenticated)
{
<form asp-area="" asp-controller="Account" asp-action="LogOff" method="post" id="logoutForm" class="navbar-right">
<div class="nav navbar-nav navbar-right mt-15">
@await Component.InvokeAsync("Cart", new { user = await UserManager.GetUserAsync(User) })
@await Component.InvokeAsync("Cart", new { user = UserManager.Parse(User) })
<div class="fr login-user">
<span class="hidden-xs">@UserManager.GetUserName(User)</span>
<span class="hidden-xs">@User.FindFirst(x => x.Type == "preferred_username").Value</span>
<i class="down-arrow"></i>
<div class="login-user-dropdown-content">
<a asp-controller="Order" asp-action="Index">MY ORDERS<i class="myorders-icon"></i></a>
@ -23,6 +23,6 @@
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="" asp-controller="Account" class="btn-login" asp-action="Login">&nbsp;Log In&nbsp;</a></li>
<li><a asp-area="" asp-controller="Account" class="btn-login" asp-action="SignIn">&nbsp;Log In&nbsp;</a></li>
</ul>
}

+ 4
- 4
src/Web/WebMVC/appsettings.json View File

@ -1,12 +1,12 @@
{
"ConnectionStrings": {
//"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"
//"DefaultConnection": "Server=identity.data;Database=aspnet-Microsoft.eShopOnContainers.WebMVC;User Id=sa;Password=Pass@word"
},
"CatalogUrl": "http://localhost:5101",
"OrderingUrl": "http://localhost:5102/",
"OrderingUrl": "http://localhost:2446",
"BasketUrl": "http://localhost:5103",
"IdentityUrl": "http://localhost:5104",
"IdentityUrl": "http://localhost:5000",
"Logging": {
"IncludeScopes": false,
"LogLevel": {


+ 5
- 14
src/Web/WebMVC/project.json View File

@ -7,10 +7,7 @@
},
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Session": "1.0.0",
"Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
@ -18,15 +15,6 @@
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer.Design": {
"version": "1.0.0",
"type": "build"
},
"Microsoft.EntityFrameworkCore.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
},
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
@ -43,13 +31,16 @@
"version": "1.0.0-preview2-final",
"type": "build"
},
"Newtonsoft.Json": "9.0.1"
"Newtonsoft.Json": "9.0.1",
"System.IdentityModel.Tokens.Jwt": "5.0.0",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0"
},
"tools": {
"BundlerMinifier.Core": "2.0.238",
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
"Microsoft.Extensions.SecretManager.Tools": "1.0.0-preview2-final",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
"version": "1.0.0-preview2-final",


Loading…
Cancel
Save