Add initial management interface support.

EraYaN-add-management-interface
Erwin de Haan 4 years ago
parent 82d27e6461
commit c5d900b164

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Jellyfin.Api.Attributes
{
/// <summary>
/// Specifies that the marked controller or method is only accessible via the management interface.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ManagementAttribute : Attribute
{
}
}

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Controllers
{
/// <summary>
/// The management controller.
/// </summary>
[Management]
public class ManagementController : BaseJellyfinApiController
{
private readonly IServerApplicationHost _appHost;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly INetworkManager _network;
private readonly ILogger<ManagementController> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ManagementController"/> class.
/// </summary>
/// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="fileSystem">Instance of <see cref="IFileSystem"/> interface.</param>
/// <param name="network">Instance of <see cref="INetworkManager"/> interface.</param>
/// <param name="logger">Instance of <see cref="ILogger{SystemController}"/> interface.</param>
public ManagementController(
IServerConfigurationManager serverConfigurationManager,
IServerApplicationHost appHost,
IFileSystem fileSystem,
INetworkManager network,
ILogger<ManagementController> logger)
{
_appPaths = serverConfigurationManager.ApplicationPaths;
_appHost = appHost;
_fileSystem = fileSystem;
_network = network;
_logger = logger;
}
/// <summary>
/// Gets information about the server.
/// </summary>
/// <response code="200">Information retrieved.</response>
/// <returns>A <see cref="SystemInfo"/> with info about the system.</returns>
[HttpGet("Test")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<int> GetTest()
{
return 123456; // secret
}
}
}

@ -159,6 +159,9 @@ namespace Jellyfin.Server.Extensions
})
.AddMvc(opts =>
{
// Seperate the management routes and the general ones.
opts.Filters.Add(typeof(ManagementInterfaceFilter));
// Allow requester to change between camelCase and PascalCase
opts.RespectBrowserAcceptHeader = true;

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Jellyfin.Server.Filters
{
internal class ManagementInterfaceFilter : IActionFilter
{
private readonly List<(IPAddress Host, int Port)> managementEndpoints;
public ManagementInterfaceFilter(IConfiguration appConfig)
{
managementEndpoints = new List<(IPAddress Host, int Port)>();
if (appConfig.UseManagementInterface())
{
var socketPath = appConfig.GetManagementInterfaceSocketPath();
var localhostPort = appConfig.GetManagementInterfaceLocalhostPort();
bool useDefault = true;
if (!string.IsNullOrEmpty(socketPath))
{
// TODO make this work, no idea where to get the SocketAddress or something similar
managementEndpoints.Add((IPAddress.Any, 0));
}
if (localhostPort > 0)
{
managementEndpoints.Add((IPAddress.Loopback, localhostPort));
managementEndpoints.Add((IPAddress.IPv6Loopback, localhostPort));
}
if (useDefault)
{
managementEndpoints.Add((IPAddress.Loopback, ServerConfiguration.DefaultManagementPort));
managementEndpoints.Add((IPAddress.IPv6Loopback, ServerConfiguration.DefaultManagementPort));
}
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
var isManagementRoute = IsManagementRoute(context);
var isManagementListenEntrypoint = IsManagementListenEntrypoint(context);
if ((isManagementRoute && !isManagementListenEntrypoint) || (!isManagementRoute && isManagementListenEntrypoint))
{
context.Result = new NotFoundResult();
}
}
private bool IsManagementRoute(ActionExecutingContext context)
{
return HasAttribute<ManagementAttribute>(context);
}
private bool HasAttribute<T>(ActionExecutingContext context)
{
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor != null)
{
// Check if the attribute exists on the action method
if (controllerActionDescriptor.MethodInfo?.GetCustomAttributes(inherit: true)?.Any(a => a.GetType().Equals(typeof(T))) ?? false)
{
return true;
}
// Check if the attribute exists on the controller
if (controllerActionDescriptor.ControllerTypeInfo?.GetCustomAttributes(typeof(T), true)?.Any() ?? false)
{
return true;
}
}
return false;
}
private bool IsManagementListenEntrypoint(ActionExecutingContext context)
{
return managementEndpoints.Contains((context.HttpContext.Connection.LocalIpAddress, context.HttpContext.Connection.LocalPort));
}
}
}

@ -16,6 +16,7 @@ using Emby.Server.Implementations.Networking;
using Jellyfin.Api.Controllers;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
@ -372,6 +373,39 @@ namespace Jellyfin.Server
options.ListenUnixSocket(socketPath);
_logger.LogInformation("Kestrel listening to unix socket {SocketPath}", socketPath);
}
// Enable the Management Interface
if (startupConfig.UseManagementInterface())
{
var socketPath = startupConfig.GetManagementInterfaceSocketPath();
var localhostPort = startupConfig.GetManagementInterfaceLocalhostPort();
bool useDefault = true;
if (!string.IsNullOrEmpty(socketPath))
{
// Workaround for https://github.com/aspnet/AspNetCore/issues/14134
if (File.Exists(socketPath))
{
File.Delete(socketPath);
}
options.ListenUnixSocket(socketPath);
_logger.LogInformation("Management interface listening to unix socket {SocketPath}", socketPath);
useDefault = false;
}
if (localhostPort > 0)
{
options.ListenLocalhost(localhostPort);
_logger.LogInformation("Management interface listening to localhost on port {LocalhostPort}", localhostPort);
useDefault = false;
}
if (useDefault)
{
options.ListenLocalhost(ServerConfiguration.DefaultManagementPort);
_logger.LogInformation("Management interface listening to localhost on default port {DefaultManagementPort}", ServerConfiguration.DefaultManagementPort);
}
}
})
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig))
.UseSerilog()

@ -49,6 +49,21 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
public const string UnixSocketPathKey = "kestrel:socketPath";
/// <summary>
/// The key for a setting that indicates whether the management interface should be enabled.
/// </summary>
public const string UseManagementInterfaceKey = "management:enabled";
/// <summary>
/// The key for a setting that indicates whether the management interface should listen on localhost.
/// </summary>
public const string ManagementInterfaceLocalhostPortKey = "management:port";
/// <summary>
/// The key for the management interface socket path.
/// </summary>
public const string ManagementInterfaceSocketPathKey = "management:socket";
/// <summary>
/// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
/// </summary>
@ -97,5 +112,29 @@ namespace MediaBrowser.Controller.Extensions
/// <returns>The unix socket path.</returns>
public static string GetUnixSocketPath(this IConfiguration configuration)
=> configuration[UnixSocketPathKey];
/// <summary>
/// Gets a value indicating whether kestrel should enable the management interface from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns><c>true</c> if kestrel should bind to a unix socket, otherwise <c>false</c>.</returns>
public static bool UseManagementInterface(this IConfiguration configuration)
=> configuration.GetValue<bool>(UseManagementInterfaceKey);
/// <summary>
/// Gets the localhost port for the management interface from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The management interface address.</returns>
public static int GetManagementInterfaceLocalhostPort(this IConfiguration configuration)
=> configuration.GetValue<int>(ManagementInterfaceLocalhostPortKey);
/// <summary>
/// Gets the path for the management interface socket from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The management interface socket path.</returns>
public static string GetManagementInterfaceSocketPath(this IConfiguration configuration)
=> configuration[ManagementInterfaceSocketPathKey];
}
}

@ -14,6 +14,7 @@ namespace MediaBrowser.Model.Configuration
public class ServerConfiguration : BaseApplicationConfiguration
{
public const int DefaultHttpPort = 8096;
public const int DefaultManagementPort = 12000;
public const int DefaultHttpsPort = 8920;
private string _baseUrl;

Loading…
Cancel
Save