2019-12-22 09:46:09 +00:00
|
|
|
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;
|
2020-05-20 17:07:53 +00:00
|
|
|
using Jellyfin.Data.Entities;
|
2020-05-13 02:10:35 +00:00
|
|
|
using Jellyfin.Data.Enums;
|
2019-12-22 09:46:09 +00:00
|
|
|
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;
|
|
|
|
|
2019-12-22 23:21:20 +00:00
|
|
|
private readonly Mock<IAuthService> _jellyfinAuthServiceMock;
|
2019-12-22 09:46:09 +00:00
|
|
|
|
|
|
|
private readonly CustomAuthenticationHandler _sut;
|
|
|
|
private readonly AuthenticationScheme _scheme;
|
|
|
|
|
|
|
|
public CustomAuthenticationHandlerTests()
|
|
|
|
{
|
2019-12-22 22:36:11 +00:00
|
|
|
var fixtureCustomizations = new AutoMoqCustomization
|
|
|
|
{
|
|
|
|
ConfigureMembers = true
|
|
|
|
};
|
|
|
|
|
|
|
|
_fixture = new Fixture().Customize(fixtureCustomizations);
|
2019-12-22 09:46:09 +00:00
|
|
|
AllowFixtureCircularDependencies();
|
|
|
|
|
2019-12-22 23:21:20 +00:00
|
|
|
_jellyfinAuthServiceMock = _fixture.Freeze<Mock<IAuthService>>();
|
2020-06-16 20:12:40 +00:00
|
|
|
var optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
|
|
|
|
var serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
|
|
|
|
var authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
|
2019-12-22 09:46:09 +00:00
|
|
|
_fixture.Register<ILoggerFactory>(() => new NullLoggerFactory());
|
|
|
|
|
2020-06-16 20:12:40 +00:00
|
|
|
serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
|
|
|
|
.Returns(authenticationServiceMock.Object);
|
2019-12-22 09:46:09 +00:00
|
|
|
|
2020-06-16 20:12:40 +00:00
|
|
|
optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
|
2019-12-22 09:46:09 +00:00
|
|
|
.Returns(new AuthenticationSchemeOptions
|
|
|
|
{
|
|
|
|
ForwardAuthenticate = null
|
|
|
|
});
|
|
|
|
|
2020-06-16 20:12:40 +00:00
|
|
|
HttpContext context = new DefaultHttpContext
|
2019-12-22 09:46:09 +00:00
|
|
|
{
|
2020-06-16 20:12:40 +00:00
|
|
|
RequestServices = serviceProviderMock.Object
|
2019-12-22 09:46:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
_scheme = new AuthenticationScheme(
|
|
|
|
_fixture.Create<string>(),
|
|
|
|
null,
|
|
|
|
typeof(CustomAuthenticationHandler));
|
|
|
|
|
|
|
|
_sut = _fixture.Create<CustomAuthenticationHandler>();
|
2020-06-16 20:12:40 +00:00
|
|
|
_sut.InitializeAsync(_scheme, context).Wait();
|
2019-12-22 09:46:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public async Task HandleAuthenticateAsyncShouldFailOnSecurityException()
|
|
|
|
{
|
|
|
|
var errorMessage = _fixture.Create<string>();
|
|
|
|
|
2019-12-22 23:21:20 +00:00
|
|
|
_jellyfinAuthServiceMock.Setup(
|
2019-12-22 09:46:09 +00:00
|
|
|
a => a.Authenticate(
|
2020-06-16 20:12:40 +00:00
|
|
|
It.IsAny<HttpRequest>()))
|
2019-12-22 09:46:09 +00:00
|
|
|
.Throws(new SecurityException(errorMessage));
|
|
|
|
|
|
|
|
var authenticateResult = await _sut.AuthenticateAsync();
|
|
|
|
|
|
|
|
Assert.False(authenticateResult.Succeeded);
|
|
|
|
Assert.Equal(errorMessage, authenticateResult.Failure.Message);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public async Task HandleAuthenticateAsyncShouldSucceedWithUser()
|
|
|
|
{
|
|
|
|
SetupUser();
|
|
|
|
var authenticateResult = await _sut.AuthenticateAsync();
|
|
|
|
|
|
|
|
Assert.True(authenticateResult.Succeeded);
|
|
|
|
Assert.Null(authenticateResult.Failure);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
public async Task HandleAuthenticateAsyncShouldAssignNameClaim()
|
|
|
|
{
|
2020-06-16 20:12:40 +00:00
|
|
|
var authorizationInfo = SetupUser();
|
2019-12-22 09:46:09 +00:00
|
|
|
var authenticateResult = await _sut.AuthenticateAsync();
|
|
|
|
|
2020-06-16 20:12:40 +00:00
|
|
|
Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username));
|
2019-12-22 09:46:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[Theory]
|
|
|
|
[InlineData(true)]
|
|
|
|
[InlineData(false)]
|
|
|
|
public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin)
|
|
|
|
{
|
2020-06-16 20:12:40 +00:00
|
|
|
var authorizationInfo = SetupUser(isAdmin);
|
2019-12-22 09:46:09 +00:00
|
|
|
var authenticateResult = await _sut.AuthenticateAsync();
|
|
|
|
|
2020-06-16 20:12:40 +00:00
|
|
|
var expectedRole = authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User;
|
2019-12-22 09:46:09 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:12:40 +00:00
|
|
|
private AuthorizationInfo SetupUser(bool isAdmin = false)
|
2019-12-22 09:46:09 +00:00
|
|
|
{
|
2020-06-16 20:12:40 +00:00
|
|
|
var authorizationInfo = _fixture.Create<AuthorizationInfo>();
|
|
|
|
authorizationInfo.User = _fixture.Create<User>();
|
|
|
|
authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin);
|
2019-12-22 09:46:09 +00:00
|
|
|
|
2019-12-22 23:21:20 +00:00
|
|
|
_jellyfinAuthServiceMock.Setup(
|
2019-12-22 09:46:09 +00:00
|
|
|
a => a.Authenticate(
|
2020-06-16 20:12:40 +00:00
|
|
|
It.IsAny<HttpRequest>()))
|
|
|
|
.Returns(authorizationInfo);
|
2019-12-22 09:46:09 +00:00
|
|
|
|
2020-06-16 20:12:40 +00:00
|
|
|
return authorizationInfo;
|
2019-12-22 09:46:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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<ThrowingRecursionBehavior>().ToList()
|
|
|
|
.ForEach(b => _fixture.Behaviors.Remove(b));
|
|
|
|
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|