CustomAuthenticationHandlerTests.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. using System;
  2. using System.Linq;
  3. using System.Security.Claims;
  4. using System.Text.Encodings.Web;
  5. using System.Threading.Tasks;
  6. using AutoFixture;
  7. using AutoFixture.AutoMoq;
  8. using Jellyfin.Api.Auth;
  9. using Jellyfin.Api.Constants;
  10. using Jellyfin.Data.Entities;
  11. using Jellyfin.Data.Enums;
  12. using MediaBrowser.Controller.Entities;
  13. using MediaBrowser.Controller.Net;
  14. using Microsoft.AspNetCore.Authentication;
  15. using Microsoft.AspNetCore.Http;
  16. using Microsoft.Extensions.Logging;
  17. using Microsoft.Extensions.Logging.Abstractions;
  18. using Microsoft.Extensions.Options;
  19. using Moq;
  20. using Xunit;
  21. namespace Jellyfin.Api.Tests.Auth
  22. {
  23. public class CustomAuthenticationHandlerTests
  24. {
  25. private readonly IFixture _fixture;
  26. private readonly Mock<IAuthService> _jellyfinAuthServiceMock;
  27. private readonly Mock<IOptionsMonitor<AuthenticationSchemeOptions>> _optionsMonitorMock;
  28. private readonly Mock<ISystemClock> _clockMock;
  29. private readonly Mock<IServiceProvider> _serviceProviderMock;
  30. private readonly Mock<IAuthenticationService> _authenticationServiceMock;
  31. private readonly UrlEncoder _urlEncoder;
  32. private readonly HttpContext _context;
  33. private readonly CustomAuthenticationHandler _sut;
  34. private readonly AuthenticationScheme _scheme;
  35. public CustomAuthenticationHandlerTests()
  36. {
  37. var fixtureCustomizations = new AutoMoqCustomization
  38. {
  39. ConfigureMembers = true
  40. };
  41. _fixture = new Fixture().Customize(fixtureCustomizations);
  42. AllowFixtureCircularDependencies();
  43. _jellyfinAuthServiceMock = _fixture.Freeze<Mock<IAuthService>>();
  44. _optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
  45. _clockMock = _fixture.Freeze<Mock<ISystemClock>>();
  46. _serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
  47. _authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
  48. _fixture.Register<ILoggerFactory>(() => new NullLoggerFactory());
  49. _urlEncoder = UrlEncoder.Default;
  50. _serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
  51. .Returns(_authenticationServiceMock.Object);
  52. _optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
  53. .Returns(new AuthenticationSchemeOptions
  54. {
  55. ForwardAuthenticate = null
  56. });
  57. _context = new DefaultHttpContext
  58. {
  59. RequestServices = _serviceProviderMock.Object
  60. };
  61. _scheme = new AuthenticationScheme(
  62. _fixture.Create<string>(),
  63. null,
  64. typeof(CustomAuthenticationHandler));
  65. _sut = _fixture.Create<CustomAuthenticationHandler>();
  66. _sut.InitializeAsync(_scheme, _context).Wait();
  67. }
  68. [Fact]
  69. public async Task HandleAuthenticateAsyncShouldFailWithNullUser()
  70. {
  71. _jellyfinAuthServiceMock.Setup(
  72. a => a.Authenticate(
  73. It.IsAny<HttpRequest>(),
  74. It.IsAny<AuthenticatedAttribute>()))
  75. .Returns((User?)null);
  76. var authenticateResult = await _sut.AuthenticateAsync();
  77. Assert.False(authenticateResult.Succeeded);
  78. Assert.True(authenticateResult.None);
  79. // TODO return when legacy API is removed.
  80. // Assert.Equal("Invalid user", authenticateResult.Failure.Message);
  81. }
  82. [Fact]
  83. public async Task HandleAuthenticateAsyncShouldFailOnSecurityException()
  84. {
  85. var errorMessage = _fixture.Create<string>();
  86. _jellyfinAuthServiceMock.Setup(
  87. a => a.Authenticate(
  88. It.IsAny<HttpRequest>(),
  89. It.IsAny<AuthenticatedAttribute>()))
  90. .Throws(new SecurityException(errorMessage));
  91. var authenticateResult = await _sut.AuthenticateAsync();
  92. Assert.False(authenticateResult.Succeeded);
  93. Assert.Equal(errorMessage, authenticateResult.Failure.Message);
  94. }
  95. [Fact]
  96. public async Task HandleAuthenticateAsyncShouldSucceedWithUser()
  97. {
  98. SetupUser();
  99. var authenticateResult = await _sut.AuthenticateAsync();
  100. Assert.True(authenticateResult.Succeeded);
  101. Assert.Null(authenticateResult.Failure);
  102. }
  103. [Fact]
  104. public async Task HandleAuthenticateAsyncShouldAssignNameClaim()
  105. {
  106. var user = SetupUser();
  107. var authenticateResult = await _sut.AuthenticateAsync();
  108. Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Username));
  109. }
  110. [Theory]
  111. [InlineData(true)]
  112. [InlineData(false)]
  113. public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin)
  114. {
  115. var user = SetupUser(isAdmin);
  116. var authenticateResult = await _sut.AuthenticateAsync();
  117. var expectedRole = user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User;
  118. Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole));
  119. }
  120. [Fact]
  121. public async Task HandleAuthenticateAsyncShouldAssignTicketCorrectScheme()
  122. {
  123. SetupUser();
  124. var authenticatedResult = await _sut.AuthenticateAsync();
  125. Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme);
  126. }
  127. private User SetupUser(bool isAdmin = false)
  128. {
  129. var user = _fixture.Create<User>();
  130. user.SetPermission(PermissionKind.IsAdministrator, isAdmin);
  131. _jellyfinAuthServiceMock.Setup(
  132. a => a.Authenticate(
  133. It.IsAny<HttpRequest>(),
  134. It.IsAny<AuthenticatedAttribute>()))
  135. .Returns(user);
  136. return user;
  137. }
  138. private void AllowFixtureCircularDependencies()
  139. {
  140. // A circular dependency exists in the User entity around parent folders,
  141. // this allows Autofixture to generate a User regardless, rather than throw
  142. // an error.
  143. _fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
  144. .ForEach(b => _fixture.Behaviors.Remove(b));
  145. _fixture.Behaviors.Add(new OmitOnRecursionBehavior());
  146. }
  147. }
  148. }