mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-04-23 10:42:37 -04:00
214 lines
8.7 KiB
C#
214 lines
8.7 KiB
C#
using System.Net;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Moq;
|
|
using allstarr.Controllers;
|
|
using allstarr.Models.Settings;
|
|
using allstarr.Services.Admin;
|
|
|
|
namespace allstarr.Tests;
|
|
|
|
public class AdminAuthControllerTests
|
|
{
|
|
[Fact]
|
|
public async Task Login_WithValidNonAdminJellyfinUser_CreatesSessionAndCookie()
|
|
{
|
|
HttpRequestMessage? capturedRequest = null;
|
|
string? capturedBody = null;
|
|
|
|
var handler = new DelegateHttpMessageHandler(async (request, _) =>
|
|
{
|
|
capturedRequest = request;
|
|
capturedBody = request.Content is null ? null : await request.Content.ReadAsStringAsync();
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent("""
|
|
{
|
|
"AccessToken":"token-123",
|
|
"ServerId":"server-1",
|
|
"User":{
|
|
"Id":"user-1",
|
|
"Name":"josh",
|
|
"Policy":{"IsAdministrator":false}
|
|
}
|
|
}
|
|
""")
|
|
};
|
|
});
|
|
|
|
var sessionService = new AdminAuthSessionService();
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers["X-Forwarded-Proto"] = "https";
|
|
|
|
var controller = CreateController(handler, sessionService, httpContext);
|
|
var result = await controller.Login(new AdminAuthController.LoginRequest
|
|
{
|
|
Username = " josh ",
|
|
Password = "secret-pass"
|
|
});
|
|
|
|
var ok = Assert.IsType<OkObjectResult>(result);
|
|
var payloadJson = JsonSerializer.Serialize(ok.Value);
|
|
using var payload = JsonDocument.Parse(payloadJson);
|
|
|
|
Assert.True(payload.RootElement.GetProperty("authenticated").GetBoolean());
|
|
Assert.Equal("user-1", payload.RootElement.GetProperty("user").GetProperty("id").GetString());
|
|
Assert.Equal("josh", payload.RootElement.GetProperty("user").GetProperty("name").GetString());
|
|
Assert.False(payload.RootElement.GetProperty("user").GetProperty("isAdministrator").GetBoolean());
|
|
|
|
Assert.NotNull(capturedRequest);
|
|
Assert.Equal(HttpMethod.Post, capturedRequest!.Method);
|
|
Assert.Equal("http://jellyfin.local/Users/AuthenticateByName", capturedRequest.RequestUri?.ToString());
|
|
Assert.Contains("X-Emby-Authorization", capturedRequest.Headers.Select(h => h.Key));
|
|
|
|
Assert.NotNull(capturedBody);
|
|
Assert.Contains("\"Username\":\"josh\"", capturedBody!);
|
|
Assert.Contains("\"Pw\":\"secret-pass\"", capturedBody!);
|
|
|
|
var setCookies = httpContext.Response.Headers.SetCookie;
|
|
Assert.Single(setCookies);
|
|
var setCookieHeader = setCookies[0] ?? string.Empty;
|
|
Assert.Contains($"{AdminAuthSessionService.SessionCookieName}=", setCookieHeader);
|
|
Assert.Contains("httponly", setCookieHeader.ToLowerInvariant());
|
|
Assert.Contains("secure", setCookieHeader.ToLowerInvariant());
|
|
Assert.Contains("samesite=strict", setCookieHeader.ToLowerInvariant());
|
|
|
|
var sessionId = ExtractCookieValue(setCookieHeader);
|
|
Assert.True(sessionService.TryGetValidSession(sessionId, out var session));
|
|
Assert.Equal("user-1", session.UserId);
|
|
Assert.Equal("josh", session.UserName);
|
|
Assert.False(session.IsAdministrator);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_WithInvalidCredentials_ReturnsUnauthorized()
|
|
{
|
|
var handler = new DelegateHttpMessageHandler((_, _) =>
|
|
Task.FromResult(new HttpResponseMessage(HttpStatusCode.Unauthorized)));
|
|
|
|
var sessionService = new AdminAuthSessionService();
|
|
var httpContext = new DefaultHttpContext();
|
|
var controller = CreateController(handler, sessionService, httpContext);
|
|
|
|
var result = await controller.Login(new AdminAuthController.LoginRequest
|
|
{
|
|
Username = "josh",
|
|
Password = "wrong"
|
|
});
|
|
|
|
var unauthorized = Assert.IsType<UnauthorizedObjectResult>(result);
|
|
Assert.Equal(StatusCodes.Status401Unauthorized, unauthorized.StatusCode);
|
|
Assert.False(httpContext.Response.Headers.ContainsKey("Set-Cookie"));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCurrentSession_WithUnknownCookie_ReturnsUnauthenticated()
|
|
{
|
|
var handler = new DelegateHttpMessageHandler((_, _) =>
|
|
Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
|
|
|
|
var sessionService = new AdminAuthSessionService();
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers.Cookie = $"{AdminAuthSessionService.SessionCookieName}=missing-session";
|
|
|
|
var controller = CreateController(handler, sessionService, httpContext);
|
|
var result = controller.GetCurrentSession();
|
|
|
|
var ok = Assert.IsType<OkObjectResult>(result);
|
|
var payloadJson = JsonSerializer.Serialize(ok.Value);
|
|
using var payload = JsonDocument.Parse(payloadJson);
|
|
|
|
Assert.False(payload.RootElement.GetProperty("authenticated").GetBoolean());
|
|
var setCookies = httpContext.Response.Headers.SetCookie;
|
|
Assert.Single(setCookies);
|
|
var setCookieHeader = setCookies[0] ?? string.Empty;
|
|
Assert.Contains($"{AdminAuthSessionService.SessionCookieName}=", setCookieHeader);
|
|
Assert.Contains("expires=", setCookieHeader.ToLowerInvariant());
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCurrentSession_WithValidCookie_ReturnsSessionUser()
|
|
{
|
|
var handler = new DelegateHttpMessageHandler((_, _) =>
|
|
Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
|
|
|
|
var sessionService = new AdminAuthSessionService();
|
|
var session = sessionService.CreateSession(
|
|
userId: "user-42",
|
|
userName: "alice",
|
|
isAdministrator: true,
|
|
jellyfinAccessToken: "token",
|
|
jellyfinServerId: "server");
|
|
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers.Cookie = $"{AdminAuthSessionService.SessionCookieName}={session.SessionId}";
|
|
|
|
var controller = CreateController(handler, sessionService, httpContext);
|
|
var result = controller.GetCurrentSession();
|
|
|
|
var ok = Assert.IsType<OkObjectResult>(result);
|
|
var payloadJson = JsonSerializer.Serialize(ok.Value);
|
|
using var payload = JsonDocument.Parse(payloadJson);
|
|
|
|
Assert.True(payload.RootElement.GetProperty("authenticated").GetBoolean());
|
|
Assert.Equal("user-42", payload.RootElement.GetProperty("user").GetProperty("id").GetString());
|
|
Assert.Equal("alice", payload.RootElement.GetProperty("user").GetProperty("name").GetString());
|
|
Assert.True(payload.RootElement.GetProperty("user").GetProperty("isAdministrator").GetBoolean());
|
|
}
|
|
|
|
private static AdminAuthController CreateController(
|
|
HttpMessageHandler handler,
|
|
AdminAuthSessionService sessionService,
|
|
HttpContext httpContext)
|
|
{
|
|
var jellyfinOptions = Options.Create(new JellyfinSettings
|
|
{
|
|
Url = "http://jellyfin.local"
|
|
});
|
|
|
|
var httpClientFactory = new Mock<IHttpClientFactory>();
|
|
httpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(new HttpClient(handler));
|
|
|
|
var logger = new Mock<ILogger<AdminAuthController>>();
|
|
var controller = new AdminAuthController(
|
|
jellyfinOptions,
|
|
httpClientFactory.Object,
|
|
sessionService,
|
|
logger.Object)
|
|
{
|
|
ControllerContext = new ControllerContext
|
|
{
|
|
HttpContext = httpContext
|
|
}
|
|
};
|
|
|
|
return controller;
|
|
}
|
|
|
|
private static string ExtractCookieValue(string setCookieHeader)
|
|
{
|
|
var cookiePart = setCookieHeader.Split(';', 2)[0];
|
|
var parts = cookiePart.Split('=', 2);
|
|
return parts.Length == 2 ? parts[1] : string.Empty;
|
|
}
|
|
|
|
private sealed class DelegateHttpMessageHandler : HttpMessageHandler
|
|
{
|
|
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handler;
|
|
|
|
public DelegateHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handler)
|
|
{
|
|
_handler = handler;
|
|
}
|
|
|
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
return _handler(request, cancellationToken);
|
|
}
|
|
}
|
|
}
|