| @@ -11,5 +11,6 @@ namespace Diligent.WebAPI.Business.Interfaces | |||
| { | |||
| Task<bool> ValidateCustomer(string username, string password); | |||
| Task<string?> GenerateToken(); | |||
| Task<Customer> GetByUserName(string username); | |||
| } | |||
| } | |||
| @@ -7,7 +7,7 @@ using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Interfaces | |||
| { | |||
| public interface ICustomerService | |||
| public interface ICustomerRepository | |||
| { | |||
| Task<Customer> GetCustomer(string username); | |||
| @@ -3,28 +3,24 @@ using Diligent.WebAPI.Data.Entities; | |||
| using Microsoft.AspNetCore.Identity; | |||
| using Microsoft.Extensions.Configuration; | |||
| using Microsoft.IdentityModel.Tokens; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IdentityModel.Tokens.Jwt; | |||
| using System.Linq; | |||
| using System.Security.Claims; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class AuthenticationService:IAuthenticationService | |||
| public class AuthenticationService : IAuthenticationService | |||
| { | |||
| private readonly UserManager<Customer> _customerManager; | |||
| private readonly IConfiguration _configuration; | |||
| private Customer _customer; | |||
| public AuthenticationService(UserManager<Customer> customerManager,IConfiguration configuration) | |||
| public AuthenticationService(UserManager<Customer> customerManager, IConfiguration configuration) | |||
| { | |||
| _customerManager = customerManager; | |||
| _configuration = configuration; | |||
| } | |||
| public async Task<bool> ValidateCustomer(string username,string password) | |||
| public async Task<bool> ValidateCustomer(string username, string password) | |||
| { | |||
| _customer = await _customerManager.FindByNameAsync(username); | |||
| return (_customer != null && await _customerManager.CheckPasswordAsync | |||
| @@ -44,7 +40,7 @@ namespace Diligent.WebAPI.Business.Services | |||
| private async Task<List<Claim>> GetClaims() | |||
| { | |||
| //method creates a list of claims with the user name inside and all the roles the user belongs to. | |||
| Claim claim = new (ClaimTypes.Name, _customer.UserName); | |||
| Claim claim = new(ClaimTypes.Name, _customer.UserName); | |||
| var claims = new List<Claim> | |||
| { | |||
| @@ -70,6 +66,11 @@ namespace Diligent.WebAPI.Business.Services | |||
| return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256); | |||
| } | |||
| public async Task<Customer> GetByUserName(string username) | |||
| { | |||
| return await _customerManager.FindByNameAsync(username); | |||
| } | |||
| private JwtSecurityToken GenerateTokenOptions(SigningCredentials | |||
| signingCredentials, List<Claim> claims) | |||
| { | |||
| @@ -1,26 +0,0 @@ | |||
| using Diligent.WebAPI.Business.MongoServices; | |||
| using Diligent.WebAPI.Data; | |||
| using Diligent.WebAPI.Data.Entities; | |||
| using Microsoft.Extensions.Options; | |||
| using MongoDB.Driver; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Diagnostics.CodeAnalysis; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class AuthorizationService : BaseMongo<Customer> | |||
| { | |||
| public AuthorizationService(IOptions<WebApiDatabaseSettings> webApiDatabaseSettings) : | |||
| base(webApiDatabaseSettings, "Customer") | |||
| { } | |||
| public async Task<Customer> GetByUserName(string username) | |||
| { | |||
| return await _mongoCollection.Find(c => c.UserName == username).FirstOrDefaultAsync(); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,21 +1,16 @@ | |||
| using Diligent.WebAPI.Business.Interfaces; | |||
| using Diligent.WebAPI.Data.Entities; | |||
| using Microsoft.AspNetCore.Identity; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Diagnostics.CodeAnalysis; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class CustomerService : ICustomerService | |||
| public class CustomerRepository : ICustomerRepository | |||
| { | |||
| private readonly UserManager<Customer> _customerManager; | |||
| public CustomerService(UserManager<Customer> customerManager) | |||
| public CustomerRepository(UserManager<Customer> customerManager) | |||
| { | |||
| _customerManager = customerManager; | |||
| } | |||
| @@ -33,9 +33,7 @@ public static class WebAppBuilder | |||
| builder.Services.AddScoped<IRoomRepository, RoomRepository>(); | |||
| builder.Services.AddScoped<IMongoDBContext, MongoDBContext>(); | |||
| builder.Services.AddScoped<IAuthenticationService, AuthenticationService>(); | |||
| builder.Services.AddScoped<ICustomerService, CustomerService>(); | |||
| builder.Services.AddSingleton<AuthorizationService>(); | |||
| builder.Services.AddScoped<ICustomerRepository, CustomerRepository>(); | |||
| builder.Services.AddMediatR(Assembly.GetExecutingAssembly()); | |||
| builder.Services.AddSignalR(options => | |||
| @@ -13,15 +13,15 @@ namespace Diligent.WebAPI.Host.Mediator.Authentication.Handlers | |||
| private readonly UserManager<Customer> _customerManager; | |||
| private readonly IAuthenticationService _authenticationService; | |||
| private readonly IMapper _mapper; | |||
| private readonly ICustomerService _customerService; | |||
| private readonly ICustomerRepository _customerRepository; | |||
| public LoginUserHandler(UserManager<Customer> customerManager, IAuthenticationService authenticationService, | |||
| IMapper mapper, ICustomerService customerService) | |||
| IMapper mapper, ICustomerRepository customerService) | |||
| { | |||
| _customerManager = customerManager; | |||
| _authenticationService = authenticationService; | |||
| _mapper = mapper; | |||
| _customerService = customerService; | |||
| _customerRepository = customerService; | |||
| } | |||
| public async Task<CustomerReadDTO> Handle(LoginUserQuery request, CancellationToken cancellationToken) | |||
| { | |||
| @@ -29,7 +29,7 @@ namespace Diligent.WebAPI.Host.Mediator.Authentication.Handlers | |||
| if (!await _authenticationService.ValidateCustomer(customerLoginDTO.Username, customerLoginDTO.Password)) | |||
| throw new BadHttpRequestException("Authentication failed.Wrong Username or password"); | |||
| Customer customer = await _customerService.GetCustomer(customerLoginDTO.Username); | |||
| Customer customer = await _customerRepository.GetCustomer(customerLoginDTO.Username); | |||
| var customerReadDTO = _mapper.Map<CustomerReadDTO>(customer); | |||
| customerReadDTO.Token = await _authenticationService.GenerateToken() ?? ""; | |||
| customerReadDTO.Roles = (List<string>)await _customerManager.GetRolesAsync(customer); | |||
| @@ -8,11 +8,11 @@ namespace Diligent.WebAPI.Host.Mediator.Notifications.Handlers | |||
| { | |||
| public class AddNotificationHandler : IRequestHandler<AddNotificationCommand, Unit> | |||
| { | |||
| private readonly ICustomerService _customerService; | |||
| private readonly ICustomerRepository _customerService; | |||
| public AddNotificationHandler(ICustomerService customerService) | |||
| public AddNotificationHandler(ICustomerRepository customerRepository) | |||
| { | |||
| _customerService = customerService; | |||
| _customerService = customerRepository; | |||
| } | |||
| public async Task<Unit> Handle(AddNotificationCommand request, CancellationToken cancellationToken) | |||
| @@ -7,16 +7,16 @@ namespace Diligent.WebAPI.Host.Mediator.Notifications.Handlers | |||
| { | |||
| public class DeleteNotificationHandler : IRequestHandler<DeleteNotificationCommand, Unit> | |||
| { | |||
| private readonly ICustomerService _customerService; | |||
| private readonly ICustomerRepository _customerRepository; | |||
| public DeleteNotificationHandler(ICustomerService customerService) | |||
| public DeleteNotificationHandler(ICustomerRepository customerRepository) | |||
| { | |||
| _customerService = customerService; | |||
| _customerRepository = customerRepository; | |||
| } | |||
| public async Task<Unit> Handle(DeleteNotificationCommand request, CancellationToken cancellationToken) | |||
| { | |||
| var user = await _customerService.GetCustomerById(request.NotificationData.UserId); | |||
| var user = await _customerRepository.GetCustomerById(request.NotificationData.UserId); | |||
| if (user == null) | |||
| { | |||
| @@ -30,7 +30,7 @@ namespace Diligent.WebAPI.Host.Mediator.Notifications.Handlers | |||
| throw new NotFoundException(); | |||
| } | |||
| await _customerService.DeleteNotification(user, notification); | |||
| await _customerRepository.DeleteNotification(user, notification); | |||
| return new Unit(); | |||
| } | |||
| @@ -8,18 +8,18 @@ namespace Diligent.WebAPI.Host.Mediator.Notifications.Handlers | |||
| { | |||
| public class GetNotificationsHandler : IRequestHandler<GetNotificationsQuery, List<NotificationReadDTO>> | |||
| { | |||
| private readonly ICustomerService _customerService; | |||
| private readonly ICustomerRepository _customerRepository; | |||
| private readonly IMapper _mapper; | |||
| public GetNotificationsHandler(ICustomerService customerService, IMapper mapper) | |||
| public GetNotificationsHandler(ICustomerRepository customerRepository, IMapper mapper) | |||
| { | |||
| _customerService = customerService; | |||
| _customerRepository = customerRepository; | |||
| _mapper = mapper; | |||
| } | |||
| public async Task<List<NotificationReadDTO>> Handle(GetNotificationsQuery request, CancellationToken cancellationToken) | |||
| { | |||
| return _mapper.Map<List<NotificationReadDTO>>(await _customerService.ReadNotifications(request.UserId)); | |||
| return _mapper.Map<List<NotificationReadDTO>>(await _customerRepository.ReadNotifications(request.UserId)); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,8 +1,10 @@ | |||
| using Diligent.WebAPI.Business.Services; | |||
| using Diligent.WebAPI.Business.Interfaces; | |||
| using Microsoft.AspNetCore.SignalR; | |||
| using Microsoft.IdentityModel.Tokens; | |||
| using System.Diagnostics.CodeAnalysis; | |||
| using System.IdentityModel.Tokens.Jwt; | |||
| using System.Net; | |||
| using System.Reflection; | |||
| using System.Text; | |||
| using System.Web.Http; | |||
| @@ -12,19 +14,22 @@ namespace Diligent.WebAPI.Host.Middlewares | |||
| public class AuthorizationHubFilter : Attribute, IHubFilter | |||
| { | |||
| public string Roles; | |||
| private JwtSecurityToken _token; | |||
| public async ValueTask<object> InvokeMethodAsync( | |||
| HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next) | |||
| { | |||
| Type type = typeof(AuthorizationHubFilter); | |||
| var arguments = Attribute.GetCustomAttributes(invocationContext.HubMethod).Where(k => k.TypeId.ToString() == type.ToString()).ToList(); | |||
| var arguments = GetCustomAttributes(invocationContext.HubMethod) | |||
| .Where(k => k.TypeId.ToString() == type.ToString()) | |||
| .ToList(); | |||
| // there is no custom attributes, so the filter does not need to perform authorization | |||
| if (!arguments.Any()) | |||
| return await next(invocationContext); | |||
| // get filter attribute | |||
| var roles = Attribute.GetCustomAttribute(invocationContext.HubMethod, arguments[0].GetType()); | |||
| var roles = GetCustomAttribute(invocationContext.HubMethod, arguments[0].GetType()); | |||
| if (roles is null) | |||
| throw new NullReferenceException(); | |||
| @@ -32,7 +37,7 @@ namespace Diligent.WebAPI.Host.Middlewares | |||
| // get roles from filter attribute and split roles into an array | |||
| var arrayOfRoles = ((AuthorizationHubFilter)roles).Roles.Split(","); | |||
| var context = invocationContext.Context.GetHttpContext(); | |||
| var context = GetHttpContext(invocationContext); | |||
| if (context is null) | |||
| throw new NullReferenceException(); | |||
| @@ -44,12 +49,12 @@ namespace Diligent.WebAPI.Host.Middlewares | |||
| return await next(invocationContext); | |||
| } | |||
| private static async Task AttachUserToContext(HttpContext context, string token, string[] roles) | |||
| public async Task AttachUserToContext(HttpContext context, string token, string[] roles) | |||
| { | |||
| bool contain = false; | |||
| try | |||
| { | |||
| var authorizationService = context.RequestServices.GetService(typeof(AuthorizationService)); | |||
| var authorizationService = context.RequestServices.GetService(typeof(IAuthenticationService)); | |||
| var configuration = context.RequestServices.GetService(typeof(IConfiguration)); | |||
| if (authorizationService is null || configuration is null) | |||
| @@ -61,20 +66,9 @@ namespace Diligent.WebAPI.Host.Middlewares | |||
| var audience = jwtSettings["validAudience"]; | |||
| var key = Encoding.UTF8.GetBytes(jwtSettings["jwtSecret"]); | |||
| tokenHandler.ValidateToken(token, new TokenValidationParameters | |||
| { | |||
| ValidateIssuerSigningKey = true, | |||
| IssuerSigningKey = new SymmetricSecurityKey(key), | |||
| ValidIssuer = issuer, | |||
| ValidAudience = audience, | |||
| ValidateIssuer = true, | |||
| ValidateAudience = true, | |||
| ValidateLifetime = true, | |||
| }, out SecurityToken validatedToken); | |||
| var jwtToken = (JwtSecurityToken)validatedToken; | |||
| var userName = jwtToken.Payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"].ToString(); | |||
| var rl = (jwtToken.Payload["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]).ToString(); | |||
| SetToken(tokenHandler, token, audience, issuer, key); | |||
| var userName = ReturnTokenPayload(_token, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"); | |||
| var rl = ReturnTokenPayload(_token, "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"); | |||
| if (rl is null) | |||
| throw new Exception(); | |||
| @@ -93,13 +87,14 @@ namespace Diligent.WebAPI.Host.Middlewares | |||
| if (!contain) | |||
| { | |||
| contain = true; | |||
| throw new Exception(); | |||
| } | |||
| // attach user to context on successful jwt validation | |||
| if (userName != null) | |||
| { | |||
| context.Items["User"] = await ((AuthorizationService)authorizationService).GetByUserName(userName); | |||
| context.Items["User"] = await ((IAuthenticationService)authorizationService).GetByUserName(userName); | |||
| } | |||
| } | |||
| @@ -113,5 +108,46 @@ namespace Diligent.WebAPI.Host.Middlewares | |||
| throw new UnauthorizedAccessException(); | |||
| } | |||
| } | |||
| [ExcludeFromCodeCoverage] | |||
| public new virtual Attribute? GetCustomAttribute(MemberInfo element, Type attributeType) | |||
| { | |||
| return Attribute.GetCustomAttribute(element, attributeType); | |||
| } | |||
| [ExcludeFromCodeCoverage] | |||
| public new virtual Attribute[] GetCustomAttributes(MemberInfo element) | |||
| { | |||
| return Attribute.GetCustomAttributes(element); | |||
| } | |||
| [ExcludeFromCodeCoverage] | |||
| public virtual HttpContext? GetHttpContext(HubInvocationContext invocationContext) | |||
| { | |||
| return invocationContext.Context.GetHttpContext(); | |||
| } | |||
| [ExcludeFromCodeCoverage] | |||
| public virtual void SetToken(JwtSecurityTokenHandler tokenHandler, string token, string audience, string issuer, byte[] key) | |||
| { | |||
| tokenHandler.ValidateToken(token, new TokenValidationParameters | |||
| { | |||
| ValidateIssuerSigningKey = true, | |||
| IssuerSigningKey = new SymmetricSecurityKey(key), | |||
| ValidIssuer = issuer, | |||
| ValidAudience = audience, | |||
| ValidateIssuer = true, | |||
| ValidateAudience = true, | |||
| ValidateLifetime = true, | |||
| }, out SecurityToken validatedToken); | |||
| _token = (JwtSecurityToken)validatedToken; | |||
| } | |||
| [ExcludeFromCodeCoverage] | |||
| public virtual string? ReturnTokenPayload(JwtSecurityToken token, string payload) | |||
| { | |||
| return _token.Payload[payload].ToString(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,101 @@ | |||
| using AutoMapper; | |||
| using Diligent.WebAPI.Business.Interfaces; | |||
| using Diligent.WebAPI.Data.Entities; | |||
| using Diligent.WebAPI.Host.DTOs.Customer; | |||
| using Diligent.WebAPI.Host.Mapper; | |||
| using Diligent.WebAPI.Host.Mediator.Authentication.Commands; | |||
| using Diligent.WebAPI.Host.Mediator.Authentication.Handlers; | |||
| using Diligent.WebAPI.Host.Mediator.Authentication.Queries; | |||
| using Microsoft.AspNetCore.Http; | |||
| using Microsoft.AspNetCore.Identity; | |||
| using Moq; | |||
| namespace Tests | |||
| { | |||
| [TestFixture] | |||
| public class AuthenticationHandlerTests | |||
| { | |||
| private Mock<IAuthenticationService> _authenticationServiceMock; | |||
| private IMapper _mapper; | |||
| private Mock<ICustomerRepository> _customerServiceMock; | |||
| private Mock<UserManager<Customer>> _userManagerMock; | |||
| private readonly Customer _customer = new() | |||
| { | |||
| Id = Guid.NewGuid(), | |||
| Email = "user@gmail.com", | |||
| FirstName = "User", | |||
| LastName = "User", | |||
| Notifications = new List<Notification>(), | |||
| Roles = new List<Guid> { Guid.NewGuid() }, | |||
| UserName = "user12" | |||
| }; | |||
| private readonly CustomerCreateDTO _customerCreateDTO = new() | |||
| { | |||
| Email = "user@gmail.com", | |||
| FirstName = "User", | |||
| LastName = "User", | |||
| Username = "user12" | |||
| }; | |||
| [SetUp] | |||
| public void Setup() | |||
| { | |||
| _authenticationServiceMock = new Mock<IAuthenticationService>(); | |||
| _customerServiceMock = new Mock<ICustomerRepository>(); | |||
| _userManagerMock = new Mock<UserManager<Customer>>(Mock.Of<IUserStore<Customer>>(), null, null, null, null, null, null, null, null); | |||
| var configuration = new MapperConfiguration(cfg => cfg.AddProfile(new CustomerMappingProfile())); | |||
| _mapper = new Mapper(configuration); | |||
| } | |||
| [Test] | |||
| public void LoginUser_UserIsNotAuthenticated_ThrowBadHttpRequestException() | |||
| { | |||
| _authenticationServiceMock.Setup(a => a.ValidateCustomer(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(false)); | |||
| var query = new LoginUserQuery(new CustomerLoginDTO { Username = "user1", Password = "somePassword" }); | |||
| var handler = new LoginUserHandler(_userManagerMock.Object, _authenticationServiceMock.Object, _mapper, _customerServiceMock.Object); | |||
| Assert.That(async () => await handler.Handle(query, new CancellationToken()), Throws.Exception.TypeOf<BadHttpRequestException>()); | |||
| } | |||
| [Test] | |||
| public async Task LoginUser_UserIsAuthenticated_ReturnUserObject() | |||
| { | |||
| var list = new List<string> { "Customer" }; | |||
| _authenticationServiceMock.Setup(a => a.ValidateCustomer(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(true)); | |||
| _authenticationServiceMock.Setup(a => a.GenerateToken()).Returns(Task.FromResult("someToken")); | |||
| _customerServiceMock.Setup(c => c.GetCustomer(It.IsAny<string>())).Returns(Task.FromResult(_customer)); | |||
| _userManagerMock.Setup(u => u.GetRolesAsync(It.IsAny<Customer>())).Returns(Task.FromResult((IList<string>)list)); | |||
| var query = new LoginUserQuery(new CustomerLoginDTO { Username = "user1", Password = "somePassword" }); | |||
| var handler = new LoginUserHandler(_userManagerMock.Object, _authenticationServiceMock.Object, _mapper, _customerServiceMock.Object); | |||
| var result = await handler.Handle(query, new CancellationToken()); | |||
| Assert.That(result.Id, Is.EqualTo(_customer.Id.ToString())); | |||
| } | |||
| [Test] | |||
| public void RegisterUser_ErrorWhenCreatingUser_ThrowBadHttpRequestException() | |||
| { | |||
| _userManagerMock.Setup(u => u.CreateAsync(It.IsAny<Customer>(), It.IsAny<string>())).ReturnsAsync(() => IdentityResult.Failed()); | |||
| var command = new RegisterUserCommand(_customerCreateDTO); | |||
| var handler = new RegisterUserHandler(_userManagerMock.Object, _authenticationServiceMock.Object, _mapper); | |||
| Assert.That(() => handler.Handle(command, new CancellationToken()), Throws.Exception.TypeOf<BadHttpRequestException>()); | |||
| } | |||
| [Test] | |||
| public async Task RegisterUser_ThereIsNoError_ReturnObject() | |||
| { | |||
| _userManagerMock.Setup(u => u.CreateAsync(It.IsAny<Customer>(), It.IsAny<string>())).ReturnsAsync(() => IdentityResult.Success); | |||
| _userManagerMock.Setup(u => u.AddToRoleAsync(It.IsAny<Customer>(), It.IsAny<string>())).ReturnsAsync(() => IdentityResult.Success); | |||
| _authenticationServiceMock.Setup(u => u.ValidateCustomer(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(true)); | |||
| _authenticationServiceMock.Setup(u => u.GenerateToken()).Returns(Task.FromResult("dasdada")); | |||
| _userManagerMock.Setup(u => u.GetRolesAsync(It.IsAny<Customer>())).Returns(Task.FromResult((IList<string>)new List<string> { "role" })); | |||
| var command = new RegisterUserCommand(_customerCreateDTO); | |||
| var handler = new RegisterUserHandler(_userManagerMock.Object, _authenticationServiceMock.Object, _mapper); | |||
| var result = await handler.Handle(command, new CancellationToken()); | |||
| Assert.That(result, Is.Not.Null); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,105 @@ | |||
| using AutoMapper; | |||
| using Diligent.WebAPI.Business.Interfaces; | |||
| using Diligent.WebAPI.Business.Services; | |||
| using Diligent.WebAPI.Data.Entities; | |||
| using Diligent.WebAPI.Host.Mapper; | |||
| using Microsoft.AspNetCore.Identity; | |||
| using Microsoft.Extensions.Configuration; | |||
| using Moq; | |||
| namespace Tests | |||
| { | |||
| public class AuthenticationServiceTests | |||
| { | |||
| private Mock<IAuthenticationService> _authenticationServiceMock; | |||
| private IAuthenticationService _authenticationService; | |||
| private IConfiguration _configuration; | |||
| private Mock<UserManager<Customer>> _userManagerMock; | |||
| private readonly Customer _customer = new() | |||
| { | |||
| Id = Guid.NewGuid(), | |||
| Email = "user@gmail.com", | |||
| FirstName = "User", | |||
| LastName = "User", | |||
| Notifications = new List<Notification>(), | |||
| Roles = new List<Guid> { Guid.NewGuid() }, | |||
| UserName = "user12" | |||
| }; | |||
| [SetUp] | |||
| public void Setup() | |||
| { | |||
| var inMemorySettings = new Dictionary<string, string> { | |||
| {"JwtSettings:jwtSecret", "Ovo je neka sifra koja treba biti tajna"}, | |||
| {"JwtSettings:validIssuer", "http://localhost:5116"}, | |||
| {"JwtSettings:validAudience", "http://localhost:3000"}, | |||
| }; | |||
| _configuration = new ConfigurationBuilder() | |||
| .AddInMemoryCollection(inMemorySettings) | |||
| .Build(); | |||
| _authenticationServiceMock = new Mock<IAuthenticationService>(); | |||
| _userManagerMock = new Mock<UserManager<Customer>>(Mock.Of<IUserStore<Customer>>(), null, null, null, null, null, null, null, null); | |||
| _authenticationService = new AuthenticationService(_userManagerMock.Object, _configuration); | |||
| var configuration = new MapperConfiguration(cfg => cfg.AddProfile(new CustomerMappingProfile())); | |||
| } | |||
| [Test] | |||
| public async Task ValidateCustomer_CustomerIsNull_CustomerIsNotValid() | |||
| { | |||
| _userManagerMock.Setup(u => u.FindByNameAsync(It.IsAny<string>())).Returns(Task.FromResult<Customer>(null)); | |||
| await _authenticationServiceMock.Object.ValidateCustomer("dasdas", "dasdasd"); | |||
| var result = await _authenticationService.ValidateCustomer(It.IsAny<string>(), It.IsAny<string>()); | |||
| Assert.That(result, Is.False); | |||
| } | |||
| [Test] | |||
| public async Task ValidateCustomer_CustomerIsNotNullAndUserCredentialsAreNotValid_CustomerIsNotValid() | |||
| { | |||
| _userManagerMock.Setup(u => u.FindByNameAsync(It.IsAny<string>())).Returns(Task.FromResult(_customer)); | |||
| _userManagerMock.Setup(u => u.CheckPasswordAsync(It.IsAny<Customer>(), It.IsAny<string>())).Returns(Task.FromResult(false)); | |||
| await _authenticationServiceMock.Object.ValidateCustomer("dasdas", "dasdasd"); | |||
| var result = await _authenticationService.ValidateCustomer(It.IsAny<string>(), It.IsAny<string>()); | |||
| Assert.That(result, Is.False); | |||
| } | |||
| [Test] | |||
| public async Task ValidateCustomer_CustomerIsNotNullAndUserCredentialsAreValid_CustomerIsValid() | |||
| { | |||
| _userManagerMock.Setup(u => u.FindByNameAsync(It.IsAny<string>())).Returns(Task.FromResult(_customer)); | |||
| _userManagerMock.Setup(u => u.CheckPasswordAsync(It.IsAny<Customer>(), It.IsAny<string>())).Returns(Task.FromResult(true)); | |||
| var result = await _authenticationService.ValidateCustomer("dasdasd", "dasdasd"); | |||
| Assert.That(result, Is.True); | |||
| } | |||
| [Test] | |||
| public async Task GenerateToken_UserIsNotValid_ReturnNull() | |||
| { | |||
| _userManagerMock.Setup(u => u.GetRolesAsync(It.IsAny<Customer>())).ReturnsAsync((IList<string>)new List<string> { "roles" }); | |||
| var result = await _authenticationService.GenerateToken(); | |||
| Assert.That(result, Is.Null); | |||
| } | |||
| [Test] | |||
| public async Task GenerateToken_UserIsValid_ReturnToken() | |||
| { | |||
| _userManagerMock.Setup(u => u.GetRolesAsync(It.IsAny<Customer>())).ReturnsAsync((IList<string>)new List<string> { "roles" }); | |||
| _userManagerMock.Setup(u => u.FindByNameAsync(It.IsAny<string>())).Returns(Task.FromResult(_customer)); | |||
| _userManagerMock.Setup(u => u.CheckPasswordAsync(It.IsAny<Customer>(), It.IsAny<string>())).Returns(Task.FromResult(true)); | |||
| await _authenticationService.ValidateCustomer("dasdas", "dasd"); //user must be first valid and then we generate token for him | |||
| var result = await _authenticationService.GenerateToken(); | |||
| Assert.That(result, Is.Not.Null); | |||
| } | |||
| } | |||
| } | |||
| @@ -21,7 +21,7 @@ namespace Tests | |||
| private IAuthenticationService _authenticationService; | |||
| private IMapper _mapper; | |||
| private IConfiguration _configuration; | |||
| private Mock<ICustomerService> _customerServiceMock; | |||
| private Mock<ICustomerRepository> _customerServiceMock; | |||
| private Mock<UserManager<Customer>> _userManagerMock; | |||
| private readonly Customer _customer = new() | |||
| { | |||
| @@ -53,7 +53,7 @@ namespace Tests | |||
| .AddInMemoryCollection(inMemorySettings) | |||
| .Build(); | |||
| _authenticationServiceMock = new Mock<IAuthenticationService>(); | |||
| _customerServiceMock = new Mock<ICustomerService>(); | |||
| _customerServiceMock = new Mock<ICustomerRepository>(); | |||
| _userManagerMock = new Mock<UserManager<Customer>>(Mock.Of<IUserStore<Customer>>(), null, null, null, null, null, null, null, null); | |||
| _authenticationService = new AuthenticationService(_userManagerMock.Object, _configuration); | |||
| var configuration = new MapperConfiguration(cfg => cfg.AddProfile(new CustomerMappingProfile())); | |||
| @@ -0,0 +1,181 @@ | |||
| using AutoMapper; | |||
| using Diligent.WebAPI.Business.Interfaces; | |||
| using Diligent.WebAPI.Business.Services; | |||
| using Diligent.WebAPI.Data.Entities; | |||
| using Diligent.WebAPI.Host.Mapper; | |||
| using Diligent.WebAPI.Host.Middlewares; | |||
| using Microsoft.AspNetCore.Http; | |||
| using Microsoft.AspNetCore.Identity; | |||
| using Microsoft.AspNetCore.SignalR; | |||
| using Microsoft.Extensions.Configuration; | |||
| using Moq; | |||
| using System.IdentityModel.Tokens.Jwt; | |||
| using System.Reflection; | |||
| using System.Web.Http; | |||
| namespace Tests | |||
| { | |||
| public class AuthorizationHubFilterTests | |||
| { | |||
| private IAuthenticationService _authenticationService; | |||
| private Mock<IAuthenticationService> _authenticationServiceMock; | |||
| private IConfiguration _configuration; | |||
| private Mock<UserManager<Customer>> _userManagerMock; | |||
| private readonly Customer _customer = new() | |||
| { | |||
| Id = Guid.NewGuid(), | |||
| Email = "user@gmail.com", | |||
| FirstName = "User", | |||
| LastName = "User", | |||
| Notifications = new List<Notification>(), | |||
| Roles = new List<Guid> { Guid.NewGuid() }, | |||
| UserName = "user12" | |||
| }; | |||
| private Mock<AuthorizationHubFilter> _authorizationHubFilterMock; | |||
| private AuthorizationHubFilter _authorizationHubFilter; | |||
| private HubInvocationContext _hubInvocationContext; | |||
| private Mock<Func<HubInvocationContext, ValueTask<object>>> _hubInvocationContextFuncMock; | |||
| private Mock<HttpContext> _httpContextMock; | |||
| [SetUp] | |||
| public void Setup() | |||
| { | |||
| var inMemorySettings = new Dictionary<string, string> { | |||
| {"JwtSettings:jwtSecret", "Ovo je neka sifra koja treba biti tajna"}, | |||
| {"JwtSettings:validIssuer", "http://localhost:5116"}, | |||
| {"JwtSettings:validAudience", "http://localhost:3000"}, | |||
| }; | |||
| _configuration = new ConfigurationBuilder() | |||
| .AddInMemoryCollection(inMemorySettings) | |||
| .Build(); | |||
| _userManagerMock = new Mock<UserManager<Customer>>(Mock.Of<IUserStore<Customer>>(), null, null, null, null, null, null, null, null); | |||
| _authenticationService = new AuthenticationService(_userManagerMock.Object, _configuration); | |||
| _authenticationServiceMock = new Mock<IAuthenticationService>(); | |||
| var configuration = new MapperConfiguration(cfg => cfg.AddProfile(new CustomerMappingProfile())); | |||
| _authorizationHubFilterMock = new Mock<AuthorizationHubFilter>(); | |||
| _authorizationHubFilter = new AuthorizationHubFilter(); | |||
| var hubCallerContext = new Mock<HubCallerContext>(); | |||
| var serviceProvider = new Mock<IServiceProvider>(); | |||
| var hub = new Mock<Hub>(); | |||
| var methodInfo = new Mock<MethodInfo>(); | |||
| _hubInvocationContext = new HubInvocationContext(hubCallerContext.Object, serviceProvider.Object, hub.Object, methodInfo.Object, null); | |||
| _hubInvocationContextFuncMock = new Mock<Func<HubInvocationContext, ValueTask<object>>>(); | |||
| _httpContextMock = new Mock<HttpContext>(); | |||
| } | |||
| [Test] | |||
| public async Task InvokeMethodAsync_ThereIsNoCustomAttributes_SkipFurtherExcecutionOfFilter() | |||
| { | |||
| _authorizationHubFilterMock.Setup(k => k.GetCustomAttributes(It.IsAny<MemberInfo>())).Returns(Array.Empty<Attribute>()); | |||
| var result = await _authorizationHubFilterMock.Object.InvokeMethodAsync(_hubInvocationContext, _hubInvocationContextFuncMock.Object); | |||
| Assert.That(result, Is.Null); | |||
| } | |||
| [Test] | |||
| public void InvokeMethodAsync_ThereIsCustomAttributeButNoRoles_ThrowNullReferenceException() | |||
| { | |||
| _authorizationHubFilterMock.Setup(k => k.GetCustomAttributes(It.IsAny<MemberInfo>())) | |||
| .Returns(new Attribute[1] { new AuthorizationHubFilter() }); | |||
| _authorizationHubFilterMock.Setup(k => k.GetCustomAttribute(It.IsAny<MemberInfo>(), It.IsAny<Type>())).Returns((Attribute)null); | |||
| Assert.That(() => _authorizationHubFilterMock.Object.InvokeMethodAsync(_hubInvocationContext, _hubInvocationContextFuncMock.Object), | |||
| Throws.Exception.TypeOf<NullReferenceException>()); | |||
| } | |||
| [Test] | |||
| public void InvokeMethodAsync_ThereIsCustomAttributeAndRolesAndHttpContextIsNull_ThrowNullReferenceException() | |||
| { | |||
| var filter = new AuthorizationHubFilter { Roles = "Customer,Support" }; | |||
| _authorizationHubFilterMock.Setup(k => k.GetCustomAttributes(It.IsAny<MemberInfo>())).Returns(new Attribute[1] { filter }); | |||
| _authorizationHubFilterMock.Setup(k => k.GetCustomAttribute(It.IsAny<MemberInfo>(), It.IsAny<Type>())).Returns(filter); | |||
| _authorizationHubFilterMock.Setup(k => k.GetHttpContext(It.IsAny<HubInvocationContext>())).Returns((HttpContext)null); | |||
| Assert.That(() => _authorizationHubFilterMock.Object.InvokeMethodAsync(_hubInvocationContext, _hubInvocationContextFuncMock.Object), | |||
| Throws.Exception.TypeOf<NullReferenceException>()); | |||
| } | |||
| [Test] | |||
| public async Task InvokeMethodAsync_ThereIsCustomAttributeAndRolesAndHttpContextIsNotNull_nextIsCalled() | |||
| { | |||
| var filter = new AuthorizationHubFilter { Roles = "Customer,Support" }; | |||
| _httpContextMock.Setup(k => k.Request.Query[It.IsAny<string>()]).Returns("someToken"); | |||
| //Optimization of this tests is to mock AttachToUser function but there is problem because that function is not virtual and if it were a virtual method there would be problems with other tests | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IAuthenticationService))).Returns(_authenticationServiceMock.Object); | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IConfiguration))).Returns(_configuration); | |||
| _httpContextMock.Setup(k => k.Items[It.IsAny<string>()]).Returns(Task.CompletedTask); | |||
| _authorizationHubFilterMock.Setup(k => k.ReturnTokenPayload(It.IsAny<JwtSecurityToken>(), It.IsAny<string>())) | |||
| .Returns("Customer"); | |||
| _authorizationHubFilterMock.Setup(k => k.GetCustomAttributes(It.IsAny<MemberInfo>())).Returns(new Attribute[1] { filter }); | |||
| _authorizationHubFilterMock.Setup(k => k.GetCustomAttribute(It.IsAny<MemberInfo>(), It.IsAny<Type>())).Returns(filter); | |||
| _authorizationHubFilterMock.Setup(k => k.GetHttpContext(It.IsAny<HubInvocationContext>())).Returns(_httpContextMock.Object); | |||
| await _authorizationHubFilterMock.Object.InvokeMethodAsync(_hubInvocationContext, _hubInvocationContextFuncMock.Object); | |||
| _hubInvocationContextFuncMock.Verify(k => k.Invoke(It.IsAny<HubInvocationContext>()), Times.Once); | |||
| } | |||
| [Test] | |||
| public void AttachUserToContext_AuthorizationServiceIsNull_ThrowHttpResponseException() | |||
| { | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IAuthenticationService))).Returns(null); | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IConfiguration))).Returns(_configuration); | |||
| Assert.That(() => _authorizationHubFilter.AttachUserToContext(_httpContextMock.Object, "1", new string[1] { "1" }), | |||
| Throws.Exception.TypeOf<HttpResponseException>()); | |||
| } | |||
| [Test] | |||
| public void AttachUserToContext_IConfigurationIsNull_ThrowHttpResponseException() | |||
| { | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IAuthenticationService))).Returns(_authenticationService); | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IConfiguration))).Returns(null); | |||
| Assert.That(() => _authorizationHubFilter.AttachUserToContext(_httpContextMock.Object, "1", new string[1] { "1" }), | |||
| Throws.Exception.TypeOf<HttpResponseException>()); | |||
| } | |||
| [Test] | |||
| public void AttachUserToContext_ThereAreNoRolesForUser_ThrowHttpResponseException() | |||
| { | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IAuthenticationService))).Returns(_authenticationService); | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IConfiguration))).Returns(_configuration); | |||
| //_authorizationHubFilterMock.Setup(k => k.Proba(It.IsAny<JwtSecurityTokenHandler>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<byte[]>())); | |||
| _authorizationHubFilterMock.Setup(k => k.ReturnTokenPayload(It.IsAny<JwtSecurityToken>(), It.IsAny<string>())) | |||
| .Returns((string)null); | |||
| Assert.That(() => _authorizationHubFilterMock.Object.AttachUserToContext(_httpContextMock.Object, "1", new string[1] { "1" }), | |||
| Throws.Exception.TypeOf<HttpResponseException>()); | |||
| } | |||
| [Test] | |||
| public void AttachUserToContext_RolesIsNotNullButUserIsNotAuthorized_ThrowUnauthorizedAccessException() | |||
| { | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IAuthenticationService))).Returns(_authenticationService); | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IConfiguration))).Returns(_configuration); | |||
| _authorizationHubFilterMock.Setup(k => k.ReturnTokenPayload(It.IsAny<JwtSecurityToken>(), It.IsAny<string>())) | |||
| .Returns("Support"); | |||
| Assert.That(() => _authorizationHubFilterMock.Object.AttachUserToContext(_httpContextMock.Object, "1", new string[1] { "Customer" }), | |||
| Throws.Exception.TypeOf<UnauthorizedAccessException>()); | |||
| } | |||
| [Test] | |||
| public async Task AttachUserToContext_RolesIsNotNullAndUserIAuthorized_GetByUsernameIsCalled() | |||
| { | |||
| _authenticationServiceMock.Setup(k => k.GetByUserName(It.IsAny<string>())).Returns(Task.FromResult(_customer)); | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IAuthenticationService))).Returns(_authenticationServiceMock.Object); | |||
| _httpContextMock.Setup(k => k.RequestServices.GetService(typeof(IConfiguration))).Returns(_configuration); | |||
| _httpContextMock.Setup(k => k.Items[It.IsAny<string>()]).Returns(Task.CompletedTask); | |||
| _authorizationHubFilterMock.Setup(k => k.ReturnTokenPayload(It.IsAny<JwtSecurityToken>(), It.IsAny<string>())) | |||
| .Returns("Customer"); | |||
| await _authorizationHubFilterMock.Object.AttachUserToContext(_httpContextMock.Object, "1", new string[1] { "Customer" }); | |||
| _authenticationServiceMock.Verify(k => k.GetByUserName(It.IsAny<string>()), Times.Once); | |||
| } | |||
| } | |||
| } | |||
| @@ -16,18 +16,18 @@ namespace Tests | |||
| [TestFixture] | |||
| public class NotificationTests | |||
| { | |||
| private Mock<ICustomerService> _customerServiceMock; | |||
| private Mock<ICustomerRepository> _customerRepositoryMock; | |||
| [SetUp] | |||
| public void Setup() | |||
| { | |||
| _customerServiceMock = new Mock<ICustomerService>(); | |||
| _customerRepositoryMock = new Mock<ICustomerRepository>(); | |||
| } | |||
| [Test] | |||
| public async Task DeleteNotification_CustomerIsNull_ThrowNotFoundException() | |||
| { | |||
| _customerServiceMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| _customerRepositoryMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| .ReturnsAsync((Customer)null); | |||
| var deleteNotificationCommand = new DeleteNotificationCommand(new NotificationDeleteDTO | |||
| @@ -36,7 +36,7 @@ namespace Tests | |||
| RoomId = "1" | |||
| }); | |||
| var deleteNotificationHandler = new DeleteNotificationHandler(_customerServiceMock.Object); | |||
| var deleteNotificationHandler = new DeleteNotificationHandler(_customerRepositoryMock.Object); | |||
| Assert.That(async () => await deleteNotificationHandler.Handle(deleteNotificationCommand, new CancellationToken()), Throws.Exception.TypeOf<NotFoundException>()); | |||
| } | |||
| @@ -44,7 +44,7 @@ namespace Tests | |||
| [Test] | |||
| public async Task DeleteNotification_UserNotificationIsNull_ThrowNotFoundException() | |||
| { | |||
| _customerServiceMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| _customerRepositoryMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| .ReturnsAsync(new Customer | |||
| { | |||
| FirstName = "Dzenis", | |||
| @@ -70,7 +70,7 @@ namespace Tests | |||
| RoomId = "3" | |||
| }); | |||
| var deleteNotificationHandler = new DeleteNotificationHandler(_customerServiceMock.Object); | |||
| var deleteNotificationHandler = new DeleteNotificationHandler(_customerRepositoryMock.Object); | |||
| Assert.That(async () => await deleteNotificationHandler.Handle(deleteNotificationCommand, new CancellationToken()), Throws.Exception.TypeOf<NotFoundException>()); | |||
| } | |||
| @@ -97,7 +97,7 @@ namespace Tests | |||
| } | |||
| }; | |||
| _customerServiceMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| _customerRepositoryMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| .ReturnsAsync(customer); | |||
| var deleteNotificationCommand = new DeleteNotificationCommand(new NotificationDeleteDTO | |||
| @@ -106,24 +106,24 @@ namespace Tests | |||
| RoomId = "2" | |||
| }); | |||
| var deleteNotificationHandler = new DeleteNotificationHandler(_customerServiceMock.Object); | |||
| var deleteNotificationHandler = new DeleteNotificationHandler(_customerRepositoryMock.Object); | |||
| await deleteNotificationHandler.Handle(deleteNotificationCommand, new CancellationToken()); | |||
| _customerServiceMock.Verify(mock => mock.DeleteNotification(customer, customer.Notifications[1])); | |||
| _customerRepositoryMock.Verify(mock => mock.DeleteNotification(customer, customer.Notifications[1])); | |||
| } | |||
| [Test] | |||
| public async Task AddNotification_ReceiverIsNull_ThrowsNotFoundException() | |||
| { | |||
| _customerServiceMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| _customerRepositoryMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| .ReturnsAsync((Customer)null); | |||
| var command = new AddNotificationCommand(new NotificationSaveDTO | |||
| { | |||
| ReceiverId = "1", | |||
| RoomId = "1" | |||
| }); | |||
| var handler = new AddNotificationHandler(_customerServiceMock.Object); | |||
| var handler = new AddNotificationHandler(_customerRepositoryMock.Object); | |||
| Assert.That(async () => await handler.Handle(command, new CancellationToken()), Throws.Exception.TypeOf<NotFoundException>()); | |||
| } | |||
| @@ -150,17 +150,17 @@ namespace Tests | |||
| } | |||
| }; | |||
| _customerServiceMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| _customerRepositoryMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| .ReturnsAsync(customer); | |||
| var command = new AddNotificationCommand(new NotificationSaveDTO | |||
| { | |||
| ReceiverId = "1", | |||
| RoomId = "3" | |||
| }); | |||
| var handler = new AddNotificationHandler(_customerServiceMock.Object); | |||
| var handler = new AddNotificationHandler(_customerRepositoryMock.Object); | |||
| await handler.Handle(command, new CancellationToken()); | |||
| _customerServiceMock.Verify(mock => mock.AddNotification(customer)); | |||
| _customerRepositoryMock.Verify(mock => mock.AddNotification(customer)); | |||
| } | |||
| [Test] | |||
| @@ -185,17 +185,17 @@ namespace Tests | |||
| } | |||
| }; | |||
| _customerServiceMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| _customerRepositoryMock.Setup(x => x.GetCustomerById(It.IsAny<string>())) | |||
| .ReturnsAsync(customer); | |||
| var command = new AddNotificationCommand(new NotificationSaveDTO | |||
| { | |||
| ReceiverId = "1", | |||
| RoomId = "2" | |||
| }); | |||
| var handler = new AddNotificationHandler(_customerServiceMock.Object); | |||
| var handler = new AddNotificationHandler(_customerRepositoryMock.Object); | |||
| await handler.Handle(command, new CancellationToken()); | |||
| _customerServiceMock.Verify(mock => mock.AddNotification(customer)); | |||
| _customerRepositoryMock.Verify(mock => mock.AddNotification(customer)); | |||
| } | |||
| } | |||
| } | |||