First commit - init service with swagger, user controller + auth controller

This commit is contained in:
Thomas Fransolet 2021-04-06 16:17:54 +02:00
parent a12c36de6a
commit 629e9c31f7
25 changed files with 1142 additions and 0 deletions

6
.gitignore vendored
View File

@ -48,3 +48,9 @@ Thumbs.db
*.mov
*.wmv
.vs
bin
obj
*.user
ManagerService/bin
ManagerService/obj

View File

@ -0,0 +1,35 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Manager.Framework.Business
{
public class ProfileLogic
{
private readonly ILogger<ProfileLogic> _logger;
public ProfileLogic(ILogger<ProfileLogic> logger)
: base()
{
_logger = logger;
}
public bool Authenticate(string email, string password)
{
if (string.IsNullOrWhiteSpace(email))
{
_logger.LogError($"Authenticate error: No e-mail provided");
throw new UnauthorizedAccessException("Authentication error");
}
if (string.IsNullOrEmpty(password))
{
_logger.LogError($"Authenticate error: No password provided");
throw new UnauthorizedAccessException("Authentication error");
}
return true;
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace Manager.Framework.Models
{
[System.Serializable]
public class RequestException : System.Exception
{
public int StatusCode { get; set; }
public object Payload { get; set; }
protected RequestException() { }
public RequestException(int statusCode) : this() { StatusCode = statusCode; }
public RequestException(int statusCode, string message) : base(message) { StatusCode = statusCode; }
public RequestException(int statusCode, string message, System.Exception inner) : base(message, inner) { StatusCode = statusCode; }
protected RequestException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
public string GetJson()
{
return JsonSerializer.Serialize(new { StatusCode, Message, Payload });
}
}
}

View File

@ -0,0 +1,8 @@
namespace Manager.Interfaces.DTO
{
public class LoginDTO
{
public string Email { get; set; }
public string Password { get; set; }
}
}

View File

@ -0,0 +1,14 @@
namespace Manager.Interfaces.DTO
{
/// <summary>
/// Swagger test client authentication data
/// </summary>
public class SwaggerTokenRequest
{
public string grant_type { get; set; }
public string username { get; set; }
public string password { get; set; }
public string client_id { get; set; }
public string client_secret { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace Manager.Interfaces.DTO
{
public class TokenDTO
{
public string access_token { get; set; }
public string refresh_token { get; set; }
public string scope { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
public DateTimeOffset expiration { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Manager.Interfaces.DTO
{
public class UserDetailDTO
{
public string Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Bson" Version="2.12.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Manager.Interfaces.Models
{
public class Policy
{
public string Name { get; set; }
public string[] Claims { get; set; }
}
}

View File

@ -0,0 +1,18 @@
namespace Manager.Interfaces.Models
{
public class TokensSettings
{
/// <summary>
/// Application secret for tokens generation
/// </summary>
public string Secret { get; set; }
/// <summary>
/// Access token expiration in minutes
/// </summary>
public int AccessTokenExpiration { get; set; } = 30;
/// <summary>
/// Refresh token expiration in minutes
/// </summary>
public int RefreshTokenExpiration { get; set; } = 4 * 60;
}
}

View File

@ -0,0 +1,53 @@
using Manager.Interfaces.DTO;
using MongoDB.Bson.Serialization.Attributes;
using System;
using System.Collections.Generic;
using System.Text;
namespace Manager.Interfaces.Models
{
/// <summary>
/// User Information
/// </summary>
public class User
{
[BsonId]
[BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("Email")]
[BsonRequired]
public string Email { get; set; } // UNIQUE !..
[BsonElement("Password")]
[BsonRequired]
public string Password { get; set; }
[BsonElement("FirstName")]
[BsonRequired]
public string FirstName { get; set; }
[BsonElement("LastName")]
[BsonRequired]
public string LastName { get; set; }
[BsonElement("Token")]
[BsonRequired]
public string Token { get; set; }
[BsonElement("DateCreation")]
public DateTime DateCreation { get; set; }
public UserDetailDTO ToDTO()
{
return new UserDetailDTO()
{
Id = Id,
Email = Email,
FirstName = FirstName,
LastName = LastName,
};
}
}
}

37
ManagerService.sln Normal file
View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagerService", "ManagerService\ManagerService.csproj", "{042E0BC4-8DCF-4EEC-8420-C71AA85D4D99}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Manager.Framework", "Manager.Framework\Manager.Framework.csproj", "{B9548312-650F-4538-85B8-8447F8BB2BD3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Manager.Interfaces", "Manager.Interfaces\Manager.Interfaces.csproj", "{0B2EDCA0-C813-4EAA-9215-E219AE884F7D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{042E0BC4-8DCF-4EEC-8420-C71AA85D4D99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{042E0BC4-8DCF-4EEC-8420-C71AA85D4D99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{042E0BC4-8DCF-4EEC-8420-C71AA85D4D99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{042E0BC4-8DCF-4EEC-8420-C71AA85D4D99}.Release|Any CPU.Build.0 = Release|Any CPU
{B9548312-650F-4538-85B8-8447F8BB2BD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9548312-650F-4538-85B8-8447F8BB2BD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9548312-650F-4538-85B8-8447F8BB2BD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9548312-650F-4538-85B8-8447F8BB2BD3}.Release|Any CPU.Build.0 = Release|Any CPU
{0B2EDCA0-C813-4EAA-9215-E219AE884F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B2EDCA0-C813-4EAA-9215-E219AE884F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B2EDCA0-C813-4EAA-9215-E219AE884F7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B2EDCA0-C813-4EAA-9215-E219AE884F7D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B478AB21-859A-4B1E-BF99-77B01959805A}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,104 @@
using Manager.Interfaces.DTO;
using Manager.Services;
using ManagerService.Service.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using NSwag.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace ManagerService.Service.Controllers
{
/// <summary>
/// Authentication controller
/// </summary>
[ApiController, Route("api/[controller]")]
[Authorize]
[OpenApiTag("Authentication", Description = "Authentication management")]
public class AuthenticationController : ControllerBase
{
private readonly ILogger<AuthenticationController> _logger;
private readonly TokensService _tokensService;
private readonly UserDatabaseService _UserDatabaseService;
public AuthenticationController(ILogger<AuthenticationController> logger, TokensService tokensService, UserDatabaseService UserDatabaseService)
{
_logger = logger;
_tokensService = tokensService;
_UserDatabaseService = UserDatabaseService;
}
/// <summary>
/// Authenticate (business)
/// </summary>
/// <param name="email">user email</param>
/// <param name="password">user password</param>
/// <returns>Token descriptor</returns>
private ObjectResult Authenticate(string email, string password)
{
try
{
// For TEST
email = "test@email.be";
password = "kljqsdkljqsd";
var token = _tokensService.Authenticate(email.ToLower(), password);
// Set user token ?
var user = _UserDatabaseService.GetByEmail(email.ToLower());
if (user == null)
throw new KeyNotFoundException("User not found");
return new OkObjectResult(token);
}
catch (UnauthorizedAccessException ex)
{
_logger?.LogError(ex, $"Authentication error for user '{email}': unauthorized access");
return Unauthorized(ex);
}
catch (Exception ex)
{
_logger?.LogError(ex, $"Authenticate error for user '{email}'");
return Problem($"Authenticate error for user '{email}': {ex.Message}");
}
}
/// <summary>
/// Authenticate with form parameters (used by Swagger test client)
/// </summary>
/// <param name="tokenRequest">Swagger token request</param>
/// <returns>Token descriptor</returns>
[AllowAnonymous]
[HttpPost("Token")]
[Consumes("application/x-www-form-urlencoded")]
[ProducesResponseType(typeof(TokenDTO), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int) HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int) HttpStatusCode.InternalServerError)]
public ObjectResult AuthenticateWithForm([FromForm] SwaggerTokenRequest tokenRequest)
{
return Authenticate(tokenRequest.username, tokenRequest.password);
}
/// <summary>
/// Authenticate with Json parameters (used by most clients)
/// </summary>
/// <param name="login">Login DTO</param>
/// <returns>Token descriptor</returns>
[AllowAnonymous]
[HttpPost("Authenticate")]
[Consumes("application/json")]
[ProducesResponseType(typeof(TokenDTO), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.InternalServerError)]
public ObjectResult AuthenticateWithJson([FromBody] LoginDTO login)
{
return Authenticate(login.Email.ToLower(), login.Password);
}
}
}

View File

@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Manager.Interfaces.DTO;
using Manager.Interfaces.Models;
using Manager.Services;
using ManagerService.Service.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace ManagerService.Controllers
{
[Authorize] // TODO Add ROLES (Roles = "Admin")
[Route("[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private UserDatabaseService _userService;
private TokensService _tokenService;
private readonly ILogger<UserController> _logger;
public UserController(ILogger<UserController> logger, UserDatabaseService userService, TokensService tokenService)
{
_logger = logger;
_userService = userService;
_tokenService = tokenService;
}
/// <summary>
/// Get a list of user
/// </summary>
[ProducesResponseType(typeof(List<User>), 200)]
[ProducesResponseType(typeof(string), 500)]
[HttpGet]
public ObjectResult Get()
{
try
{
List<User> users = _userService.GetAll();
return new OkObjectResult(users);
}
catch (Exception ex)
{
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
/// <summary>
/// Get a specific user
/// </summary>
/// <param name="id">id user</param>
[ProducesResponseType(typeof(UserDetailDTO), 200)]
[ProducesResponseType(typeof(string), 404)]
[ProducesResponseType(typeof(string), 500)]
[HttpGet("{id}")]
public ObjectResult Get(string id)
{
try
{
User user = _userService.GetById(id);
if (user == null)
throw new KeyNotFoundException("This user was not found");
return new OkObjectResult(user.ToDTO());
}
catch (KeyNotFoundException ex)
{
return new NotFoundObjectResult(ex.Message) {};
}
catch (Exception ex)
{
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
/// <summary>
/// Create an user
/// </summary>
/// <param name="newUser">New user info</param>
[AllowAnonymous]
[ProducesResponseType(typeof(UserDetailDTO), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(typeof(string), 409)]
[ProducesResponseType(typeof(string), 500)]
[HttpPost]
public ObjectResult CreateUser([FromBody] User newUser)
{
try
{
if (newUser == null)
throw new ArgumentNullException("User param is null");
newUser.Token = _tokenService.GenerateToken(newUser.Email).ToString();
newUser.DateCreation = DateTime.Now;
List<User> users = _userService.GetAll();
if (users.Select(u => u.Email).Contains(newUser.Email))
throw new InvalidOperationException("This Email is already used");
User userCreated = _userService.Create(newUser);
return new OkObjectResult(userCreated.ToDTO());
}
catch (ArgumentNullException ex)
{
return new BadRequestObjectResult(ex.Message) {};
}
catch (InvalidOperationException ex)
{
return new ConflictObjectResult(ex.Message) {};
}
catch (Exception ex)
{
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
/// <summary>
/// Update an user
/// </summary>
/// <param name="updatedUser">User to update</param>
[ProducesResponseType(typeof(UserDetailDTO), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(typeof(string), 404)]
[ProducesResponseType(typeof(string), 500)]
[HttpPut]
public ObjectResult UpdateUser([FromBody] User updatedUser)
{
try
{
if (updatedUser == null)
throw new ArgumentNullException("User param is null");
User user = _userService.GetById(updatedUser.Id);
if (user == null)
throw new KeyNotFoundException("User does not exist");
User userModified = _userService.Update(updatedUser.Id, updatedUser);
return new OkObjectResult(userModified.ToDTO());
}
catch (ArgumentNullException ex)
{
return new BadRequestObjectResult(ex.Message) {};
}
catch (KeyNotFoundException ex)
{
return new NotFoundObjectResult(ex.Message) {};
}
catch (Exception ex)
{
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
/// <summary>
/// Delete an user
/// </summary>
/// <param name="id">Id of user to delete</param>
[ProducesResponseType(typeof(string), 202)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(typeof(string), 404)]
[ProducesResponseType(typeof(string), 500)]
[HttpDelete("{id}")]
public ObjectResult DeleteUser(string id)
{
try
{
if (id == null)
throw new ArgumentNullException("User param is null");
User user = _userService.GetById(id);
if (user == null)
throw new KeyNotFoundException("User does not exist");
_userService.Remove(id);
return new ObjectResult("The user has been deleted") { StatusCode = 202 };
}
catch (ArgumentNullException ex)
{
return new BadRequestObjectResult(ex.Message) { };
}
catch (KeyNotFoundException ex)
{
return new NotFoundObjectResult(ex.Message) { };
}
catch (Exception ex)
{
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.2" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.10.0" />
<PackageReference Include="MongoDB.Driver" Version="2.12.1" />
<PackageReference Include="NSwag.AspNetCore" Version="13.10.8" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Manager.Framework\Manager.Framework.csproj" />
<ProjectReference Include="..\Manager.Interfaces\Manager.Interfaces.csproj" />
</ItemGroup>
</Project>

26
ManagerService/Program.cs Normal file
View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ManagerService
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -0,0 +1,31 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50185",
"sslPort": 44339
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_URLS": "http://*:5000/",
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"ManagerService": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

View File

@ -0,0 +1,60 @@
using Manager.Interfaces.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ManagerService.Service
{
internal static class Security
{
public const string Scope = "Manager-api";
/// <summary>
/// Permissions
/// </summary>
private class Permissions
{
/// <summary>
/// Admin access
/// </summary>
public const string Admin = "Manager.admin";
}
/// <summary>
/// Custom claims types
/// </summary>
public class ClaimTypes
{
public const string Permission = "Permission";
}
/// <summary>
/// Permissions for each type of profile
/// </summary>
public static readonly Dictionary<System.Type, string[]> ProfilesConfiguration = new Dictionary<System.Type, string[]>()
{
// An admin has access to everything
//{ typeof(AdminProfile), new[] { Permissions.Admin} },
};
/// <summary>
/// Policies names
/// </summary>
public class Policies
{
/// <summary>
/// Administration
/// </summary>
public const string Admin = "Manager.Administration";
}
/// <summary>
/// Policies
/// </summary>
public static readonly Policy[] PoliciesConfiguration = new[]
{
new Policy() { Name = Policies.Admin, Claims = new[] { Permissions.Admin} }
};
}
}

View File

@ -0,0 +1,140 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using Manager.Interfaces.Models;
using Microsoft.IdentityModel.Tokens;
using Manager.Framework.Business;
using Manager.Interfaces.DTO;
using System.IdentityModel.Tokens.Jwt;
namespace ManagerService.Service.Services
{
/// <summary>
/// Tokens service
/// </summary>
public class TokensService
{
private readonly ILogger<TokensService> _logger;
private readonly TokensSettings _tokenSettings;
private readonly ProfileLogic _profileLogic;
private readonly SigningCredentials _signingCredentials;
/// <summary>
/// Constructor
/// </summary>
/// <param name="logger">Logger</param>
/// <param name="tokenSettings">Tokens settings</param>
/// <param name="context">Database context</param>
/// <param name="profileLogic">Profile logic</param>
/// <param name="emailClient">Email client</param>
public TokensService(ILogger<TokensService> logger, IOptions<TokensSettings> tokenSettings, ProfileLogic profileLogic)
{
_logger = logger;
_tokenSettings = tokenSettings.Value;
_profileLogic = profileLogic;
var key = Encoding.UTF8.GetBytes(_tokenSettings.Secret);
_signingCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature);
}
/// <summary>
/// Authenticate
/// </summary>
/// <param name="email">Email</param>
/// <param name="password">Password</param>
/// <returns>Token DTO in case of success</returns>
public TokenDTO Authenticate(string email, string password)
{
try
{
var claims = new List<System.Security.Claims.Claim>();
var expiration = DateTime.UtcNow.AddMinutes(_tokenSettings.AccessTokenExpiration);
// Todo nothing good here..
var profile = _profileLogic.Authenticate(email, password);
claims.Add(new Claim(ClaimTypes.Email, email));
// TODO: add refresh token support
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(claims),
Expires = expiration,
SigningCredentials = _signingCredentials
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return new TokenDTO()
{
access_token = tokenHandler.WriteToken(token),
expires_in = _tokenSettings.AccessTokenExpiration * 60,
expiration = new DateTimeOffset(token.ValidTo),
token_type = "Bearer",
scope = Security.Scope
};
}
catch (UnauthorizedAccessException ex)
{
_logger?.LogError(ex, $"Authenticate error for user '{email}': unauthorized access");
throw;
}
catch (Exception ex)
{
_logger?.LogError(ex, $"Authenticate error for user '{email}': {ex.Message}");
throw;
}
}
public object GenerateToken(string username)
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("WVD[&vAwis9=#883bM$FRc0Mw8h")); // Put the secret in a file or something
var claims = new Claim[] {
new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Email, "john.doe@blinkingcaret.com"),
new Claim(ClaimTypes.Role, "Admin")
};
var token = new JwtSecurityToken(
issuer: "Manager App",
audience: "Manager client",
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddDays(28),
signingCredentials: new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256)
);
string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return jwtToken;
}
public static string GenerateSHA256String(string inputString)
{
SHA256 sha256 = SHA256Managed.Create();
byte[] bytes = Encoding.UTF8.GetBytes(inputString);
byte[] hash = sha256.ComputeHash(bytes);
return GetStringFromHash(hash);
}
public static string GetStringFromHash(byte[] hash)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
result.Append(hash[i].ToString("X2"));
}
return result.ToString();
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Manager.Interfaces.Models;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
namespace Manager.Services
{
public class UserDatabaseService
{
private readonly IMongoCollection<User> _Users;
public UserDatabaseService(IConfiguration config)
{
var client = new MongoClient(config.GetConnectionString("TabletDb"));
var database = client.GetDatabase("TabletDb");
_Users = database.GetCollection<User>("Users");
}
public List<User> GetAll()
{
return _Users.Find(m => true).ToList();
}
public User GetByEmail(string email)
{
return _Users.Find<User>(m => m.Email == email).FirstOrDefault();
}
public User GetById(string id)
{
return _Users.Find<User>(m => m.Id == id).FirstOrDefault();
}
public bool IsExist(string id)
{
return _Users.Find<User>(d => d.Id == id).FirstOrDefault() != null ? true : false;
}
public User Create(User user)
{
_Users.InsertOne(user);
return user;
}
public User Update(string id, User userIn)
{
_Users.ReplaceOne(user => user.Id == id, userIn);
return userIn;
}
public void Remove(string id)
{
_Users.DeleteOne(user => user.Id == id);
}
}
}

185
ManagerService/Startup.cs Normal file
View File

@ -0,0 +1,185 @@
using Manager.Framework.Business;
using Manager.Framework.Models;
using Manager.Interfaces.Models;
using Manager.Services;
using ManagerService.Service;
using ManagerService.Service.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using NSwag;
using NSwag.Generation.AspNetCore;
using NSwag.Generation.Processors.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace ManagerService
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Swagger
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
services.AddOpenApiDocument(config =>
{
ConfigureSwagger(config);
});
services.AddCors(o => o.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Authentication
var tokensConfiguration = Configuration.GetSection("Tokens");
var tokenSettings = tokensConfiguration.Get<TokensSettings>();
services.Configure<TokensSettings>(tokensConfiguration);
foreach (var policy in Security.PoliciesConfiguration)
services.AddAuthorization(options =>
{
options.AddPolicy(policy.Name, policyAdmin =>
{
foreach (var claim in policy.Claims)
policyAdmin.RequireClaim(Security.ClaimTypes.Permission, claim);
});
});
services
.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenSettings.Secret)),
ValidateIssuer = false,
ValidateAudience = false,
RequireExpirationTime = false,
ValidateLifetime = true
};
});
services.AddScoped<TokensService>();
services.AddScoped(typeof(ProfileLogic));
services.AddScoped<UserDatabaseService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
//app.UseDeveloperExceptionPage();
app.UseExceptionHandler(HandleError);
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseOpenApi();
app.UseSwaggerUi3(configure =>
{
configure.OperationsSorter = "alpha";
configure.TagsSorter = "alpha";
});
}
private void ConfigureSwagger(AspNetCoreOpenApiDocumentGeneratorSettings config)
{
config.GenerateEnumMappingDescription = true;
config.AddSecurity("bearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.OAuth2,
Description = "Manager Authentication",
Flow = OpenApiOAuth2Flow.Password,
Flows = new OpenApiOAuthFlows()
{
Password = new OpenApiOAuthFlow()
{
Scopes = new Dictionary<string, string>
{
{ Security.Scope, "Manager WebAPI" }
},
TokenUrl = "/api/authentication/Token",
AuthorizationUrl = "/authentication/Token",
}
}
});
config.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("bearer"));
config.PostProcess = document =>
{
document.Info.Title = "Manager Service";
document.Info.Description = "API description";
document.Info.Version = "Version Pre-Alpha";
};
}
private void HandleError(IApplicationBuilder error)
{
error.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = exceptionHandlerPathFeature?.Error as RequestException;
if (exception != null)
{
var json = exception.GetJson();
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception.StatusCode;
await context.Response.WriteAsync(json);
}
});
}
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace ManagerService
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -0,0 +1,25 @@
{
"ConnectionStrings": {
"TabletDb": "mongodb://admin:MioTech4ever!@localhost:27017" //TO CHANGE
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Tokens": {
"Secret": "WVD[&vAwis9=#883bM$FRc0Mw8h",
"AccessTokenExpiration": 86400,
"RefreshTokenExpiration": 518400
},
"SecuritySettings": {
"Secret": "kfexxgohdxeelabz",
"Issuer": "Manager",
"Audience": "the client of your app",
"IdType": "Name",
"TokenExpiryInHours": 2
}
}