diff options
Diffstat (limited to 'src/Services/DevHive.Services')
9 files changed, 222 insertions, 219 deletions
diff --git a/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs b/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs index fefa6d8..4534511 100644 --- a/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs +++ b/src/Services/DevHive.Services/Configurations/Mapping/RatingMappings.cs @@ -8,6 +8,14 @@ namespace DevHive.Services.Configurations.Mapping { public RatingMappings() { + CreateMap<CreateRatingServiceModel, Rating>() + .ForMember(dest => dest.User, src => src.Ignore()) + .ForMember(dest => dest.Post, src => src.Ignore()) + .ForMember(dest => dest.Id, src => src.Ignore()); + + CreateMap<Rating, ReadRatingServiceModel>(); + + CreateMap<UpdateRatingServiceModel, Rating>(); } } } diff --git a/src/Services/DevHive.Services/DevHive.Services.csproj b/src/Services/DevHive.Services/DevHive.Services.csproj index 650a304..f51c1b6 100644 --- a/src/Services/DevHive.Services/DevHive.Services.csproj +++ b/src/Services/DevHive.Services/DevHive.Services.csproj @@ -12,11 +12,13 @@ <PackageReference Include="AutoMapper" Version="10.1.1"/> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1"/> <PackageReference Include="CloudinaryDotNet" Version="1.14.0"/> - <PackageReference Include="SonarAnalyzer.CSharp" Version="8.18.0.27296"/> + <PackageReference Include="SonarAnalyzer.CSharp" Version="8.19.0.28253"/> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\Data\DevHive.Data\DevHive.Data.csproj"/> <ProjectReference Include="..\DevHive.Services.Models\DevHive.Services.Models.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common\DevHive.Common.csproj"/> + <ProjectReference Include="..\..\Common\DevHive.Common.Models\DevHive.Common.Models.csproj"/> </ItemGroup> <PropertyGroup> <EnableNETAnalyzers>true</EnableNETAnalyzers> diff --git a/src/Services/DevHive.Services/Interfaces/IRateService.cs b/src/Services/DevHive.Services/Interfaces/IRateService.cs deleted file mode 100644 index 359ef55..0000000 --- a/src/Services/DevHive.Services/Interfaces/IRateService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Threading.Tasks; -using DevHive.Data.Models; -using DevHive.Services.Models.Post.Rating; - -namespace DevHive.Services.Interfaces -{ - public interface IRateService - { - Task<ReadPostRatingServiceModel> RatePost(RatePostServiceModel ratePostServiceModel); - - bool HasUserRatedThisPost(User user, Post post); - } -} diff --git a/src/Services/DevHive.Services/Interfaces/IRatingService.cs b/src/Services/DevHive.Services/Interfaces/IRatingService.cs new file mode 100644 index 0000000..beea821 --- /dev/null +++ b/src/Services/DevHive.Services/Interfaces/IRatingService.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; +using DevHive.Data.Models; +using DevHive.Services.Models.Post.Rating; + +namespace DevHive.Services.Interfaces +{ + public interface IRatingService + { + Task<Guid> RatePost(CreateRatingServiceModel createRatingServiceModel); + + Task<ReadRatingServiceModel> GetRatingById(Guid ratingId); + Task<ReadRatingServiceModel> GetRatingByPostAndUser(Guid userId, Guid postId); + + + Task<ReadRatingServiceModel> UpdateRating(UpdateRatingServiceModel updateRatingServiceModel); + + Task<bool> DeleteRating(Guid ratingId); + + Task<bool> HasUserRatedThisPost(Guid userId, Guid postId); + } +} 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/Options/JwtOptions.cs b/src/Services/DevHive.Services/Options/JwtOptions.cs deleted file mode 100644 index d973f45..0000000 --- a/src/Services/DevHive.Services/Options/JwtOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.Options; - -namespace DevHive.Services.Options -{ - public class JwtOptions - { - public JwtOptions(string secret) - { - this.Secret = secret; - } - - public string Secret { get; init; } - } -} diff --git a/src/Services/DevHive.Services/Services/RateService.cs b/src/Services/DevHive.Services/Services/RateService.cs deleted file mode 100644 index caf4b80..0000000 --- a/src/Services/DevHive.Services/Services/RateService.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using DevHive.Data.Interfaces; -using DevHive.Data.Models; -using DevHive.Services.Interfaces; -using DevHive.Services.Models.Post.Rating; - -namespace DevHive.Services.Services -{ - public class RateService : IRateService - { - private readonly IPostRepository _postRepository; - private readonly IUserRepository _userRepository; - private readonly IRatingRepository _ratingRepository; - private readonly IMapper _mapper; - - public RateService(IPostRepository postRepository, IRatingRepository ratingRepository, IUserRepository userRepository, IMapper mapper) - { - this._postRepository = postRepository; - this._ratingRepository = ratingRepository; - this._userRepository = userRepository; - this._mapper = mapper; - } - - public async Task<ReadPostRatingServiceModel> RatePost(RatePostServiceModel ratePostServiceModel) - { - throw new NotImplementedException(); - // if (!await this._postRepository.DoesPostExist(ratePostServiceModel.PostId)) - // throw new ArgumentException("Post does not exist!"); - - // if (!await this._userRepository.DoesUserExistAsync(ratePostServiceModel.UserId)) - // throw new ArgumentException("User does not exist!"); - - // Post post = await this._postRepository.GetByIdAsync(ratePostServiceModel.PostId); - // User user = await this._userRepository.GetByIdAsync(ratePostServiceModel.UserId); - - // if (this.HasUserRatedThisPost(user, post)) - // throw new ArgumentException("You can't rate the same post more then one(duh, amigo)"); - - // this.Rate(user, post, ratePostServiceModel.Liked); - - // bool success = await this._ratingRepository.EditAsync(post.Rating.Id, post.Rating); - // if (!success) - // throw new InvalidOperationException("Unable to rate the post!"); - - // Rating newRating = await this._ratingRepository.GetByIdAsync(post.Rating.Id); - // return this._mapper.Map<ReadPostRatingServiceModel>(newRating); - } - - public async Task<ReadPostRatingServiceModel> RemoveUserRateFromPost(Guid userId, Guid postId) - { - throw new NotImplementedException(); - // Post post = await this._postRepository.GetByIdAsync(postId); - // User user = await this._userRepository.GetByIdAsync(userId); - - // if (!this.HasUserRatedThisPost(user, post)) - // throw new ArgumentException("You haven't rated this post, lmao!"); - } - - public bool HasUserRatedThisPost(User user, Post post) - { - throw new NotImplementedException(); - // return post.Rating.UsersThatRated - // .Any(x => x.Id == user.Id); - } - - private void Rate(User user, Post post, bool liked) - { - throw new NotImplementedException(); - // if (liked) - // post.Rating.Rate++; - // else - // post.Rating.Rate--; - - // post.Rating.UsersThatRated.Add(user); - } - } -} diff --git a/src/Services/DevHive.Services/Services/RatingService.cs b/src/Services/DevHive.Services/Services/RatingService.cs new file mode 100644 index 0000000..6ddba1c --- /dev/null +++ b/src/Services/DevHive.Services/Services/RatingService.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using AutoMapper; +using DevHive.Data.Interfaces; +using DevHive.Data.Models; +using DevHive.Services.Interfaces; +using DevHive.Services.Models.Post.Rating; + +namespace DevHive.Services.Services +{ + public class RatingService : IRatingService + { + private readonly IPostRepository _postRepository; + private readonly IUserRepository _userRepository; + private readonly IRatingRepository _ratingRepository; + private readonly IMapper _mapper; + + public RatingService(IPostRepository postRepository, IRatingRepository ratingRepository, IUserRepository userRepository, IMapper mapper) + { + this._postRepository = postRepository; + this._ratingRepository = ratingRepository; + this._userRepository = userRepository; + this._mapper = mapper; + } + + #region Create + public async Task<Guid> RatePost(CreateRatingServiceModel createRatingServiceModel) + { + if (!await this._postRepository.DoesPostExist(createRatingServiceModel.PostId)) + throw new ArgumentException("Post does not exist!"); + + if (await this._ratingRepository.UserRatedPost(createRatingServiceModel.UserId, createRatingServiceModel.PostId)) + throw new ArgumentException("User already rated the post!"); + + Rating rating = this._mapper.Map<Rating>(createRatingServiceModel); + + rating.User = await this._userRepository.GetByIdAsync(createRatingServiceModel.UserId); + rating.Post = await this._postRepository.GetByIdAsync(createRatingServiceModel.PostId); + + bool success = await this._ratingRepository.AddAsync(rating); + + if (success) + { + Rating newRating = await this._ratingRepository.GetRatingByUserAndPostId(rating.User.Id, rating.Post.Id); + + return newRating.Id; + } + else + return Guid.Empty; + } + #endregion + + #region Read + public async Task<ReadRatingServiceModel> GetRatingById(Guid ratingId) + { + Rating rating = await this._ratingRepository.GetByIdAsync(ratingId) ?? + throw new ArgumentException("The rating does not exist"); + + ReadRatingServiceModel readRatingServiceModel = this._mapper.Map<ReadRatingServiceModel>(rating); + readRatingServiceModel.UserId = rating.User.Id; + + return readRatingServiceModel; + } + + public async Task<ReadRatingServiceModel> GetRatingByPostAndUser(Guid userId, Guid postId) + { + Rating rating = await this._ratingRepository.GetRatingByUserAndPostId(userId, postId) ?? + throw new ArgumentException("The rating does not exist"); + + ReadRatingServiceModel readRatingServiceModel = this._mapper.Map<ReadRatingServiceModel>(rating); + readRatingServiceModel.UserId = rating.User.Id; + + return readRatingServiceModel; + } + #endregion + + #region Update + public async Task<ReadRatingServiceModel> UpdateRating(UpdateRatingServiceModel updateRatingServiceModel) + { + Rating rating = await this._ratingRepository.GetByIdAsync(updateRatingServiceModel.Id) ?? + throw new ArgumentException("Rating does not exist!"); + + User user = await this._userRepository.GetByIdAsync(updateRatingServiceModel.UserId) ?? + throw new ArgumentException("User does not exist!"); + + if (!await this._ratingRepository.UserRatedPost(updateRatingServiceModel.UserId, updateRatingServiceModel.PostId)) + throw new ArgumentException("User has not rated the post!"); + + rating.User = user; + rating.IsLike = updateRatingServiceModel.IsLike; + + bool result = await this._ratingRepository.EditAsync(updateRatingServiceModel.Id, rating); + + if (result) + { + ReadRatingServiceModel readRatingServiceModel = this._mapper.Map<ReadRatingServiceModel>(rating); + return readRatingServiceModel; + } + else + return null; + } + #endregion + + #region Delete + public async Task<bool> DeleteRating(Guid ratingId) + { + if (!await this._ratingRepository.DoesRatingExist(ratingId)) + throw new ArgumentException("Rating does not exist!"); + + Rating rating = await this._ratingRepository.GetByIdAsync(ratingId); + return await this._ratingRepository.DeleteAsync(rating); + } + #endregion + + public async Task<bool> HasUserRatedThisPost(Guid userId, Guid postId) + { + return await this._ratingRepository + .UserRatedPost(userId, postId); + } + } +} diff --git a/src/Services/DevHive.Services/Services/UserService.cs b/src/Services/DevHive.Services/Services/UserService.cs index dfd45cc..4f74b06 100644 --- a/src/Services/DevHive.Services/Services/UserService.cs +++ b/src/Services/DevHive.Services/Services/UserService.cs @@ -1,21 +1,15 @@ 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 DevHive.Common.Models.Misc; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; +using DevHive.Common.Jwt.Interfaces; namespace DevHive.Services.Services { @@ -26,31 +20,27 @@ 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; public UserService(IUserRepository userRepository, ILanguageRepository languageRepository, IRoleRepository roleRepository, ITechnologyRepository technologyRepository, IMapper mapper, - JwtOptions jwtOptions, - ICloudService cloudService) + 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; + this._jwtService = jwtService; } #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)) @@ -61,12 +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> - /// Returns a new JSON Web Token (that can be used for authorization) for the given user - /// </summary> public async Task<TokenModel> RegisterUser(RegisterServiceModel registerModel) { if (await this._userRepository.DoesUsernameExistAsync(registerModel.UserName)) @@ -86,7 +74,9 @@ namespace DevHive.Services.Services throw new ArgumentException("Unable to add role to user"); User createdUser = await this._userRepository.GetByUsernameAsync(registerModel.UserName); - return new TokenModel(WriteJWTSecurityToken(createdUser.Id, createdUser.UserName, createdUser.Roles)); + + List<string> roleNames = createdUser.Roles.Select(x => x.Name).ToList(); + return new TokenModel(this._jwtService.GenerateJwtToken(createdUser.Id, createdUser.UserName, roleNames)); } #endregion @@ -130,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); @@ -169,57 +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> - public async Task<bool> ValidJWT(Guid id, string 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) @@ -241,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 @@ -294,9 +199,13 @@ namespace DevHive.Services.Services user.Roles.Add(admin); await this._userRepository.EditAsync(user.Id, user); - User newUser = await this._userRepository.GetByIdAsync(userId); + User createdUser = await this._userRepository.GetByIdAsync(userId); + List<string> roleNames = createdUser + .Roles + .Select(x => x.Name) + .ToList(); - return new TokenModel(WriteJWTSecurityToken(newUser.Id, newUser.UserName, newUser.Roles)); + return new TokenModel(this._jwtService.GenerateJwtToken(createdUser.Id, createdUser.UserName, roleNames)); } private async Task PopulateUserModel(User user, UpdateUserServiceModel updateUserServiceModel) |
