diff options
| author | transtrike <transtrike@gmail.com> | 2021-02-28 13:00:16 +0200 |
|---|---|---|
| committer | transtrike <transtrike@gmail.com> | 2021-02-28 13:00:16 +0200 |
| commit | 26b18fe3727507d1b47ffb53ed773f133122eee8 (patch) | |
| tree | cad0cdb64cd98edf1ced707b2296fb16da505801 | |
| parent | e4331fe503547df8f17095540cbd4170bbaf2b25 (diff) | |
| download | DevHive-26b18fe3727507d1b47ffb53ed773f133122eee8.tar DevHive-26b18fe3727507d1b47ffb53ed773f133122eee8.tar.gz DevHive-26b18fe3727507d1b47ffb53ed773f133122eee8.zip | |
Integrated new JWT validation where needed
| -rw-r--r-- | src/.editorconfig | 5 | ||||
| -rw-r--r-- | src/Common/DevHive.Common/Jwt/Interfaces/IJwtService.cs | 17 | ||||
| -rw-r--r-- | src/Common/DevHive.Common/Jwt/JwtService.cs | 2 | ||||
| -rw-r--r-- | src/Services/DevHive.Services/Interfaces/IUserService.cs | 49 | ||||
| -rw-r--r-- | src/Services/DevHive.Services/Services/UserService.cs | 117 | ||||
| -rw-r--r-- | src/Web/DevHive.Web/Controllers/CommentController.cs | 10 | ||||
| -rw-r--r-- | src/Web/DevHive.Web/Controllers/PostController.cs | 10 | ||||
| -rw-r--r-- | src/Web/DevHive.Web/Controllers/UserController.cs | 12 |
8 files changed, 95 insertions, 127 deletions
diff --git a/src/.editorconfig b/src/.editorconfig index ea2af21..9f0e74b 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -44,9 +44,10 @@ dotnet_diagnostic.IDE0055.severity = warning # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true dotnet_separate_import_directive_groups = false + # Avoid "this." and "Me." if not necessary -dotnet_style_qualification_for_field = false:refactoring -dotnet_style_qualification_for_property = false:refactoring +dotnet_style_qualification_for_field = true:refactoring +dotnet_style_qualification_for_property = true:refactoring dotnet_style_qualification_for_method = false:refactoring dotnet_style_qualification_for_event = false:refactoring diff --git a/src/Common/DevHive.Common/Jwt/Interfaces/IJwtService.cs b/src/Common/DevHive.Common/Jwt/Interfaces/IJwtService.cs index 6f844f5..352a7d5 100644 --- a/src/Common/DevHive.Common/Jwt/Interfaces/IJwtService.cs +++ b/src/Common/DevHive.Common/Jwt/Interfaces/IJwtService.cs @@ -5,7 +5,24 @@ namespace DevHive.Common.Jwt.Interfaces { public interface IJwtService { + /// <summary> + /// The generation of a JWT, when a new user registers or log ins + /// Tokens have an expiration time of 7 days. + /// </summary> + /// <param name="userId">User's Guid</param> + /// <param name="username">Users's username</param> + /// <param name="roleNames">List of user's roles</param> + /// <returns>Return a new JWT, containing the user id, username and roles.</returns> string GenerateJwtToken(Guid userId, string username, List<string> roleNames); + + /// <summary> + /// Checks whether the given user, gotten by the "id" property, + /// is the same user as the one in the token (unless the user in the token has the admin role) + /// and the roles in the token are the same as those in the user, gotten by the id in the token + /// </summary> + /// <param name="userId">Guid of the user being validated</param> + /// <param name="rawToken">The raw token coming from the request</param> + /// <returns>Bool result of is the user authenticated to do an action</returns> bool ValidateToken(Guid userId, string rawToken); } } diff --git a/src/Common/DevHive.Common/Jwt/JwtService.cs b/src/Common/DevHive.Common/Jwt/JwtService.cs index a0c49db..9f316da 100644 --- a/src/Common/DevHive.Common/Jwt/JwtService.cs +++ b/src/Common/DevHive.Common/Jwt/JwtService.cs @@ -1,11 +1,9 @@ using System; -using System.Buffers.Text; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Security.Principal; -using System.Text; using DevHive.Common.Jwt.Interfaces; using Microsoft.IdentityModel.Tokens; diff --git a/src/Services/DevHive.Services/Interfaces/IUserService.cs b/src/Services/DevHive.Services/Interfaces/IUserService.cs index 4a9ffc8..a55f9dd 100644 --- a/src/Services/DevHive.Services/Interfaces/IUserService.cs +++ b/src/Services/DevHive.Services/Interfaces/IUserService.cs @@ -7,19 +7,64 @@ namespace DevHive.Services.Interfaces { public interface IUserService { + /// <summary> + /// Log ins an existing user and gives him/her a JWT Token for further authorization + /// </summary> + /// <param name="loginModel">Login service model, conaining user's username and password</param> + /// <returns>A JWT Token for authorization</returns> Task<TokenModel> LoginUser(LoginServiceModel loginModel); + + /// <summary> + /// Registers a new user and gives him/her a JWT Token for further authorization + /// </summary> + /// <param name="registerModel">Register service model, containing the new user's data</param> + /// <returns>A JWT Token for authorization</returns> Task<TokenModel> RegisterUser(RegisterServiceModel registerModel); + /// <summary> + /// Get a user by his username. Used for querying profiles without provided authentication + /// </summary> + /// <param name="username">User's username, who's to be queried</param> + /// <returns>The queried user or null, if non existant</returns> Task<UserServiceModel> GetUserByUsername(string username); + + /// <summary> + /// Get a user by his Guid. Used for querying full user's profile + /// Requires authenticated user + /// </summary> + /// <param name="id">User's username, who's to be queried</param> + /// <returns>The queried user or null, if non existant</returns> Task<UserServiceModel> GetUserById(Guid id); + /// <summary> + /// Updates a user's data, provided a full model with new details + /// Requires authenticated user + /// </summary> + /// <param name="updateUserServiceModel">Full update user model for updating</param> + /// <returns>Read model of the new user</returns> Task<UserServiceModel> UpdateUser(UpdateUserServiceModel updateUserServiceModel); + + /// <summary> + /// Uploads the given picture and assigns it's link to the user in the database + /// Requires authenticated user + /// </summary> + /// <param name="updateProfilePictureServiceModel">Contains User's Guid and the new picture to be updated</param> + /// <returns>The new picture's URL</returns> Task<ProfilePictureServiceModel> UpdateProfilePicture(UpdateProfilePictureServiceModel updateProfilePictureServiceModel); + /// <summary> + /// Deletes a user from the database and removes his data entirely + /// Requires authenticated user + /// </summary> + /// <param name="id">The user's Guid, who's to be deleted</param> + /// <returns>True if successfull, false otherwise</returns> Task<bool> DeleteUser(Guid id); - Task<bool> ValidJWT(Guid id, string rawTokenData); - + /// <summary> + /// We don't talk about that! + /// </summary> + /// <param name="userId"></param> + /// <returns></returns> Task<TokenModel> SuperSecretPromotionToAdmin(Guid userId); } } diff --git a/src/Services/DevHive.Services/Services/UserService.cs b/src/Services/DevHive.Services/Services/UserService.cs index cbcb116..4f74b06 100644 --- a/src/Services/DevHive.Services/Services/UserService.cs +++ b/src/Services/DevHive.Services/Services/UserService.cs @@ -1,20 +1,14 @@ using AutoMapper; -using DevHive.Services.Options; using DevHive.Services.Models.User; using System.Threading.Tasks; using DevHive.Data.Models; using System; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using Microsoft.IdentityModel.Tokens; -using System.Text; using System.Collections.Generic; using DevHive.Common.Models.Identity; using DevHive.Services.Interfaces; using DevHive.Data.Interfaces; using System.Linq; using Microsoft.AspNetCore.Http; -using DevHive.Common.Jwt; using DevHive.Common.Jwt.Interfaces; namespace DevHive.Services.Services @@ -26,7 +20,6 @@ namespace DevHive.Services.Services private readonly ILanguageRepository _languageRepository; private readonly ITechnologyRepository _technologyRepository; private readonly IMapper _userMapper; - private readonly JwtOptions _jwtOptions; private readonly ICloudService _cloudService; private readonly IJwtService _jwtService; @@ -35,14 +28,12 @@ namespace DevHive.Services.Services IRoleRepository roleRepository, ITechnologyRepository technologyRepository, IMapper mapper, - JwtOptions jwtOptions, ICloudService cloudService, IJwtService jwtService) { this._userRepository = userRepository; this._roleRepository = roleRepository; this._userMapper = mapper; - this._jwtOptions = jwtOptions; this._languageRepository = languageRepository; this._technologyRepository = technologyRepository; this._cloudService = cloudService; @@ -50,10 +41,6 @@ namespace DevHive.Services.Services } #region Authentication - /// <summary> - /// Adds a new user to the database with the values from the given model. - /// Returns a JSON Web Token (that can be used for authorization) - /// </summary> public async Task<TokenModel> LoginUser(LoginServiceModel loginModel) { if (!await this._userRepository.DoesUsernameExistAsync(loginModel.UserName)) @@ -64,14 +51,10 @@ namespace DevHive.Services.Services if (!await this._userRepository.VerifyPassword(user, loginModel.Password)) throw new ArgumentException("Incorrect password!"); - return new TokenModel(WriteJWTSecurityToken(user.Id, user.UserName, user.Roles)); + List<string> roleNames = user.Roles.Select(x => x.Name).ToList(); + return new TokenModel(this._jwtService.GenerateJwtToken(user.Id, user.UserName, roleNames)); } - /// <summary> - /// Register a user in the database and return a - /// </summary> - /// <param name="registerModel">Register model, containing registration information</param> - /// <returns>A Token model, containing JWT Token for further verification</returns> public async Task<TokenModel> RegisterUser(RegisterServiceModel registerModel) { if (await this._userRepository.DoesUsernameExistAsync(registerModel.UserName)) @@ -91,11 +74,8 @@ namespace DevHive.Services.Services throw new ArgumentException("Unable to add role to user"); User createdUser = await this._userRepository.GetByUsernameAsync(registerModel.UserName); - List<string> roleNames = createdUser - .Roles - .Select(x => x.Name) - .ToList(); + List<string> roleNames = createdUser.Roles.Select(x => x.Name).ToList(); return new TokenModel(this._jwtService.GenerateJwtToken(createdUser.Id, createdUser.UserName, roleNames)); } #endregion @@ -140,9 +120,6 @@ namespace DevHive.Services.Services return this._userMapper.Map<UserServiceModel>(newUser); } - /// <summary> - /// Uploads the given picture and assigns it's link to the user in the database - /// </summary> public async Task<ProfilePictureServiceModel> UpdateProfilePicture(UpdateProfilePictureServiceModel updateProfilePictureServiceModel) { User user = await this._userRepository.GetByIdAsync(updateProfilePictureServiceModel.UserId); @@ -179,61 +156,7 @@ namespace DevHive.Services.Services #region Validations /// <summary> - /// Checks whether the given user, gotten by the "id" property, - /// is the same user as the one in the token (unless the user in the token has the admin role) - /// and the roles in the token are the same as those in the user, gotten by the id in the token - /// </summary> - /// <param name="id"></param> - /// <param name="rawTokenData"></param> - /// <returns></returns> - public async Task<bool> ValidJWT(Guid id, string rawTokenData) - { - return this._jwtService.ValidateToken(rawTokenData); - // There is authorization name in the beginning, i.e. "Bearer eyJh..." - // var jwt = new JwtSecurityTokenHandler().ReadJwtToken(rawTokenData.Remove(0, 7)); - - // Guid jwtUserID = new(UserService.GetClaimTypeValues("ID", jwt.Claims).First()); - // List<string> jwtRoleNames = UserService.GetClaimTypeValues("role", jwt.Claims); - - // User user = await this._userRepository.GetByIdAsync(jwtUserID) - // ?? throw new ArgumentException("User does not exist!"); - - // /* Check if he is an admin */ - // if (user.Roles.Any(x => x.Name == Role.AdminRole)) - // return true; - - // if (!jwtRoleNames.Contains(Role.AdminRole) && user.Id != id) - // return false; - - // // Check if jwt contains all user roles (if it doesn't, jwt is either old or tampered with) - // foreach (var role in user.Roles) - // if (!jwtRoleNames.Contains(role.Name)) - // return false; - - // // Check if jwt contains only roles of user - // if (jwtRoleNames.Count != user.Roles.Count) - // return false; - - // return true; - } - - /// <summary> - /// Returns all values from a given claim type - /// </summary> - private static List<string> GetClaimTypeValues(string type, IEnumerable<Claim> claims) - { - List<string> toReturn = new(); - - foreach (var claim in claims) - if (claim.Type == type) - toReturn.Add(claim.Value); - - return toReturn; - } - - /// <summary> - /// Checks whether the user in the model exists - /// and whether the username in the model is already taken. + /// Checks whether the user in the model exists and whether the username in the model is already taken. /// If the check fails (is false), it throws an exception, otherwise nothing happens /// </summary> private async Task ValidateUserOnUpdate(UpdateUserServiceModel updateUserServiceModel) @@ -255,38 +178,6 @@ namespace DevHive.Services.Services if (!await this._userRepository.ValidateFriendsCollectionAsync(usernames)) throw new ArgumentException("One or more friends do not exist!"); } - - /// <summary> - /// Return a new JSON Web Token, containing the user id, username and roles. - /// Tokens have an expiration time of 7 days. - /// </summary> - private string WriteJWTSecurityToken(Guid userId, string username, HashSet<Role> roles) - { - byte[] signingKey = Encoding.ASCII.GetBytes(_jwtOptions.Secret); - HashSet<Claim> claims = new() - { - new Claim("ID", $"{userId}"), - new Claim("Username", username) - }; - - foreach (var role in roles) - { - claims.Add(new Claim(ClaimTypes.Role, role.Name)); - } - - SecurityTokenDescriptor tokenDescriptor = new() - { - Subject = new ClaimsIdentity(claims), - Expires = DateTime.Today.AddDays(7), - SigningCredentials = new SigningCredentials( - new SymmetricSecurityKey(signingKey), - SecurityAlgorithms.HmacSha512Signature) - }; - - JwtSecurityTokenHandler tokenHandler = new(); - SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); - return tokenHandler.WriteToken(token); - } #endregion #region Misc diff --git a/src/Web/DevHive.Web/Controllers/CommentController.cs b/src/Web/DevHive.Web/Controllers/CommentController.cs index c38e300..b4fae5c 100644 --- a/src/Web/DevHive.Web/Controllers/CommentController.cs +++ b/src/Web/DevHive.Web/Controllers/CommentController.cs @@ -6,6 +6,7 @@ using DevHive.Web.Models.Comment; using DevHive.Services.Models.Comment; using Microsoft.AspNetCore.Authorization; using DevHive.Services.Interfaces; +using DevHive.Common.Jwt.Interfaces; namespace DevHive.Web.Controllers { @@ -16,16 +17,21 @@ namespace DevHive.Web.Controllers { private readonly ICommentService _commentService; private readonly IMapper _commentMapper; + private readonly IJwtService _jwtService; - public CommentController(ICommentService commentService, IMapper commentMapper) + public CommentController(ICommentService commentService, IMapper commentMapper, IJwtService jwtService) { this._commentService = commentService; this._commentMapper = commentMapper; + this._jwtService = jwtService; } [HttpPost] public async Task<IActionResult> AddComment(Guid userId, [FromBody] CreateCommentWebModel createCommentWebModel, [FromHeader] string authorization) { + if (!this._jwtService.ValidateToken(userId, authorization)) + return new UnauthorizedResult(); + if (!await this._commentService.ValidateJwtForCreating(userId, authorization)) return new UnauthorizedResult(); @@ -53,7 +59,7 @@ namespace DevHive.Web.Controllers [HttpPut] public async Task<IActionResult> UpdateComment(Guid userId, [FromBody] UpdateCommentWebModel updateCommentWebModel, [FromHeader] string authorization) { - if (!await this._commentService.ValidateJwtForComment(updateCommentWebModel.CommentId, authorization)) + if (!this._jwtService.ValidateToken(userId, authorization)) return new UnauthorizedResult(); UpdateCommentServiceModel updateCommentServiceModel = diff --git a/src/Web/DevHive.Web/Controllers/PostController.cs b/src/Web/DevHive.Web/Controllers/PostController.cs index d3fdbf6..309070c 100644 --- a/src/Web/DevHive.Web/Controllers/PostController.cs +++ b/src/Web/DevHive.Web/Controllers/PostController.cs @@ -6,6 +6,7 @@ using DevHive.Web.Models.Post; using DevHive.Services.Models.Post; using Microsoft.AspNetCore.Authorization; using DevHive.Services.Interfaces; +using DevHive.Common.Jwt.Interfaces; namespace DevHive.Web.Controllers { @@ -16,18 +17,20 @@ namespace DevHive.Web.Controllers { private readonly IPostService _postService; private readonly IMapper _postMapper; + private readonly IJwtService _jwtService; - public PostController(IPostService postService, IMapper postMapper) + public PostController(IPostService postService, IMapper postMapper, IJwtService jwtService) { this._postService = postService; this._postMapper = postMapper; + this._jwtService = jwtService; } #region Create [HttpPost] public async Task<IActionResult> Create(Guid userId, [FromForm] CreatePostWebModel createPostWebModel, [FromHeader] string authorization) { - if (!await this._postService.ValidateJwtForCreating(userId, authorization)) + if (!this._jwtService.ValidateToken(userId, authorization)) return new UnauthorizedResult(); CreatePostServiceModel createPostServiceModel = @@ -58,6 +61,9 @@ namespace DevHive.Web.Controllers [HttpPut] public async Task<IActionResult> Update(Guid userId, [FromForm] UpdatePostWebModel updatePostWebModel, [FromHeader] string authorization) { + if (!this._jwtService.ValidateToken(userId, authorization)) + return new UnauthorizedResult(); + if (!await this._postService.ValidateJwtForPost(updatePostWebModel.PostId, authorization)) return new UnauthorizedResult(); diff --git a/src/Web/DevHive.Web/Controllers/UserController.cs b/src/Web/DevHive.Web/Controllers/UserController.cs index a1e87f4..b01ecc1 100644 --- a/src/Web/DevHive.Web/Controllers/UserController.cs +++ b/src/Web/DevHive.Web/Controllers/UserController.cs @@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using DevHive.Common.Models.Identity; using DevHive.Services.Interfaces; +using DevHive.Common.Jwt.Interfaces; +using DevHive.Web.Models.Attributes; namespace DevHive.Web.Controllers { @@ -16,11 +18,13 @@ namespace DevHive.Web.Controllers { private readonly IUserService _userService; private readonly IMapper _userMapper; + private readonly IJwtService _jwtService; - public UserController(IUserService userService, IMapper mapper) + public UserController(IUserService userService, IMapper mapper, IJwtService jwtService) { this._userService = userService; this._userMapper = mapper; + this._jwtService = jwtService; } #region Authentication @@ -56,7 +60,7 @@ namespace DevHive.Web.Controllers [Authorize(Roles = "User,Admin")] public async Task<IActionResult> GetById(Guid id, [FromHeader] string authorization) { - if (!await this._userService.ValidJWT(id, authorization)) + if (!this._jwtService.ValidateToken(id, authorization)) return new UnauthorizedResult(); UserServiceModel userServiceModel = await this._userService.GetUserById(id); @@ -82,7 +86,7 @@ namespace DevHive.Web.Controllers [Authorize(Roles = "User,Admin")] public async Task<IActionResult> Update(Guid id, [FromBody] UpdateUserWebModel updateUserWebModel, [FromHeader] string authorization) { - if (!await this._userService.ValidJWT(id, authorization)) + if (!this._jwtService.ValidateToken(id, authorization)) return new UnauthorizedResult(); UpdateUserServiceModel updateUserServiceModel = this._userMapper.Map<UpdateUserServiceModel>(updateUserWebModel); @@ -100,7 +104,7 @@ namespace DevHive.Web.Controllers [Authorize(Roles = "User,Admin")] public async Task<IActionResult> Delete(Guid id, [FromHeader] string authorization) { - if (!await this._userService.ValidJWT(id, authorization)) + if (!this._jwtService.ValidateToken(id, authorization)) return new UnauthorizedResult(); bool result = await this._userService.DeleteUser(id); |
