diff --git a/Dockerfile b/Dockerfile index 456af7ea3e..467e07d22c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,22 +17,19 @@ RUN apt-get update \ WORKDIR /OoS-Backend -COPY ./OutOfSchool/*.sln ./ -COPY ./OutOfSchool/OutOfSchool.Tests/*.csproj ./OutOfSchool.Tests/ COPY ./OutOfSchool/OutOfSchool.WebApi/*.csproj ./OutOfSchool.WebApi/ -COPY ./OutOfSchool/IdentityServer/*.csproj ./IdentityServer/ COPY ./OutOfSchool/OutOfSchool.DataAccess/*.csproj ./OutOfSchool.DataAccess/ -RUN dotnet restore +RUN dotnet restore ./OutOfSchool.WebApi/OutOfSchool.WebApi.csproj -COPY ./OutOfSchool/ ./ +COPY ./OutOfSchool/OutOfSchool.WebApi/ ./OutOfSchool.WebApi/ +COPY ./OutOfSchool/OutOfSchool.DataAccess/ ./OutOfSchool.DataAccess/ -RUN dotnet build -c $Configuration -o /app +RUN dotnet build ./OutOfSchool.WebApi/OutOfSchool.WebApi.csproj -c $Configuration -o /app FROM builder AS publish ARG Configuration=Release -RUN dotnet publish -c $Configuration -o /app -#TODO: Remove unnecessary projects +RUN dotnet publish ./OutOfSchool.WebApi/OutOfSchool.WebApi.csproj -c $Configuration -o /app FROM base AS final COPY --from=publish /app . EXPOSE 5000 5001 diff --git a/OutOfSchool/IdentityServer/Controllers/AuthController.cs b/OutOfSchool/IdentityServer/Controllers/AuthController.cs index 8edd88a7e7..1d89f1fce6 100644 --- a/OutOfSchool/IdentityServer/Controllers/AuthController.cs +++ b/OutOfSchool/IdentityServer/Controllers/AuthController.cs @@ -1,36 +1,54 @@ using System; using System.Linq; +using System.Threading.Tasks; using IdentityServer4.Services; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; -using System.Threading.Tasks; using OutOfSchool.Services.Models; namespace IdentityServer.Controllers { + /// + /// Handles authentication. + /// Contains methods for log in and sign up. + /// public class AuthController : Controller { - private readonly SignInManager _signInManager; - private readonly UserManager _userManager; - private readonly IIdentityServerInteractionService _interactionService; + private readonly SignInManager signInManager; + private readonly UserManager userManager; + private readonly IIdentityServerInteractionService interactionService; + private readonly RoleManager roleManager; + /// + /// Initializes a new instance of the class. + /// + /// ASP.Net Core Identity User Manager. + /// ASP.Net Core Identity Sign in Manager. + /// ASP.Net Core Identity Role Manager. + /// Identity Server 4 interaction service. public AuthController( UserManager userManager, SignInManager signInManager, + RoleManager roleManager, IIdentityServerInteractionService interactionService) { - _signInManager = signInManager; - _userManager = userManager; - _interactionService = interactionService; + roleManager = roleManager; + signInManager = signInManager; + userManager = userManager; + interactionService = interactionService; } + /// + /// Logging out a user who is authenticated. + /// + /// Identifier of cookie captured the current state needed for sign out. + /// A representing the result of the asynchronous operation. [HttpGet] public async Task Logout(string logoutId) { - await _signInManager.SignOutAsync(); + await signInManager.SignOutAsync(); - var logoutRequest = await _interactionService.GetLogoutContextAsync(logoutId); + var logoutRequest = await interactionService.GetLogoutContextAsync(logoutId); if (string.IsNullOrEmpty(logoutRequest.PostLogoutRedirectUri)) { @@ -40,62 +58,108 @@ public async Task Logout(string logoutId) return Redirect(logoutRequest.PostLogoutRedirectUri); } + /// + /// Generates a view for user to log in. + /// + /// URL used to redirect user back to client. + /// A representing the result of the asynchronous operation. [HttpGet] - public async Task Login(string returnUrl="Login") + public async Task Login(string returnUrl = "Login") { - var externalProviders = await _signInManager.GetExternalAuthenticationSchemesAsync(); + var externalProviders = await signInManager.GetExternalAuthenticationSchemesAsync(); return View(new LoginViewModel { ReturnUrl = returnUrl, - ExternalProviders = externalProviders + ExternalProviders = externalProviders, }); } + /// + /// Authenticate user based on model. + /// + /// View model that contains credentials for logging in. + /// A representing the result of the asynchronous operation. [HttpPost] - public async Task Login(LoginViewModel vm) + public async Task Login(LoginViewModel model) { - // check if the model is valid + if (!ModelState.IsValid) + { + return View(new LoginViewModel + { + ExternalProviders = await signInManager.GetExternalAuthenticationSchemesAsync(), + }); + } - var result = await _signInManager.PasswordSignInAsync(vm.Username, vm.Password, false, false); + var result = await signInManager.PasswordSignInAsync(model.Username, model.Password, false, false); if (result.Succeeded) { - return Redirect(vm.ReturnUrl); + return Redirect(model.ReturnUrl); } - else if (result.IsLockedOut) - { + if (result.IsLockedOut) + { + return BadRequest(); } - return View(); + + ModelState.AddModelError(string.Empty, "Login or password is wrong"); + return View(new LoginViewModel + { + ExternalProviders = await signInManager.GetExternalAuthenticationSchemesAsync(), + }); } + /// + /// Generates a view for user to register. + /// + /// URL used to redirect user back to client. + /// A representing the result of the asynchronous operation. [HttpGet] - public IActionResult Register(string returnUrl="Login") + public IActionResult Register(string returnUrl = "Login") { - return View(new RegisterViewModel { ReturnUrl = returnUrl }); + return View( + new RegisterViewModel { ReturnUrl = returnUrl, AllRoles = roleManager.Roles.ToList() }); } + /// + /// Creates user based on model. + /// + /// View model that contains credentials for signing in. + /// A representing the result of the asynchronous operation. [HttpPost] - public async Task Register(RegisterViewModel vm) + public async Task Register(RegisterViewModel model) { if (!ModelState.IsValid) { - return View(vm); + model.AllRoles = roleManager.Roles.ToList(); + return View(model); } var user = new User() { - UserName = vm.Username, - PhoneNumber = vm.PhoneNumber, CreatingTime = DateTime.Now + UserName = model.Username, + PhoneNumber = model.PhoneNumber, + CreatingTime = DateTime.Now, }; - var result = await _userManager.CreateAsync(user, vm.Password); + var result = await userManager.CreateAsync(user, model.Password); + var selectedRole = roleManager.Roles.First(role => role.Id == model.UserRoleId).Name; if (result.Succeeded) { - await _signInManager.SignInAsync(user, false); + var resultRoleAssign = await userManager.AddToRoleAsync(user, selectedRole); + if (resultRoleAssign.Succeeded) + { + await signInManager.SignInAsync(user, false); - return Redirect(vm.ReturnUrl); + return Redirect(model.ReturnUrl); + } } - return View(); + + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + + return View(model); } } -} +} \ No newline at end of file diff --git a/OutOfSchool/IdentityServer/Dockerfile b/OutOfSchool/IdentityServer/Dockerfile index 9adb7bb6dc..b2a3fe786a 100644 --- a/OutOfSchool/IdentityServer/Dockerfile +++ b/OutOfSchool/IdentityServer/Dockerfile @@ -16,22 +16,20 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* WORKDIR /OoS-Backend -COPY ./OutOfSchool/*.sln ./ -COPY ./OutOfSchool/OutOfSchool.Tests/*.csproj ./OutOfSchool.Tests/ -COPY ./OutOfSchool/OutOfSchool.WebApi/*.csproj ./OutOfSchool.WebApi/ COPY ./OutOfSchool/IdentityServer/*.csproj ./IdentityServer/ COPY ./OutOfSchool/OutOfSchool.DataAccess/*.csproj ./OutOfSchool.DataAccess/ -RUN dotnet restore +RUN dotnet restore ./IdentityServer/OutOfSchool.IdentityServer.csproj -COPY ./OutOfSchool/ ./ +COPY ./OutOfSchool/IdentityServer/ ./IdentityServer/ +COPY ./OutOfSchool/OutOfSchool.DataAccess/ ./OutOfSchool.DataAccess/ -WORKDIR /OoS-Backend -RUN dotnet build -c $Configuration -o /app + +RUN dotnet build ./IdentityServer/OutOfSchool.IdentityServer.csproj -c $Configuration -o /app FROM builder AS publish ARG Configuration=Release -RUN dotnet publish IdentityServer -c $Configuration -o /app +RUN dotnet publish ./IdentityServer/OutOfSchool.IdentityServer.csproj -c $Configuration -o /app FROM base AS final WORKDIR /app diff --git a/OutOfSchool/IdentityServer/OutOfSchool.IdentityServer.csproj b/OutOfSchool/IdentityServer/OutOfSchool.IdentityServer.csproj index 14b9d32dc1..f0b24966d9 100644 --- a/OutOfSchool/IdentityServer/OutOfSchool.IdentityServer.csproj +++ b/OutOfSchool/IdentityServer/OutOfSchool.IdentityServer.csproj @@ -20,17 +20,29 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + + + + true + PreserveNewest + PreserveNewest + + + diff --git a/OutOfSchool/IdentityServer/Program.cs b/OutOfSchool/IdentityServer/Program.cs index fdf7744178..292af3e82c 100644 --- a/OutOfSchool/IdentityServer/Program.cs +++ b/OutOfSchool/IdentityServer/Program.cs @@ -7,12 +7,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using OutOfSchool.IdentityServer; -using OutOfSchool.IdentityServer.Data; using OutOfSchool.Services; namespace IdentityServer @@ -41,6 +38,13 @@ public static void Main(string[] args) context.Database.Migrate(); identityContext.Database.Migrate(); + var manager = scope.ServiceProvider.GetRequiredService>(); + + if (!manager.Roles.Any()) + { + RolesInit(manager); + } + if (!context.Clients.Any()) { foreach (var client in Config.Clients(clientSecret)) @@ -80,6 +84,21 @@ public static void Main(string[] args) host.Run(); } + private static void RolesInit(RoleManager manager) + { + var roles = new IdentityRole[] + { + new IdentityRole {Name = "parent"}, + new IdentityRole {Name = "organization"}, + new IdentityRole {Name = "admin"} + }; + foreach (var role in roles) + { + manager.CreateAsync(role).Wait(); + } + + } + public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => diff --git a/OutOfSchool/IdentityServer/Properties/launchSettings.json b/OutOfSchool/IdentityServer/Properties/launchSettings.json index 8169a1fe20..9bd747875f 100644 --- a/OutOfSchool/IdentityServer/Properties/launchSettings.json +++ b/OutOfSchool/IdentityServer/Properties/launchSettings.json @@ -11,6 +11,7 @@ "IdentityServer": { "commandName": "Project", "launchBrowser": true, + "launchUrl": "Auth/Login", "applicationUrl": "http://localhost:5443;https://localhost:5444", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/OutOfSchool/IdentityServer/ViewModels/RegisterViewModel.cs b/OutOfSchool/IdentityServer/ViewModels/RegisterViewModel.cs index 03615ed766..32292b4f1d 100644 --- a/OutOfSchool/IdentityServer/ViewModels/RegisterViewModel.cs +++ b/OutOfSchool/IdentityServer/ViewModels/RegisterViewModel.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Identity; namespace IdentityServer.Controllers { @@ -10,7 +12,7 @@ public class RegisterViewModel public string Username { get; set; } [Required(ErrorMessage = "Password is required")] [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$", - ErrorMessage = "Password must contain at least one capital, number and symbol.")] + ErrorMessage = "Password must contain at least one capital, number and symbol(@$!%*?&).")] [DataType(DataType.Password)] public string Password { get; set; } [Required(ErrorMessage = "Password confirmation is required")] @@ -23,7 +25,7 @@ public class RegisterViewModel [DataType(DataType.PhoneNumber)] [Required(ErrorMessage = "Phone number is required")] [RegularExpression(@"([0-9]{3})([-]?)([0-9]{3})([-]?)([0-9]{2})([-]?)([0-9]{2})", - ErrorMessage = "Phone number format is incorrect. Example: XXX-XXX-XX-XX")] + ErrorMessage = "Phone number format is incorrect. Example: +38XXX-XXX-XX-XX")] public string PhoneNumber { get; set; } [DataType(DataType.DateTime)] public DateTime CreatingTime { get; set; } @@ -31,5 +33,9 @@ public class RegisterViewModel [DataType(DataType.DateTime)] public DateTime? LastLogin { get; set; } public string ReturnUrl { get; set; } + [Required] + public string UserRoleId { get; set; } + public List AllRoles { get; set; } + } } \ No newline at end of file diff --git a/OutOfSchool/IdentityServer/Views/Auth/Login.cshtml b/OutOfSchool/IdentityServer/Views/Auth/Login.cshtml index 5aaa7f416f..64e23a268f 100644 --- a/OutOfSchool/IdentityServer/Views/Auth/Login.cshtml +++ b/OutOfSchool/IdentityServer/Views/Auth/Login.cshtml @@ -18,20 +18,25 @@ -