aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs28
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketManager.cs3
-rw-r--r--Emby.Server.Implementations/Images/BaseFolderImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs5
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs2
-rw-r--r--Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs18
-rw-r--r--Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs16
-rw-r--r--Jellyfin.Networking/Extensions/NetworkExtensions.cs2
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs11
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs12
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs21
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs5
-rw-r--r--Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs7
-rw-r--r--MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs60
-rw-r--r--MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs38
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs2
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketConnection.cs17
-rw-r--r--MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs10
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs8
20 files changed, 211 insertions, 56 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index dfb61df0a..d5a87d269 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -166,6 +166,7 @@
- [RealGreenDragon](https://github.com/RealGreenDragon)
- [ipitio](https://github.com/ipitio)
- [TheTyrius](https://github.com/TheTyrius)
+ - [tallbl0nde](https://github.com/tallbl0nde)
# Emby Contributors
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index fd7653a32..7f620d666 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -12,6 +12,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Net.WebSocketMessages;
using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
using MediaBrowser.Model.Session;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.HttpServer
@@ -43,14 +44,17 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="socket">The socket.</param>
+ /// <param name="authorizationInfo">The authorization information.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
public WebSocketConnection(
ILogger<WebSocketConnection> logger,
WebSocket socket,
+ AuthorizationInfo authorizationInfo,
IPAddress? remoteEndPoint)
{
_logger = logger;
_socket = socket;
+ AuthorizationInfo = authorizationInfo;
RemoteEndPoint = remoteEndPoint;
_jsonOptions = JsonDefaults.Options;
@@ -60,30 +64,22 @@ namespace Emby.Server.Implementations.HttpServer
/// <inheritdoc />
public event EventHandler<EventArgs>? Closed;
- /// <summary>
- /// Gets the remote end point.
- /// </summary>
+ /// <inheritdoc />
+ public AuthorizationInfo AuthorizationInfo { get; }
+
+ /// <inheritdoc />
public IPAddress? RemoteEndPoint { get; }
- /// <summary>
- /// Gets or sets the receive action.
- /// </summary>
- /// <value>The receive action.</value>
+ /// <inheritdoc />
public Func<WebSocketMessageInfo, Task>? OnReceive { get; set; }
- /// <summary>
- /// Gets the last activity date.
- /// </summary>
- /// <value>The last activity date.</value>
+ /// <inheritdoc />
public DateTime LastActivityDate { get; private set; }
/// <inheritdoc />
public DateTime LastKeepAliveDate { get; set; }
- /// <summary>
- /// Gets the state.
- /// </summary>
- /// <value>The state.</value>
+ /// <inheritdoc />
public WebSocketState State => _socket.State;
/// <inheritdoc />
@@ -101,7 +97,7 @@ namespace Emby.Server.Implementations.HttpServer
}
/// <inheritdoc />
- public async Task ProcessAsync(CancellationToken cancellationToken = default)
+ public async Task ReceiveAsync(CancellationToken cancellationToken = default)
{
var pipe = new Pipe();
var writer = pipe.Writer;
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
index ecfb242f6..52f14b0b1 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs
@@ -51,6 +51,7 @@ namespace Emby.Server.Implementations.HttpServer
using var connection = new WebSocketConnection(
_loggerFactory.CreateLogger<WebSocketConnection>(),
webSocket,
+ authorizationInfo,
context.GetNormalizedRemoteIP())
{
OnReceive = ProcessWebSocketMessageReceived
@@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.HttpServer
await Task.WhenAll(tasks).ConfigureAwait(false);
- await connection.ProcessAsync().ConfigureAwait(false);
+ await connection.ReceiveAsync().ConfigureAwait(false);
_logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress);
}
catch (Exception ex) // Otherwise ASP.Net will ignore the exception
diff --git a/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs
index 84c21931c..539d4a63a 100644
--- a/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseFolderImageProvider.cs
@@ -31,6 +31,7 @@ namespace Emby.Server.Implementations.Images
return _libraryManager.GetItemList(new InternalItemsQuery
{
Parent = item,
+ Recursive = true,
DtoOptions = new DtoOptions(true),
ImageTypes = new ImageType[] { ImageType.Primary },
OrderBy = new (string, SortOrder)[]
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 5f6dc93fb..03ff96b19 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -24,6 +24,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Events.Authentication;
using MediaBrowser.Controller.Events.Session;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
@@ -1462,7 +1463,7 @@ namespace Emby.Server.Implementations.Session
if (user is null)
{
- await _eventManager.PublishAsync(new GenericEventArgs<AuthenticationRequest>(request)).ConfigureAwait(false);
+ await _eventManager.PublishAsync(new AuthenticationRequestEventArgs(request)).ConfigureAwait(false);
throw new AuthenticationException("Invalid username or password entered.");
}
@@ -1498,7 +1499,7 @@ namespace Emby.Server.Implementations.Session
ServerId = _appHost.SystemId
};
- await _eventManager.PublishAsync(new GenericEventArgs<AuthenticationResult>(returnResult)).ConfigureAwait(false);
+ await _eventManager.PublishAsync(new AuthenticationResultEventArgs(returnResult)).ConfigureAwait(false);
return returnResult;
}
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 4f61af35e..1be40111d 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -494,7 +494,7 @@ public class UserController : BaseJellyfinApiController
var isLocal = HttpContext.IsLocal()
|| _networkManager.IsInLocalNetwork(ip);
- if (isLocal)
+ if (!isLocal)
{
_logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip);
}
diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
index 4a5e0ecd4..5b90d65d8 100644
--- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
+++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
@@ -1,6 +1,8 @@
using System;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
+using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Session;
@@ -9,7 +11,7 @@ using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.WebSocketListeners;
/// <summary>
-/// Class SessionInfoWebSocketListener.
+/// Class ActivityLogWebSocketListener.
/// </summary>
public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState>
{
@@ -56,6 +58,20 @@ public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<Activi
base.Dispose(dispose);
}
+ /// <summary>
+ /// Starts sending messages over an activity log web socket.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ protected override void Start(WebSocketMessageInfo message)
+ {
+ if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
+ {
+ throw new AuthenticationException("Only admin users can retrieve the activity log.");
+ }
+
+ base.Start(message);
+ }
+
private async void OnEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
{
await SendData(true).ConfigureAwait(false);
diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs
index 0d8bf205c..b403ff46d 100644
--- a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs
+++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
@@ -66,6 +68,20 @@ public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnume
base.Dispose(dispose);
}
+ /// <summary>
+ /// Starts sending messages over a session info web socket.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ protected override void Start(WebSocketMessageInfo message)
+ {
+ if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
+ {
+ throw new AuthenticationException("Only admin users can subscribe to session information.");
+ }
+
+ base.Start(message);
+ }
+
private async void OnSessionManagerSessionActivity(object? sender, SessionEventArgs e)
{
await SendData(false).ConfigureAwait(false);
diff --git a/Jellyfin.Networking/Extensions/NetworkExtensions.cs b/Jellyfin.Networking/Extensions/NetworkExtensions.cs
index d55f78135..e45fa3bcb 100644
--- a/Jellyfin.Networking/Extensions/NetworkExtensions.cs
+++ b/Jellyfin.Networking/Extensions/NetworkExtensions.cs
@@ -104,7 +104,7 @@ public static partial class NetworkExtensions
Span<byte> bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? Network.IPv4MaskBytes : Network.IPv6MaskBytes];
if (!mask.TryWriteBytes(bytes, out var bytesWritten))
{
- Console.WriteLine("Unable to write address bytes, only {bytesWritten} bytes written.");
+ Console.WriteLine("Unable to write address bytes, only ${bytesWritten} bytes written.");
}
var zeroed = false;
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs
index f899b4497..b5f18d983 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationFailedLogger.cs
@@ -2,9 +2,8 @@
using System.Globalization;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
-using Jellyfin.Data.Events;
using MediaBrowser.Controller.Events;
-using MediaBrowser.Controller.Session;
+using MediaBrowser.Controller.Events.Authentication;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Globalization;
using Microsoft.Extensions.Logging;
@@ -14,7 +13,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
/// <summary>
/// Creates an entry in the activity log when there is a failed login attempt.
/// </summary>
- public class AuthenticationFailedLogger : IEventConsumer<GenericEventArgs<AuthenticationRequest>>
+ public class AuthenticationFailedLogger : IEventConsumer<AuthenticationRequestEventArgs>
{
private readonly ILocalizationManager _localizationManager;
private readonly IActivityManager _activityManager;
@@ -31,13 +30,13 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
}
/// <inheritdoc />
- public async Task OnEvent(GenericEventArgs<AuthenticationRequest> eventArgs)
+ public async Task OnEvent(AuthenticationRequestEventArgs eventArgs)
{
await _activityManager.CreateAsync(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("FailedLoginAttemptWithUserName"),
- eventArgs.Argument.Username),
+ eventArgs.Username),
"AuthenticationFailed",
Guid.Empty)
{
@@ -45,7 +44,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("LabelIpAddressValue"),
- eventArgs.Argument.RemoteEndPoint),
+ eventArgs.RemoteEndPoint),
}).ConfigureAwait(false);
}
}
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
index 8b0bd84c6..2ee5b4e88 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
@@ -2,8 +2,8 @@
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
-using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Events.Authentication;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Globalization;
@@ -12,7 +12,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
/// <summary>
/// Creates an entry in the activity log when there is a successful login attempt.
/// </summary>
- public class AuthenticationSucceededLogger : IEventConsumer<GenericEventArgs<AuthenticationResult>>
+ public class AuthenticationSucceededLogger : IEventConsumer<AuthenticationResultEventArgs>
{
private readonly ILocalizationManager _localizationManager;
private readonly IActivityManager _activityManager;
@@ -29,20 +29,20 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
}
/// <inheritdoc />
- public async Task OnEvent(GenericEventArgs<AuthenticationResult> eventArgs)
+ public async Task OnEvent(AuthenticationResultEventArgs eventArgs)
{
await _activityManager.CreateAsync(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"),
- eventArgs.Argument.User.Name),
+ eventArgs.User.Name),
"AuthenticationSucceeded",
- eventArgs.Argument.User.Id)
+ eventArgs.User.Id)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("LabelIpAddressValue"),
- eventArgs.Argument.SessionInfo.RemoteEndPoint),
+ eventArgs.SessionInfo?.RemoteEndPoint),
}).ConfigureAwait(false);
}
}
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
index aeb62e814..27726a57a 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
@@ -58,15 +58,18 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
var user = eventArgs.Users[0];
await _activityManager.CreateAsync(new ActivityLog(
- string.Format(
- CultureInfo.InvariantCulture,
- _localizationManager.GetLocalizedString("UserStartedPlayingItemWithValues"),
- user.Username,
- GetItemName(eventArgs.MediaInfo),
- eventArgs.DeviceName),
- GetPlaybackNotificationType(eventArgs.MediaInfo.MediaType),
- user.Id))
- .ConfigureAwait(false);
+ string.Format(
+ CultureInfo.InvariantCulture,
+ _localizationManager.GetLocalizedString("UserStartedPlayingItemWithValues"),
+ user.Username,
+ GetItemName(eventArgs.MediaInfo),
+ eventArgs.DeviceName),
+ GetPlaybackNotificationType(eventArgs.MediaInfo.MediaType),
+ user.Id)
+ {
+ ItemId = eventArgs.Item?.Id.ToString("N", CultureInfo.InvariantCulture),
+ })
+ .ConfigureAwait(false);
}
private static string GetItemName(BaseItemDto item)
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs
index dd7290fb8..6b16477aa 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs
@@ -73,7 +73,10 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
GetItemName(item),
eventArgs.DeviceName),
notificationType,
- user.Id))
+ user.Id)
+ {
+ ItemId = eventArgs.Item?.Id.ToString("N", CultureInfo.InvariantCulture),
+ })
.ConfigureAwait(false);
}
diff --git a/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs b/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs
index 5d558189b..9a473de52 100644
--- a/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs
+++ b/Jellyfin.Server.Implementations/Events/EventingServiceCollectionExtensions.cs
@@ -8,12 +8,11 @@ using Jellyfin.Server.Implementations.Events.Consumers.System;
using Jellyfin.Server.Implementations.Events.Consumers.Updates;
using Jellyfin.Server.Implementations.Events.Consumers.Users;
using MediaBrowser.Common.Updates;
-using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Events.Authentication;
using MediaBrowser.Controller.Events.Session;
using MediaBrowser.Controller.Events.Updates;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.DependencyInjection;
@@ -35,8 +34,8 @@ namespace Jellyfin.Server.Implementations.Events
collection.AddScoped<IEventConsumer<SubtitleDownloadFailureEventArgs>, SubtitleDownloadFailureLogger>();
// Security consumers
- collection.AddScoped<IEventConsumer<GenericEventArgs<AuthenticationRequest>>, AuthenticationFailedLogger>();
- collection.AddScoped<IEventConsumer<GenericEventArgs<AuthenticationResult>>, AuthenticationSucceededLogger>();
+ collection.AddScoped<IEventConsumer<AuthenticationRequestEventArgs>, AuthenticationFailedLogger>();
+ collection.AddScoped<IEventConsumer<AuthenticationResultEventArgs>, AuthenticationSucceededLogger>();
// Session consumers
collection.AddScoped<IEventConsumer<PlaybackStartEventArgs>, PlaybackStartLogger>();
diff --git a/MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs b/MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs
new file mode 100644
index 000000000..2143c6998
--- /dev/null
+++ b/MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs
@@ -0,0 +1,60 @@
+using System;
+using MediaBrowser.Controller.Session;
+
+namespace MediaBrowser.Controller.Events.Authentication;
+
+/// <summary>
+/// A class representing an authentication result event.
+/// </summary>
+public class AuthenticationRequestEventArgs : EventArgs
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationRequestEventArgs"/> class.
+ /// </summary>
+ /// <param name="request">The <see cref="AuthenticationRequest"/>.</param>
+ public AuthenticationRequestEventArgs(AuthenticationRequest request)
+ {
+ Username = request.Username;
+ UserId = request.UserId;
+ App = request.App;
+ AppVersion = request.AppVersion;
+ DeviceId = request.DeviceId;
+ DeviceName = request.DeviceName;
+ RemoteEndPoint = request.RemoteEndPoint;
+ }
+
+ /// <summary>
+ /// Gets or sets the user name.
+ /// </summary>
+ public string? Username { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user id.
+ /// </summary>
+ public Guid? UserId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the app.
+ /// </summary>
+ public string? App { get; set; }
+
+ /// <summary>
+ /// Gets or sets the app version.
+ /// </summary>
+ public string? AppVersion { get; set; }
+
+ /// <summary>
+ /// Gets or sets the device id.
+ /// </summary>
+ public string? DeviceId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the device name.
+ /// </summary>
+ public string? DeviceName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the remote endpoint.
+ /// </summary>
+ public string? RemoteEndPoint { get; set; }
+}
diff --git a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs
new file mode 100644
index 000000000..357ef9406
--- /dev/null
+++ b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs
@@ -0,0 +1,38 @@
+using System;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Controller.Events.Authentication;
+
+/// <summary>
+/// A class representing an authentication result event.
+/// </summary>
+public class AuthenticationResultEventArgs : EventArgs
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationResultEventArgs"/> class.
+ /// </summary>
+ /// <param name="result">The <see cref="AuthenticationResult"/>.</param>
+ public AuthenticationResultEventArgs(AuthenticationResult result)
+ {
+ User = result.User;
+ SessionInfo = result.SessionInfo;
+ ServerId = result.ServerId;
+ }
+
+ /// <summary>
+ /// Gets or sets the user.
+ /// </summary>
+ public UserDto User { get; set; }
+
+ /// <summary>
+ /// Gets or sets the session information.
+ /// </summary>
+ public SessionInfo? SessionInfo { get; set; }
+
+ /// <summary>
+ /// Gets or sets the server id.
+ /// </summary>
+ public string? ServerId { get; set; }
+}
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index 8f38d4976..e0942e490 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Net
/// Starts sending messages over a web socket.
/// </summary>
/// <param name="message">The message.</param>
- private void Start(WebSocketMessageInfo message)
+ protected virtual void Start(WebSocketMessageInfo message)
{
var vals = message.Data.Split(',');
diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
index 79f0846b4..bba5a6b85 100644
--- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Net;
using System.Net.WebSockets;
@@ -9,6 +7,9 @@ using MediaBrowser.Controller.Net.WebSocketMessages;
namespace MediaBrowser.Controller.Net
{
+ /// <summary>
+ /// Interface for WebSocket connections.
+ /// </summary>
public interface IWebSocketConnection : IAsyncDisposable, IDisposable
{
/// <summary>
@@ -41,6 +42,11 @@ namespace MediaBrowser.Controller.Net
WebSocketState State { get; }
/// <summary>
+ /// Gets the authorization information.
+ /// </summary>
+ public AuthorizationInfo AuthorizationInfo { get; }
+
+ /// <summary>
/// Gets the remote end point.
/// </summary>
/// <value>The remote end point.</value>
@@ -65,6 +71,11 @@ namespace MediaBrowser.Controller.Net
/// <exception cref="ArgumentNullException">The message is null.</exception>
Task SendAsync<T>(OutboundWebSocketMessage<T> message, CancellationToken cancellationToken);
- Task ProcessAsync(CancellationToken cancellationToken = default);
+ /// <summary>
+ /// Receives a message asynchronously.
+ /// </summary>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task ReceiveAsync(CancellationToken cancellationToken = default);
}
}
diff --git a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
index a8d88d8a1..9f9c922a7 100644
--- a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
+++ b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
@@ -25,8 +25,18 @@ namespace MediaBrowser.Model.Providers
public float? CommunityRating { get; set; }
+ public float? FrameRate { get; set; }
+
public int? DownloadCount { get; set; }
public bool? IsHashMatch { get; set; }
+
+ public bool? AiTranslated { get; set; }
+
+ public bool? MachineTranslated { get; set; }
+
+ public bool? Forced { get; set; }
+
+ public bool? HearingImpaired { get; set; }
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
index f01611819..22667ee82 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
@@ -13,7 +13,7 @@ namespace Jellyfin.Server.Implementations.Tests.HttpServer
[Fact]
public void DeserializeWebSocketMessage_SingleSegment_Success()
{
- var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!);
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json");
con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed);
Assert.Equal(109, bytesConsumed);
@@ -23,7 +23,7 @@ namespace Jellyfin.Server.Implementations.Tests.HttpServer
public void DeserializeWebSocketMessage_MultipleSegments_Success()
{
const int SplitPos = 64;
- var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!);
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json");
var seg1 = new BufferSegment(new Memory<byte>(bytes, 0, SplitPos));
var seg2 = seg1.Append(new Memory<byte>(bytes, SplitPos, bytes.Length - SplitPos));
@@ -34,7 +34,7 @@ namespace Jellyfin.Server.Implementations.Tests.HttpServer
[Fact]
public void DeserializeWebSocketMessage_ValidPartial_Success()
{
- var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!);
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
var bytes = File.ReadAllBytes("Test Data/HttpServer/ValidPartial.json");
con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed);
Assert.Equal(109, bytesConsumed);
@@ -43,7 +43,7 @@ namespace Jellyfin.Server.Implementations.Tests.HttpServer
[Fact]
public void DeserializeWebSocketMessage_Partial_ThrowJsonException()
{
- var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!);
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
var bytes = File.ReadAllBytes("Test Data/HttpServer/Partial.json");
Assert.Throws<JsonException>(() => con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed));
}