From 82d270a66b8ffca28e321f29b2eb90b2412ac9a7 Mon Sep 17 00:00:00 2001 From: Syndamia Date: Sat, 8 May 2021 18:10:08 +0300 Subject: Implemented authorization; Replaced Role with IdentityRole; Renamed UserController to AccountController, updated links --- ExamTemplate/Common/RoleConst.cs | 8 ++ ExamTemplate/Data/Models/Role.cs | 16 --- ExamTemplate/Data/Models/User.cs | 1 - ExamTemplate/Data/TemplateContext.cs | 7 +- ExamTemplate/Services/UserService.cs | 15 +-- ExamTemplate/Web/Controllers/AccountController.cs | 140 ++++++++++++++++++++++ ExamTemplate/Web/Controllers/UserController.cs | 131 -------------------- ExamTemplate/Web/Startup.cs | 31 +++-- ExamTemplate/Web/Views/Account/Edit.cshtml | 20 ++++ ExamTemplate/Web/Views/Account/Login.cshtml | 14 +++ ExamTemplate/Web/Views/Account/Profile.cshtml | 27 +++++ ExamTemplate/Web/Views/Account/Register.cshtml | 20 ++++ ExamTemplate/Web/Views/Shared/_Navbar.cshtml | 8 +- ExamTemplate/Web/Views/User/EditProfile.cshtml | 20 ---- ExamTemplate/Web/Views/User/Login.cshtml | 14 --- ExamTemplate/Web/Views/User/Profile.cshtml | 27 ----- ExamTemplate/Web/Views/User/Register.cshtml | 20 ---- 17 files changed, 260 insertions(+), 259 deletions(-) create mode 100644 ExamTemplate/Common/RoleConst.cs delete mode 100644 ExamTemplate/Data/Models/Role.cs create mode 100644 ExamTemplate/Web/Controllers/AccountController.cs delete mode 100644 ExamTemplate/Web/Controllers/UserController.cs create mode 100644 ExamTemplate/Web/Views/Account/Edit.cshtml create mode 100644 ExamTemplate/Web/Views/Account/Login.cshtml create mode 100644 ExamTemplate/Web/Views/Account/Profile.cshtml create mode 100644 ExamTemplate/Web/Views/Account/Register.cshtml delete mode 100644 ExamTemplate/Web/Views/User/EditProfile.cshtml delete mode 100644 ExamTemplate/Web/Views/User/Login.cshtml delete mode 100644 ExamTemplate/Web/Views/User/Profile.cshtml delete mode 100644 ExamTemplate/Web/Views/User/Register.cshtml diff --git a/ExamTemplate/Common/RoleConst.cs b/ExamTemplate/Common/RoleConst.cs new file mode 100644 index 0000000..3f0dfb7 --- /dev/null +++ b/ExamTemplate/Common/RoleConst.cs @@ -0,0 +1,8 @@ +namespace ExamTemplate.Common +{ + public static class RoleConst + { + public static string User = "User"; + public static string Admin = "Administrator"; + } +} diff --git a/ExamTemplate/Data/Models/Role.cs b/ExamTemplate/Data/Models/Role.cs deleted file mode 100644 index 9a23f13..0000000 --- a/ExamTemplate/Data/Models/Role.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Identity; -using System.ComponentModel.DataAnnotations.Schema; - -namespace ExamTemplate.Data.Models -{ - [Table("Roles")] - public class Role : IdentityRole - { - public static string UserRole = "User"; - public static string AdminRole = "Administrator"; - - public List Users { get; set; } = new List(); - } -} diff --git a/ExamTemplate/Data/Models/User.cs b/ExamTemplate/Data/Models/User.cs index cb96942..e8aecfa 100644 --- a/ExamTemplate/Data/Models/User.cs +++ b/ExamTemplate/Data/Models/User.cs @@ -10,6 +10,5 @@ namespace ExamTemplate.Data.Models { public string FirstName { get; set; } public string LastName { get; set; } - public List Roles { get; set; } = new List(); } } diff --git a/ExamTemplate/Data/TemplateContext.cs b/ExamTemplate/Data/TemplateContext.cs index ef675a3..268812d 100644 --- a/ExamTemplate/Data/TemplateContext.cs +++ b/ExamTemplate/Data/TemplateContext.cs @@ -1,11 +1,12 @@ using System; using ExamTemplate.Data.Models; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace ExamTemplate.Data { - public class TemplateContext : IdentityDbContext + public class TemplateContext : IdentityDbContext, Guid> { public TemplateContext(DbContextOptions options) : base(options) { } @@ -16,10 +17,6 @@ namespace ExamTemplate.Data .HasIndex(x => x.UserName) .IsUnique(); - builder.Entity() - .HasMany(x => x.Roles) - .WithMany(x => x.Users); - base.OnModelCreating(builder); } } diff --git a/ExamTemplate/Services/UserService.cs b/ExamTemplate/Services/UserService.cs index 90a4bf4..e78443a 100644 --- a/ExamTemplate/Services/UserService.cs +++ b/ExamTemplate/Services/UserService.cs @@ -1,4 +1,5 @@ -using System.Security.Claims; +using System; +using System.Security.Claims; using System.Threading.Tasks; using AutoMapper; using ExamTemplate.Data; @@ -15,9 +16,9 @@ namespace ExamTemplate.Services private readonly TemplateContext _context; private readonly SignInManager _signInManager; private readonly UserManager _userManager; - private readonly RoleManager _roleManager; + private readonly RoleManager> _roleManager; - public UserService(IMapper autoMapper, TemplateContext templateContext, SignInManager signInManager, UserManager userManager, RoleManager roleManager) + public UserService(IMapper autoMapper, TemplateContext templateContext, SignInManager signInManager, UserManager userManager, RoleManager> roleManager) { this._autoMapper = autoMapper; this._context = templateContext; @@ -32,14 +33,9 @@ namespace ExamTemplate.Services user.PasswordHash = this._userManager.PasswordHasher.HashPassword(user, registerUserServiceModel.Password); IdentityResult userCreateResult = await this._userManager.CreateAsync(user); - - // Many to many relationships with Roles can cause problems, - // that's why I add the Role to the User and add the User to the Role IdentityResult addRoleResult = await this._userManager.AddToRoleAsync(user, "User"); - user.Roles.Add(await this._roleManager.FindByNameAsync("User")); - bool roleAddedSuccessfuly = await this._context.SaveChangesAsync() >= 1; - return userCreateResult.Succeeded && addRoleResult.Succeeded && roleAddedSuccessfuly; + return userCreateResult.Succeeded && addRoleResult.Succeeded; } public async Task LoginUserAsync(LoginUserServiceModel loginUserServiceModel) @@ -57,7 +53,6 @@ namespace ExamTemplate.Services public async Task GetUserByUsernameAsync(string username) { User user = await this._userManager.Users - .Include(x => x.Roles) .FirstOrDefaultAsync(x => x.UserName == username); return this._autoMapper.Map(user); diff --git a/ExamTemplate/Web/Controllers/AccountController.cs b/ExamTemplate/Web/Controllers/AccountController.cs new file mode 100644 index 0000000..7fb7ab3 --- /dev/null +++ b/ExamTemplate/Web/Controllers/AccountController.cs @@ -0,0 +1,140 @@ +using ExamTemplate.Services; +using Microsoft.AspNetCore.Mvc; +using ExamTemplate.Web.Models.User; +using AutoMapper; +using ExamTemplate.Services.Models; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; + +namespace ExamTemplate.Web.Controllers +{ + [Authorize] + public class AccountController : Controller + { + private readonly IMapper _autoMapper; + private readonly UserService _userService; + + public AccountController(IMapper autoMapper, UserService userService) + { + this._autoMapper = autoMapper; + this._userService = userService; + } + + [HttpGet] + [AllowAnonymous] + public IActionResult Register() + { + return View(); + } + + [HttpPost] + [AllowAnonymous] + public async Task Register(RegisterUserViewModel registerUserViewModel) + { + if (!ModelState.IsValid) + return View(registerUserViewModel); + + RegisterUserServiceModel registerUserServiceModel = this._autoMapper.Map(registerUserViewModel); + + bool result = await this._userService.RegisterUserAsync(registerUserServiceModel); + + if (result) + return RedirectToAction("Index", "Home"); + else + return View(); + } + + [HttpGet] + [AllowAnonymous] + public IActionResult Login() + { + return View(); + } + + [HttpPost] + [AllowAnonymous] + public async Task Login(LoginUserViewModel loginUserViewModel) + { + if (!ModelState.IsValid) + return View(loginUserViewModel); + + LoginUserServiceModel loginUserServiceModel = this._autoMapper.Map(loginUserViewModel); + + bool result = await this._userService.LoginUserAsync(loginUserServiceModel); + + if (result) + return RedirectToAction("Index", "Home"); + else + return View(); + } + + [HttpPost] + public async Task Logout() + { + await this._userService.LogoutAsync(); + + return RedirectToAction("Login"); + } + + [HttpGet] + [AllowAnonymous] + public async Task Profile(string username) + { + UserServiceModel userServiceModel = await this._userService.GetUserByUsernameAsync(username); + + if (userServiceModel == default(UserServiceModel)) + return RedirectToAction("Login"); + + UserViewModel userViewModel = this._autoMapper.Map(userServiceModel); + + return View(userViewModel); + } + + [HttpGet] + public async Task Edit() + { + UserServiceModel userServiceModel = await this._userService.GetUserByClaimsAsync(this.HttpContext.User); + + if (userServiceModel == default(UserServiceModel)) + return RedirectToAction("Login"); + + EditUserViewModel editUserViewModel = this._autoMapper.Map(userServiceModel); + + return View(editUserViewModel); + } + + [HttpPost] + public async Task Edit(EditUserViewModel editUserViewModel) + { + if (!await this._userService.IsAuthorizedToModify(HttpContext.User, editUserViewModel.OriginalUsername)) + return new UnauthorizedResult(); + + if (!ModelState.IsValid) + return View(editUserViewModel); + + if (!this._userService.IsSignedIn(HttpContext.User)) + return RedirectToAction("Login"); + + UserServiceModel loggedInUser = await this._userService.GetUserByClaimsAsync(HttpContext.User); + + EditUserServiceModel editUserServiceModel = this._autoMapper.Map(editUserViewModel); + bool result = await this._userService.EditUserAsync(HttpContext.User, editUserServiceModel); + + if (result) + { + if (loggedInUser.Username != editUserViewModel.Username) + await this._userService.LogoutAsync(); + + return RedirectToAction("Profile", new { username = editUserViewModel.Username }); + } + else + return RedirectToAction("Profile", new { username = loggedInUser.Username }); + } + + // [HttpPost] + // public async Task DeleteProfile(string username) + // { + // throw new System.NotImplementedException(); + // } + } +} diff --git a/ExamTemplate/Web/Controllers/UserController.cs b/ExamTemplate/Web/Controllers/UserController.cs deleted file mode 100644 index c7183ca..0000000 --- a/ExamTemplate/Web/Controllers/UserController.cs +++ /dev/null @@ -1,131 +0,0 @@ -using ExamTemplate.Services; -using Microsoft.AspNetCore.Mvc; -using ExamTemplate.Web.Models.User; -using AutoMapper; -using ExamTemplate.Services.Models; -using System.Threading.Tasks; - -namespace ExamTemplate.Web.Controllers -{ - public class UserController : Controller - { - private readonly IMapper _autoMapper; - private readonly UserService _userService; - - public UserController(IMapper autoMapper, UserService userService) - { - this._autoMapper = autoMapper; - this._userService = userService; - } - - [HttpGet] - [Route("/Register")] - public IActionResult Register() - { - return View(); - } - - [HttpPost] - [Route("/Register")] - public async Task Register(RegisterUserViewModel registerUserViewModel) - { - if (!ModelState.IsValid) - return View(registerUserViewModel); - - RegisterUserServiceModel registerUserServiceModel = this._autoMapper.Map(registerUserViewModel); - - bool result = await this._userService.RegisterUserAsync(registerUserServiceModel); - - if (result) - return RedirectToAction("Index", "Home"); - else - return View(); - } - - [HttpGet] - [Route("/Login")] - public IActionResult Login() - { - return View(); - } - - [HttpPost] - [Route("/Login")] - public async Task Login(LoginUserViewModel loginUserViewModel) - { - if (!ModelState.IsValid) - return View(loginUserViewModel); - - LoginUserServiceModel loginUserServiceModel = this._autoMapper.Map(loginUserViewModel); - - bool result = await this._userService.LoginUserAsync(loginUserServiceModel); - - if (result) - return RedirectToAction("Index", "Home"); - else - return View(); - } - - [HttpPost] - public async Task Logout() - { - await this._userService.LogoutAsync(); - - return RedirectToAction("Login"); - } - - [HttpGet] - [Route("/Profile/{username}")] - public async Task Profile(string username) - { - UserServiceModel userServiceModel = await this._userService.GetUserByUsernameAsync(username); - - if (userServiceModel == default(UserServiceModel)) - return RedirectToAction("Login"); - - UserViewModel userViewModel = this._autoMapper.Map(userServiceModel); - - return View(userViewModel); - } - - [HttpGet] - [Route("/EditProfile")] - public async Task EditProfile() - { - UserServiceModel userServiceModel = await this._userService.GetUserByClaimsAsync(this.HttpContext.User); - - if (userServiceModel == default(UserServiceModel)) - return RedirectToAction("Login"); - - EditUserViewModel editUserViewModel = this._autoMapper.Map(userServiceModel); - - return View(editUserViewModel); - } - - [HttpPost] - [Route("/EditProfile")] - public async Task EditProfile(EditUserViewModel editUserViewModel) - { - if (!ModelState.IsValid) - return View(editUserViewModel); - - if (!this._userService.IsSignedIn(HttpContext.User)) - return RedirectToAction("Login"); - - UserServiceModel loggedInUser = await this._userService.GetUserByClaimsAsync(HttpContext.User); - - EditUserServiceModel editUserServiceModel = this._autoMapper.Map(editUserViewModel); - bool result = await this._userService.EditUserAsync(HttpContext.User, editUserServiceModel); - - if (result) - { - if (loggedInUser.Username != editUserViewModel.Username) - await this._userService.LogoutAsync(); - - return RedirectToAction("Profile", new { username = editUserViewModel.Username }); - } - else - return RedirectToAction("Profile", new { username = loggedInUser.Username }); - } - } -} diff --git a/ExamTemplate/Web/Startup.cs b/ExamTemplate/Web/Startup.cs index 0754bff..00d94c0 100644 --- a/ExamTemplate/Web/Startup.cs +++ b/ExamTemplate/Web/Startup.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using ExamTemplate.Common; using ExamTemplate.Data; using ExamTemplate.Data.Models; using ExamTemplate.Services; @@ -11,9 +12,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace Web { - public class Startup + public class Startup { public Startup(IConfiguration configuration) { @@ -41,10 +41,19 @@ namespace Web options.UseNpgsql(this.Configuration.GetConnectionString("LocalDBConnection"))); // Needed for SignInManager and UserManager - services.AddIdentity() - .AddRoles() + services.AddIdentity>(options => + { + options.SignIn.RequireConfirmedAccount = false; + + // Password settings + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequireUppercase = false; + options.Password.RequiredLength = 3; + options.Password.RequiredUniqueChars = 0; + }).AddRoles>() .AddEntityFrameworkStores(); - } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -65,8 +74,8 @@ namespace Web app.UseRouting(); - app.UseAuthorization(); app.UseAuthentication(); + app.UseAuthorization(); app.UseEndpoints(endpoints => { @@ -85,15 +94,15 @@ namespace Web dbContext.Database.Migrate(); - var roleManager = (RoleManager)serviceScope.ServiceProvider.GetService(typeof(RoleManager)); - if (!dbContext.Roles.Any(x => x.Name == Role.UserRole)) + var roleManager = (RoleManager>)serviceScope.ServiceProvider.GetService(typeof(RoleManager>)); + if (!dbContext.Roles.Any(x => x.Name == RoleConst.User)) { - Role userRole = new() { Name = Role.UserRole }; + IdentityRole userRole = new() { Name = RoleConst.User }; roleManager.CreateAsync(userRole).Wait(); } - if (!dbContext.Roles.Any(x => x.Name == Role.AdminRole)) + if (!dbContext.Roles.Any(x => x.Name == RoleConst.Admin)) { - Role adminRole = new() { Name = Role.AdminRole }; + IdentityRole adminRole = new() { Name = RoleConst.Admin }; roleManager.CreateAsync(adminRole).Wait(); } diff --git a/ExamTemplate/Web/Views/Account/Edit.cshtml b/ExamTemplate/Web/Views/Account/Edit.cshtml new file mode 100644 index 0000000..da08d9a --- /dev/null +++ b/ExamTemplate/Web/Views/Account/Edit.cshtml @@ -0,0 +1,20 @@ +@model EditUserViewModel +@{ + ViewData["Title"] = "Edit Profile"; +} + +
+ + + + + + + + + + + + + +
diff --git a/ExamTemplate/Web/Views/Account/Login.cshtml b/ExamTemplate/Web/Views/Account/Login.cshtml new file mode 100644 index 0000000..688c547 --- /dev/null +++ b/ExamTemplate/Web/Views/Account/Login.cshtml @@ -0,0 +1,14 @@ +@model ExamTemplate.Web.Models.User.LoginUserViewModel +@{ + ViewData["Title"] = "Login"; +} + +
+ + + + + + + +
diff --git a/ExamTemplate/Web/Views/Account/Profile.cshtml b/ExamTemplate/Web/Views/Account/Profile.cshtml new file mode 100644 index 0000000..c6f3e5c --- /dev/null +++ b/ExamTemplate/Web/Views/Account/Profile.cshtml @@ -0,0 +1,27 @@ +@using Microsoft.AspNetCore.Identity + +@inject SignInManager SignInManager +@inject UserManager UserManager + +@model UserViewModel +@{ + ViewData["Title"] = Model.Username + "'s Profile"; +} + +

+

+ @Model.FirstName @Model.LastName +

+
+ @Model.Username +
+ @if (SignInManager.IsSignedIn(User)) + { + @if(UserManager.GetUserName(User) == Model.Username) + { +
+ +
+ } + } +

diff --git a/ExamTemplate/Web/Views/Account/Register.cshtml b/ExamTemplate/Web/Views/Account/Register.cshtml new file mode 100644 index 0000000..d255287 --- /dev/null +++ b/ExamTemplate/Web/Views/Account/Register.cshtml @@ -0,0 +1,20 @@ +@model ExamTemplate.Web.Models.User.RegisterUserViewModel +@{ + ViewData["Title"] = "Register"; +} + +
+ + + + + + + + + + + + + +
diff --git a/ExamTemplate/Web/Views/Shared/_Navbar.cshtml b/ExamTemplate/Web/Views/Shared/_Navbar.cshtml index 7ae8f50..0ec5c4d 100644 --- a/ExamTemplate/Web/Views/Shared/_Navbar.cshtml +++ b/ExamTemplate/Web/Views/Shared/_Navbar.cshtml @@ -13,18 +13,18 @@
@if (SignInManager.IsSignedIn(User)) { - + @UserManager.GetUserName(User) -
+
} else { - Login - Register + Login + Register }
diff --git a/ExamTemplate/Web/Views/User/EditProfile.cshtml b/ExamTemplate/Web/Views/User/EditProfile.cshtml deleted file mode 100644 index da08d9a..0000000 --- a/ExamTemplate/Web/Views/User/EditProfile.cshtml +++ /dev/null @@ -1,20 +0,0 @@ -@model EditUserViewModel -@{ - ViewData["Title"] = "Edit Profile"; -} - -
- - - - - - - - - - - - - -
diff --git a/ExamTemplate/Web/Views/User/Login.cshtml b/ExamTemplate/Web/Views/User/Login.cshtml deleted file mode 100644 index 7cb5ac5..0000000 --- a/ExamTemplate/Web/Views/User/Login.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@model ExamTemplate.Web.Models.User.LoginUserViewModel -@{ - ViewData["Title"] = "Login"; -} - -
- - - - - - - -
diff --git a/ExamTemplate/Web/Views/User/Profile.cshtml b/ExamTemplate/Web/Views/User/Profile.cshtml deleted file mode 100644 index 4120766..0000000 --- a/ExamTemplate/Web/Views/User/Profile.cshtml +++ /dev/null @@ -1,27 +0,0 @@ -@using Microsoft.AspNetCore.Identity - -@inject SignInManager SignInManager -@inject UserManager UserManager - -@model UserViewModel -@{ - ViewData["Title"] = Model.Username + "'s Profile"; -} - -

-

- @Model.FirstName @Model.LastName -

-
- @Model.Username -
- @if (SignInManager.IsSignedIn(User)) - { - @if(UserManager.GetUserName(User) == Model.Username) - { -
- -
- } - } -

diff --git a/ExamTemplate/Web/Views/User/Register.cshtml b/ExamTemplate/Web/Views/User/Register.cshtml deleted file mode 100644 index 3b7f6d9..0000000 --- a/ExamTemplate/Web/Views/User/Register.cshtml +++ /dev/null @@ -1,20 +0,0 @@ -@model ExamTemplate.Web.Models.User.RegisterUserViewModel -@{ - ViewData["Title"] = "Register"; -} - -
- - - - - - - - - - - - - -
-- cgit v1.2.3