From 22261f42cad2ea7a5e1a8ea6db5c570e745abfd7 Mon Sep 17 00:00:00 2001 From: Reginald Cherenfant Jasmin Date: Fri, 18 Aug 2023 15:30:34 -0400 Subject: [PATCH] User Role and Authorization features added --- .../DamageAssesment.Api.Answers.csproj | 1 + .../appsettings.json | 3 + .../DamageAssesment.Api.Attachments.csproj | 1 + .../appsettings.json | 3 + .../DamageAssesment.Api.Employees.csproj | 1 + .../appsettings.json | 3 + .../DamageAssesment.Api.Locations.csproj | 1 + .../appsettings.json | 3 + .../DamageAssesment.Api.Questions.csproj | 1 + .../appsettings.json | 3 + ...DamageAssesment.Api.SurveyResponses.csproj | 2 +- .../Controllers/SurveysController.cs | 2 + .../DamageAssesment.Api.Survey.csproj | 1 + .../DamageAssesment.Api.Surveys/Program.cs | 22 ++ .../Providers/SurveysProvider.cs | 8 +- .../appsettings.json | 3 + .../Bases/ServiceProviderBase.cs | 21 ++ .../Controllers/UsersAccessController.cs | 97 +++++ .../DamageAssesment.Api.UsersAccess.csproj | 19 + .../Db/Role.cs | 21 ++ .../Db/Token.cs | 17 + .../Db/User.cs | 27 ++ .../Db/UsersAccessDbContext.cs | 17 + .../Interfaces/IEmployeeServiceProvider.cs | 10 + .../Interfaces/IRoleProvider.cs | 12 + .../Interfaces/ITokenServiceProvider.cs | 11 + .../Interfaces/IUsersAccessProvider.cs | 16 + .../Models/Employee.cs | 21 ++ .../Models/JwtSettings.cs | 9 + .../Models/Role.cs | 9 + .../Models/Token.cs | 10 + .../Models/TokenResponse.cs | 8 + .../Models/User.cs | 12 + .../Models/UserCredentials.cs | 5 + .../Profiles/UsersAccessProfile.cs | 14 + .../Program.cs | 77 ++++ .../Properties/launchSettings.json | 31 ++ .../Providers/EmployeeServiceProvider.cs | 58 +++ .../Providers/TokenServiceProvider.cs | 57 +++ .../Providers/UserAccessProvider.cs | 341 ++++++++++++++++++ .../appsettings.Development.json | 8 + .../appsettings.json | 15 + DamageAssesmentApi/DamageAssesment.sln | 7 + 43 files changed, 1003 insertions(+), 5 deletions(-) create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Bases/ServiceProviderBase.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Controllers/UsersAccessController.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/DamageAssesment.Api.UsersAccess.csproj create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/Role.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/Token.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/User.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/UsersAccessDbContext.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IEmployeeServiceProvider.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IRoleProvider.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/ITokenServiceProvider.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IUsersAccessProvider.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Employee.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/JwtSettings.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Role.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Token.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/TokenResponse.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/User.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/UserCredentials.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Profiles/UsersAccessProfile.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Program.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Properties/launchSettings.json create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/EmployeeServiceProvider.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/TokenServiceProvider.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/UserAccessProvider.cs create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/appsettings.Development.json create mode 100644 DamageAssesmentApi/DamageAssesment.Api.UsersAccess/appsettings.json diff --git a/DamageAssesmentApi/DamageAssesment.Api.Answers/DamageAssesment.Api.Answers.csproj b/DamageAssesmentApi/DamageAssesment.Api.Answers/DamageAssesment.Api.Answers.csproj index ca5ee38..260a911 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Answers/DamageAssesment.Api.Answers.csproj +++ b/DamageAssesmentApi/DamageAssesment.Api.Answers/DamageAssesment.Api.Answers.csproj @@ -8,6 +8,7 @@ + diff --git a/DamageAssesmentApi/DamageAssesment.Api.Answers/appsettings.json b/DamageAssesmentApi/DamageAssesment.Api.Answers/appsettings.json index 10f68b8..ff5949d 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Answers/appsettings.json +++ b/DamageAssesmentApi/DamageAssesment.Api.Answers/appsettings.json @@ -1,4 +1,7 @@ { + "JwtSettings": { + "securitykey": "bWlhbWkgZGFkZSBzY2hvb2xzIHNlY3JldCBrZXk=" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/DamageAssesmentApi/DamageAssesment.Api.Attachments/DamageAssesment.Api.Attachments.csproj b/DamageAssesmentApi/DamageAssesment.Api.Attachments/DamageAssesment.Api.Attachments.csproj index 3454bff..4d9cb44 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Attachments/DamageAssesment.Api.Attachments.csproj +++ b/DamageAssesmentApi/DamageAssesment.Api.Attachments/DamageAssesment.Api.Attachments.csproj @@ -9,6 +9,7 @@ + diff --git a/DamageAssesmentApi/DamageAssesment.Api.Attachments/appsettings.json b/DamageAssesmentApi/DamageAssesment.Api.Attachments/appsettings.json index ceb0a25..78aa6b5 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Attachments/appsettings.json +++ b/DamageAssesmentApi/DamageAssesment.Api.Attachments/appsettings.json @@ -1,4 +1,7 @@ { + "JwtSettings": { + "securitykey": "bWlhbWkgZGFkZSBzY2hvb2xzIHNlY3JldCBrZXk=" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/DamageAssesmentApi/DamageAssesment.Api.Employees/DamageAssesment.Api.Employees.csproj b/DamageAssesmentApi/DamageAssesment.Api.Employees/DamageAssesment.Api.Employees.csproj index 53e8700..515ad43 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Employees/DamageAssesment.Api.Employees.csproj +++ b/DamageAssesmentApi/DamageAssesment.Api.Employees/DamageAssesment.Api.Employees.csproj @@ -8,6 +8,7 @@ + diff --git a/DamageAssesmentApi/DamageAssesment.Api.Employees/appsettings.json b/DamageAssesmentApi/DamageAssesment.Api.Employees/appsettings.json index 1cf89f5..1a1f3fe 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Employees/appsettings.json +++ b/DamageAssesmentApi/DamageAssesment.Api.Employees/appsettings.json @@ -1,4 +1,7 @@ { + "JwtSettings": { + "securitykey": "bWlhbWkgZGFkZSBzY2hvb2xzIHNlY3JldCBrZXk=" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/DamageAssesmentApi/DamageAssesment.Api.Locations/DamageAssesment.Api.Locations.csproj b/DamageAssesmentApi/DamageAssesment.Api.Locations/DamageAssesment.Api.Locations.csproj index 47751c7..5264699 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Locations/DamageAssesment.Api.Locations.csproj +++ b/DamageAssesmentApi/DamageAssesment.Api.Locations/DamageAssesment.Api.Locations.csproj @@ -8,6 +8,7 @@ + diff --git a/DamageAssesmentApi/DamageAssesment.Api.Locations/appsettings.json b/DamageAssesmentApi/DamageAssesment.Api.Locations/appsettings.json index 10f68b8..ff5949d 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Locations/appsettings.json +++ b/DamageAssesmentApi/DamageAssesment.Api.Locations/appsettings.json @@ -1,4 +1,7 @@ { + "JwtSettings": { + "securitykey": "bWlhbWkgZGFkZSBzY2hvb2xzIHNlY3JldCBrZXk=" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/DamageAssesmentApi/DamageAssesment.Api.Questions/DamageAssesment.Api.Questions.csproj b/DamageAssesmentApi/DamageAssesment.Api.Questions/DamageAssesment.Api.Questions.csproj index 53e8700..515ad43 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Questions/DamageAssesment.Api.Questions.csproj +++ b/DamageAssesmentApi/DamageAssesment.Api.Questions/DamageAssesment.Api.Questions.csproj @@ -8,6 +8,7 @@ + diff --git a/DamageAssesmentApi/DamageAssesment.Api.Questions/appsettings.json b/DamageAssesmentApi/DamageAssesment.Api.Questions/appsettings.json index 10f68b8..ff5949d 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Questions/appsettings.json +++ b/DamageAssesmentApi/DamageAssesment.Api.Questions/appsettings.json @@ -1,4 +1,7 @@ { + "JwtSettings": { + "securitykey": "bWlhbWkgZGFkZSBzY2hvb2xzIHNlY3JldCBrZXk=" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/DamageAssesmentApi/DamageAssesment.Api.SurveyResponses/DamageAssesment.Api.SurveyResponses.csproj b/DamageAssesmentApi/DamageAssesment.Api.SurveyResponses/DamageAssesment.Api.SurveyResponses.csproj index a2383fa..fec1ec5 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.SurveyResponses/DamageAssesment.Api.SurveyResponses.csproj +++ b/DamageAssesmentApi/DamageAssesment.Api.SurveyResponses/DamageAssesment.Api.SurveyResponses.csproj @@ -8,9 +8,9 @@ + - diff --git a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Controllers/SurveysController.cs b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Controllers/SurveysController.cs index 56f2e29..a68fd9f 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Controllers/SurveysController.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Controllers/SurveysController.cs @@ -1,4 +1,5 @@ using DamageAssesment.Api.Surveys.Interfaces; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -25,6 +26,7 @@ namespace DamageAssesment.Api.Surveys.Controllers } return NoContent(); } + [HttpGet("{Id}")] public async Task GetSurveysAsync(int Id) { diff --git a/DamageAssesmentApi/DamageAssesment.Api.Surveys/DamageAssesment.Api.Survey.csproj b/DamageAssesmentApi/DamageAssesment.Api.Surveys/DamageAssesment.Api.Survey.csproj index 53e8700..515ad43 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Surveys/DamageAssesment.Api.Survey.csproj +++ b/DamageAssesmentApi/DamageAssesment.Api.Surveys/DamageAssesment.Api.Survey.csproj @@ -8,6 +8,7 @@ + diff --git a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs index ebb638f..91e052c 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Program.cs @@ -1,11 +1,32 @@ using DamageAssesment.Api.Surveys.Db; using DamageAssesment.Api.Surveys.Interfaces; using DamageAssesment.Api.Surveys.Providers; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using System.Text; var builder = WebApplication.CreateBuilder(args); // Add services to the container. +var authkey = builder.Configuration.GetValue("JwtSettings:securitykey"); +builder.Services.AddAuthentication(item => +{ + item.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + item.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}).AddJwtBearer(item => +{ + item.RequireHttpsMetadata = true; + item.SaveToken = true; + item.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authkey)), + ValidateIssuer = false, + ValidateAudience = false, + ClockSkew = TimeSpan.Zero + }; +}); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle @@ -26,6 +47,7 @@ if (app.Environment.IsDevelopment()) app.UseSwaggerUI(); } +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Providers/SurveysProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Providers/SurveysProvider.cs index c85ed4c..3eb111c 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Surveys/Providers/SurveysProvider.cs +++ b/DamageAssesmentApi/DamageAssesment.Api.Surveys/Providers/SurveysProvider.cs @@ -26,7 +26,7 @@ namespace DamageAssesment.Api.Surveys.Providers surveyDbContext.Surveys.Add(new Db.Survey { Id = 1, Title = "Sample Survey Title:Damage Assesment 2014", IsEnabled = true, StartDate = DateTime.Now, EndDate = DateTime.Now.AddDays(90) }); surveyDbContext.Surveys.Add(new Db.Survey { Id = 2, Title = "Sample Survey Title: Damage Assesment 2016", IsEnabled = true, StartDate = DateTime.Now, EndDate = DateTime.Now.AddDays(90) }); surveyDbContext.Surveys.Add(new Db.Survey { Id = 3, Title = "Sample Survey Title: Damage Assesment 2018", IsEnabled = true, StartDate = DateTime.Now, EndDate = DateTime.Now.AddDays(90) }); - surveyDbContext.SaveChanges(); + surveyDbContext.SaveChangesAsync(); } } @@ -80,7 +80,7 @@ namespace DamageAssesment.Api.Surveys.Providers var surveys = await surveyDbContext.Surveys.ToListAsync(); survey.Id = surveys.Count + 1; surveyDbContext.Surveys.Add(mapper.Map(survey)); - surveyDbContext.SaveChanges(); + await surveyDbContext.SaveChangesAsync(); return (true, survey, "Successful"); } else @@ -110,7 +110,7 @@ namespace DamageAssesment.Api.Surveys.Providers _survey.IsEnabled = survey.IsEnabled; _survey.StartDate = survey.StartDate; _survey.EndDate = survey.EndDate; - surveyDbContext.SaveChanges(); + await surveyDbContext.SaveChangesAsync(); return (true, mapper.Map(_survey), "Successful"); } else @@ -141,7 +141,7 @@ namespace DamageAssesment.Api.Surveys.Providers if (survey != null) { surveyDbContext.Surveys.Remove(survey); - surveyDbContext.SaveChanges(); + await surveyDbContext.SaveChangesAsync(); return (true, mapper.Map(survey), $"Survey Id: {Id} deleted Successfuly"); } else diff --git a/DamageAssesmentApi/DamageAssesment.Api.Surveys/appsettings.json b/DamageAssesmentApi/DamageAssesment.Api.Surveys/appsettings.json index 10f68b8..ff5949d 100644 --- a/DamageAssesmentApi/DamageAssesment.Api.Surveys/appsettings.json +++ b/DamageAssesmentApi/DamageAssesment.Api.Surveys/appsettings.json @@ -1,4 +1,7 @@ { + "JwtSettings": { + "securitykey": "bWlhbWkgZGFkZSBzY2hvb2xzIHNlY3JldCBrZXk=" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Bases/ServiceProviderBase.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Bases/ServiceProviderBase.cs new file mode 100644 index 0000000..799f082 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Bases/ServiceProviderBase.cs @@ -0,0 +1,21 @@ +namespace DamageAssesment.Api.UsersAccess.Bases +{ + public class ServiceProviderBase + { + protected readonly IConfiguration configuration; + protected readonly HttpClient httpClient; + protected private readonly ILogger logger; + protected string ressource; + protected string urlBase; + + + public ServiceProviderBase(IConfiguration configuration, HttpClient httpClient, ILogger logger, string ressource, string urlBase) + { + this.configuration = configuration; + this.httpClient = httpClient; + this.logger = logger; + this.ressource = ressource; + this.urlBase = urlBase; + } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Controllers/UsersAccessController.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Controllers/UsersAccessController.cs new file mode 100644 index 0000000..5e16362 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Controllers/UsersAccessController.cs @@ -0,0 +1,97 @@ +using DamageAssesment.Api.UsersAccess.Interfaces; +using DamageAssesment.Api.UsersAccess.Models; +using Microsoft.AspNetCore.Mvc; + +namespace DamageAssesment.Api.UsersAccess.Controllers +{ + [Route("api")] + [ApiController] + public class UsersAccessController : ControllerBase + { + private IUsersAccessProvider userAccessProvider; + + public UsersAccessController(IUsersAccessProvider userAccessProvider) + { + this.userAccessProvider = userAccessProvider; + } + [HttpPost("authenticate")] + public async Task AuthenticateAsync(UserCredentials userCredentials) + { + var result = await userAccessProvider.AuthenticateAsync(userCredentials); + if (result.IsSuccess) + { + return Ok(result.TokenResponse); + } + return Unauthorized(result.ErrorMessage); + } + + [HttpPost("refreshToken")] + public async Task RefreshTokenAsync(TokenResponse tokenResponse) + { + var result = await userAccessProvider.RefreshTokenAsync(tokenResponse); + if (result.IsSuccess) + { + return Ok(result.TokenResponse); + } + return Unauthorized(result.ErrorMessage); + } + + [HttpGet("users")] + public async Task GetUsersAsync() + { + var result = await userAccessProvider.GetUsersAsync(); + if (result.IsSuccess) + { + return Ok(result.Users); + } + return NoContent(); + } + + [HttpGet("users/{Id}")] + public async Task GetUsersAsync(int Id) + { + var result = await userAccessProvider.GetUsersAsync(Id); + if (result.IsSuccess) + { + return Ok(result.User); + } + return NotFound(); + } + + [HttpPost("users")] + public async Task PostUserAsync(User user) + { + var result = await userAccessProvider.PostUserAsync(user); + if (result.IsSuccess) + { + return Ok(result.User); + } + return BadRequest(result.ErrorMessage); + } + + [HttpPut("users/{Id}")] + public async Task PutUserAsync(int Id, User user) + { + var result = await userAccessProvider.PutUserAsync(Id, user); + if (result.IsSuccess) + { + return Ok(result.User); + } + if (result.ErrorMessage == "Not Found") + return NotFound(result.ErrorMessage); + + return BadRequest(result.ErrorMessage); + } + + [HttpDelete("users/{Id}")] + public async Task DeleteSurveysAsync(int Id) + { + var result = await userAccessProvider.DeleteUserAsync(Id); + if (result.IsSuccess) + { + return Ok(result.User); + } + return NotFound(); + } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/DamageAssesment.Api.UsersAccess.csproj b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/DamageAssesment.Api.UsersAccess.csproj new file mode 100644 index 0000000..d56ba51 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/DamageAssesment.Api.UsersAccess.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/Role.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/Role.cs new file mode 100644 index 0000000..93e522a --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/Role.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace DamageAssesment.Api.UsersAccess.Db +{ + public class Role + { + [Key] + public int Id { get; set; } + + [StringLength(100)] + [Required] + public string Name { get; set; } + + // add a status field + + [StringLength(100)] + public string? Description { get; set; } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/Token.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/Token.cs new file mode 100644 index 0000000..cfea1e2 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/Token.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace DamageAssesment.Api.UsersAccess.Db +{ + public class Token + { + [Key] + public string Id { get; set; } + [Required] + [ForeignKey("User")] + public int UserId { get; set; } + public string? RefreshToken { get; set; } + public bool? IsActive { get; set; } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/User.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/User.cs new file mode 100644 index 0000000..d5fc4ee --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/User.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace DamageAssesment.Api.UsersAccess.Db +{ + public class User + { + [Key] + public int Id { get; set; } + + [ForeignKey("Employee")] + public string EmployeeId { get; set; } + + [ForeignKey("Role")] + [Required] + public int RoleId { get; set; } + [Required] + public bool? IsActive { get; set; } = true; + + [Required] + public DateTime? CreateDate { get; set; } = DateTime.Now; + + public DateTime? UpdateDate { get; set; } + + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/UsersAccessDbContext.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/UsersAccessDbContext.cs new file mode 100644 index 0000000..a46a10d --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Db/UsersAccessDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; + +namespace DamageAssesment.Api.UsersAccess.Db +{ + public class UsersAccessDbContext:DbContext + { + public DbSet Users { get; set; } + public DbSet Roles { get; set; } + public DbSet Tokens { get; set; } + public UsersAccessDbContext(DbContextOptions options) : base(options) + { + + } + + + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IEmployeeServiceProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IEmployeeServiceProvider.cs new file mode 100644 index 0000000..fc35b00 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IEmployeeServiceProvider.cs @@ -0,0 +1,10 @@ +using DamageAssesment.Api.UsersAccess.Models; + +namespace DamageAssesment.Api.UsersAccess.Interfaces +{ + public interface IEmployeeServiceProvider + { + Task> getEmployeesAsync(); + Task getEmployeeAsync(string employeeID); + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IRoleProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IRoleProvider.cs new file mode 100644 index 0000000..6ca0209 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IRoleProvider.cs @@ -0,0 +1,12 @@ +namespace DamageAssesment.Api.UsersAccess.Interfaces +{ + public interface IRoleProvider + { + Task<(bool IsSuccess, IEnumerable< Models.Role> Roles, string ErrorMessage)> GetRolesAsync(); + Task<(bool IsSuccess, Models.Role Roles, string ErrorMessage)> GetRolesAsync(int Id); + Task<(bool IsSuccess, Models.Role Role, string ErrorMessage)> PostRoleAsync(Models.Role Role); + Task<(bool IsSuccess, Models.Role Role, string ErrorMessage)> PutRoleAsync(int Id,Models.Role Role); + Task<(bool IsSuccess, Models.Role Role, string ErrorMessage)> DeleteRoleAsync(int Id); + + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/ITokenServiceProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/ITokenServiceProvider.cs new file mode 100644 index 0000000..58574b7 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/ITokenServiceProvider.cs @@ -0,0 +1,11 @@ +using DamageAssesment.Api.UsersAccess.Models; +using System.Security.Claims; + +namespace DamageAssesment.Api.UsersAccess.Interfaces +{ + public interface ITokenServiceProvider + { + Task GenerateToken(User user); + Task TokenAuthenticate(User user, Claim[] claims); + } +} \ No newline at end of file diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IUsersAccessProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IUsersAccessProvider.cs new file mode 100644 index 0000000..7e39e11 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Interfaces/IUsersAccessProvider.cs @@ -0,0 +1,16 @@ +using DamageAssesment.Api.UsersAccess.Models; + +namespace DamageAssesment.Api.UsersAccess.Interfaces +{ + public interface IUsersAccessProvider + { + public Task<(bool IsSuccess, IEnumerable< Models.User> Users, string ErrorMessage)> GetUsersAsync(); + public Task<(bool IsSuccess, Models.User User, string ErrorMessage)> GetUsersAsync(int Id); + public Task<(bool IsSuccess, Models.User User, string ErrorMessage)> PostUserAsync(Models.User User); + public Task<(bool IsSuccess, Models.User User, string ErrorMessage)> PutUserAsync(int Id,Models.User User); + public Task<(bool IsSuccess, Models.User User, string ErrorMessage)> DeleteUserAsync(int Id); + public Task<(bool IsSuccess, IEnumerable Roles, string ErrorMessage)> GetRolesAsync(); + public Task<(bool IsSuccess, Models.TokenResponse TokenResponse, string ErrorMessage)> AuthenticateAsync(UserCredentials userCredentials); + public Task<(bool IsSuccess, Models.TokenResponse TokenResponse, string ErrorMessage)>RefreshTokenAsync(TokenResponse tokenResponse); + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Employee.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Employee.cs new file mode 100644 index 0000000..e14f27d --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Employee.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace DamageAssesment.Api.UsersAccess.Models +{ + public class Employee + { + public string Id { get; set; } + + [StringLength(50)] + public string Name { get; set; } + + public DateTime BirthDate { get; set; } + + [StringLength(50)] + public string OfficePhoneNumber { get; set; } + + [StringLength(50)] + public string Email { get; set; } + public bool IsActive { get; set; } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/JwtSettings.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/JwtSettings.cs new file mode 100644 index 0000000..3f9dadf --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/JwtSettings.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; +namespace DamageAssesment.Api.UsersAccess.Models +{ + + public class JwtSettings + { + public string securitykey { get; set; } + } +} \ No newline at end of file diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Role.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Role.cs new file mode 100644 index 0000000..809e677 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Role.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; +namespace DamageAssesment.Api.UsersAccess.Models +{ + public class Role { + public int Id { get; set; } + public string Name { get; set; } + public string? Description { get; set; } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Token.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Token.cs new file mode 100644 index 0000000..9c46d81 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/Token.cs @@ -0,0 +1,10 @@ +namespace DamageAssesment.Api.UsersAccess.Models +{ + public class Token + { + public string Id { get; set; } + public int UserId { get; set; } + public string? RefreshToken { get; set; } + public bool? IsActive { get; set; } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/TokenResponse.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/TokenResponse.cs new file mode 100644 index 0000000..4d0b6da --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/TokenResponse.cs @@ -0,0 +1,8 @@ +namespace DamageAssesment.Api.UsersAccess.Models +{ + public class TokenResponse + { + public string? jwttoken { get; set; } + public string? refreshtoken { get; set; } + } +} \ No newline at end of file diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/User.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/User.cs new file mode 100644 index 0000000..f3c9971 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/User.cs @@ -0,0 +1,12 @@ +namespace DamageAssesment.Api.UsersAccess.Models +{ + public class User + { + public int Id { get; set; } + public string EmployeeId { get; set; } + public int RoleId { get; set; } + public bool? IsActive { get; set; } + public DateTime? CreateDate { get; set; } + public DateTime? UpdateDate { get; set; } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/UserCredentials.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/UserCredentials.cs new file mode 100644 index 0000000..a23a43b --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Models/UserCredentials.cs @@ -0,0 +1,5 @@ +public class UserCredentials +{ + public string? username { get; set; } + // public string? password { get; set; } +} \ No newline at end of file diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Profiles/UsersAccessProfile.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Profiles/UsersAccessProfile.cs new file mode 100644 index 0000000..bf744eb --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Profiles/UsersAccessProfile.cs @@ -0,0 +1,14 @@ +namespace DamageAssesment.Api.UsersAccess.Profiles +{ + public class UsersAccessProfile : AutoMapper.Profile + { + public UsersAccessProfile() + { + CreateMap(); + CreateMap(); + + CreateMap(); + CreateMap(); + } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Program.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Program.cs new file mode 100644 index 0000000..a7fb1ac --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Program.cs @@ -0,0 +1,77 @@ +using DamageAssesment.Api.UsersAccess.Db; +using DamageAssesment.Api.UsersAccess.Interfaces; +using DamageAssesment.Api.UsersAccess.Providers; +using DamageAssesment.Api.UsersAccess.Models; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using Polly; +using DamageAssesment.Api.SurveyResponses.Providers; + +const int maxApiCallRetries = 3; +const int intervalToRetry = 2; //2 seconds +const int maxRetryForCircuitBraker = 5; +const int intervalForCircuitBraker = 5; //5 seconds + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +var authkey = builder.Configuration.GetValue("JwtSettings:securitykey"); +builder.Services.AddAuthentication(item => +{ + item.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + item.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}).AddJwtBearer(item => +{ + item.RequireHttpsMetadata = true; + item.SaveToken = true; + item.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authkey)), + ValidateIssuer = false, + ValidateAudience = false, + ClockSkew = TimeSpan.Zero + }; +}); + +var _jwtsettings = builder.Configuration.GetSection("JwtSettings"); +builder.Services.Configure(_jwtsettings); + + + +builder.Services.AddHttpClient(). + AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(maxApiCallRetries, _ => TimeSpan.FromSeconds(intervalToRetry))). + AddTransientHttpErrorPolicy(policy => policy.CircuitBreakerAsync(maxRetryForCircuitBraker, TimeSpan.FromSeconds(intervalForCircuitBraker))); + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddDbContext(option => +{ + option.UseInMemoryDatabase("UsersAccess"); +}); + + + + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Properties/launchSettings.json b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Properties/launchSettings.json new file mode 100644 index 0000000..859e680 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:28382", + "sslPort": 0 + } + }, + "profiles": { + "DamageAssesment.Api.Users": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5027", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/EmployeeServiceProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/EmployeeServiceProvider.cs new file mode 100644 index 0000000..ace59ee --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/EmployeeServiceProvider.cs @@ -0,0 +1,58 @@ +using DamageAssesment.Api.UsersAccess.Bases; +using DamageAssesment.Api.UsersAccess.Interfaces; +using DamageAssesment.Api.UsersAccess.Models; +using Newtonsoft.Json; +using System.Reflection; + +namespace DamageAssesment.Api.SurveyResponses.Providers +{ + public class EmployeeServiceProvider :ServiceProviderBase, IEmployeeServiceProvider + { + public EmployeeServiceProvider(IConfiguration configuration, HttpClient httpClient, ILogger logger) : base(configuration, httpClient, logger, "/api/Employees", configuration.GetValue("EndPointSettings:EmployeeUrlBase")) + { + } + + public async Task> getEmployeesAsync() + { + try + { + httpClient.BaseAddress = new Uri(urlBase); + var response = await httpClient.GetAsync(ressource); + response.EnsureSuccessStatusCode(); + var responseString = await response.Content.ReadAsStringAsync(); + var employees = JsonConvert.DeserializeObject>(responseString); + + if (employees == null || !employees.Any()) + return null; + else return employees; + } + catch (Exception ex) + { + logger?.LogError($"Exception Found : {ex.Message} - Ref: EmployeeServiceProvider.getEmployeesAsync()"); + return null; + } + } + + public async Task getEmployeeAsync(string employeeID) + { + try + { + httpClient.BaseAddress = new Uri(urlBase); + //ressource = ressource + "/" + employeeID; + var response = await httpClient.GetAsync("/api/Employees/"+ employeeID); + response.EnsureSuccessStatusCode(); + var responseString = await response.Content.ReadAsStringAsync(); + var employee = JsonConvert.DeserializeObject(responseString); + + if (employee == null ) + return null; + else return employee; + } + catch (Exception ex) + { + logger?.LogError($"Exception Found : {ex.Message} - Ref: EmployeeServiceProvider.getEmployeeAsync()"); + return null; + } + } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/TokenServiceProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/TokenServiceProvider.cs new file mode 100644 index 0000000..a77d6bc --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/TokenServiceProvider.cs @@ -0,0 +1,57 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using DamageAssesment.Api.UsersAccess.Db; +using DamageAssesment.Api.UsersAccess.Interfaces; +using DamageAssesment.Api.UsersAccess.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; + +public class TokenServiceProvider : ITokenServiceProvider +{ + private readonly UsersAccessDbContext usersAccessDbContext; + private readonly JwtSettings jwtSettings; + public TokenServiceProvider(IOptions options,UsersAccessDbContext usersAccessDbContext) + { + this.usersAccessDbContext = usersAccessDbContext; + this.jwtSettings = options.Value; + } + public async Task GenerateToken(DamageAssesment.Api.UsersAccess.Models.User user) + { + var randomnumber = new byte[32]; + using (var ramdomnumbergenerator = RandomNumberGenerator.Create()) + { + ramdomnumbergenerator.GetBytes(randomnumber); + string refreshtoken = Convert.ToBase64String(randomnumber); + var token = await usersAccessDbContext.Tokens.FirstOrDefaultAsync(item => item.UserId == user.Id); + if (token != null) + { + token.RefreshToken = refreshtoken; + } + else + { + usersAccessDbContext.Tokens.Add(new DamageAssesment.Api.UsersAccess.Db.Token() + { + Id = new Random().Next().ToString(), + UserId = user.Id, + RefreshToken = refreshtoken, + IsActive = true + }); + } + await usersAccessDbContext.SaveChangesAsync(); + + return refreshtoken; + } + } + + public async Task TokenAuthenticate(DamageAssesment.Api.UsersAccess.Models.User user, Claim[] claims) + { + var token = new JwtSecurityToken(claims: claims, expires: DateTime.Now.AddSeconds(20), + signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.securitykey)), SecurityAlgorithms.HmacSha256) + ); + var jwttoken = new JwtSecurityTokenHandler().WriteToken(token); + return new TokenResponse() { jwttoken = jwttoken, refreshtoken = await GenerateToken(user) }; + } +} \ No newline at end of file diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/UserAccessProvider.cs b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/UserAccessProvider.cs new file mode 100644 index 0000000..6188d92 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/Providers/UserAccessProvider.cs @@ -0,0 +1,341 @@ +using AutoMapper; +using DamageAssesment.Api.UsersAccess.Db; +using DamageAssesment.Api.UsersAccess.Interfaces; +using DamageAssesment.Api.UsersAccess.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System.Data; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace DamageAssesment.Api.UsersAccess.Providers +{ + public class UsersAccessProvider : IUsersAccessProvider + { + private readonly UsersAccessDbContext userAccessDbContext; + private readonly ILogger logger; + private readonly IMapper mapper; + private readonly IEmployeeServiceProvider employeeServiceProvider; + private readonly JwtSettings jwtSettings; + private readonly ITokenServiceProvider tokenServiceProvider; + + public UsersAccessProvider(IOptions options, ITokenServiceProvider tokenServiceProvider, UsersAccessDbContext userAccessDbContext, IEmployeeServiceProvider employeeServiceProvider, ILogger logger, IMapper mapper) + { + this.userAccessDbContext = userAccessDbContext; + this.employeeServiceProvider = employeeServiceProvider; + this.logger = logger; + this.mapper = mapper; + jwtSettings = options.Value; + this.tokenServiceProvider = tokenServiceProvider; + seedData(); + } + + private void seedData() + { + if (!userAccessDbContext.Users.Any()) + { + userAccessDbContext.Users.Add(new Db.User { Id = 1, EmployeeId = "Emp1", RoleId = 1 }); + userAccessDbContext.Users.Add(new Db.User { Id = 2, EmployeeId = "Emp2", RoleId = 2 }); + userAccessDbContext.Users.Add(new Db.User { Id = 3, EmployeeId = "Emp3", RoleId = 3 }); + userAccessDbContext.SaveChanges(); + } + + if (!userAccessDbContext.Roles.Any()) + { + userAccessDbContext.Roles.Add(new Db.Role { Id = 1, Name = "admin" }); + userAccessDbContext.Roles.Add(new Db.Role { Id = 2, Name = "user" }); + userAccessDbContext.Roles.Add(new Db.Role { Id = 3, Name = "survey" }); + userAccessDbContext.Roles.Add(new Db.Role { Id = 4, Name = "report" }); + userAccessDbContext.Roles.Add(new Db.Role { Id = 5, Name = "document" }); + userAccessDbContext.SaveChanges(); + } + } + + public async Task<(bool IsSuccess, IEnumerable Users, string ErrorMessage)> GetUsersAsync() + { + try + { + logger?.LogInformation("Gell all Users from DB"); + var users = await userAccessDbContext.Users.ToListAsync(); + if (users != null) + { + logger?.LogInformation($"{users.Count} Items(s) found"); + var result = mapper.Map, IEnumerable>(users); + return (true, result, null); + } + return (false, null, "Not found"); + } + catch (Exception ex) + { + logger?.LogError(ex.ToString()); + return (false, null, ex.Message); + } + } + + public async Task<(bool IsSuccess, Models.User User, string ErrorMessage)> GetUsersAsync(int Id) + { + try + { + logger?.LogInformation("Querying Users table"); + var user = await userAccessDbContext.Users.SingleOrDefaultAsync(s => s.Id == Id); + if (user != null) + { + logger?.LogInformation($"User Id: {Id} found"); + var result = mapper.Map(user); + return (true, result, null); + } + return (false, null, "Not found"); + } + catch (Exception ex) + { + logger?.LogError(ex.ToString()); + return (false, null, ex.Message); + } + } + + public async Task<(bool IsSuccess, Models.User User, string ErrorMessage)> PostUserAsync(Models.User user) + { + try + { + if (user != null) + { + var users = await userAccessDbContext.Users.ToListAsync(); + int count = users.Where(u => u.EmployeeId == user.EmployeeId).Count(); + if (count == 0) + { + user.Id = users.Count + 1; + userAccessDbContext.Users.Add(mapper.Map(user)); + await userAccessDbContext.SaveChangesAsync(); + return (true, user, "Successful"); + } + else + { + logger?.LogInformation($"Employee Id: {user.EmployeeId} is already exist"); + return (false, null, $"Employee Id: {user.EmployeeId} is already exist"); + } + } + else + { + logger?.LogInformation($"Employee Id: {user.EmployeeId} cannot be added"); + return (false, null, $"Employee Id: {user.EmployeeId} cannot be added"); + } + } + catch (Exception ex) + { + logger?.LogError(ex.ToString()); + return (false, null, ex.Message); + } + } + + public async Task<(bool IsSuccess, Models.User User, string ErrorMessage)> PutUserAsync(int Id, Models.User user) + { + try + { + if (user != null) + { + var _user = await userAccessDbContext.Users.Where(s => s.Id == Id).SingleOrDefaultAsync(); + + if (_user != null) + { + int count = userAccessDbContext.Users.Where(u => u.Id != user.Id && u.EmployeeId == user.EmployeeId).Count(); + if (count == 0) + { + _user.EmployeeId = user.EmployeeId; + _user.RoleId = user.RoleId; + _user.IsActive = user.IsActive; + _user.UpdateDate = DateTime.Now; + await userAccessDbContext.SaveChangesAsync(); + + + logger?.LogInformation($"Employee Id: {user.EmployeeId} updated successfuly"); + return (true, mapper.Map(_user), $"Employee Id: {_user.EmployeeId} updated successfuly"); + } + else + { + logger?.LogInformation($"Employee Id: {user.EmployeeId} is already exist"); + return (false, null, $"Employee Id: {user.EmployeeId} is already exist"); + } + } + else + { + logger?.LogInformation($"User Id : {Id} Not found"); + return (false, null, "Not Found"); + } + } + else + { + logger?.LogInformation($"User Id: {Id} Bad Request"); + return (false, null, "Bad request"); + } + } + catch (Exception ex) + { + logger?.LogError(ex.ToString()); + return (false, null, ex.Message); + } + } + + public async Task<(bool IsSuccess, Models.User User, string ErrorMessage)> DeleteUserAsync(int Id) + { + try + { + var user = await userAccessDbContext.Users.Where(x => x.Id == Id).SingleOrDefaultAsync(); + + if (user != null) + { + userAccessDbContext.Users.Remove(user); + await userAccessDbContext.SaveChangesAsync(); + logger?.LogInformation($"User Id: {Id} deleted Successfuly"); + return (true, mapper.Map(user), $"User Id: {Id} deleted Successfuly"); + } + else + { + logger?.LogInformation($"User Id : {Id} Not found"); + return (false, null, "Not Found"); + } + } + catch (Exception ex) + { + logger?.LogError(ex.ToString()); + return (false, null, ex.Message); + } + } + + public async Task<(bool IsSuccess, TokenResponse TokenResponse, string ErrorMessage)> AuthenticateAsync(UserCredentials userCredentials) + { + + if (userCredentials != null) + { + //implementation for dadeschools authentication + + var employee = await employeeServiceProvider.getEmployeeAsync(userCredentials.username); + + if (employee != null) + { + var result = await GetUsersAsync(); + + if (result.IsSuccess) + { + var user = result.Users.Where(x => x.IsActive == true && x.EmployeeId.ToLower().Equals(userCredentials.username.ToLower())).SingleOrDefault(); + + if (user != null) + { + + var r = await GetRolesAsync(); + var role = r.Roles.Where(x => x.Id == user.RoleId).SingleOrDefault(); + + var authClaims = new List { + new Claim(ClaimTypes.Name, user.EmployeeId), + new Claim(ClaimTypes.Role, role.Name), + new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()) + + }; + + /// Generate Token + var tokenhandler = new JwtSecurityTokenHandler(); + var tokenkey = Encoding.UTF8.GetBytes(jwtSettings.securitykey); + var tokendesc = new SecurityTokenDescriptor + { + Audience = "", + NotBefore = DateTime.Now, + Subject = new ClaimsIdentity(authClaims), + Expires = DateTime.Now.AddMinutes(30), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(tokenkey), SecurityAlgorithms.HmacSha256) + }; + var token = tokenhandler.CreateToken(tokendesc); + string finaltoken = tokenhandler.WriteToken(token); + + var response = new TokenResponse() { jwttoken = finaltoken, refreshtoken = await tokenServiceProvider.GenerateToken(user) }; + + + return (true, response, "Authentication success and token issued."); + + } + else + { + return (false, null, "user inactive or not exist."); + } + } + else + { + return (false, null, "users list empty."); + } + } + else + { + return (false, null, "Employee not exist."); + } + } + else + { + return (false, null, "Credentials are required to authenticate."); + + } + + } + public async Task<(bool IsSuccess, IEnumerable Roles, string ErrorMessage)> GetRolesAsync() + { + try + { + logger?.LogInformation("Gell all Roles from DB"); + var roles = await userAccessDbContext.Roles.ToListAsync(); + if (roles != null) + { + logger?.LogInformation($"{roles.Count} Items(s) found"); + var result = mapper.Map, IEnumerable>(roles); + return (true, result, null); + } + return (false, null, "Not found"); + } + catch (Exception ex) + { + logger?.LogError(ex.ToString()); + return (false, null, ex.Message); + } + } + + public async Task<(bool IsSuccess, Models.TokenResponse TokenResponse, string ErrorMessage)> RefreshTokenAsync(TokenResponse tokenResponse) + { + //Generate token + var tokenhandler = new JwtSecurityTokenHandler(); + var tokenkey = Encoding.UTF8.GetBytes(this.jwtSettings.securitykey); + SecurityToken securityToken; + var principal = tokenhandler.ValidateToken(tokenResponse.jwttoken, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(tokenkey), + ValidateIssuer = false, + ValidateAudience = false, + + }, out securityToken); + + var token = securityToken as JwtSecurityToken; + if (token != null && !token.Header.Alg.Equals(SecurityAlgorithms.HmacSha256)) + { + return (false, null, "Unauthorized"); + } + var username = principal.Identity?.Name; + + var tokens = await userAccessDbContext.Tokens.ToListAsync(); + var users = await userAccessDbContext.Users.ToListAsync(); + + var user = (from u in users + join t in tokens + on u.Id equals t.UserId + where u.EmployeeId == username + && t.RefreshToken == tokenResponse.refreshtoken + select u).FirstOrDefault(); + + if (user == null) + return (false, null, "Invalid Token Response object provided"); + + var _user = mapper.Map(user); + var response = tokenServiceProvider.TokenAuthenticate(_user, principal.Claims.ToArray()).Result; + return (true, response, "Token authenticated and refreshed."); + } + + + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/appsettings.Development.json b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/appsettings.json b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/appsettings.json new file mode 100644 index 0000000..4a1b1b5 --- /dev/null +++ b/DamageAssesmentApi/DamageAssesment.Api.UsersAccess/appsettings.json @@ -0,0 +1,15 @@ +{ + "JwtSettings": { + "securitykey": "bWlhbWkgZGFkZSBzY2hvb2xzIHNlY3JldCBrZXk=" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "EndPointSettings": { + "EmployeeUrlBase": "http://localhost:5135" + }, + "AllowedHosts": "*" +} diff --git a/DamageAssesmentApi/DamageAssesment.sln b/DamageAssesmentApi/DamageAssesment.sln index a8b1fcc..8deb2aa 100644 --- a/DamageAssesmentApi/DamageAssesment.sln +++ b/DamageAssesmentApi/DamageAssesment.sln @@ -37,6 +37,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DamageAssesment.Api.Employe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DamageAssesment.Api.Employees.Test", "DamageAssesment.Api.Employees.Test\DamageAssesment.Api.Employees.Test.csproj", "{D6BF9AE9-72FA-4726-A326-35A35D27FFB8}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DamageAssesment.Api.UsersAccess", "DamageAssesment.Api.UsersAccess\DamageAssesment.Api.UsersAccess.csproj", "{83177BB9-DD23-4A85-A000-D60F843D0835}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,6 +66,7 @@ Global {D11808FE-AD1C-4BA6-87FD-9D18B2DC81F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {D11808FE-AD1C-4BA6-87FD-9D18B2DC81F2}.Release|Any CPU.Build.0 = Release|Any CPU {35CD9231-034D-4999-BCFC-1786DD007ED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35CD9231-034D-4999-BCFC-1786DD007ED2}.Debug|Any CPU.Build.0 = Debug|Any CPU {35CD9231-034D-4999-BCFC-1786DD007ED2}.Release|Any CPU.ActiveCfg = Release|Any CPU {35CD9231-034D-4999-BCFC-1786DD007ED2}.Release|Any CPU.Build.0 = Release|Any CPU {ADFB79E3-83C9-454F-A070-49D167BD28CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -98,6 +101,10 @@ Global {D6BF9AE9-72FA-4726-A326-35A35D27FFB8}.Debug|Any CPU.Build.0 = Debug|Any CPU {D6BF9AE9-72FA-4726-A326-35A35D27FFB8}.Release|Any CPU.ActiveCfg = Release|Any CPU {D6BF9AE9-72FA-4726-A326-35A35D27FFB8}.Release|Any CPU.Build.0 = Release|Any CPU + {83177BB9-DD23-4A85-A000-D60F843D0835}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83177BB9-DD23-4A85-A000-D60F843D0835}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83177BB9-DD23-4A85-A000-D60F843D0835}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83177BB9-DD23-4A85-A000-D60F843D0835}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE