diff --git a/OutOfSchool/OutOfSchool.IdentityServer.Tests/Controllers/AccountControllerTests.cs b/OutOfSchool/OutOfSchool.IdentityServer.Tests/Controllers/AccountControllerTests.cs index a0aa96e160..ccce058009 100644 --- a/OutOfSchool/OutOfSchool.IdentityServer.Tests/Controllers/AccountControllerTests.cs +++ b/OutOfSchool/OutOfSchool.IdentityServer.Tests/Controllers/AccountControllerTests.cs @@ -1,4 +1,5 @@ -using IdentityServer4.Models; +using System.Security.Claims; +using IdentityServer4.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; @@ -10,7 +11,9 @@ using Microsoft.AspNetCore.Identity; using OutOfSchool.Services.Models; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; +using OutOfSchool.Common; using OutOfSchool.IdentityServer.Config; using OutOfSchool.RazorTemplatesData.Services; @@ -54,6 +57,16 @@ public void Setup() fakeLocalizer.Object, fakeIdentityServerConfig.Object ); + + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim(IdentityResourceClaimsTypes.Sub, "example"), + }, "mock")); + + accountController.ControllerContext = new ControllerContext() + { + HttpContext = new DefaultHttpContext { User = user } + }; } [Test] diff --git a/OutOfSchool/OutOfSchool.IdentityServer/Controllers/AccountController.cs b/OutOfSchool/OutOfSchool.IdentityServer/Controllers/AccountController.cs index e23aeb1f5f..8dd8f85442 100644 --- a/OutOfSchool/OutOfSchool.IdentityServer/Controllers/AccountController.cs +++ b/OutOfSchool/OutOfSchool.IdentityServer/Controllers/AccountController.cs @@ -28,8 +28,6 @@ public class AccountController : Controller private readonly IRazorViewToStringRenderer renderer; private readonly IStringLocalizer localizer; private readonly IdentityServerConfig identityServerConfig; - private string userId; - private string path; public AccountController( SignInManager signInManager, @@ -50,12 +48,6 @@ public AccountController( this.identityServerConfig = identityServerConfig.Value; } - public override void OnActionExecuting(ActionExecutingContext context) - { - userId = User.GetUserPropertyByClaimType(IdentityResourceClaimsTypes.Sub) ?? "unlogged"; - path = $"{context.HttpContext.Request.Path.Value}[{context.HttpContext.Request.Method}]"; - } - [HttpGet] [Authorize] public IActionResult ChangeEmail(string returnUrl = "Login") @@ -67,19 +59,21 @@ public IActionResult ChangeEmail(string returnUrl = "Login") [Authorize] public async Task ChangeEmail(ChangeEmailViewModel model) { - logger.LogDebug($"{path} started. User(id): {userId}."); + var userId = User.GetUserPropertyByClaimType(IdentityResourceClaimsTypes.Sub) ?? "unlogged"; + var path = $"{HttpContext.Request.Path.Value}[{HttpContext.Request.Method}]"; + logger.LogDebug("{Path} started. User(id): {UserId}", path, userId); if (model.Submit == localizer["Cancel"]) { if (!string.IsNullOrWhiteSpace(model.Email)) { - logger.LogInformation($"{path} Cancel click, but user enter the new email, show confirmation."); + logger.LogInformation("{Path} Cancel click, but user enter the new email, show confirmation", path); return View("Email/CancelChangeEmail"); } else { - logger.LogInformation($"{path} Cancel click, close window."); + logger.LogInformation("{Path} Cancel click, close window", path); return new ContentResult() { @@ -91,15 +85,18 @@ public async Task ChangeEmail(ChangeEmailViewModel model) if (!ModelState.IsValid) { - logger.LogError($"{path} Input data was not valid for User(id): {userId}. " + - $"Entered new Email: {model.Email}"); + logger.LogError( + "{Path} Input data was not valid for User(id): {UserId}. Entered new Email: {Email}", + path, + userId, + model.Email); return View("Email/ChangeEmail", new ChangeEmailViewModel()); } if (model.CurrentEmail != User.Identity.Name.ToLower()) { - logger.LogError($"{path} Current Email mismatch. Entered current Email: {model.CurrentEmail}"); + logger.LogError("{Path} Current Email mismatch. Entered current Email: {CurrentEmail}", path, model.CurrentEmail); model.Submit = "emailMismatch"; return View("Email/ChangeEmail", model); @@ -108,7 +105,7 @@ public async Task ChangeEmail(ChangeEmailViewModel model) var userNewEmail = await userManager.FindByEmailAsync(model.Email); if (userNewEmail != null) { - logger.LogError($"{path} Email already used. Entered new Email: {model.Email}"); + logger.LogError("{Path} Email already used. Entered new Email: {Email}", path, model.Email); model.Submit = "emailUsed"; return View("Email/ChangeEmail", model); @@ -122,11 +119,12 @@ public async Task ChangeEmail(ChangeEmailViewModel model) [HttpGet] public async Task ConfirmChangeEmail(string userId, string email, string token) { - logger.LogDebug($"{path} started. User(id): {userId}."); + var path = $"{HttpContext.Request.Path.Value}[{HttpContext.Request.Method}]"; + logger.LogDebug("{Path} started. User(id): {UserId}", path, userId); if (userId == null || email == null || token == null) { - logger.LogError($"{path} Parameters were not valid. User(id): {userId}."); + logger.LogError("{Path} Parameters were not valid. User(id): {UserId}", path, userId); return BadRequest("One or more parameters are null."); } @@ -134,7 +132,7 @@ public async Task ConfirmChangeEmail(string userId, string email, var user = await userManager.FindByIdAsync(userId); if (user == null) { - logger.LogError($"{path} User(id): {userId} was not found."); + logger.LogError("{Path} User(id): {UserId} was not found", path, userId); return NotFound($"Changing email for user with ID: '{userId}' was not allowed."); } @@ -142,8 +140,11 @@ public async Task ConfirmChangeEmail(string userId, string email, var result = await userManager.ChangeEmailAsync(user, email, token); if (!result.Succeeded) { - logger.LogError($"{path} Changing email was failed for User(id): {user.Id}. " + - $"{string.Join(System.Environment.NewLine, result.Errors.Select(e => e.Description))}"); + logger.LogError( + "{Path} Changing email was failed for User(id): {UserId}. {Errors}", + path, + user.Id, + string.Join(Environment.NewLine, result.Errors.Select(e => e.Description))); return BadRequest(); } @@ -151,13 +152,16 @@ public async Task ConfirmChangeEmail(string userId, string email, var setUserNameResult = await userManager.SetUserNameAsync(user, email); if (!setUserNameResult.Succeeded) { - logger.LogError($"{path} Setting username was failed for User(id): {userId}. " + - $"{string.Join(System.Environment.NewLine, result.Errors.Select(e => e.Description))}"); + logger.LogError( + "{Path} Setting username was failed for User(id): {UserId}. {Errors}", + path, + userId, + string.Join(Environment.NewLine, result.Errors.Select(e => e.Description))); return BadRequest(); } - logger.LogInformation($"{path} Successfully logged. User(id): {userId}"); + logger.LogInformation("{Path} Successfully logged. User(id): {UserId}", path, userId); await signInManager.RefreshSignInAsync(user); return View("Email/ConfirmChangeEmail"); @@ -167,7 +171,9 @@ public async Task ConfirmChangeEmail(string userId, string email, [Authorize] public async Task SendConfirmEmail() { - logger.LogDebug($"{path} started. User(id): {userId}."); + var userId = User.GetUserPropertyByClaimType(IdentityResourceClaimsTypes.Sub) ?? "unlogged"; + var path = $"{HttpContext.Request.Path.Value}[{HttpContext.Request.Method}]"; + logger.LogDebug("{Path} started. User(id): {UserId}", path, userId); var user = await userManager.FindByEmailAsync(User.Identity.Name); var token = await userManager.GenerateEmailConfirmationTokenAsync(user); @@ -185,36 +191,39 @@ public async Task SendConfirmEmail() var content = await renderer.GetHtmlPlainStringAsync(RazorTemplates.ResetPassword, userActionViewModel); await emailSender.SendAsync(email, subject, content); - logger.LogInformation($"Confirmation message was sent. User(id): {userId}."); + logger.LogInformation("Confirmation message was sent. User(id): {UserId}", userId); return Ok(); } [HttpGet] - public async Task EmailConfirmation(string token) + public async Task EmailConfirmation(string email, string token) { - logger.LogDebug($"{path} started. User(id): {userId}."); + var path = $"{HttpContext.Request.Path.Value}[{HttpContext.Request.Method}]"; + logger.LogDebug("{Path} started. User(email): {Email}", path, email); - if (userId == null || token == null) + if (email == null || token == null) { - logger.LogError($"{path} Parameters were not valid. User(id): {userId}."); + logger.LogError("{Path} Parameters were not valid. User(email): {Email}", path, email); return BadRequest("One or more parameters are null."); } - var user = await userManager.FindByIdAsync(userId); + var user = await userManager.FindByEmailAsync(email); if (user == null) { - logger.LogError($"{path} User with UserId: {userId} was not found."); + logger.LogError("{Path} User with Email: {Email} was not found", path, email); - return NotFound($"Unable to load user with ID: '{userId}'."); + // TODO: add nice page with redirect to register + // TODO: saying you need to register first :) + return NotFound("Wrong email"); } var purpose = UserManager.ConfirmEmailTokenPurpose; var checkToken = await userManager.VerifyUserTokenAsync(user, userManager.Options.Tokens.EmailConfirmationTokenProvider, purpose, token); if (!checkToken) { - logger.LogError($"{path} Token is not valid for user: {user.Id}"); + logger.LogError("{Path} Token is not valid for user: {UserId}", path, user.Id); return View("Email/ConfirmEmailFailed", localizer["Invalid email confirmation token"]); } @@ -222,17 +231,20 @@ public async Task EmailConfirmation(string token) var result = await userManager.ConfirmEmailAsync(user, token); if (!result.Succeeded) { - logger.LogError($"{path} Email сonfirmation was failed for User(id): {userId} " + - $"{string.Join(System.Environment.NewLine, result.Errors.Select(e => e.Description))}"); + logger.LogError( + "{Path} Email сonfirmation was failed for User(id): {UserId}. {Errors}", + path, + user.Id, + string.Join(Environment.NewLine, result.Errors.Select(e => e.Description))); return BadRequest(); } - logger.LogInformation($"{path} Email was confirmed. User(id): {userId}."); + logger.LogInformation("{Path} Email was confirmed. User(id): {UserId}", path, user.Id); var redirectUrl = identityServerConfig.RedirectFromEmailConfirmationUrl; - return string.IsNullOrEmpty(redirectUrl) ? Ok() : Redirect(redirectUrl); + return string.IsNullOrEmpty(redirectUrl) ? Ok("Email confirmed.") : Redirect(redirectUrl); } [HttpGet] @@ -244,11 +256,13 @@ public IActionResult ForgotPassword(string returnUrl = "Login") [HttpPost] public async Task ForgotPassword(ForgotPasswordViewModel model) { - logger.LogDebug($"{path} started. User(id): {userId}"); + var userId = User.GetUserPropertyByClaimType(IdentityResourceClaimsTypes.Sub) ?? "unlogged"; + var path = $"{HttpContext.Request.Path.Value}[{HttpContext.Request.Method}]"; + logger.LogDebug("{Path} started. User(id): {UserId}", path, userId); if (!ModelState.IsValid) { - logger.LogError($"{path} Input data was not valid."); + logger.LogError("{Path} Input data was not valid", path); return View("Password/ForgotPassword", new ForgotPasswordViewModel()); } @@ -257,14 +271,20 @@ public async Task ForgotPassword(ForgotPasswordViewModel model) if (user == null) { - logger.LogError($"{path} User with Email: {model.Email} was not found or Email was not confirmed."); + logger.LogError( + "{Path} User with Email: {Email} was not found or Email was not confirmed", + path, + model.Email); ModelState.AddModelError(string.Empty, "Користувача з такою адресою не знайдено."); return View("Password/ForgotPassword", model); } if (!await userManager.IsEmailConfirmedAsync(user)) { - logger.LogError($"{path} User with Email: {model.Email} was not found or Email was not confirmed."); + logger.LogError( + "{Path} User with Email: {Email} was not found or Email was not confirmed", + path, + model.Email); ModelState.AddModelError(string.Empty, "Ця електронна адреса не підтверджена"); return View("Password/ForgotPassword", model); } @@ -284,7 +304,7 @@ public async Task ForgotPassword(ForgotPasswordViewModel model) var content = await renderer.GetHtmlPlainStringAsync(RazorTemplates.ResetPassword, userActionViewModel); await emailSender.SendAsync(email, subject, content); - logger.LogInformation($"{path} Message to change password was sent. User(id): {user.Id}."); + logger.LogInformation("{Path} Message to change password was sent. User(id): {UserId}", path, user.Id); return View("Password/ForgotPasswordConfirmation"); } @@ -292,18 +312,23 @@ public async Task ForgotPassword(ForgotPasswordViewModel model) [HttpGet] public async Task ResetPassword(string token = null, string email = null) { - logger.LogDebug($"{path} started. User(id): {userId}"); + var userId = User.GetUserPropertyByClaimType(IdentityResourceClaimsTypes.Sub) ?? "unlogged"; + var path = $"{HttpContext.Request.Path.Value}[{HttpContext.Request.Method}]"; + logger.LogDebug("{Path} started. User(id): {UserId}", path, userId); if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(email)) { - logger.LogError($"{path} Token or email was not supplied for reset password. User(id): {userId}"); + logger.LogError( + "{Path} Token or email was not supplied for reset password. User(id): {UserId}", + path, + userId); return BadRequest("A token and email must be supplied for password reset."); } var user = await userManager.FindByEmailAsync(email); if (user == null) { - logger.LogError($"{path} User not found. Email: {email}"); + logger.LogError("{Path} User not found. Email: {Email}", path, email); // If message will be "user not found", someone can use this url to check registered emails. I decide to show "invalid token" return View("Password/ResetPasswordFailed", localizer["Change password invalid token"]); @@ -313,7 +338,7 @@ public async Task ResetPassword(string token = null, string email var checkToken = await userManager.VerifyUserTokenAsync(user, userManager.Options.Tokens.PasswordResetTokenProvider, purpose, token); if (!checkToken) { - logger.LogError($"{path} Token is not valid for user: {user.Id}"); + logger.LogError("{Path} Token is not valid for user: {UserId}", path, user.Id); return View("Password/ResetPasswordFailed", localizer["Change password invalid token"]); } @@ -324,11 +349,13 @@ public async Task ResetPassword(string token = null, string email [HttpPost] public async Task ResetPassword(ResetPasswordViewModel model) { - logger.LogDebug($"{path} started. User(id): {userId}"); + var userId = User.GetUserPropertyByClaimType(IdentityResourceClaimsTypes.Sub) ?? "unlogged"; + var path = $"{HttpContext.Request.Path.Value}[{HttpContext.Request.Method}]"; + logger.LogDebug("{Path} started. User(id): {UserId}", path, userId); if (!ModelState.IsValid) { - logger.LogError($"{path} Input data was not valid. User(id): {userId}"); + logger.LogError("{Path} Input data was not valid. User(id): {UserId}", path, userId); return View("Password/ResetPassword", new ResetPasswordViewModel()); } @@ -336,7 +363,11 @@ public async Task ResetPassword(ResetPasswordViewModel model) var user = await userManager.FindByEmailAsync(model.Email); if (user == null) { - logger.LogError($"{path} User with Email:{model.Email} was not found. User(id): {userId}"); + logger.LogError( + "{Path} User with Email:{Email} was not found. User(id): {UserId}", + path, + model.Email, + userId); return View("Password/ResetPasswordFailed", localizer["Change password failed"]); } @@ -344,13 +375,16 @@ public async Task ResetPassword(ResetPasswordViewModel model) var result = await userManager.ResetPasswordAsync(user, model.Token, model.Password); if (result.Succeeded) { - logger.LogInformation($"{path} Password was successfully reseted. User(id): {user.Id}"); + logger.LogInformation("{Path} Password was successfully reset. User(id): {UserId}", path, user.Id); return View("Password/ResetPasswordConfirmation"); } - logger.LogError($"{path} Reset password was failed. User(id): {userId}. " + - $"{string.Join(System.Environment.NewLine, result.Errors.Select(e => e.Description))}"); + logger.LogError( + "{Path} Reset password was failed. User(id): {UserId}. {Errors}", + path, + userId, + string.Join(Environment.NewLine, result.Errors.Select(e => e.Description))); return View("Password/ResetPasswordFailed", localizer["Change password failed"]); } @@ -366,11 +400,13 @@ public IActionResult ChangePassword(string returnUrl = "Login") [Authorize] public async Task ChangePassword(ChangePasswordViewModel model) { - logger.LogDebug($"{path} started. User(id): {userId}."); + var userId = User.GetUserPropertyByClaimType(IdentityResourceClaimsTypes.Sub) ?? "unlogged"; + var path = $"{HttpContext.Request.Path.Value}[{HttpContext.Request.Method}]"; + logger.LogDebug("{Path} started. User(id): {UserId}", path, userId); if (!ModelState.IsValid) { - logger.LogError($"{path} Input data was not valid. User(id): {userId}."); + logger.LogError("{Path} Input data was not valid. User(id): {UserId}", path, userId); return View("Password/ChangePassword"); } @@ -379,13 +415,16 @@ public async Task ChangePassword(ChangePasswordViewModel model) var result = await userManager.ChangePasswordAsync(user, model.CurrentPassword, model.NewPassword); if (result.Succeeded) { - logger.LogInformation($"{path} Password was changed. User(id): {userId}."); + logger.LogInformation("{Path} Password was changed. User(id): {UserId}", path, userId); return View("Password/ChangePasswordConfirmation"); } - logger.LogError($"{path} Changing password was failed for User(id): {userId}." + - $"{string.Join(System.Environment.NewLine, result.Errors.Select(e => e.Description))}"); + logger.LogError( + "{Path} Changing password was failed for User(id): {UserId}. {Errors}", + path, + userId, + string.Join(Environment.NewLine, result.Errors.Select(e => e.Description))); ModelState.AddModelError(string.Empty, localizer["Change password failed"]); return View("Password/ChangePassword"); diff --git a/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230203165048_AddIsBlockedColumnToWorkshop.cs b/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230203165048_AddIsBlockedColumnToWorkshop.cs index 25ea910207..7f46930c63 100644 --- a/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230203165048_AddIsBlockedColumnToWorkshop.cs +++ b/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230203165048_AddIsBlockedColumnToWorkshop.cs @@ -2,25 +2,24 @@ #nullable disable -namespace OutOfSchool.IdentityServer.Data.Migrations.OutOfSchoolMigrations +namespace OutOfSchool.IdentityServer.Data.Migrations.OutOfSchoolMigrations; + +public partial class AddIsBlockedColumnToWorkshop : Migration { - public partial class AddIsBlockedColumnToWorkshop : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "IsBlocked", - table: "Workshops", - type: "tinyint(1)", - nullable: false, - defaultValue: false); - } + migrationBuilder.AddColumn( + name: "IsBlocked", + table: "Workshops", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "IsBlocked", - table: "Workshops"); - } + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsBlocked", + table: "Workshops"); } -} +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230217224301_AddIndexesToRatingAndNotification.cs b/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230217224301_AddIndexesToRatingAndNotification.cs index 2a460b5fcf..e61b1532ea 100644 --- a/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230217224301_AddIndexesToRatingAndNotification.cs +++ b/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230217224301_AddIndexesToRatingAndNotification.cs @@ -2,52 +2,51 @@ #nullable disable -namespace OutOfSchool.IdentityServer.Data.Migrations.OutOfSchoolMigrations +namespace OutOfSchool.IdentityServer.Data.Migrations.OutOfSchoolMigrations; + +public partial class AddIndexesToRatingAndNotification : Migration { - public partial class AddIndexesToRatingAndNotification : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( + migrationBuilder.AlterColumn( name: "UserId", table: "Notifications", type: "varchar(255)", nullable: false, oldClrType: typeof(string), oldType: "longtext") - .Annotation("MySql:CharSet", "utf8mb4") - .OldAnnotation("MySql:CharSet", "utf8mb4"); + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.CreateIndex( - name: "IX_Ratings_EntityId", - table: "Ratings", - column: "EntityId"); + migrationBuilder.CreateIndex( + name: "IX_Ratings_EntityId", + table: "Ratings", + column: "EntityId"); - migrationBuilder.CreateIndex( - name: "IX_Notifications_UserId", - table: "Notifications", - column: "UserId"); - } + migrationBuilder.CreateIndex( + name: "IX_Notifications_UserId", + table: "Notifications", + column: "UserId"); + } - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_Ratings_EntityId", - table: "Ratings"); + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Ratings_EntityId", + table: "Ratings"); - migrationBuilder.DropIndex( - name: "IX_Notifications_UserId", - table: "Notifications"); + migrationBuilder.DropIndex( + name: "IX_Notifications_UserId", + table: "Notifications"); - migrationBuilder.AlterColumn( + migrationBuilder.AlterColumn( name: "UserId", table: "Notifications", type: "longtext", nullable: false, oldClrType: typeof(string), oldType: "varchar(255)") - .Annotation("MySql:CharSet", "utf8mb4") - .OldAnnotation("MySql:CharSet", "utf8mb4"); - } + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); } -} +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230307201444_AddIsGovernmentToInstitution.cs b/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230307201444_AddIsGovernmentToInstitution.cs index 9eb047462b..def7758e1f 100644 --- a/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230307201444_AddIsGovernmentToInstitution.cs +++ b/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230307201444_AddIsGovernmentToInstitution.cs @@ -2,43 +2,42 @@ #nullable disable -namespace OutOfSchool.IdentityServer.Data.Migrations.OutOfSchoolMigrations +namespace OutOfSchool.IdentityServer.Data.Migrations.OutOfSchoolMigrations; + +public partial class AddIsGovernmentToInstitution : Migration { - public partial class AddIsGovernmentToInstitution : Migration + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsGovernment", + table: "Institutions", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateIndex( + name: "IX_QuartzJobs_IsDeleted", + table: "QuartzJobs", + column: "IsDeleted"); + + migrationBuilder.CreateIndex( + name: "IX_AverageRatings_IsDeleted", + table: "AverageRatings", + column: "IsDeleted"); + } + + protected override void Down(MigrationBuilder migrationBuilder) { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "IsGovernment", - table: "Institutions", - type: "tinyint(1)", - nullable: false, - defaultValue: false); - - migrationBuilder.CreateIndex( - name: "IX_QuartzJobs_IsDeleted", - table: "QuartzJobs", - column: "IsDeleted"); - - migrationBuilder.CreateIndex( - name: "IX_AverageRatings_IsDeleted", - table: "AverageRatings", - column: "IsDeleted"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_QuartzJobs_IsDeleted", - table: "QuartzJobs"); - - migrationBuilder.DropIndex( - name: "IX_AverageRatings_IsDeleted", - table: "AverageRatings"); - - migrationBuilder.DropColumn( - name: "IsGovernment", - table: "Institutions"); - } + migrationBuilder.DropIndex( + name: "IX_QuartzJobs_IsDeleted", + table: "QuartzJobs"); + + migrationBuilder.DropIndex( + name: "IX_AverageRatings_IsDeleted", + table: "AverageRatings"); + + migrationBuilder.DropColumn( + name: "IsGovernment", + table: "Institutions"); } -} +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230315225321_AddOperationsWithObjects.cs b/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230315225321_AddOperationsWithObjects.cs index b2244b3b77..96b33dcd42 100644 --- a/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230315225321_AddOperationsWithObjects.cs +++ b/OutOfSchool/OutOfSchool.IdentityServer/Data/Migrations/OutOfSchoolMigrations/20230315225321_AddOperationsWithObjects.cs @@ -3,13 +3,13 @@ #nullable disable -namespace OutOfSchool.IdentityServer.Data.Migrations.OutOfSchoolMigrations +namespace OutOfSchool.IdentityServer.Data.Migrations.OutOfSchoolMigrations; + +public partial class AddOperationsWithObjects : Migration { - public partial class AddOperationsWithObjects : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( + migrationBuilder.CreateTable( name: "OperationsWithObjects", columns: table => new { @@ -27,33 +27,32 @@ protected override void Up(MigrationBuilder migrationBuilder) { table.PrimaryKey("PK_OperationsWithObjects", x => x.Id); }) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.CreateIndex( - name: "IX_OperationsWithObjects_EntityId", - table: "OperationsWithObjects", - column: "EntityId"); - - migrationBuilder.CreateIndex( - name: "IX_OperationsWithObjects_EntityType", - table: "OperationsWithObjects", - column: "EntityType"); - - migrationBuilder.CreateIndex( - name: "IX_OperationsWithObjects_OperationType", - table: "OperationsWithObjects", - column: "OperationType"); - - migrationBuilder.CreateIndex( - name: "IX_OperationsWithObjects_RowSeparator", - table: "OperationsWithObjects", - column: "RowSeparator"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "OperationsWithObjects"); - } + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_OperationsWithObjects_EntityId", + table: "OperationsWithObjects", + column: "EntityId"); + + migrationBuilder.CreateIndex( + name: "IX_OperationsWithObjects_EntityType", + table: "OperationsWithObjects", + column: "EntityType"); + + migrationBuilder.CreateIndex( + name: "IX_OperationsWithObjects_OperationType", + table: "OperationsWithObjects", + column: "OperationType"); + + migrationBuilder.CreateIndex( + name: "IX_OperationsWithObjects_RowSeparator", + table: "OperationsWithObjects", + column: "RowSeparator"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OperationsWithObjects"); } -} +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.IdentityServer/appsettings.Compose.json b/OutOfSchool/OutOfSchool.IdentityServer/appsettings.Compose.json deleted file mode 100644 index 386d45b5d2..0000000000 --- a/OutOfSchool/OutOfSchool.IdentityServer/appsettings.Compose.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Serilog": { - "Using": [ "Serilog.Sinks.Console" ], - "WriteTo": [ - { - "Name": "Console" - } - ] - }, - "ConnectionStrings": { - "DefaultConnection": "Server=sql-server; Database=OutOfSchool;User Id=oos_auth;Password=Oos-password1" - }, - "IdentityAccessConfig": { - "AdditionalIdentityClients": [ - { - "ClientId": "angular", - "RedirectUris": [ - "http://localhost:4200", - "https://oos.local" - ], - "PostLogoutRedirectUris": [ - "http://localhost:4200", - "https://oos.local" - ], - "AllowedCorsOrigins": [ - "http://localhost:4200", - "https://oos.local" - ] - }, - { - "ClientId": "Swagger", - "RedirectUris": [ - "https://oos.local/webapi/swagger/oauth2-redirect.html" - ], - "PostLogoutRedirectUris": [ - "https://oos.local/webapi/swagger/oauth2-redirect.html" - ], - "AllowedCorsOrigins": [ - "https://oos.local" - ] - } - ] - }, - "Issuer": { - "Uri": "https://oos.local/identity" - } -} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.IdentityServer/appsettings.Release.json b/OutOfSchool/OutOfSchool.IdentityServer/appsettings.Release.json index 19682d92c7..772acd05eb 100644 --- a/OutOfSchool/OutOfSchool.IdentityServer/appsettings.Release.json +++ b/OutOfSchool/OutOfSchool.IdentityServer/appsettings.Release.json @@ -1,3 +1,81 @@ { - + "ConnectionStringsOverride": { + "DefaultConnection": { + "UseOverride": true, + "Server": "mysql", + "Port": 3306, + "Database": "replace_me", + "UserId": "replace_me", + "Password": "replace_me", + "GuidFormat": "Binary16" + } + }, + "AppDefaults": { + "Version": "replace_me" + }, + "AllowedHosts": "*", + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Exceptions" ], + "WriteTo": [ + { + "Name": "Console" + } + ] + }, + "Identity": { + "Authority": "https://pozashkillia.iea.gov.ua/auth", + "RedirectToStartPageUrl": "https://pozashkillia.iea.gov.ua/", + "RedirectFromEmailConfirmationUrl": "https://pozashkillia.iea.gov.ua/#/login" + }, + "Email": { + "AddressFrom": "replace_me", + "NameFrom": "Позашкілля", + "Enabled": true, + "SendGridKey": "replace_me" + }, + "IdentityAccessConfig": { + "AdditionalIdentityClients": [ + { + "ClientId": "angular", + "RedirectUris": [ + "https://pozashkillia.iea.gov.ua" + ], + "PostLogoutRedirectUris": [ + "https://pozashkillia.iea.gov.ua" + ], + "AllowedCorsOrigins": [ + "https://pozashkillia.iea.gov.ua" + ] + }, + { + "ClientId": "Swagger", + "RedirectUris": [ + "https://pozashkillia.iea.gov.ua/web/swagger/oauth2-redirect.html" + ], + "PostLogoutRedirectUris": [ + "https://pozashkillia.iea.gov.ua/web/swagger/oauth2-redirect.html" + ], + "AllowedCorsOrigins": [ + "https://pozashkillia.iea.gov.ua" + ] + } + ] + }, + "ReverseProxy": { + "BasePath": "auth" + }, + "Issuer": { + "Uri": "https://pozashkillia.iea.gov.ua/auth", + "CertificateExpirationDays": 365 + }, + "GRPC": { + "Enabled": false + }, + "ExternalUris": { + "AngularClientScope": { + "Login": "https://pozashkillia.iea.gov.ua/#/login" + } + }, + "MySQLServerVersion": "8.0.32", + "CheckConnectivityDelay": 5000 } \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.IdentityServer/appsettings.json b/OutOfSchool/OutOfSchool.IdentityServer/appsettings.json index 5ede01b1f1..dafdf1d58a 100644 --- a/OutOfSchool/OutOfSchool.IdentityServer/appsettings.json +++ b/OutOfSchool/OutOfSchool.IdentityServer/appsettings.json @@ -89,6 +89,6 @@ "Login": "https://oos.dmytrominochkin.cloud/#/login" } }, - "MySQLServerVersion": "8.0.27", + "MySQLServerVersion": "8.0.32", "CheckConnectivityDelay": 5000 } \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.WebApi/appsettings.Compose.json b/OutOfSchool/OutOfSchool.WebApi/appsettings.Compose.json deleted file mode 100644 index dd09316b08..0000000000 --- a/OutOfSchool/OutOfSchool.WebApi/appsettings.Compose.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "Serilog": { - "Using": [ "Serilog.Sinks.Console" ], - "WriteTo": [ - { - "Name": "Console" - } - ] - }, - "Identity": { - "Authority": "https://oos.local/identity" - }, - "Swagger": { - "IdentityAccess": { - "BaseUrl": "https://oos.local/identity" - } - }, - "ConnectionStrings": { - "DefaultConnection": "Server=sql-server; Database=OutOfSchool;User Id=oos_api;Password=Oos-password1" - }, - "Elasticsearch": { - "EnsureIndex": true, - "EnableDebugMode": false, - "WorkshopIndexName": "workshop", - "Urls": [ - "http://elasticsearch:9200/" - ], - "User": "admin", - "Password": "admin", - "SynchronizationScheduler": { - "OperationsPerTask": 10, - "DelayBetweenTasksInMilliseconds": 60000 - } - } -} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.WebApi/appsettings.Release.json b/OutOfSchool/OutOfSchool.WebApi/appsettings.Release.json index 077404aaa4..8b65cc0963 100644 --- a/OutOfSchool/OutOfSchool.WebApi/appsettings.Release.json +++ b/OutOfSchool/OutOfSchool.WebApi/appsettings.Release.json @@ -1,3 +1,149 @@ { - -} \ No newline at end of file + "ConnectionStringsOverride": { + "DefaultConnection": { + "UseOverride": true, + "Server": "mysql", + "Port": 3306, + "Database": "replace_me", + "UserId": "replace_me", + "Password": "replace_me", + "GuidFormat": "Binary16" + } + }, + "AppDefaults": { + "City": "Київ", + "AccessLogEnabled": true, + "Version": "replace_me" + }, + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Exceptions" ], + "WriteTo": [ + { + "Name": "Console", + "Args": { + "formatter": "Serilog.Formatting.Elasticsearch.ElasticsearchJsonFormatter, Serilog.Formatting.Elasticsearch" + } + } + ] + }, + "ProviderAdmin": { + "MaxNumberAdmins": 100 + }, + + "Communication": { + "TimeoutInSeconds": 15, + "MaxNumberOfRetries": 3, + "ClientName": "WebApi" + }, + "Identity": { + "Authority": "https://pozashkillia.iea.gov.ua/auth" + }, + "Swagger": { + "IdentityAccess": { + "BaseUrl": "https://pozashkillia.iea.gov.ua/auth" + }, + "ApiInfo": { + "Title": "Позашкілля API", + "Description": "Позашкілля API", + "Contact": { + "FullName": "Admin", + "Email": "PozashkilliaUA@gmail.com" + }, + "DeprecationMessage": "This API version has been deprecated." + }, + "SecurityDefinitions": { + "Title": "Identity server", + "Description": "Identity server", + "AccessScopes": [ + "openid", + "outofschoolapi.read", + "offline_access" + ] + } + }, + "AllowedHosts": "*", + + "AllowedCorsOrigins": "https://pozashkillia.iea.gov.ua", + "ReverseProxy": { + "BasePath": "web" + }, + + "FeatureManagement": { + "Release1": true, + "Release2": true, + "Release3": false, + "Images": false + }, + + "Notifications": { + "Enabled": true, + "Grouped": [ + "Application" + ] + }, + + "Parent": { + "ChildrenMaxNumber": 20 + }, + + "ApplicationsConstraints": { + "ApplicationsLimit": 2, + "ApplicationsLimitDays": 7 + }, + + "GRPC": { + "Enabled": false + }, + + "MySQLServerVersion": "8.0.32", + + "Redis": { + "Enabled": true, + "Server": "redis-master", + "Port": 6379, + "Password": "replace_me", + "AbsoluteExpirationRelativeToNowInterval": "01:00:00", + "SlidingExpirationInterval": "00:10:00", + "CheckAlivePollingInterval": "00:01:00" + }, + "Elasticsearch": { + "EnsureIndex": true, + "EnableDebugMode": false, + "WorkshopIndexName": "workshop", + "Urls": [ + "http://elastic-es-http:9200/" + ], + "User": "replace_me", + "Password": "replace_me", + "SynchronizationScheduler": { + "OperationsPerTask": 10, + "DelayBetweenTasksInMilliseconds": 600000 + } + }, + "Quartz": { + "ConnectionStringKey": "DefaultConnection", + "CronSchedules": { + "StatisticReportsMakingCronScheduleString": "0 0 0 * * ?", + "ApplicationStatusChangingCronScheduleString": "0 0 0 1 OCT ? *", + "NotificationsClearingCronScheduleString": "0 0 0 * * ?", + "AverageRatingCalculatingCronScheduleString": "0 0 4 * * ?" + } + }, + + "ChangesLog": { + "TrackedProperties": { + "Provider": [ "FullTitle", "EdrpouIpn", "Director", "LegalAddress", "InstitutionId" ], + "Application": [ "Status" ] + } + }, + "GeoCoding": { + "BaseUrl": "https://api.visicom.ua/data-api/5.0/uk/geocode.json", + "ApiKey": "replace_me", + "Radius": 50 + }, + "StatisticReports": { + "UseExternalStorage": false + } +} + + diff --git a/k8s/outofschool/Chart.lock b/k8s/outofschool/Chart.lock index 2c770f1246..a2434c35e7 100644 --- a/k8s/outofschool/Chart.lock +++ b/k8s/outofschool/Chart.lock @@ -1,12 +1,12 @@ dependencies: - name: webapp repository: file://../webapp - version: 0.1.0 + version: 0.2.1 - name: webapp repository: file://../webapp - version: 0.1.0 + version: 0.2.1 - name: webapp repository: file://../webapp - version: 0.1.0 -digest: sha256:d379fea11d030b2540f33bfc17dd089940d531381ffc887c2d0f67d863062685 -generated: "2023-03-26T19:30:59.530932+03:00" + version: 0.2.1 +digest: sha256:db251c2b96dddd61828e77e7c7cc1b7f66415475d1ba8859bb8a41454918991b +generated: "2023-04-18T16:47:28.080993+03:00" diff --git a/k8s/outofschool/Chart.yaml b/k8s/outofschool/Chart.yaml index a57356d001..b290ae1db0 100644 --- a/k8s/outofschool/Chart.yaml +++ b/k8s/outofschool/Chart.yaml @@ -2,40 +2,29 @@ apiVersion: v2 name: outofschool description: Umbrella chart for OutOfSchool application to manage its dependencies -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. type: application -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.2.0 +version: 1.0.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "fa5778e" +appVersion: "release-1.0.0" dependencies: - name: webapp - version: 0.1.0 + version: 0.2.1 repository: "file://../webapp" condition: identity.enabled alias: identity - name: webapp - version: 0.1.0 + version: 0.2.1 repository: "file://../webapp" condition: webapi.enabled alias: webapi - name: webapp - version: 0.1.0 + version: 0.2.1 repository: "file://../webapp" condition: frontend.enabled alias: frontend diff --git a/k8s/outofschool/charts/webapp-0.1.0.tgz b/k8s/outofschool/charts/webapp-0.1.0.tgz deleted file mode 100644 index b9541a5b08..0000000000 Binary files a/k8s/outofschool/charts/webapp-0.1.0.tgz and /dev/null differ diff --git a/k8s/outofschool/charts/webapp-0.2.1.tgz b/k8s/outofschool/charts/webapp-0.2.1.tgz new file mode 100644 index 0000000000..32bee13a2a Binary files /dev/null and b/k8s/outofschool/charts/webapp-0.2.1.tgz differ diff --git a/k8s/outofschool/values.yaml b/k8s/outofschool/values.yaml index 61dbeaa9d4..9ceb3eafd4 100644 --- a/k8s/outofschool/values.yaml +++ b/k8s/outofschool/values.yaml @@ -4,36 +4,19 @@ global: {} -# TODO: Configure global pull secrets -imagePullSecrets: [] - -# TODO: Do we need overrides? nameOverride: "" fullnameOverride: "" -# TODO: remove after fixing test connection -service: - port: 8080 - -# TODO: Already have a service account for app, do we need a global one? -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" webapi: enabled: true image: - repository: gcr.io/gcp101292-pozashkillya/oos-api + registry: gcr.io/gcp101292-pozashkillya + repository: oos-api pullPolicy: IfNotPresent tag: testing containerPort: 8080 configuration: extraEnvironment: - # ReverseProxy__BasePath: /webapi ASPNETCORE_ENVIRONMENT: Kubernetes AppDefaults__Version: testing secrets: @@ -68,7 +51,8 @@ webapi: identity: enabled: false image: - repository: gcr.io/gcp101292-pozashkillya/oos-auth + registry: gcr.io/gcp101292-pozashkillya + repository: oos-auth pullPolicy: IfNotPresent tag: 1.0.0-RC1 extraEnvironment: {} @@ -81,6 +65,7 @@ identity: frontend: enabled: false image: - repository: gcr.io/gcp101292-pozashkillya/oos-frontend + registry: gcr.io/gcp101292-pozashkillya + repository: oos-frontend pullPolicy: IfNotPresent tag: 1.0.0-RC1 diff --git a/k8s/webapp/Chart.yaml b/k8s/webapp/Chart.yaml index 0c80347719..c1d8a0ae0d 100644 --- a/k8s/webapp/Chart.yaml +++ b/k8s/webapp/Chart.yaml @@ -2,23 +2,8 @@ apiVersion: v2 name: webapp description: A Helm chart for Kubernetes -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. type: application -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.2.1 -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. appVersion: "" diff --git a/k8s/webapp/templates/deployment.yaml b/k8s/webapp/templates/deployment.yaml index dd9a46e890..85c4744096 100644 --- a/k8s/webapp/templates/deployment.yaml +++ b/k8s/webapp/templates/deployment.yaml @@ -54,7 +54,7 @@ spec: {{- end }} containers: - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy | quote }} {{- if .Values.containerSecurity.enabled }} securityContext: diff --git a/k8s/webapp/templates/serviceaccount.yaml b/k8s/webapp/templates/serviceaccount.yaml index 03a8eef652..8fa8bee5d5 100644 --- a/k8s/webapp/templates/serviceaccount.yaml +++ b/k8s/webapp/templates/serviceaccount.yaml @@ -10,4 +10,8 @@ metadata: annotations: {{- toYaml . | nindent 4 }} {{- end }} +{{- with .Values.serviceAccount.imagePullSecrets }} +imagePullSecrets: + {{- toYaml . | nindent 2 }} +{{- end }} {{- end }} diff --git a/k8s/webapp/values.yaml b/k8s/webapp/values.yaml index 7d63ef5d4d..0032101a2b 100644 --- a/k8s/webapp/values.yaml +++ b/k8s/webapp/values.yaml @@ -32,7 +32,8 @@ configuration: secrets: [] image: - repository: nginx + registry: "" + repository: "" pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" @@ -48,6 +49,8 @@ serviceAccount: # If not set and create is true, a name is generated using the fullname template name: "" + imagePullSecrets: [] + podAnnotations: {} service: