using System; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; using Jellyfin.Api.Auth; using Jellyfin.Api.Constants; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using Xunit; namespace Jellyfin.Api.Tests.Auth { public class CustomAuthenticationHandlerTests { private readonly IFixture _fixture; private readonly Mock _jellyfinAuthServiceMock; private readonly CustomAuthenticationHandler _sut; private readonly AuthenticationScheme _scheme; public CustomAuthenticationHandlerTests() { var fixtureCustomizations = new AutoMoqCustomization { ConfigureMembers = true }; _fixture = new Fixture().Customize(fixtureCustomizations); AllowFixtureCircularDependencies(); _jellyfinAuthServiceMock = _fixture.Freeze>(); var optionsMonitorMock = _fixture.Freeze>>(); var serviceProviderMock = _fixture.Freeze>(); var authenticationServiceMock = _fixture.Freeze>(); _fixture.Register(() => new NullLoggerFactory()); serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService))) .Returns(authenticationServiceMock.Object); optionsMonitorMock.Setup(o => o.Get(It.IsAny())) .Returns(new AuthenticationSchemeOptions { ForwardAuthenticate = null }); HttpContext context = new DefaultHttpContext { RequestServices = serviceProviderMock.Object }; _scheme = new AuthenticationScheme( _fixture.Create(), null, typeof(CustomAuthenticationHandler)); _sut = _fixture.Create(); _sut.InitializeAsync(_scheme, context).Wait(); } [Fact] public async Task HandleAuthenticateAsyncShouldProvideNoResultOnAuthenticationException() { var errorMessage = _fixture.Create(); _jellyfinAuthServiceMock.Setup( a => a.Authenticate( It.IsAny())) .Throws(new AuthenticationException(errorMessage)); var authenticateResult = await _sut.AuthenticateAsync(); Assert.False(authenticateResult.Succeeded); Assert.True(authenticateResult.None); } [Fact] public async Task HandleAuthenticateAsyncShouldSucceedWithUser() { SetupUser(); var authenticateResult = await _sut.AuthenticateAsync(); Assert.True(authenticateResult.Succeeded); Assert.Null(authenticateResult.Failure); } [Fact] public async Task HandleAuthenticateAsyncShouldAssignNameClaim() { var authorizationInfo = SetupUser(); var authenticateResult = await _sut.AuthenticateAsync(); Assert.True(authenticateResult.Principal?.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username)); } [Theory] [InlineData(true)] [InlineData(false)] public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin) { var authorizationInfo = SetupUser(isAdmin); var authenticateResult = await _sut.AuthenticateAsync(); var expectedRole = authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User; Assert.True(authenticateResult.Principal?.HasClaim(ClaimTypes.Role, expectedRole)); } [Fact] public async Task HandleAuthenticateAsyncShouldAssignTicketCorrectScheme() { SetupUser(); var authenticatedResult = await _sut.AuthenticateAsync(); Assert.Equal(_scheme.Name, authenticatedResult.Ticket?.AuthenticationScheme); } private AuthorizationInfo SetupUser(bool isAdmin = false) { var authorizationInfo = _fixture.Create(); authorizationInfo.User = _fixture.Create(); authorizationInfo.User.AddDefaultPermissions(); authorizationInfo.User.AddDefaultPreferences(); authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin); authorizationInfo.IsApiKey = false; authorizationInfo.HasToken = true; authorizationInfo.Token = "fake-token"; _jellyfinAuthServiceMock.Setup( a => a.Authenticate( It.IsAny())) .Returns(Task.FromResult(authorizationInfo)); return authorizationInfo; } private void AllowFixtureCircularDependencies() { // A circular dependency exists in the User entity around parent folders, // this allows Autofixture to generate a User regardless, rather than throw // an error. _fixture.Behaviors.OfType().ToList() .ForEach(b => _fixture.Behaviors.Remove(b)); _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); } } }